├── .gitignore ├── test ├── test.pdf ├── valgrind-suppressions.txt ├── test_cpp_wrapper.cpp ├── example_html.c ├── example_simple.c ├── test_nossl.c ├── seams.h ├── test.h └── seams.c ├── src ├── SMTPMail.cpp ├── SMTPMail.h ├── mailx.c └── smtp.h ├── README.md ├── COPYING ├── Makefile └── doc.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test/config 3 | -------------------------------------------------------------------------------- /test/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/somnisoft/smtp-client/HEAD/test/test.pdf -------------------------------------------------------------------------------- /test/valgrind-suppressions.txt: -------------------------------------------------------------------------------- 1 | { 2 | OpenSSL malloc 3 | Memcheck:Leak 4 | fun:malloc 5 | fun:CRYPTO_malloc 6 | ... 7 | obj:*libcrypto* 8 | } 9 | { 10 | OpenSSL zalloc 11 | Memcheck:Leak 12 | fun:malloc 13 | fun:CRYPTO_zalloc 14 | ... 15 | obj:*libcrypto* 16 | } 17 | { 18 | OpenSSL realloc 19 | Memcheck:Leak 20 | fun:realloc 21 | fun:CRYPTO_realloc 22 | ... 23 | obj:*libcrypto* 24 | } 25 | -------------------------------------------------------------------------------- /test/test_cpp_wrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief Test the smtp-client CPP wrapper. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Example program demonstrating how to use the CPP wrapper class. 8 | * 9 | * This software has been placed into the public domain using CC0. 10 | */ 11 | #include 12 | 13 | #include 14 | 15 | /** 16 | * Main program entry point for the example smtp-client CPP class wrapper. 17 | * 18 | * @param[in] argc Number of arguments in @p argv. 19 | * @param[in] argv String array containing the program name and any optional 20 | * parameters described above. 21 | * @retval 0 Email has been sent. 22 | * @retval 1 An error occurred while sending email. Although unlikely, an email 23 | * can still get sent even after returning with this error code. 24 | */ 25 | int main(int argc, char *argv[]){ 26 | SMTPMail *mail; 27 | 28 | mail = new SMTPMail(); 29 | try{ 30 | mail->open("localhost", "25", SMTP_SECURITY_NONE, SMTP_DEBUG, NULL); 31 | mail->auth(SMTP_AUTH_NONE, NULL, NULL); 32 | mail->address_add(SMTP_ADDRESS_FROM, 33 | "mail@somnisoft.com", 34 | "From Address"); 35 | mail->address_add(SMTP_ADDRESS_TO, 36 | "mail@somnisoft.com", 37 | "To Address"); 38 | mail->header_add("Subject", "Test email (SMTPMail)"); 39 | mail->mail("Email sent using CPP SMTPMail class"); 40 | mail->close(); 41 | } 42 | catch(SMTPMailException sme){ 43 | errx(1, "Failed to send email: %s\n", sme.what()); 44 | } 45 | delete mail; 46 | 47 | return 0; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /test/example_html.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "smtp.h" 3 | #define MAIL_SERVER "localhost" 4 | #define MAIL_PORT "587" 5 | #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_NONE 6 | #define MAIL_FLAGS SMTP_DEBUG 7 | #define MAIL_CAFILE NULL 8 | #define MAIL_AUTH SMTP_AUTH_NONE 9 | #define MAIL_USER "mail@somnisoft.com" 10 | #define MAIL_PASS "password" 11 | #define MAIL_FROM "mail@somnisoft.com" 12 | #define MAIL_FROM_NAME "From Name" 13 | #define MAIL_SUBJECT "Subject Line" 14 | #define MAIL_TO "mail@somnisoft.com" 15 | #define MAIL_TO_NAME "To Name" 16 | int main(void){ 17 | struct smtp *smtp; 18 | enum smtp_status_code rc; 19 | const char *const email_body = 20 | "\n" 21 | " HTML Email\n" 22 | " \n" 23 | "

H1

\n" 24 | "

H2

\n" 25 | "

H3

\n" 26 | "

H4

\n" 27 | "
H5
\n" 28 | "
H6
\n" 29 | " \n" 30 | "\n"; 31 | smtp_open(MAIL_SERVER, 32 | MAIL_PORT, 33 | MAIL_CONNECTION_SECURITY, 34 | MAIL_FLAGS, 35 | MAIL_CAFILE, 36 | &smtp); 37 | smtp_auth(smtp, 38 | MAIL_AUTH, 39 | MAIL_USER, 40 | MAIL_PASS); 41 | smtp_address_add(smtp, 42 | SMTP_ADDRESS_FROM, 43 | MAIL_FROM, 44 | MAIL_FROM_NAME); 45 | smtp_address_add(smtp, 46 | SMTP_ADDRESS_TO, 47 | MAIL_TO, 48 | MAIL_TO_NAME); 49 | smtp_header_add(smtp, 50 | "Subject", 51 | MAIL_SUBJECT); 52 | smtp_header_add(smtp, 53 | "Content-Type", 54 | "text/html"); 55 | smtp_mail(smtp, email_body); 56 | rc = smtp_close(smtp); 57 | if(rc != SMTP_STATUS_OK){ 58 | fprintf(stderr, "smtp failed: %s\n", smtp_status_code_errstr(rc)); 59 | return 1; 60 | } 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /test/example_simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "smtp.h" 3 | #define MAIL_SERVER "mail.example.com" 4 | #define MAIL_PORT "587" 5 | #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_STARTTLS 6 | #define MAIL_FLAGS (SMTP_DEBUG | \ 7 | SMTP_NO_CERT_VERIFY) /* Do not verify cert. */ 8 | #define MAIL_CAFILE NULL 9 | #define MAIL_AUTH SMTP_AUTH_PLAIN 10 | #define MAIL_USER "mail@example.com" 11 | #define MAIL_PASS "password" 12 | #define MAIL_FROM "mail@example.com" 13 | #define MAIL_FROM_NAME "From Name" 14 | #define MAIL_SUBJECT "Subject Line" 15 | #define MAIL_BODY "Email Body" 16 | #define MAIL_TO "to@example.com" 17 | #define MAIL_TO_NAME "To Name" 18 | int main(void) 19 | { 20 | struct smtp *smtp; 21 | int rc; 22 | rc = smtp_open(MAIL_SERVER, 23 | MAIL_PORT, 24 | MAIL_CONNECTION_SECURITY, 25 | MAIL_FLAGS, 26 | MAIL_CAFILE, 27 | &smtp); 28 | rc = smtp_auth(smtp, 29 | MAIL_AUTH, 30 | MAIL_USER, 31 | MAIL_PASS); 32 | rc = smtp_address_add(smtp, 33 | SMTP_ADDRESS_FROM, 34 | MAIL_FROM, 35 | MAIL_FROM_NAME); 36 | rc = smtp_address_add(smtp, 37 | SMTP_ADDRESS_TO, 38 | MAIL_TO, 39 | MAIL_TO_NAME); 40 | rc = smtp_header_add(smtp, 41 | "Subject", 42 | MAIL_SUBJECT); 43 | rc = smtp_attachment_add_mem(smtp, 44 | "test.txt", 45 | "Test email attachment.", 46 | -1); 47 | rc = smtp_mail(smtp, 48 | MAIL_BODY); 49 | rc = smtp_close(smtp); 50 | if(rc != SMTP_STATUS_OK){ 51 | fprintf(stderr, "smtp failed: %s\n", smtp_status_code_errstr(rc)); 52 | return 1; 53 | } 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /test/test_nossl.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief Test the smtp-client library without OpenSSL. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * These functional tests ensure that the smtp-client library works when 8 | * configured without OpenSSL. 9 | * 10 | * This software has been placed into the public domain using CC0. 11 | */ 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | /** 19 | * Load the test email to send from a configuration file. 20 | * 21 | * @param[out] email String buffer to store the email in. 22 | * @param[in] emailsz Number of bytes in email. 23 | */ 24 | static void 25 | load_test_email(char *const email, 26 | size_t emailsz){ 27 | FILE *fp; 28 | int rc; 29 | size_t bytes_read; 30 | 31 | fp = fopen("test/config/test_email.txt", "r"); 32 | assert(fp); 33 | 34 | bytes_read = fread(email, sizeof(*email), emailsz, fp); 35 | assert(bytes_read > 0); 36 | 37 | email[bytes_read - 1] = '\0'; 38 | 39 | rc = ferror(fp); 40 | assert(rc == 0); 41 | 42 | rc = fclose(fp); 43 | assert(rc == 0); 44 | } 45 | 46 | /** 47 | * Load the configuration file and send a single test email. 48 | */ 49 | static void 50 | test_nossl_smtp(void){ 51 | int rc; 52 | struct smtp *smtp; 53 | char email[1000]; 54 | 55 | load_test_email(email, sizeof(email)); 56 | 57 | rc = smtp_open("localhost", 58 | "25", 59 | SMTP_SECURITY_NONE, 60 | SMTP_DEBUG, 61 | NULL, 62 | &smtp); 63 | assert(rc == SMTP_STATUS_OK); 64 | 65 | rc = smtp_address_add(smtp, 66 | SMTP_ADDRESS_FROM, 67 | email, 68 | "Test Email"); 69 | assert(rc == SMTP_STATUS_OK); 70 | 71 | rc = smtp_address_add(smtp, 72 | SMTP_ADDRESS_TO, 73 | email, 74 | "Test Email"); 75 | assert(rc == SMTP_STATUS_OK); 76 | 77 | rc = smtp_header_add(smtp, 78 | "Subject", 79 | "SMTP Test: Build Without OpenSSL"); 80 | assert(rc == SMTP_STATUS_OK); 81 | 82 | rc = smtp_mail(smtp, 83 | "This email tests the build without OpenSSL compiled into" 84 | " the library."); 85 | assert(rc == SMTP_STATUS_OK); 86 | 87 | rc = smtp_close(smtp); 88 | assert(rc == SMTP_STATUS_OK); 89 | } 90 | 91 | /** 92 | * Main program entry point for testing the smtp-client library 93 | * build without OpenSSL. 94 | * 95 | * @retval 0 All tests passed. 96 | * @retval 1 Error. 97 | */ 98 | int main(void){ 99 | test_nossl_smtp(); 100 | return 0; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/SMTPMail.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief SMTPMail class wrapper for smtp-client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Thin CPP wrapper class around the smtp-client C library. 8 | * 9 | * This software has been placed into the public domain using CC0. 10 | */ 11 | #include "SMTPMail.h" 12 | 13 | SMTPMailException::SMTPMailException(enum smtp_status_code status_code){ 14 | this->status_code = status_code; 15 | } 16 | 17 | const char *const 18 | SMTPMailException::what() throw(){ 19 | return smtp_status_code_errstr(this->status_code); 20 | } 21 | 22 | SMTPMail::SMTPMail(void){ 23 | } 24 | 25 | SMTPMail::~SMTPMail(void){ 26 | } 27 | 28 | void SMTPMail::open(const char *const server, 29 | const char *const port, 30 | enum smtp_connection_security connection_security, 31 | enum smtp_flag flags, 32 | const char *const cafile){ 33 | this->rc = smtp_open(server, 34 | port, 35 | connection_security, 36 | flags, 37 | cafile, 38 | &this->smtp); 39 | this->throw_bad_status_code(); 40 | } 41 | 42 | void SMTPMail::auth(enum smtp_authentication_method auth_method, 43 | const char *const user, 44 | const char *const pass){ 45 | this->rc = smtp_auth(this->smtp, auth_method, user, pass); 46 | this->throw_bad_status_code(); 47 | } 48 | 49 | void SMTPMail::mail(const char *const body){ 50 | this->rc = smtp_mail(this->smtp, body); 51 | this->throw_bad_status_code(); 52 | } 53 | 54 | void SMTPMail::close(void){ 55 | this->rc = smtp_close(this->smtp); 56 | this->throw_bad_status_code(); 57 | } 58 | 59 | int SMTPMail::status_code_get(void){ 60 | return smtp_status_code_get(this->smtp); 61 | } 62 | 63 | void SMTPMail::status_code_set(enum smtp_status_code new_status_code){ 64 | this->rc = smtp_status_code_set(this->smtp, new_status_code); 65 | this->throw_bad_status_code(); 66 | } 67 | 68 | void SMTPMail::header_add(const char *const key, 69 | const char *const value){ 70 | this->rc = smtp_header_add(this->smtp, key, value); 71 | this->throw_bad_status_code(); 72 | } 73 | 74 | void SMTPMail::header_clear_all(void){ 75 | smtp_header_clear_all(this->smtp); 76 | } 77 | 78 | void SMTPMail::address_add(enum smtp_address_type type, 79 | const char *const email, 80 | const char *const name){ 81 | this->rc = smtp_address_add(this->smtp, type, email, name); 82 | this->throw_bad_status_code(); 83 | } 84 | 85 | void SMTPMail::address_clear_all(void){ 86 | smtp_address_clear_all(this->smtp); 87 | } 88 | 89 | void SMTPMail::attachment_add_path(const char *const name, 90 | const char *const path){ 91 | this->rc = smtp_attachment_add_path(this->smtp, name, path); 92 | this->throw_bad_status_code(); 93 | } 94 | 95 | void SMTPMail::attachment_add_fp(const char *const name, 96 | FILE *fp){ 97 | this->rc = smtp_attachment_add_fp(this->smtp, name, fp); 98 | this->throw_bad_status_code(); 99 | } 100 | 101 | void SMTPMail::attachment_add_mem(const char *const name, 102 | const void *const data, 103 | ssize_t datasz){ 104 | this->rc = smtp_attachment_add_mem(this->smtp, name, data, datasz); 105 | this->throw_bad_status_code(); 106 | } 107 | 108 | void SMTPMail::attachment_clear_all(void){ 109 | smtp_attachment_clear_all(this->smtp); 110 | } 111 | 112 | void SMTPMail::throw_bad_status_code(void){ 113 | if(this->rc != SMTP_STATUS_OK){ 114 | throw SMTPMailException(this->rc); 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smtp-client 2 | 3 | This is an SMTP client library written in C which can get included 4 | directly into another program. 5 | 6 | This library has been released into the public domain using 7 | [CC0](https://creativecommons.org/publicdomain/zero/1.0/). 8 | 9 | Official repository location: 10 | [www.somnisoft.com/smtp-client](https://www.somnisoft.com/smtp-client) 11 | 12 | ## Feature list 13 | * C89 14 | * Cross-platform (POSIX, BSD, MacOS, Windows) 15 | * Send attachments 16 | * Send custom email headers 17 | * Specify multiple TO, CC, and BCC recipients 18 | * Simple API and simple error handling (see Examples section below) 19 | * Optional OpenSSL TLS connection and authentication methods 20 | * Test cases with 100% code and branch coverage 21 | * Doxygen with 100% documentation including the test code 22 | * Free software (permissive - CC0) 23 | 24 | Supports the following connection methods: 25 | * No encryption 26 | * STARTTLS (requires OpenSSL) 27 | * TLS direct connection (requires OpenSSL) 28 | 29 | Supports the following authentication methods: 30 | * none 31 | * PLAIN 32 | * LOGIN 33 | * CRAM-MD5 (requires OpenSSL) 34 | 35 | To include the library into your application, simply copy the src/smtp.h and 36 | src/smtp.c files into your project directory. Then include the smtp.h header 37 | into your C file, compile smtp.c, and include the resulting object file into 38 | the build system. 39 | 40 | ## Examples 41 | The following example code demonstrates how to use the library. 42 | 43 | ```C 44 | #include 45 | #include "smtp.h" 46 | #define MAIL_SERVER "mail.example.com" 47 | #define MAIL_PORT "587" 48 | #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_STARTTLS 49 | #define MAIL_FLAGS (SMTP_DEBUG | \ 50 | SMTP_NO_CERT_VERIFY) /* Do not verify cert. */ 51 | #define MAIL_CAFILE NULL 52 | #define MAIL_AUTH SMTP_AUTH_PLAIN 53 | #define MAIL_USER "mail@example.com" 54 | #define MAIL_PASS "password" 55 | #define MAIL_FROM "mail@example.com" 56 | #define MAIL_FROM_NAME "From Name" 57 | #define MAIL_SUBJECT "Subject Line" 58 | #define MAIL_BODY "Email Body" 59 | #define MAIL_TO "to@example.com" 60 | #define MAIL_TO_NAME "To Name" 61 | int main(void) 62 | { 63 | struct smtp *smtp; 64 | int rc; 65 | rc = smtp_open(MAIL_SERVER, 66 | MAIL_PORT, 67 | MAIL_CONNECTION_SECURITY, 68 | MAIL_FLAGS, 69 | MAIL_CAFILE, 70 | &smtp); 71 | rc = smtp_auth(smtp, 72 | MAIL_AUTH, 73 | MAIL_USER, 74 | MAIL_PASS); 75 | rc = smtp_address_add(smtp, 76 | SMTP_ADDRESS_FROM, 77 | MAIL_FROM, 78 | MAIL_FROM_NAME); 79 | rc = smtp_address_add(smtp, 80 | SMTP_ADDRESS_TO, 81 | MAIL_TO, 82 | MAIL_TO_NAME); 83 | rc = smtp_header_add(smtp, 84 | "Subject", 85 | MAIL_SUBJECT); 86 | rc = smtp_attachment_add_mem(smtp, 87 | "test.txt", 88 | "Test email attachment.", 89 | -1); 90 | rc = smtp_mail(smtp, 91 | MAIL_BODY); 92 | rc = smtp_close(smtp); 93 | if(rc != SMTP_STATUS_OK){ 94 | fprintf(stderr, "smtp failed: %s\n", smtp_status_code_errstr(rc)); 95 | return 1; 96 | } 97 | return 0; 98 | } 99 | ``` 100 | 101 | Place the code snippet above into a file named 'test.c' and change each #define 102 | to the appropriate values for your mail server. Then copy smtp.c and smtp.h 103 | into the same directory and run the following commands to compile with OpenSSL 104 | support. 105 | 106 | cc -DSMTP_OPENSSL smtp.c -c -o smtp.o 107 | 108 | cc -DSMTP_OPENSSL test.c -c -o test.o 109 | 110 | cc test.o smtp.o -o smtp_test -lssl -lcrypto 111 | 112 | If you do not need OpenSSL support, remove the -DSMTP_OPENSSL and the 113 | -lssl and -lcrypto arguments. The commands as above should create an 114 | executable called 'smtp_test' which can send a test email using the specified 115 | mail server. 116 | 117 | A minimum amount of error checking has been included. Note that in the above 118 | example, the program checks for an error at the end of the mail session after 119 | calling smtp_close. We can do this because if an error occurs in any of the 120 | prior functions, the error will continue to propagate through any future 121 | function calls until either closing the SMTP context using smtp_close or by 122 | resetting the error condition using smtp_status_code_clear. 123 | 124 | The following example demonstrates how to send an HTML email by overriding the 125 | Content-Type header. When overriding this header, any attachments added using 126 | the smtp_attachment_add\* functions will get ignored. The application must 127 | generate the appropriate MIME sections (if needed) when overriding this 128 | header. 129 | 130 | ```C 131 | #include 132 | #include "smtp.h" 133 | #define MAIL_SERVER "localhost" 134 | #define MAIL_PORT "25" 135 | #define MAIL_CONNECTION_SECURITY SMTP_SECURITY_NONE 136 | #define MAIL_FLAGS SMTP_DEBUG 137 | #define MAIL_CAFILE NULL 138 | #define MAIL_AUTH SMTP_AUTH_NONE 139 | #define MAIL_USER "mail@somnisoft.com" 140 | #define MAIL_PASS "password" 141 | #define MAIL_FROM "mail@somnisoft.com" 142 | #define MAIL_FROM_NAME "From Name" 143 | #define MAIL_SUBJECT "Subject Line" 144 | #define MAIL_TO "mail@somnisoft.com" 145 | #define MAIL_TO_NAME "To Name" 146 | int main(void){ 147 | struct smtp *smtp; 148 | enum smtp_status_code rc; 149 | const char *const email_body = 150 | "\n" 151 | " HTML Email\n" 152 | " \n" 153 | "

H1

\n" 154 | "

H2

\n" 155 | "

H3

\n" 156 | "

H4

\n" 157 | "
H5
\n" 158 | "
H6
\n" 159 | " \n" 160 | "\n"; 161 | smtp_open(MAIL_SERVER, 162 | MAIL_PORT, 163 | MAIL_CONNECTION_SECURITY, 164 | MAIL_FLAGS, 165 | MAIL_CAFILE, 166 | &smtp); 167 | smtp_auth(smtp, 168 | MAIL_AUTH, 169 | MAIL_USER, 170 | MAIL_PASS); 171 | smtp_address_add(smtp, 172 | SMTP_ADDRESS_FROM, 173 | MAIL_FROM, 174 | MAIL_FROM_NAME); 175 | smtp_address_add(smtp, 176 | SMTP_ADDRESS_TO, 177 | MAIL_TO, 178 | MAIL_TO_NAME); 179 | smtp_header_add(smtp, 180 | "Subject", 181 | MAIL_SUBJECT); 182 | smtp_header_add(smtp, 183 | "Content-Type", 184 | "text/html"); 185 | smtp_mail(smtp, email_body); 186 | rc = smtp_close(smtp); 187 | if(rc != SMTP_STATUS_OK){ 188 | fprintf(stderr, "smtp failed: %s\n", smtp_status_code_errstr(rc)); 189 | return 1; 190 | } 191 | return 0; 192 | } 193 | ``` 194 | 195 | ## Technical Documentation 196 | See the [Technical Documentation]( 197 | https://www.somnisoft.com/smtp-client/technical-documentation/index.html) 198 | generated from Doxygen. 199 | 200 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | CC0 1.0 Universal 3 | Official translations of this legal tool are available 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL 6 | SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT 7 | RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. 8 | CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE 9 | INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES 10 | RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 11 | HEREUNDER. 12 | 13 | Statement of Purpose 14 | 15 | The laws of most jurisdictions throughout the world automatically confer 16 | exclusive Copyright and Related Rights (defined below) upon the creator and 17 | subsequent owner(s) (each and all, an "owner") of an original work of 18 | authorship and/or a database (each, a "Work"). 19 | 20 | Certain owners wish to permanently relinquish those rights to a Work for the 21 | purpose of contributing to a commons of creative, cultural and scientific works 22 | ("Commons") that the public can reliably and without fear of later claims of 23 | infringement build upon, modify, incorporate in other works, reuse and 24 | redistribute as freely as possible in any form whatsoever and for any purposes, 25 | including without limitation commercial purposes. These owners may contribute 26 | to the Commons to promote the ideal of a free culture and the further 27 | production of creative, cultural and scientific works, or to gain reputation 28 | or greater distribution for their Work in part through the use and efforts of 29 | others. 30 | 31 | For these and/or other purposes and motivations, and without any expectation 32 | of additional consideration or compensation, the person associating CC0 with a 33 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 34 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 35 | and publicly distribute the Work under its terms, with knowledge of his or her 36 | Copyright and Related Rights in the Work and the meaning and intended legal 37 | effect of CC0 on those rights. 38 | 39 | 1. Copyright and Related Rights. 40 | 41 | A Work made available under CC0 may be protected by copyright and related 42 | or neighboring rights ("Copyright and Related Rights"). Copyright and 43 | Related Rights include, but are not limited to, the following: 44 | 45 | the right to reproduce, adapt, distribute, perform, display, communicate, 46 | and translate a Work; 47 | 48 | moral rights retained by the original author(s) and/or performer(s); 49 | 50 | publicity and privacy rights pertaining to a person's image or likeness 51 | depicted in a Work; 52 | 53 | rights protecting against unfair competition in regards to a Work, subject 54 | to the limitations in paragraph 4(a), below; 55 | 56 | rights protecting the extraction, dissemination, use and reuse of data in 57 | a Work; 58 | 59 | database rights (such as those arising under Directive 96/9/EC of the 60 | European Parliament and of the Council of 11 March 1996 on the legal 61 | protection of databases, and under any national implementation thereof, 62 | including any amended or successor version of such directive); and 63 | 64 | other similar, equivalent or corresponding rights throughout the world 65 | based on applicable law or treaty, and any national implementations 66 | thereof. 67 | 68 | 2. Waiver. 69 | 70 | To the greatest extent permitted by, but not in contravention of, applicable 71 | law, Affirmer hereby overtly, fully, permanently, irrevocably and 72 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 73 | and Related Rights and associated claims and causes of action, whether now 74 | known or unknown (including existing as well as future claims and causes of 75 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 76 | duration provided by applicable law or treaty (including future time 77 | extensions), (iii) in any current or future medium and for any number of 78 | copies, and (iv) for any purpose whatsoever, including without limitation 79 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer 80 | makes the Waiver for the benefit of each member of the public at large and 81 | to the detriment of Affirmer's heirs and successors, fully intending that 82 | such Waiver shall not be subject to revocation, rescission, cancellation, 83 | termination, or any other legal or equitable action to disrupt the quiet 84 | enjoyment of the Work by the public as contemplated by Affirmer's express 85 | Statement of Purpose. 86 | 87 | 3. Public License Fallback. 88 | 89 | Should any part of the Waiver for any reason be judged legally invalid or 90 | ineffective under applicable law, then the Waiver shall be preserved to the 91 | maximum extent permitted taking into account Affirmer's express Statement of 92 | Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby 93 | grants to each affected person a royalty-free, non transferable, non 94 | sublicensable, non exclusive, irrevocable and unconditional license to 95 | exercise Affirmer's Copyright and Related Rights in the Work (i) in all 96 | territories worldwide, (ii) for the maximum duration provided by applicable 97 | law or treaty (including future time extensions), (iii) in any current or 98 | future medium and for any number of copies, and (iv) for any purpose 99 | whatsoever, including without limitation commercial, advertising or 100 | promotional purposes (the "License"). The License shall be deemed effective 101 | as of the date CC0 was applied by Affirmer to the Work. Should any part of 102 | the License for any reason be judged legally invalid or ineffective under 103 | applicable law, such partial invalidity or ineffectiveness shall not 104 | invalidate the remainder of the License, and in such case Affirmer hereby 105 | affirms that he or she will not (i) exercise any of his or her remaining 106 | Copyright and Related Rights in the Work or (ii) assert any associated 107 | claims and causes of action with respect to the Work, in either case 108 | contrary to Affirmer's express Statement of Purpose. 109 | 110 | 4. Limitations and Disclaimers. 111 | 112 | No trademark or patent rights held by Affirmer are waived, abandoned, 113 | surrendered, licensed or otherwise affected by this document. 114 | 115 | Affirmer offers the Work as-is and makes no representations or warranties 116 | of any kind concerning the Work, express, implied, statutory or otherwise, 117 | including without limitation warranties of title, merchantability, fitness 118 | for a particular purpose, non infringement, or the absence of latent or 119 | other defects, accuracy, or the present or absence of errors, whether or not 120 | discoverable, all to the greatest extent permissible under applicable law. 121 | 122 | Affirmer disclaims responsibility for clearing rights of other persons that 123 | may apply to the Work or any use thereof, including without limitation any 124 | person's Copyright and Related Rights in the Work. Further, Affirmer 125 | disclaims responsibility for obtaining any necessary consents, permissions 126 | or other rights required for any use of the Work. 127 | 128 | Affirmer understands and acknowledges that Creative Commons is not a party 129 | to this document and has no duty or obligation with respect to this CC0 or 130 | use of the Work. 131 | 132 | -------------------------------------------------------------------------------- /src/SMTPMail.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief SMTPMail class wrapper for smtp-client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Thin CPP wrapper class around the smtp-client C library. 8 | * 9 | * This software has been placed into the public domain using CC0. 10 | */ 11 | #ifndef SMTP_MAIL_H 12 | #define SMTP_MAIL_H 13 | 14 | #include 15 | 16 | #include "smtp.h" 17 | 18 | /** 19 | * @class SMTPMailException 20 | * 21 | * This exception will get thrown whenever an SMTP client function fails. 22 | */ 23 | class SMTPMailException : public std::exception{ 24 | public: 25 | /** 26 | * Create the exception with the corresponding @p status_code. 27 | * 28 | * @param[in] status_code An error status code returned from a previous call 29 | * to an smtp-client library function. 30 | */ 31 | SMTPMailException(enum smtp_status_code status_code); 32 | 33 | /** 34 | * Get a description of the smtp-client status code that caused 35 | * this exception. 36 | * 37 | * @return Null-terminated string describing the smtp-client status code. 38 | */ 39 | virtual const char *const what() throw(); 40 | 41 | private: 42 | /** 43 | * The status code generated by any of the smtp-client library functions. 44 | */ 45 | enum smtp_status_code status_code; 46 | }; 47 | 48 | /** 49 | * @class SMTPMail 50 | * 51 | * Thin CPP wrapper class over the smtp-client C library. 52 | */ 53 | class SMTPMail{ 54 | public: 55 | /** 56 | * Create the @ref SMTPMail object. 57 | */ 58 | SMTPMail(void); 59 | 60 | /** 61 | * Free the @ref SMTPMail object. 62 | */ 63 | ~SMTPMail(void); 64 | 65 | /** 66 | * Open a connection to an SMTP server. 67 | * 68 | * See @ref smtp_open. 69 | * 70 | * @param[in] server Server name or IP address. 71 | * @param[in] port Server port number. 72 | * @param[in] connection_security See @ref smtp_connection_security. 73 | * @param[in] flags See @ref smtp_flag. 74 | * @param[in] cafile Path to certificate file, or NULL to use 75 | * certificates in the default path. 76 | */ 77 | void open(const char *const server, 78 | const char *const port, 79 | enum smtp_connection_security connection_security, 80 | enum smtp_flag flags, 81 | const char *const cafile); 82 | 83 | /** 84 | * Authenticate the user using one of the methods listed in 85 | * @ref smtp_authentication_method. 86 | * 87 | * See @ref smtp_auth. 88 | * 89 | * @param[in] auth_method See @ref smtp_authentication_method. 90 | * @param[in] user Server authentication user name. 91 | * @param[in] pass Server authentication user password. 92 | */ 93 | void auth(enum smtp_authentication_method auth_method, 94 | const char *const user, 95 | const char *const pass); 96 | 97 | /** 98 | * Sends an email using the addresses, attachments, and headers defined 99 | * in the current SMTP context. 100 | * 101 | * See @ref smtp_mail. 102 | * 103 | * @param[in] body Null-terminated string to send in the email body. 104 | */ 105 | void mail(const char *const body); 106 | 107 | /** 108 | * Close the SMTP connection and frees all resources held by the 109 | * SMTP context. 110 | * 111 | * See @ref smtp_close. 112 | */ 113 | void close(void); 114 | 115 | /** 116 | * Get the current status/error code described in @ref smtp_status_code. 117 | * 118 | * See @ref smtp_status_code_get. 119 | * 120 | * @return @ref smtp_status_code. 121 | */ 122 | int status_code_get(void); 123 | 124 | /** 125 | * Set the error status of the SMTP client context. 126 | * 127 | * See @ref smtp_status_code_set. 128 | * 129 | * @param[in] new_status_code See @ref smtp_status_code. 130 | */ 131 | void status_code_set(enum smtp_status_code new_status_code); 132 | 133 | /** 134 | * Add a key/value header to the header list in the SMTP context. 135 | * 136 | * See @ref smtp_header_add. 137 | * 138 | * @param[in] key Key name for new header. It must consist only of 139 | * printable US-ASCII characters except colon. 140 | * @param[in] value Value for new header. It must consist only of printable 141 | * US-ASCII, space, or horizontal tab. If set to NULL, 142 | * this will prevent the header from printing out. 143 | */ 144 | void header_add(const char *const key, 145 | const char *const value); 146 | 147 | /** 148 | * Free all memory related to email headers. 149 | * 150 | * See @ref smtp_header_clear_all. 151 | */ 152 | void header_clear_all(void); 153 | 154 | /** 155 | * Add a FROM, TO, CC, or BCC address destination to this SMTP context. 156 | * 157 | * See @ref smtp_address_add. 158 | * 159 | * @param[in] type See @ref smtp_address_type. 160 | * @param[in] email The email address of the party. Must consist only of 161 | * printable characters excluding the angle brackets 162 | * (<) and (>). 163 | * @param[in] name Name or description of the party. Must consist only of 164 | * printable characters, excluding the quote characters. If 165 | * set to NULL, no name will get associated with this email. 166 | */ 167 | void address_add(enum smtp_address_type type, 168 | const char *const email, 169 | const char *const name); 170 | 171 | /** 172 | * Free all memory related to the address list. 173 | * 174 | * See @ref smtp_address_clear_all. 175 | */ 176 | void address_clear_all(void); 177 | 178 | /** 179 | * Add a file attachment from a path. 180 | * 181 | * See @ref smtp_attachment_add_mem. 182 | * 183 | * @param[in] name Filename of the attachment shown to recipients. 184 | * @param[in] path Path of file location to read from. 185 | */ 186 | void attachment_add_path(const char *const name, 187 | const char *const path); 188 | 189 | /** 190 | * Add an attachment using a file pointer. 191 | * 192 | * See @ref smtp_attachment_add_fp. 193 | * 194 | * @param[in] name Filename of the attachment shown to recipients. 195 | * @param[in] fp File pointer already opened by the caller. 196 | */ 197 | void attachment_add_fp(const char *const name, 198 | FILE *fp); 199 | 200 | /** 201 | * Add an attachment with the data pulled from memory. 202 | * 203 | * See @ref smtp_attachment_add_mem. 204 | * 205 | * @param[in] name Filename of the attachment shown to recipients. Must 206 | * consist only of printable characters excluding the 207 | * quote characters (') and ("), or the space character 208 | * ( ). 209 | * @param[in] data Raw attachment data stored in memory. 210 | * @param[in] datasz Number of bytes in @p data, or -1 if data 211 | * null-terminated. 212 | */ 213 | void attachment_add_mem(const char *const name, 214 | const void *const data, 215 | ssize_t datasz); 216 | 217 | /** 218 | * Remove all attachments from the SMTP client context. 219 | * 220 | * See @ref smtp_attachment_clear_all. 221 | */ 222 | void attachment_clear_all(void); 223 | 224 | private: 225 | /** 226 | * SMTP client context. 227 | */ 228 | struct smtp *smtp; 229 | 230 | /** 231 | * Store the last smtp-client library function return code. 232 | */ 233 | enum smtp_status_code rc; 234 | 235 | /** 236 | * Throw @ref SMTPMailException if the last smtp-client library function 237 | * failed. 238 | */ 239 | void throw_bad_status_code(void); 240 | }; 241 | 242 | #endif /* SMTP_MAIL_H */ 243 | 244 | -------------------------------------------------------------------------------- /test/seams.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief Test seams for the smtp-client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Used by the smtp-client testing framework to inject specific return values 8 | * by some standard library functions. This makes it possible to test less 9 | * common errors like out of memory conditions and input/output errors. 10 | * 11 | * This software has been placed into the public domain using CC0. 12 | */ 13 | #ifndef SMTP_TEST_SEAMS_H 14 | #define SMTP_TEST_SEAMS_H 15 | 16 | #include "test.h" 17 | 18 | /* 19 | * Redefine these functions to internal test seam functions. 20 | */ 21 | #undef BIO_new_socket 22 | #undef BIO_should_retry 23 | #undef calloc 24 | #undef close 25 | #undef connect 26 | #undef ERR_peek_error 27 | #undef fclose 28 | #undef ferror 29 | #undef gmtime_r 30 | #undef HMAC 31 | #undef localtime_r 32 | #undef malloc 33 | #undef mktime 34 | #undef realloc 35 | #undef recv 36 | #undef select 37 | #undef send 38 | #undef socket 39 | #undef SSL_connect 40 | #undef SSL_CTX_new 41 | #undef SSL_do_handshake 42 | #undef SSL_get_peer_certificate 43 | #undef X509_check_host 44 | #undef SSL_new 45 | #undef SSL_read 46 | #undef SSL_write 47 | #undef sprintf 48 | #undef strlen 49 | #undef time 50 | 51 | /** 52 | * Redefine this function from smtp.c and inject a test seam which 53 | * can control when this function fails. 54 | * 55 | * See @ref smtp_test_seam_bio_new_socket. 56 | */ 57 | #define BIO_new_socket smtp_test_seam_bio_new_socket 58 | 59 | /** 60 | * Redefine this function from smtp.c and inject a test seam which 61 | * can control when this function fails. 62 | * 63 | * See @ref smtp_test_seam_bio_should_retry. 64 | */ 65 | #define BIO_should_retry smtp_test_seam_bio_should_retry 66 | 67 | /** 68 | * Redefine this function from smtp.c and inject a test seam which 69 | * can control when this function fails. 70 | * 71 | * See @ref smtp_test_seam_calloc. 72 | */ 73 | #define calloc smtp_test_seam_calloc 74 | 75 | /** 76 | * Redefine this function from smtp.c and inject a test seam which 77 | * can control when this function fails. 78 | * 79 | * See @ref smtp_test_seam_close. 80 | */ 81 | #define close smtp_test_seam_close 82 | 83 | /** 84 | * Redefine this function from smtp.c and inject a test seam which 85 | * can control when this function fails. 86 | * 87 | * See @ref smtp_test_seam_connect. 88 | */ 89 | #define connect smtp_test_seam_connect 90 | 91 | /** 92 | * Redefine this function from smtp.c and inject a test seam which 93 | * can control when this function fails. 94 | * 95 | * See @ref smtp_test_seam_err_peek_error. 96 | */ 97 | #define ERR_peek_error smtp_test_seam_err_peek_error 98 | 99 | /** 100 | * Redefine this function from smtp.c and inject a test seam which 101 | * can control when this function fails. 102 | * 103 | * See @ref smtp_test_seam_fclose. 104 | */ 105 | #define fclose smtp_test_seam_fclose 106 | 107 | /** 108 | * Redefine this function from smtp.c and inject a test seam which 109 | * can control when this function fails. 110 | * 111 | * See @ref smtp_test_seam_ferror. 112 | */ 113 | #define ferror smtp_test_seam_ferror 114 | 115 | /** 116 | * Redefine this function from smtp.c and inject a test seam which 117 | * can control when this function fails. 118 | * 119 | * See @ref smtp_test_seam_gmtime_r. 120 | */ 121 | #define gmtime_r smtp_test_seam_gmtime_r 122 | 123 | /** 124 | * Redefine this function from smtp.c and inject a test seam which 125 | * can control when this function fails. 126 | * 127 | * See @ref smtp_test_seam_hmac. 128 | */ 129 | #define HMAC smtp_test_seam_hmac 130 | 131 | /** 132 | * Redefine this function from smtp.c and inject a test seam which 133 | * can control when this function fails. 134 | * 135 | * See @ref smtp_test_seam_localtime_r. 136 | */ 137 | #define localtime_r smtp_test_seam_localtime_r 138 | 139 | /** 140 | * Redefine this function from smtp.c and inject a test seam which 141 | * can control when this function fails. 142 | * 143 | * See @ref smtp_test_seam_malloc. 144 | */ 145 | #define malloc smtp_test_seam_malloc 146 | 147 | /** 148 | * Redefine this function from smtp.c and inject a test seam which 149 | * can control when this function fails. 150 | * 151 | * See @ref smtp_test_seam_mktime. 152 | */ 153 | #define mktime smtp_test_seam_mktime 154 | 155 | /** 156 | * Redefine this function from smtp.c and inject a test seam which 157 | * can control when this function fails. 158 | * 159 | * See @ref smtp_test_seam_realloc. 160 | */ 161 | #define realloc smtp_test_seam_realloc 162 | 163 | /** 164 | * Redefine this function from smtp.c and inject a test seam which 165 | * can control when this function fails. 166 | * 167 | * See @ref smtp_test_seam_recv. 168 | */ 169 | #define recv smtp_test_seam_recv 170 | 171 | /** 172 | * Redefine this function from smtp.c and inject a test seam which 173 | * can control when this function fails. 174 | * 175 | * See @ref smtp_test_seam_select. 176 | */ 177 | #define select smtp_test_seam_select 178 | 179 | /** 180 | * Redefine this function from smtp.c and inject a test seam which 181 | * can control when this function fails. 182 | * 183 | * See @ref smtp_test_seam_send. 184 | */ 185 | #define send smtp_test_seam_send 186 | 187 | /** 188 | * Redefine this function from smtp.c and inject a test seam which 189 | * can control when this function fails. 190 | * 191 | * See @ref smtp_test_seam_socket. 192 | */ 193 | #define socket smtp_test_seam_socket 194 | 195 | /** 196 | * Redefine this function from smtp.c and inject a test seam which 197 | * can control when this function fails. 198 | * 199 | * See @ref smtp_test_seam_ssl_connect. 200 | */ 201 | #define SSL_connect smtp_test_seam_ssl_connect 202 | 203 | /** 204 | * Redefine this function from smtp.c and inject a test seam which 205 | * can control when this function fails. 206 | * 207 | * See @ref smtp_test_seam_ssl_ctx_new. 208 | */ 209 | #define SSL_CTX_new smtp_test_seam_ssl_ctx_new 210 | 211 | /** 212 | * Redefine this function from smtp.c and inject a test seam which 213 | * can control when this function fails. 214 | * 215 | * See @ref smtp_test_seam_ssl_do_handshake. 216 | */ 217 | #define SSL_do_handshake smtp_test_seam_ssl_do_handshake 218 | 219 | /** 220 | * Redefine this function from smtp.c and inject a test seam which 221 | * can control when this function fails. 222 | * 223 | * See @ref smtp_test_seam_ssl_get_peer_certificate. 224 | */ 225 | #define SSL_get_peer_certificate smtp_test_seam_ssl_get_peer_certificate 226 | 227 | /** 228 | * Redefine this function from smtp.c and inject a test seam which 229 | * can control when this function fails. 230 | * 231 | * See @ref smtp_test_seam_x509_check_host. 232 | */ 233 | #define X509_check_host smtp_test_seam_x509_check_host 234 | 235 | /** 236 | * Redefine this function from smtp.c and inject a test seam which 237 | * can control when this function fails. 238 | * 239 | * See @ref smtp_test_seam_ssl_new. 240 | */ 241 | #define SSL_new smtp_test_seam_ssl_new 242 | 243 | /** 244 | * Redefine this function from smtp.c and inject a test seam which 245 | * can control when this function fails. 246 | * 247 | * See @ref smtp_test_seam_ssl_read. 248 | */ 249 | #define SSL_read smtp_test_seam_ssl_read 250 | 251 | /** 252 | * Redefine this function from smtp.c and inject a test seam which 253 | * can control when this function fails. 254 | * 255 | * See @ref smtp_test_seam_ssl_write. 256 | */ 257 | #define SSL_write smtp_test_seam_ssl_write 258 | 259 | /** 260 | * Redefine this function from smtp.c and inject a test seam which 261 | * can control when this function fails. 262 | * 263 | * See @ref smtp_test_seam_sprintf. 264 | */ 265 | #define sprintf smtp_test_seam_sprintf 266 | 267 | /** 268 | * Redefine this function from smtp.c and inject a test seam which 269 | * can control the return value of this function. 270 | * 271 | * See @ref smtp_test_seam_strlen. 272 | */ 273 | #define strlen smtp_test_seam_strlen 274 | 275 | /** 276 | * Redefine this function from smtp.c and inject a test seam which 277 | * can control when this function fails. 278 | * 279 | * See @ref smtp_test_seam_time. 280 | */ 281 | #define time smtp_test_seam_time 282 | 283 | #endif 284 | 285 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## @file 3 | ## @brief SMTP Developer Makefile. 4 | ## @author James Humphrey (mail@somnisoft.com) 5 | ## @version 1.00 6 | ## 7 | ## This Makefile used internally to build and test the SMTP client library. 8 | ## Do not use this Makefile for building the library into your application. 9 | ## Instead, include the src/smtp.h and src/smtp.c directly into your project 10 | ## and add those files as part of your own build system. 11 | ## 12 | ## This software has been placed into the public domain using CC0. 13 | ## 14 | .PHONY: all clean doc gcov install release test test_unit 15 | .SUFFIXES: 16 | 17 | BDIR = build 18 | INSTALL_PREFIX = /usr/local 19 | 20 | GCC_VERSION_MAJOR = $(shell gcc -dumpversion | sed 's/\..*//g' | tr -d '\n') 21 | 22 | CWARN += -Waggregate-return 23 | CWARN += -Wall 24 | CWARN += -Wbad-function-cast 25 | CWARN += -Wcast-align 26 | CWARN += -Wcast-qual 27 | CWARN += -Wdeclaration-after-statement 28 | CWARN += -Wdisabled-optimization 29 | CWARN += -Wdouble-promotion 30 | CWARN += -Werror 31 | CWARN += -Wextra 32 | CWARN += -Wfatal-errors 33 | CWARN += -Wfloat-equal 34 | CWARN += -Wformat=2 35 | CWARN += -Wframe-larger-than=5000 36 | CWARN += -Winit-self 37 | CWARN += -Winline 38 | CWARN += -Winvalid-pch 39 | CWARN += -Wlarger-than=10000 40 | CWARN += -Wlong-long 41 | CWARN += -Wmissing-declarations 42 | CWARN += -Wmissing-include-dirs 43 | CWARN += -Wmissing-prototypes 44 | CWARN += -Wnested-externs 45 | CWARN += -Wnull-dereference 46 | CWARN += -Wold-style-definition 47 | CWARN += -Wpacked 48 | CWARN += -Wpedantic 49 | CWARN += -pedantic-errors 50 | CWARN += -Wredundant-decls 51 | CWARN += -Wshadow 52 | CWARN += -Wstack-protector 53 | CWARN += -Wstrict-aliasing 54 | CWARN += -Wstrict-overflow=5 55 | CWARN += -Wstrict-prototypes 56 | CWARN += -Wswitch-default 57 | CWARN += -Wswitch-enum 58 | CWARN += -Wundef 59 | CWARN += -Wuninitialized 60 | CWARN += -Wunknown-pragmas 61 | CWARN += -Wunused-parameter 62 | CWARN += -Wvla 63 | CWARN += -Wwrite-strings 64 | 65 | ifeq ($(shell test $(GCC_VERSION_MAJOR) -ge 7 ; echo $$?), 0) 66 | CWARN.gcc += -Wduplicated-branches 67 | CWARN.gcc += -Wrestrict 68 | CWARN.gcc += -Wstringop-overflow=4 69 | endif 70 | 71 | CWARN.gcc += -Wno-aggressive-loop-optimizations 72 | CWARN.gcc += -Wduplicated-cond 73 | CWARN.gcc += -Wjump-misses-init 74 | CWARN.gcc += -Wlogical-op 75 | CWARN.gcc += -Wnormalized=nfkc 76 | CWARN.gcc += -Wstack-usage=5000 77 | CWARN.gcc += -Wshift-overflow=2 78 | CWARN.gcc += -Wsync-nand 79 | CWARN.gcc += -Wtrampolines 80 | ##CWARN.gcc += -Wunsafe-loop-optimizations 81 | CWARN.gcc += -Wunsuffixed-float-constants 82 | CWARN.gcc += -Wvector-operation-performance 83 | 84 | CWARN.clang += -Weverything 85 | CWARN.clang += -Wno-format-nonliteral 86 | CWARN.clang += -Wno-documentation-deprecated-sync 87 | CWARN.clang += -fcomment-block-commands=retval 88 | 89 | CFLAGS += $(CWARN) 90 | CFLAGS += -fstack-protector-all 91 | CFLAGS += -fstrict-overflow 92 | CFLAGS += -std=c89 93 | CFLAGS += -MD 94 | CFLAGS += -DSMTP_OPENSSL 95 | 96 | CFLAGS.debug += $(CFLAGS) 97 | CFLAGS.debug += -g3 98 | CFLAGS.debug += -DSMTP_TEST 99 | CFLAGS.debug += -Wno-missing-prototypes 100 | CFLAGS.debug += -fprofile-arcs -ftest-coverage 101 | CFLAGS.debug += -ftrapv 102 | 103 | CFLAGS.clang += -fsanitize=undefined 104 | 105 | CFLAGS.release += $(CFLAGS) 106 | CFLAGS.release += -O3 107 | 108 | CPPFLAGS += -DSMTP_OPENSSL 109 | CPPFLAGS += -MD 110 | CPPFLAGS += -fpermissive 111 | 112 | CPPFLAGS.release = $(CPPFLAGS) 113 | 114 | CFLAGS.gcc.debug += $(CFLAGS.debug) $(CWARN.gcc) 115 | CFLAGS.gcc.release += $(CFLAGS.release) $(CWARN.gcc) 116 | CFLAGS.clang.debug += $(CFLAGS.debug) $(CFLAGS.clang) $(CWARN.clang) 117 | 118 | CDEF_POSIX = -D_POSIX_C_SOURCE=200112 119 | 120 | SCAN_BUILD = $(SILENT) scan-build -maxloop 100 \ 121 | -o $(BDIR)/scan-build \ 122 | --status-bugs \ 123 | clang -c -o $(BDIR)/debug/scan-build-smtp.o src/smtp.c 124 | 125 | VFLAGS += -q 126 | VFLAGS += --error-exitcode=1 127 | VFLAGS += --gen-suppressions=yes 128 | VFLAGS += --num-callers=40 129 | 130 | VFLAGS_MEMCHECK += --tool=memcheck 131 | VFLAGS_MEMCHECK += --expensive-definedness-checks=yes 132 | VFLAGS_MEMCHECK += --track-origins=yes 133 | VFLAGS_MEMCHECK += --leak-check=full 134 | VFLAGS_MEMCHECK += --leak-resolution=high 135 | VFLAGS_MEMCHECK += --suppressions=test/valgrind-suppressions.txt 136 | VALGRIND_MEMCHECK = $(SILENT) valgrind $(VFLAGS) $(VFLAGS_MEMCHECK) 137 | 138 | GCOV = gcov -b $(BDIR)/debug/smtp.o 139 | 140 | CC = gcc 141 | CPP = g++ 142 | 143 | CC.clang = clang 144 | 145 | AR.c.debug = $(SILENT) $(AR) -c -r $@ $^ 146 | AR.c.release = $(SILENT) $(AR) -c -r $@ $^ 147 | COMPILE.c.debug = $(SILENT) $(CC) $(CFLAGS.gcc.debug) -c -o $@ $< 148 | COMPILE.c.release = $(SILENT) $(CC) $(CFLAGS.gcc.release) -c -o $@ $< 149 | COMPILE.c.clang = $(SILENT) $(CC.clang) $(CFLAGS.clang.debug) -c -o $@ $< 150 | COMPILE.cpp.release = $(SILENT) $(CPP) $(CPPFLAGS.release) -c -o $@ $< 151 | LINK.c.debug = $(SILENT) $(CC) $(CFLAGS.gcc.debug) -o $@ $^ 152 | LINK.c.release = $(SILENT) $(CC) $(CFLAGS.gcc.release) -o $@ $^ 153 | LINK.c.clang = $(SILENT) $(CC.clang) $(CFLAGS.clang.debug) -o $@ $^ 154 | LINK.cpp.release = $(SILENT) $(CPP) $(CPPFLAGS.release) -o $@ $^ 155 | MKDIR = $(SILENT) mkdir -p $@ 156 | CP = $(SILENT) cp $< $@ 157 | 158 | all: $(BDIR)/debug/libsmtp.a \ 159 | $(BDIR)/release/libsmtp_nossl.a \ 160 | $(BDIR)/release/libsmtp.a \ 161 | $(BDIR)/debug/mailx \ 162 | $(BDIR)/release/mailx_nossl \ 163 | $(BDIR)/release/mailx \ 164 | $(BDIR)/release/test_cpp_wrapper \ 165 | $(BDIR)/doc/html/index.html \ 166 | $(BDIR)/debug/test \ 167 | $(BDIR)/debug/clang_test \ 168 | $(BDIR)/release/example_simple \ 169 | $(BDIR)/release/example_html \ 170 | $(BDIR)/release/test_nossl 171 | 172 | clean: 173 | $(SILENT) rm -rf $(BDIR) 174 | 175 | doc $(BDIR)/doc/html/index.html: src/mailx.c \ 176 | src/SMTPMail.h \ 177 | src/SMTPMail.cpp \ 178 | src/smtp.h \ 179 | src/smtp.c \ 180 | test/seams.h \ 181 | test/seams.c \ 182 | test/test.h \ 183 | test/test.c \ 184 | test/test_cpp_wrapper.cpp \ 185 | test/test_nossl.c \ 186 | doc.cfg | $(BDIR)/doc 187 | $(SILENT) doxygen doc.cfg 188 | 189 | gcov: 190 | $(GCOV) 191 | 192 | install: all 193 | cp src/smtp.h $(INSTALL_PREFIX)/include/smtp.h 194 | cp $(BDIR)/release/libsmtp.a $(INSTALL_PREFIX)/lib/libsmtp.a 195 | 196 | test: all \ 197 | test_unit 198 | $(SCAN_BUILD) 199 | $(VALGRIND_MEMCHECK) $(BDIR)/debug/test 200 | ##$(VALGRIND_MEMCHECK) $(BDIR)/debug/clang_test 201 | $(VALGRIND_MEMCHECK) $(BDIR)/release/test_nossl 202 | $(BDIR)/release/test_cpp_wrapper 203 | $(GCOV) 204 | 205 | test_unit: all 206 | $(VALGRIND_MEMCHECK) $(BDIR)/debug/test -u 207 | $(VALGRIND_MEMCHECK) $(BDIR)/debug/clang_test -u 208 | 209 | -include $(shell find $(BDIR)/ -name "*.d" 2> /dev/null) 210 | 211 | $(BDIR)/doc: 212 | $(MKDIR) 213 | 214 | $(BDIR)/release: 215 | $(MKDIR) 216 | 217 | $(BDIR)/debug: 218 | $(MKDIR) 219 | 220 | $(BDIR): 221 | $(MKDIR) 222 | 223 | $(BDIR)/debug/libsmtp.a: $(BDIR)/debug/smtp.o 224 | $(AR.c.debug) 225 | 226 | $(BDIR)/release/libsmtp_nossl.a: $(BDIR)/release/smtp_nossl.o 227 | $(AR.c.release) 228 | 229 | $(BDIR)/release/libsmtp.a : $(BDIR)/release/smtp.o 230 | $(AR.c.release) 231 | 232 | $(BDIR)/debug/mailx: $(BDIR)/debug/seams.o \ 233 | $(BDIR)/debug/mailx.o \ 234 | $(BDIR)/debug/libsmtp.a 235 | $(LINK.c.debug) -lssl -lcrypto 236 | 237 | $(BDIR)/release/mailx_nossl: $(BDIR)/release/mailx_nossl.o \ 238 | $(BDIR)/release/libsmtp_nossl.a 239 | $(LINK.c.release) 240 | 241 | $(BDIR)/release/mailx: $(BDIR)/release/mailx.o \ 242 | $(BDIR)/release/libsmtp.a 243 | $(LINK.c.release) -lssl -lcrypto 244 | 245 | $(BDIR)/debug/mailx.o: src/mailx.c | $(BDIR)/debug 246 | $(COMPILE.c.debug) -Isrc 247 | 248 | $(BDIR)/release/mailx_nossl.o: src/mailx.c | $(BDIR)/release 249 | $(COMPILE.c.release) -Isrc -USMTP_OPENSSL 250 | 251 | $(BDIR)/release/mailx.o: src/mailx.c | $(BDIR)/release 252 | $(COMPILE.c.release) -Isrc 253 | 254 | $(BDIR)/release/test_cpp_wrapper: $(BDIR)/release/SMTPMail.o \ 255 | $(BDIR)/release/test_cpp_wrapper.o \ 256 | $(BDIR)/release/libsmtp.a 257 | $(LINK.cpp.release) -lssl -lcrypto 258 | 259 | $(BDIR)/release/SMTPMail.o: src/SMTPMail.cpp | $(BDIR)/release 260 | $(COMPILE.cpp.release) -Isrc 261 | 262 | $(BDIR)/release/test_cpp_wrapper.o: test/test_cpp_wrapper.cpp | $(BDIR)/release 263 | $(COMPILE.cpp.release) -Isrc 264 | 265 | $(BDIR)/debug/smtp.o: src/smtp.c | $(BDIR)/debug 266 | $(COMPILE.c.debug) $(CDEF_POSIX) 267 | 268 | $(BDIR)/release/smtp_nossl.o: src/smtp.c | $(BDIR)/release 269 | $(COMPILE.c.release) $(CDEF_POSIX) -USMTP_OPENSSL 270 | 271 | $(BDIR)/release/smtp.o: src/smtp.c | $(BDIR)/release 272 | $(COMPILE.c.release) $(CDEF_POSIX) 273 | 274 | $(BDIR)/debug/test: $(BDIR)/debug/seams.o \ 275 | $(BDIR)/debug/smtp.o \ 276 | $(BDIR)/debug/test.o 277 | $(LINK.c.debug) -lssl -lcrypto -lgcov 278 | 279 | $(BDIR)/debug/test.o: test/test.c | $(BDIR)/debug 280 | $(COMPILE.c.debug) $(CDEF_POSIX) -Isrc/ 281 | 282 | $(BDIR)/debug/seams.o: test/seams.c | $(BDIR)/debug 283 | $(COMPILE.c.debug) $(CDEF_POSIX) 284 | 285 | $(BDIR)/debug/clang_test: $(BDIR)/debug/clang_seams.o \ 286 | $(BDIR)/debug/clang_smtp.o \ 287 | $(BDIR)/debug/clang_test.o 288 | $(LINK.c.clang) -lssl -lcrypto -lgcov -lubsan 289 | 290 | $(BDIR)/debug/clang_seams.o: test/seams.c | $(BDIR)/debug 291 | $(COMPILE.c.clang) $(CDEF_POSIX) 292 | 293 | $(BDIR)/debug/clang_smtp.o: src/smtp.c | $(BDIR)/debug 294 | $(COMPILE.c.clang) $(CDEF_POSIX) 295 | 296 | $(BDIR)/debug/clang_test.o: test/test.c | $(BDIR)/debug 297 | $(COMPILE.c.clang) $(CDEF_POSIX) -Isrc/ 298 | 299 | $(BDIR)/release/example_simple: $(BDIR)/release/example_simple.o \ 300 | $(BDIR)/release/smtp.o 301 | $(LINK.c.release) -lssl -lcrypto 302 | $(BDIR)/release/example_simple.o: test/example_simple.c | $(BDIR)/release 303 | $(COMPILE.c.release) -Isrc 304 | 305 | $(BDIR)/release/example_html: $(BDIR)/release/example_html.o \ 306 | $(BDIR)/release/smtp.o 307 | $(LINK.c.release) -lssl -lcrypto 308 | $(BDIR)/release/example_html.o: test/example_html.c | $(BDIR)/release 309 | $(COMPILE.c.release) -Isrc 310 | 311 | $(BDIR)/release/test_nossl: $(BDIR)/release/smtp_nossl.o \ 312 | $(BDIR)/release/test_nossl.o 313 | $(LINK.c.release) 314 | 315 | $(BDIR)/release/test_nossl.o: test/test_nossl.c | $(BDIR)/release 316 | $(COMPILE.c.release) -Isrc/ -USMTP_OPENSSL 317 | 318 | release: $(BDIR)/smtp-client.tar.gz \ 319 | $(BDIR)/smtp-client.zip 320 | $(BDIR)/smtp-client.tar.gz: $(BDIR)/smtp-client/smtp.c \ 321 | $(BDIR)/smtp-client/smtp.h 322 | $(SILENT) tar -C $(BDIR) -c -z -v -f $@ smtp-client 323 | $(BDIR)/smtp-client.zip: $(BDIR)/smtp-client/smtp.c \ 324 | $(BDIR)/smtp-client/smtp.h 325 | $(SILENT) cd $(BDIR) && zip -r -T -v smtp-client.zip smtp-client 326 | 327 | $(BDIR)/smtp-client/smtp.c: src/smtp.c | $(BDIR)/smtp-client 328 | $(CP) 329 | 330 | $(BDIR)/smtp-client/smtp.h: src/smtp.h | $(BDIR)/smtp-client 331 | $(CP) 332 | 333 | $(BDIR)/smtp-client: 334 | $(MKDIR) 335 | 336 | -------------------------------------------------------------------------------- /doc.cfg: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------- 2 | # Project options 3 | #--------------------------------------------------------------------------- 4 | DOXYFILE_ENCODING = UTF-8 5 | PROJECT_NAME = "smtp-client" 6 | PROJECT_NUMBER = 7 | PROJECT_BRIEF = "SMTP Client C Library" 8 | PROJECT_LOGO = 9 | OUTPUT_DIRECTORY = "build/doc" 10 | CREATE_SUBDIRS = NO 11 | ALLOW_UNICODE_NAMES = NO 12 | OUTPUT_LANGUAGE = English 13 | BRIEF_MEMBER_DESC = YES 14 | REPEAT_BRIEF = YES 15 | ABBREVIATE_BRIEF = 16 | ALWAYS_DETAILED_SEC = NO 17 | INLINE_INHERITED_MEMB = NO 18 | FULL_PATH_NAMES = YES 19 | STRIP_FROM_PATH = 20 | STRIP_FROM_INC_PATH = 21 | SHORT_NAMES = NO 22 | JAVADOC_AUTOBRIEF = NO 23 | QT_AUTOBRIEF = NO 24 | MULTILINE_CPP_IS_BRIEF = NO 25 | INHERIT_DOCS = YES 26 | SEPARATE_MEMBER_PAGES = NO 27 | TAB_SIZE = 4 28 | ALIASES = 29 | TCL_SUBST = 30 | OPTIMIZE_OUTPUT_FOR_C = YES 31 | OPTIMIZE_OUTPUT_JAVA = NO 32 | OPTIMIZE_FOR_FORTRAN = NO 33 | OPTIMIZE_OUTPUT_VHDL = NO 34 | EXTENSION_MAPPING = 35 | MARKDOWN_SUPPORT = YES 36 | AUTOLINK_SUPPORT = YES 37 | BUILTIN_STL_SUPPORT = NO 38 | CPP_CLI_SUPPORT = NO 39 | SIP_SUPPORT = NO 40 | IDL_PROPERTY_SUPPORT = YES 41 | DISTRIBUTE_GROUP_DOC = NO 42 | GROUP_NESTED_COMPOUNDS = NO 43 | SUBGROUPING = YES 44 | INLINE_GROUPED_CLASSES = NO 45 | INLINE_SIMPLE_STRUCTS = NO 46 | TYPEDEF_HIDES_STRUCT = NO 47 | LOOKUP_CACHE_SIZE = 0 48 | 49 | #--------------------------------------------------------------------------- 50 | # Build options 51 | #--------------------------------------------------------------------------- 52 | EXTRACT_ALL = NO 53 | EXTRACT_PRIVATE = YES 54 | EXTRACT_PACKAGE = NO 55 | EXTRACT_STATIC = YES 56 | EXTRACT_LOCAL_CLASSES = YES 57 | EXTRACT_LOCAL_METHODS = YES 58 | EXTRACT_ANON_NSPACES = NO 59 | HIDE_UNDOC_MEMBERS = NO 60 | HIDE_UNDOC_CLASSES = NO 61 | HIDE_FRIEND_COMPOUNDS = NO 62 | HIDE_IN_BODY_DOCS = NO 63 | INTERNAL_DOCS = NO 64 | CASE_SENSE_NAMES = YES 65 | HIDE_SCOPE_NAMES = NO 66 | HIDE_COMPOUND_REFERENCE= NO 67 | SHOW_INCLUDE_FILES = YES 68 | SHOW_GROUPED_MEMB_INC = NO 69 | FORCE_LOCAL_INCLUDES = NO 70 | INLINE_INFO = YES 71 | SORT_MEMBER_DOCS = YES 72 | SORT_BRIEF_DOCS = NO 73 | SORT_MEMBERS_CTORS_1ST = NO 74 | SORT_GROUP_NAMES = NO 75 | SORT_BY_SCOPE_NAME = NO 76 | STRICT_PROTO_MATCHING = YES 77 | GENERATE_TODOLIST = YES 78 | GENERATE_TESTLIST = YES 79 | GENERATE_BUGLIST = YES 80 | GENERATE_DEPRECATEDLIST= YES 81 | ENABLED_SECTIONS = 82 | MAX_INITIALIZER_LINES = 30 83 | SHOW_USED_FILES = YES 84 | SHOW_FILES = YES 85 | SHOW_NAMESPACES = YES 86 | FILE_VERSION_FILTER = 87 | LAYOUT_FILE = 88 | CITE_BIB_FILES = 89 | 90 | #--------------------------------------------------------------------------- 91 | # Warning and progress messages 92 | #--------------------------------------------------------------------------- 93 | QUIET = YES 94 | WARNINGS = YES 95 | WARN_IF_UNDOCUMENTED = YES 96 | WARN_IF_DOC_ERROR = YES 97 | WARN_NO_PARAMDOC = YES 98 | WARN_AS_ERROR = YES 99 | WARN_FORMAT = "$file:$line: $text" 100 | WARN_LOGFILE = 101 | 102 | #--------------------------------------------------------------------------- 103 | # Input files 104 | #--------------------------------------------------------------------------- 105 | INPUT = src/mailx.c \ 106 | src/SMTPMail.h \ 107 | src/SMTPMail.cpp \ 108 | src/smtp.h \ 109 | src/smtp.c \ 110 | test/seams.h \ 111 | test/seams.c \ 112 | test/test.h \ 113 | test/test.c \ 114 | test/test_cpp_wrapper.cpp \ 115 | test/test_nossl.c 116 | INPUT_ENCODING = UTF-8 117 | FILE_PATTERNS = 118 | RECURSIVE = NO 119 | EXCLUDE = 120 | EXCLUDE_SYMLINKS = NO 121 | EXCLUDE_PATTERNS = 122 | EXCLUDE_SYMBOLS = 123 | EXAMPLE_PATH = 124 | EXAMPLE_PATTERNS = 125 | EXAMPLE_RECURSIVE = NO 126 | IMAGE_PATH = 127 | INPUT_FILTER = 128 | FILTER_PATTERNS = 129 | FILTER_SOURCE_FILES = NO 130 | FILTER_SOURCE_PATTERNS = 131 | USE_MDFILE_AS_MAINPAGE = 132 | 133 | #--------------------------------------------------------------------------- 134 | # Source browsing 135 | #--------------------------------------------------------------------------- 136 | SOURCE_BROWSER = YES 137 | INLINE_SOURCES = YES 138 | STRIP_CODE_COMMENTS = YES 139 | REFERENCED_BY_RELATION = YES 140 | REFERENCES_RELATION = YES 141 | REFERENCES_LINK_SOURCE = YES 142 | SOURCE_TOOLTIPS = YES 143 | USE_HTAGS = NO 144 | VERBATIM_HEADERS = YES 145 | CLANG_ASSISTED_PARSING = NO 146 | CLANG_OPTIONS = 147 | 148 | #--------------------------------------------------------------------------- 149 | # Alphabetical class index 150 | #--------------------------------------------------------------------------- 151 | ALPHABETICAL_INDEX = YES 152 | COLS_IN_ALPHA_INDEX = 5 153 | IGNORE_PREFIX = 154 | 155 | #--------------------------------------------------------------------------- 156 | # HTML output 157 | #--------------------------------------------------------------------------- 158 | GENERATE_HTML = YES 159 | HTML_OUTPUT = html 160 | HTML_FILE_EXTENSION = .html 161 | HTML_HEADER = 162 | HTML_FOOTER = 163 | HTML_STYLESHEET = 164 | HTML_EXTRA_STYLESHEET = 165 | HTML_EXTRA_FILES = 166 | HTML_COLORSTYLE_HUE = 220 167 | HTML_COLORSTYLE_SAT = 100 168 | HTML_COLORSTYLE_GAMMA = 80 169 | HTML_TIMESTAMP = NO 170 | HTML_DYNAMIC_SECTIONS = YES 171 | HTML_INDEX_NUM_ENTRIES = 100 172 | GENERATE_DOCSET = NO 173 | DOCSET_FEEDNAME = "Doxygen generated docs" 174 | DOCSET_BUNDLE_ID = org.doxygen.Project 175 | DOCSET_PUBLISHER_ID = org.doxygen.Publisher 176 | DOCSET_PUBLISHER_NAME = Publisher 177 | GENERATE_HTMLHELP = NO 178 | CHM_FILE = 179 | HHC_LOCATION = 180 | GENERATE_CHI = NO 181 | CHM_INDEX_ENCODING = 182 | BINARY_TOC = NO 183 | TOC_EXPAND = YES 184 | GENERATE_QHP = NO 185 | QCH_FILE = 186 | QHP_NAMESPACE = org.doxygen.Project 187 | QHP_VIRTUAL_FOLDER = doc 188 | QHP_CUST_FILTER_NAME = 189 | QHP_CUST_FILTER_ATTRS = 190 | QHP_SECT_FILTER_ATTRS = 191 | QHG_LOCATION = 192 | GENERATE_ECLIPSEHELP = NO 193 | ECLIPSE_DOC_ID = org.doxygen.Project 194 | DISABLE_INDEX = NO 195 | GENERATE_TREEVIEW = YES 196 | ENUM_VALUES_PER_LINE = 4 197 | TREEVIEW_WIDTH = 250 198 | EXT_LINKS_IN_WINDOW = NO 199 | FORMULA_FONTSIZE = 10 200 | FORMULA_TRANSPARENT = YES 201 | USE_MATHJAX = NO 202 | MATHJAX_FORMAT = HTML-CSS 203 | MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest 204 | MATHJAX_EXTENSIONS = 205 | MATHJAX_CODEFILE = 206 | SEARCHENGINE = YES 207 | SERVER_BASED_SEARCH = NO 208 | EXTERNAL_SEARCH = NO 209 | SEARCHENGINE_URL = 210 | SEARCHDATA_FILE = searchdata.xml 211 | EXTERNAL_SEARCH_ID = 212 | EXTRA_SEARCH_MAPPINGS = 213 | 214 | #--------------------------------------------------------------------------- 215 | # LaTeX output 216 | #--------------------------------------------------------------------------- 217 | GENERATE_LATEX = NO 218 | LATEX_OUTPUT = latex 219 | LATEX_CMD_NAME = latex 220 | MAKEINDEX_CMD_NAME = makeindex 221 | COMPACT_LATEX = NO 222 | PAPER_TYPE = a4 223 | EXTRA_PACKAGES = 224 | LATEX_HEADER = 225 | LATEX_FOOTER = 226 | LATEX_EXTRA_STYLESHEET = 227 | LATEX_EXTRA_FILES = 228 | PDF_HYPERLINKS = YES 229 | USE_PDFLATEX = YES 230 | LATEX_BATCHMODE = NO 231 | LATEX_HIDE_INDICES = NO 232 | LATEX_SOURCE_CODE = YES 233 | LATEX_BIB_STYLE = plain 234 | LATEX_TIMESTAMP = NO 235 | 236 | #--------------------------------------------------------------------------- 237 | # RTF output 238 | #--------------------------------------------------------------------------- 239 | GENERATE_RTF = NO 240 | RTF_OUTPUT = rtf 241 | COMPACT_RTF = NO 242 | RTF_HYPERLINKS = NO 243 | RTF_STYLESHEET_FILE = 244 | RTF_EXTENSIONS_FILE = 245 | RTF_SOURCE_CODE = YES 246 | 247 | #--------------------------------------------------------------------------- 248 | # man page output 249 | #--------------------------------------------------------------------------- 250 | GENERATE_MAN = NO 251 | MAN_OUTPUT = man 252 | MAN_EXTENSION = .3 253 | MAN_SUBDIR = 254 | MAN_LINKS = NO 255 | 256 | #--------------------------------------------------------------------------- 257 | # XML output 258 | #--------------------------------------------------------------------------- 259 | GENERATE_XML = NO 260 | XML_OUTPUT = xml 261 | XML_PROGRAMLISTING = YES 262 | 263 | #--------------------------------------------------------------------------- 264 | # DOCBOOK output 265 | #--------------------------------------------------------------------------- 266 | GENERATE_DOCBOOK = NO 267 | DOCBOOK_OUTPUT = docbook 268 | DOCBOOK_PROGRAMLISTING = YES 269 | 270 | #--------------------------------------------------------------------------- 271 | # AutoGen Definitions output 272 | #--------------------------------------------------------------------------- 273 | GENERATE_AUTOGEN_DEF = NO 274 | 275 | #--------------------------------------------------------------------------- 276 | # Perl module output 277 | #--------------------------------------------------------------------------- 278 | GENERATE_PERLMOD = NO 279 | PERLMOD_LATEX = NO 280 | PERLMOD_PRETTY = YES 281 | PERLMOD_MAKEVAR_PREFIX = 282 | 283 | #--------------------------------------------------------------------------- 284 | # Preprocessor 285 | #--------------------------------------------------------------------------- 286 | ENABLE_PREPROCESSING = YES 287 | MACRO_EXPANSION = NO 288 | EXPAND_ONLY_PREDEF = NO 289 | SEARCH_INCLUDES = YES 290 | INCLUDE_PATH = 291 | INCLUDE_FILE_PATTERNS = 292 | PREDEFINED = SMTP_OPENSSL SMTP_INTERNAL_DEFINE 293 | EXPAND_AS_DEFINED = 294 | SKIP_FUNCTION_MACROS = YES 295 | 296 | #--------------------------------------------------------------------------- 297 | # External references 298 | #--------------------------------------------------------------------------- 299 | TAGFILES = 300 | GENERATE_TAGFILE = 301 | ALLEXTERNALS = NO 302 | EXTERNAL_GROUPS = YES 303 | EXTERNAL_PAGES = YES 304 | PERL_PATH = /usr/bin/perl 305 | 306 | #--------------------------------------------------------------------------- 307 | # Dot tool 308 | #--------------------------------------------------------------------------- 309 | CLASS_DIAGRAMS = YES 310 | MSCGEN_PATH = 311 | DIA_PATH = 312 | HIDE_UNDOC_RELATIONS = YES 313 | HAVE_DOT = YES 314 | DOT_NUM_THREADS = 0 315 | DOT_FONTNAME = Helvetica 316 | DOT_FONTSIZE = 10 317 | DOT_FONTPATH = 318 | CLASS_GRAPH = YES 319 | COLLABORATION_GRAPH = YES 320 | GROUP_GRAPHS = YES 321 | UML_LOOK = YES 322 | UML_LIMIT_NUM_FIELDS = 0 323 | TEMPLATE_RELATIONS = YES 324 | INCLUDE_GRAPH = YES 325 | INCLUDED_BY_GRAPH = YES 326 | CALL_GRAPH = YES 327 | CALLER_GRAPH = YES 328 | GRAPHICAL_HIERARCHY = YES 329 | DIRECTORY_GRAPH = YES 330 | DOT_IMAGE_FORMAT = png 331 | INTERACTIVE_SVG = NO 332 | DOT_PATH = 333 | DOTFILE_DIRS = 334 | MSCFILE_DIRS = 335 | DIAFILE_DIRS = 336 | PLANTUML_JAR_PATH = 337 | PLANTUML_INCLUDE_PATH = 338 | DOT_GRAPH_MAX_NODES = 50 339 | MAX_DOT_GRAPH_DEPTH = 0 340 | DOT_TRANSPARENT = NO 341 | DOT_MULTI_TARGETS = YES 342 | GENERATE_LEGEND = YES 343 | DOT_CLEANUP = YES 344 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief Test the smtp-client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * This smtp-client testing framework has 100% branch coverage on POSIX 8 | * systems. It requires a Postfix SMTP server that supports all of the 9 | * connection security and authentication methods. These functional tests 10 | * also require the user to manually check and ensure that the destination 11 | * addresses received all of the test emails. 12 | * 13 | * This software has been placed into the public domain using CC0. 14 | * 15 | * @section test_seams_countdown_global 16 | * 17 | * The test harnesses control most of the test seams through the use of 18 | * global counter values. 19 | * 20 | * Setting a global counter to -1 will make the test seam function operate 21 | * as it normally would. If set to a positive value, the value will continue 22 | * to decrement every time the function gets called. When the counter reaches 23 | * 0, the test seam will force the function to return an error value. 24 | * 25 | * For example, initially setting the counter to 0 will force the test seam 26 | * to return an error condition the first time it gets called. Setting the 27 | * value to 1 initially will force the test seam to return an error condition 28 | * on the second time it gets called. 29 | */ 30 | #ifndef SMTP_TEST_H 31 | #define SMTP_TEST_H 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include "../src/smtp.h" 38 | 39 | #ifdef SMTP_OPENSSL 40 | # include 41 | # include 42 | # include 43 | # include 44 | # include 45 | #endif /* SMTP_OPENSSL */ 46 | 47 | struct smtp_command; 48 | struct str_getdelimfd; 49 | 50 | int 51 | smtp_si_add_size_t(const size_t a, 52 | const size_t b, 53 | size_t *const result); 54 | 55 | int 56 | smtp_si_sub_size_t(const size_t a, 57 | const size_t b, 58 | size_t *const result); 59 | 60 | int 61 | smtp_si_mul_size_t(const size_t a, 62 | const size_t b, 63 | size_t *const result); 64 | 65 | size_t 66 | smtp_base64_decode(const char *const buf, 67 | unsigned char **decode); 68 | 69 | char * 70 | smtp_base64_encode(const char *const buf, 71 | size_t buflen); 72 | 73 | char * 74 | smtp_bin2hex(const unsigned char *const s, 75 | size_t slen); 76 | 77 | enum smtp_status_code 78 | smtp_write(struct smtp *const smtp, 79 | const char *const buf, 80 | size_t len); 81 | 82 | int 83 | smtp_str_getdelimfd(struct str_getdelimfd *const gdfd); 84 | 85 | int 86 | smtp_str_getdelimfd_set_line_and_buf(struct str_getdelimfd *const gdfd, 87 | size_t copy_len); 88 | 89 | void 90 | smtp_str_getdelimfd_free(struct str_getdelimfd *const gdfd); 91 | 92 | char * 93 | smtp_stpcpy(char *s1, 94 | const char *s2); 95 | 96 | void * 97 | smtp_reallocarray(void *ptr, 98 | size_t nmemb, 99 | size_t size); 100 | 101 | char * 102 | smtp_strdup(const char *s); 103 | 104 | char * 105 | smtp_str_replace(const char *const search, 106 | const char *const replace, 107 | const char *const s); 108 | 109 | size_t 110 | smtp_utf8_charlen(char c); 111 | 112 | int 113 | smtp_str_has_nonascii_utf8(const char *const s); 114 | 115 | size_t 116 | smtp_strnlen_utf8(const char *s, 117 | size_t maxlen); 118 | 119 | size_t 120 | smtp_fold_whitespace_get_offset(const char *const s, 121 | unsigned int maxlen); 122 | 123 | char * 124 | smtp_fold_whitespace(const char *const s, 125 | unsigned int maxlen); 126 | 127 | char * 128 | smtp_chunk_split(const char *const s, 129 | size_t chunklen, 130 | const char *const end); 131 | 132 | char * 133 | smtp_ffile_get_contents(FILE *stream, 134 | size_t *bytes_read); 135 | 136 | char * 137 | smtp_file_get_contents(const char *const filename, 138 | size_t *bytes_read); 139 | 140 | int 141 | smtp_parse_cmd_line(char *const line, 142 | struct smtp_command *const cmd); 143 | 144 | int 145 | smtp_date_rfc_2822(char *const date); 146 | 147 | int 148 | smtp_address_validate_email(const char *const email); 149 | 150 | int 151 | smtp_address_validate_name(const char *const name); 152 | 153 | int 154 | smtp_attachment_validate_name(const char *const name); 155 | 156 | int 157 | smtp_header_key_validate(const char *const key); 158 | 159 | int 160 | smtp_header_value_validate(const char *const value); 161 | 162 | /* test seams */ 163 | int 164 | smtp_test_seam_dec_err_ctr(int *const test_err_ctr); 165 | 166 | BIO * 167 | smtp_test_seam_bio_new_socket(int sock, 168 | int close_flag); 169 | 170 | int 171 | smtp_test_seam_bio_should_retry(BIO *bio); 172 | 173 | void * 174 | smtp_test_seam_calloc(size_t nelem, 175 | size_t elsize); 176 | 177 | int 178 | smtp_test_seam_close(int fildes); 179 | 180 | int 181 | smtp_test_seam_connect(int socket, 182 | const struct sockaddr *address, 183 | socklen_t address_len); 184 | 185 | unsigned long 186 | smtp_test_seam_err_peek_error(void); 187 | 188 | int 189 | smtp_test_seam_fclose(FILE *stream); 190 | 191 | int 192 | smtp_test_seam_ferror(FILE *stream); 193 | 194 | struct tm * 195 | smtp_test_seam_gmtime_r(const time_t *timep, 196 | struct tm *result); 197 | 198 | unsigned char * 199 | smtp_test_seam_hmac(const EVP_MD *evp_md, 200 | const void *key, 201 | int key_len, 202 | const unsigned char *d, 203 | size_t n, 204 | unsigned char *md, 205 | unsigned int *md_len); 206 | 207 | struct tm * 208 | smtp_test_seam_localtime_r(const time_t *timep, 209 | struct tm *result); 210 | 211 | void * 212 | smtp_test_seam_malloc(size_t size); 213 | 214 | time_t 215 | smtp_test_seam_mktime(struct tm *timeptr); 216 | 217 | 218 | void * 219 | smtp_test_seam_realloc(void *ptr, 220 | size_t size); 221 | 222 | long 223 | smtp_test_seam_recv(int socket, 224 | void *buffer, 225 | size_t length, 226 | int flags); 227 | 228 | int 229 | smtp_test_seam_select(int nfds, 230 | fd_set *readfds, 231 | fd_set *writefds, 232 | fd_set *errorfds, 233 | struct timeval *timeout); 234 | 235 | ssize_t 236 | smtp_test_seam_send(int socket, 237 | const void *buffer, 238 | size_t length, 239 | int flags); 240 | 241 | int 242 | smtp_test_seam_socket(int domain, 243 | int type, 244 | int protocol); 245 | 246 | int 247 | smtp_test_seam_ssl_connect(SSL *ssl); 248 | 249 | SSL_CTX * 250 | smtp_test_seam_ssl_ctx_new(const SSL_METHOD *method); 251 | 252 | int 253 | smtp_test_seam_ssl_do_handshake(SSL *ssl); 254 | 255 | X509 * 256 | smtp_test_seam_ssl_get_peer_certificate(const SSL *ssl); 257 | 258 | int 259 | smtp_test_seam_x509_check_host(X509 *cert, 260 | const char *name, 261 | size_t namelen, 262 | unsigned int flags, 263 | char **peername); 264 | 265 | SSL * 266 | smtp_test_seam_ssl_new(SSL_CTX *ctx); 267 | 268 | int 269 | smtp_test_seam_ssl_read(SSL *ssl, 270 | void *buf, 271 | int num); 272 | 273 | int 274 | smtp_test_seam_ssl_write(SSL *ssl, 275 | const void *buf, 276 | int num); 277 | 278 | int 279 | smtp_test_seam_sprintf(char *s, 280 | const char *format, ...); 281 | 282 | size_t 283 | smtp_test_seam_strlen(const char *s); 284 | 285 | time_t 286 | smtp_test_seam_time(time_t *tloc); 287 | 288 | /** 289 | * Counter for @ref smtp_test_seam_bio_new_socket. 290 | * 291 | * See @ref test_seams_countdown_global for more details. 292 | */ 293 | extern int g_smtp_test_err_bio_new_socket_ctr; 294 | 295 | /** 296 | * Counter for @ref smtp_test_seam_bio_should_retry. 297 | * 298 | * See @ref test_seams_countdown_global for more details. 299 | */ 300 | extern int g_smtp_test_err_bio_should_retry_ctr; 301 | 302 | /** 303 | * Value to force the BIO_should_retry() function to return. 304 | * 305 | * This value will only get returned if 306 | * @ref g_smtp_test_err_bio_should_retry_ctr 307 | * has a value of 0 and this does not have a value of -1. 308 | */ 309 | extern int g_smtp_test_err_bio_should_retry_rc; 310 | 311 | /** 312 | * Counter for @ref smtp_test_seam_calloc. 313 | * 314 | * See @ref test_seams_countdown_global for more details. 315 | */ 316 | extern int g_smtp_test_err_calloc_ctr; 317 | 318 | /** 319 | * Counter for @ref smtp_test_seam_close. 320 | * 321 | * See @ref test_seams_countdown_global for more details. 322 | */ 323 | extern int g_smtp_test_err_close_ctr; 324 | 325 | /** 326 | * Counter for @ref smtp_test_seam_connect. 327 | * 328 | * See @ref test_seams_countdown_global for more details. 329 | */ 330 | extern int g_smtp_test_err_connect_ctr; 331 | 332 | /** 333 | * Counter for @ref smtp_test_seam_err_peek_error. 334 | * 335 | * See @ref test_seams_countdown_global for more details. 336 | */ 337 | extern int g_smtp_test_err_err_peek_error_ctr; 338 | 339 | /** 340 | * Counter for @ref smtp_test_seam_fclose. 341 | * 342 | * See @ref test_seams_countdown_global for more details. 343 | */ 344 | extern int g_smtp_test_err_fclose_ctr; 345 | 346 | /** 347 | * Counter for @ref smtp_test_seam_ferror. 348 | * 349 | * See @ref test_seams_countdown_global for more details. 350 | */ 351 | extern int g_smtp_test_err_ferror_ctr; 352 | 353 | /** 354 | * Counter for @ref smtp_test_seam_gmtime_r. 355 | * 356 | * See @ref test_seams_countdown_global for more details. 357 | */ 358 | extern int g_smtp_test_err_gmtime_r_ctr; 359 | 360 | /** 361 | * Counter for @ref smtp_test_seam_hmac. 362 | * 363 | * See @ref test_seams_countdown_global for more details. 364 | */ 365 | extern int g_smtp_test_err_hmac_ctr; 366 | 367 | /** 368 | * Counter for @ref smtp_test_seam_localtime_r. 369 | * 370 | * See @ref test_seams_countdown_global for more details. 371 | */ 372 | extern int g_smtp_test_err_localtime_r_ctr; 373 | 374 | /** 375 | * Counter for @ref smtp_test_seam_malloc. 376 | * 377 | * See @ref test_seams_countdown_global for more details. 378 | */ 379 | extern int g_smtp_test_err_malloc_ctr; 380 | 381 | /** 382 | * Counter for @ref smtp_test_seam_mktime. 383 | * 384 | * See @ref test_seams_countdown_global for more details. 385 | */ 386 | extern int g_smtp_test_err_mktime_ctr; 387 | 388 | /** 389 | * Counter for @ref smtp_test_seam_realloc. 390 | * 391 | * See @ref test_seams_countdown_global for more details. 392 | */ 393 | extern int g_smtp_test_err_realloc_ctr; 394 | 395 | /** 396 | * Counter for @ref smtp_test_seam_recv. 397 | * 398 | * See @ref test_seams_countdown_global for more details. 399 | */ 400 | extern int g_smtp_test_err_recv_ctr; 401 | 402 | /** 403 | * Value to force the recv() function to return. 404 | * 405 | * This value will only get returned if 406 | * @ref g_smtp_test_err_recv_ctr 407 | * has a value of 0 and this does not have a value of -1. 408 | */ 409 | extern int g_smtp_test_err_recv_rc; 410 | 411 | /** 412 | * Set the received bytes in recv() and SSL_read() to this value if it 413 | * contains a null-terminated string at least one bytes long. 414 | * 415 | * This makes it easier to inject a bad server response for testing the 416 | * smtp-client handling of those bad responses. 417 | * 418 | * See @ref test_seams_countdown_global for more details. 419 | */ 420 | extern char g_smtp_test_err_recv_bytes[90]; 421 | 422 | /** 423 | * Counter for @ref smtp_test_seam_select. 424 | * 425 | * See @ref test_seams_countdown_global for more details. 426 | */ 427 | extern int g_smtp_test_err_select_ctr; 428 | 429 | /** 430 | * Counter for @ref smtp_test_seam_send. 431 | * 432 | * See @ref test_seams_countdown_global for more details. 433 | */ 434 | extern int g_smtp_test_err_send_ctr; 435 | 436 | /** 437 | * Indicate if we should only send one byte at a time. 438 | */ 439 | extern int g_smtp_test_send_one_byte; 440 | 441 | /** 442 | * Counter for @ref smtp_si_add_size_t. 443 | * 444 | * See @ref test_seams_countdown_global for more details. 445 | */ 446 | extern int g_smtp_test_err_si_add_size_t_ctr; 447 | 448 | /** 449 | * Counter for @ref smtp_si_sub_size_t. 450 | * 451 | * See @ref test_seams_countdown_global for more details. 452 | */ 453 | extern int g_smtp_test_err_si_sub_size_t_ctr; 454 | 455 | /** 456 | * Counter for @ref smtp_si_mul_size_t. 457 | * 458 | * See @ref test_seams_countdown_global for more details. 459 | */ 460 | extern int g_smtp_test_err_si_mul_size_t_ctr; 461 | 462 | /** 463 | * Counter for @ref smtp_test_seam_socket. 464 | * 465 | * See @ref test_seams_countdown_global for more details. 466 | */ 467 | extern int g_smtp_test_err_socket_ctr; 468 | 469 | /** 470 | * Counter for @ref smtp_test_seam_ssl_connect. 471 | * 472 | * See @ref test_seams_countdown_global for more details. 473 | */ 474 | extern int g_smtp_test_err_ssl_connect_ctr; 475 | 476 | /** 477 | * Counter for @ref smtp_test_seam_ssl_ctx_new. 478 | * 479 | * See @ref test_seams_countdown_global for more details. 480 | */ 481 | extern int g_smtp_test_err_ssl_ctx_new_ctr; 482 | 483 | /** 484 | * Counter for @ref smtp_test_seam_ssl_do_handshake. 485 | * 486 | * See @ref test_seams_countdown_global for more details. 487 | */ 488 | extern int g_smtp_test_err_ssl_do_handshake_ctr; 489 | 490 | /** 491 | * Counter for @ref smtp_test_seam_ssl_get_peer_certificate. 492 | * 493 | * See @ref test_seams_countdown_global for more details. 494 | */ 495 | extern int g_smtp_test_err_ssl_get_peer_certificate_ctr; 496 | 497 | /** 498 | * Counter for @ref smtp_test_seam_x509_check_host. 499 | * 500 | * See @ref test_seams_countdown_global for more details. 501 | */ 502 | extern int g_smtp_test_err_x509_check_host_ctr; 503 | 504 | /** 505 | * Counter for @ref smtp_test_seam_ssl_new. 506 | * 507 | * See @ref test_seams_countdown_global for more details. 508 | */ 509 | extern int g_smtp_test_err_ssl_new_ctr; 510 | 511 | /** 512 | * Counter for @ref smtp_test_seam_ssl_read. 513 | * 514 | * See @ref test_seams_countdown_global for more details. 515 | */ 516 | extern int g_smtp_test_err_ssl_read_ctr; 517 | 518 | /** 519 | * Counter for @ref smtp_test_seam_ssl_write. 520 | * 521 | * See @ref test_seams_countdown_global for more details. 522 | */ 523 | extern int g_smtp_test_err_ssl_write_ctr; 524 | 525 | /** 526 | * Counter for @ref smtp_test_seam_sprintf. 527 | * 528 | * See @ref test_seams_countdown_global for more details. 529 | */ 530 | extern int g_smtp_test_err_sprintf_ctr; 531 | 532 | /** 533 | * Value to force the sprintf() function to return. 534 | * 535 | * This value will only get returned if @ref g_smtp_test_err_sprintf_ctr has 536 | * a value of 0. 537 | */ 538 | extern int g_smtp_test_err_sprintf_rc; 539 | 540 | /** 541 | * Indicates if the strlen() function should return a test value. 542 | * 543 | * This can get set to one of two values: 544 | * - 0 - The strlen() function will operate normally. 545 | * - !0 - The strlen() function will return the value specified in 546 | * @ref g_smtp_test_strlen_ret_value. 547 | */ 548 | extern int g_smtp_test_strlen_custom_ret; 549 | 550 | /** 551 | * Value to force the strlen() function to return. 552 | * 553 | * This value will only get returned if @ref g_smtp_test_strlen_custom_ret 554 | * has been set. 555 | */ 556 | extern size_t g_smtp_test_strlen_ret_value; 557 | 558 | /** 559 | * Indicates if the time() function should return a custom value. 560 | * 561 | * This can get set to one of two values: 562 | * - 0 - The time() function will operate normally. 563 | * - !0 - The time() function will return the value specified in 564 | * @ref g_smtp_test_time_ret_value. 565 | */ 566 | extern int g_smtp_test_time_custom_ret; 567 | 568 | /** 569 | * Value to force the time() function to return. 570 | * 571 | * This value will only get returned if @ref g_smtp_test_time_custom_ret has 572 | * a positive value. 573 | */ 574 | extern time_t g_smtp_test_time_ret_value; 575 | 576 | #endif /* SMTP_TEST_H */ 577 | 578 | -------------------------------------------------------------------------------- /src/mailx.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief POSIX mailx utility. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Implementation of POSIX mailx utility in send mode. 8 | * 9 | * mailx [-s subject] [[-S option]...] [[-a attachment]...] address... 10 | * 11 | * This software has been placed into the public domain using CC0. 12 | */ 13 | 14 | /** 15 | * Required on some POSIX systems to include some standard functions. 16 | */ 17 | #define _POSIX_C_SOURCE 200809L 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "smtp.h" 26 | 27 | /** 28 | * Stores the to and from email addresses. 29 | */ 30 | struct mailx_address{ 31 | /** 32 | * See @ref smtp_address_type. 33 | */ 34 | enum smtp_address_type address_type; 35 | 36 | /** 37 | * Email address. 38 | */ 39 | char email[1000]; 40 | }; 41 | 42 | /** 43 | * The attachment name and path stored for each attachment to send to the 44 | * recipient. 45 | */ 46 | struct mailx_attachment{ 47 | /** 48 | * File name for this attachment to display to the recipient. 49 | */ 50 | char name[1000]; 51 | 52 | /** 53 | * Local file path pointing to the attachment to send. 54 | */ 55 | char path[1000]; 56 | }; 57 | 58 | /** 59 | * The mailx context structure containing the parameters for setting up the 60 | * SMTP connection and sending the email. 61 | */ 62 | struct mailx{ 63 | /** 64 | * SMTP client context. 65 | */ 66 | struct smtp *smtp; 67 | 68 | /** 69 | * Email subject line. 70 | */ 71 | const char *subject; 72 | 73 | /** 74 | * Email body text. 75 | */ 76 | char *body; 77 | 78 | /** 79 | * SMTP server name or IP address. 80 | */ 81 | char *server; 82 | 83 | /** 84 | * SMTP server port number. 85 | */ 86 | char *port; 87 | 88 | /** 89 | * SMTP account user name used for authenticating. 90 | */ 91 | char *user; 92 | 93 | /** 94 | * SMTP account password used for authenticating. 95 | */ 96 | char *pass; 97 | 98 | /** 99 | * From email address or name. 100 | */ 101 | char *from; 102 | 103 | /** 104 | * Determine if using a TLS encrypted connection or plain socket. 105 | */ 106 | enum smtp_connection_security connection_security; 107 | 108 | /** 109 | * SMTP user account authentication method. 110 | */ 111 | enum smtp_authentication_method auth_method; 112 | 113 | /** 114 | * Miscellaneous control flags for smtp-lib. 115 | * 116 | * See @ref smtp_flag for more details. 117 | */ 118 | enum smtp_flag smtp_flags; 119 | 120 | /** 121 | * List of email addresses to send to. 122 | */ 123 | struct mailx_address *address_list; 124 | 125 | /** 126 | * Number of email addresses in @ref address_list. 127 | */ 128 | size_t num_address; 129 | 130 | /** 131 | * List of files to attach in the email. 132 | */ 133 | struct mailx_attachment *attachment_list; 134 | 135 | /** 136 | * Number of attachments in @ref attachment_list. 137 | */ 138 | size_t num_attachment; 139 | }; 140 | 141 | /** 142 | * Read the entire contents of a file stream and store the data into a 143 | * dynamically allocated buffer. 144 | * 145 | * @param[in] stream File stream already opened by the caller. 146 | * @param[out] bytes_read Number of bytes stored in the return buffer. 147 | * @retval char* A dynamically allocated buffer which contains the entire 148 | * contents of @p stream. The caller must free this memory 149 | * when done. 150 | * @retval NULL Memory allocation or file read error. 151 | */ 152 | static char * 153 | smtp_ffile_get_contents(FILE *stream, 154 | size_t *bytes_read){ 155 | char *read_buf; 156 | size_t bufsz; 157 | char *new_buf; 158 | const size_t BUFSZ_INCREMENT = 512; 159 | 160 | read_buf = NULL; 161 | bufsz = 0; 162 | 163 | if(bytes_read){ 164 | *bytes_read = 0; 165 | } 166 | 167 | do{ 168 | size_t bytes_read_loop; 169 | if((new_buf = realloc(read_buf, bufsz + BUFSZ_INCREMENT)) == NULL){ 170 | free(read_buf); 171 | return NULL; 172 | } 173 | read_buf = new_buf; 174 | bufsz += BUFSZ_INCREMENT; 175 | 176 | bytes_read_loop = fread(&read_buf[bufsz - BUFSZ_INCREMENT], 177 | sizeof(char), 178 | BUFSZ_INCREMENT, 179 | stream); 180 | if(bytes_read){ 181 | *bytes_read += bytes_read_loop; 182 | } 183 | if(ferror(stream)){ 184 | free(read_buf); 185 | return NULL; 186 | } 187 | } while(!feof(stream)); 188 | 189 | return read_buf; 190 | } 191 | 192 | /** 193 | * Append this email to the list of email addresses to send to. 194 | * 195 | * @param[in] mailx Append the email address into this mailx context. 196 | * @param[in] address_type See @ref smtp_address_type. 197 | * @param[in] email Email address to send to. 198 | */ 199 | static void 200 | mailx_address_append(struct mailx *const mailx, 201 | enum smtp_address_type address_type, 202 | const char *const email){ 203 | struct mailx_address *new_address; 204 | size_t new_address_list_sz; 205 | 206 | mailx->num_address += 1; 207 | new_address_list_sz = mailx->num_address * sizeof(*mailx->address_list); 208 | if((mailx->address_list = realloc(mailx->address_list, 209 | new_address_list_sz)) == NULL){ 210 | err(1, "realloc"); 211 | } 212 | 213 | new_address = &mailx->address_list[mailx->num_address - 1]; 214 | new_address->address_type = address_type; 215 | strncpy(new_address->email, email, sizeof(new_address->email)); 216 | new_address->email[sizeof(new_address->email) - 1] = '\0'; 217 | } 218 | 219 | /** 220 | * Send the email using the configuration options in the @p mailx context. 221 | * 222 | * @param[in] mailx Email context. 223 | */ 224 | static void 225 | mailx_send(struct mailx *const mailx){ 226 | int rc; 227 | size_t i; 228 | const struct mailx_address *address; 229 | const struct mailx_attachment *attachment; 230 | 231 | smtp_open(mailx->server, 232 | mailx->port, 233 | mailx->connection_security, 234 | mailx->smtp_flags, 235 | NULL, 236 | &mailx->smtp); 237 | 238 | smtp_auth(mailx->smtp, 239 | mailx->auth_method, 240 | mailx->user, 241 | mailx->pass); 242 | 243 | for(i = 0; i < mailx->num_address; i++){ 244 | address = &mailx->address_list[i]; 245 | smtp_address_add(mailx->smtp, address->address_type, address->email, NULL); 246 | } 247 | 248 | for(i = 0; i < mailx->num_attachment; i++){ 249 | attachment = &mailx->attachment_list[i]; 250 | smtp_attachment_add_path(mailx->smtp, attachment->name, attachment->path); 251 | } 252 | smtp_header_add(mailx->smtp, "Subject", mailx->subject); 253 | smtp_mail(mailx->smtp, mailx->body); 254 | 255 | rc = smtp_close(mailx->smtp); 256 | 257 | if(rc != SMTP_STATUS_OK){ 258 | errx(1, "%s", smtp_status_code_errstr(rc)); 259 | } 260 | } 261 | 262 | /** 263 | * Attach a file to the @p mailx context. 264 | * 265 | * @param[in] mailx Store the attachment details into this mailx context. 266 | * @param[in] filename File name to display to the recipient. 267 | * @param[in] path Local path of file to attach. 268 | */ 269 | static void 270 | mailx_append_attachment(struct mailx *const mailx, 271 | const char *const filename, 272 | const char *const path){ 273 | struct mailx_attachment *new_attachment; 274 | size_t new_attachment_list_sz; 275 | 276 | if(filename == NULL || path == NULL){ 277 | errx(1, "must provide attachment with valid name:path"); 278 | } 279 | 280 | new_attachment_list_sz = (mailx->num_attachment + 1) * 281 | sizeof(*mailx->attachment_list); 282 | if((mailx->attachment_list = realloc(mailx->attachment_list, 283 | new_attachment_list_sz)) == NULL){ 284 | err(1, "realloc: attachment list"); 285 | } 286 | new_attachment = &mailx->attachment_list[mailx->num_attachment]; 287 | mailx->num_attachment += 1; 288 | 289 | strncpy(new_attachment->name, filename, sizeof(new_attachment->name)); 290 | new_attachment->name[sizeof(new_attachment->name) - 1] = '\0'; 291 | 292 | strncpy(new_attachment->path, path, sizeof(new_attachment->path)); 293 | new_attachment->path[sizeof(new_attachment->path) - 1] = '\0'; 294 | } 295 | 296 | /** 297 | * Parse the file name and path and attach it to the @p mailx context. 298 | * 299 | * @param[in] mailx Store the attachment details into this mailx context. 300 | * @param[in] attach_arg String with format: 'filename:filepath'. 301 | */ 302 | static void 303 | mailx_append_attachment_arg(struct mailx *const mailx, 304 | const char *const attach_arg){ 305 | char *attach_arg_dup; 306 | char *filename; 307 | char *filepath; 308 | 309 | if((attach_arg_dup = strdup(attach_arg)) == NULL){ 310 | err(1, "strdup: %s", attach_arg); 311 | } 312 | 313 | filename = strtok(attach_arg_dup, ":"); 314 | filepath = strtok(NULL, ":"); 315 | 316 | mailx_append_attachment(mailx, filename, filepath); 317 | 318 | free(attach_arg_dup); 319 | } 320 | 321 | /** 322 | * Parses the -S option which contains a key/value pair separated by an '=' 323 | * character. 324 | * 325 | * @param[in] mailx Store the results of the option parsing into the relevant 326 | * field in this mailx context. 327 | * @param[in] option String containing key/value option to parse. 328 | */ 329 | static void 330 | mailx_parse_smtp_option(struct mailx *const mailx, 331 | const char *const option){ 332 | char *optdup; 333 | char *opt_key; 334 | char *opt_value; 335 | int rc; 336 | 337 | rc = 0; 338 | 339 | if((optdup = strdup(option)) == NULL){ 340 | err(1, "strdup: option: %s", option); 341 | } 342 | 343 | if((opt_key = strtok(optdup, "=")) == NULL){ 344 | errx(1, "strtok: %s", optdup); 345 | } 346 | 347 | opt_value = strtok(NULL, "="); 348 | 349 | if(strcmp(opt_key, "smtp-security") == 0){ 350 | if(strcmp(opt_value, "none") == 0){ 351 | mailx->connection_security = SMTP_SECURITY_NONE; 352 | } 353 | #ifdef SMTP_OPENSSL 354 | else if(strcmp(opt_value, "tls") == 0){ 355 | mailx->connection_security = SMTP_SECURITY_TLS; 356 | } 357 | else if(strcmp(opt_value, "starttls") == 0){ 358 | mailx->connection_security = SMTP_SECURITY_STARTTLS; 359 | } 360 | #endif /* SMTP_OPENSSL */ 361 | else{ 362 | rc = -1; 363 | } 364 | } 365 | else if(strcmp(opt_key, "smtp-auth") == 0){ 366 | if(strcmp(opt_value, "none") == 0){ 367 | mailx->auth_method = SMTP_AUTH_NONE; 368 | } 369 | else if(strcmp(opt_value, "plain") == 0){ 370 | mailx->auth_method = SMTP_AUTH_PLAIN; 371 | } 372 | else if(strcmp(opt_value, "login") == 0){ 373 | mailx->auth_method = SMTP_AUTH_LOGIN; 374 | } 375 | #ifdef SMTP_OPENSSL 376 | else if(strcmp(opt_value, "cram-md5") == 0){ 377 | mailx->auth_method = SMTP_AUTH_CRAM_MD5; 378 | } 379 | #endif /* SMTP_OPENSSL */ 380 | else{ 381 | rc = -1; 382 | } 383 | } 384 | else if(strcmp(opt_key, "smtp-flag") == 0){ 385 | if(strcmp(opt_value, "debug") == 0){ 386 | mailx->smtp_flags |= SMTP_DEBUG; 387 | } 388 | else if(strcmp(opt_value, "no-cert-verify") == 0){ 389 | mailx->smtp_flags |= SMTP_NO_CERT_VERIFY; 390 | } 391 | else{ 392 | rc = -1; 393 | } 394 | } 395 | else if(strcmp(opt_key, "smtp-server") == 0){ 396 | if((mailx->server = strdup(opt_value)) == NULL){ 397 | err(1, "strdup"); 398 | } 399 | } 400 | else if(strcmp(opt_key, "smtp-port") == 0){ 401 | if((mailx->port = strdup(opt_value)) == NULL){ 402 | err(1, "strdup"); 403 | } 404 | } 405 | else if(strcmp(opt_key, "smtp-user") == 0){ 406 | if((mailx->user = strdup(opt_value)) == NULL){ 407 | err(1, "strdup"); 408 | } 409 | } 410 | else if(strcmp(opt_key, "smtp-pass") == 0){ 411 | if((mailx->pass = strdup(opt_value)) == NULL){ 412 | err(1, "strdup"); 413 | } 414 | } 415 | else if(strcmp(opt_key, "smtp-from") == 0){ 416 | if((mailx->from = strdup(opt_value)) == NULL){ 417 | err(1, "strdup"); 418 | } 419 | } 420 | else{ 421 | rc = -1; 422 | } 423 | 424 | free(optdup); 425 | 426 | if(rc < 0){ 427 | errx(1, "invalid argument: %s", option); 428 | } 429 | } 430 | 431 | /** 432 | * Initialize and set the default options in the mailx context. 433 | * 434 | * See description of -S argument in main for more details. 435 | * 436 | * @param[in] mailx The mailx content to initialize. 437 | */ 438 | static void 439 | mailx_init_default_values(struct mailx *const mailx){ 440 | memset(mailx, 0, sizeof(*mailx)); 441 | mailx->subject = ""; 442 | mailx->connection_security = SMTP_SECURITY_NONE; 443 | mailx->auth_method = SMTP_AUTH_NONE; 444 | } 445 | 446 | /** 447 | * Frees the allocated memory associated with the mailx context. 448 | * 449 | * @param[in] mailx The mailx context to free. 450 | */ 451 | static void 452 | mailx_free(const struct mailx *const mailx){ 453 | free(mailx->body); 454 | free(mailx->server); 455 | free(mailx->port); 456 | free(mailx->user); 457 | free(mailx->pass); 458 | free(mailx->from); 459 | } 460 | 461 | /** 462 | * Main program entry point for the mailx utility. 463 | * 464 | * This program supports the following options: 465 | * - -a 'name:path' - Attach a file with name to display to recipient and 466 | * file path pointing to file location on local storage. 467 | * - -s subject - Email subject line. 468 | * - -S key=value - A key/value pair to set various configuration options, 469 | * controlling the behavior of the SMTP client connection. 470 | * 471 | * The following list contains possible options for the -S argument: 472 | * - smtp-security - none, tls, starttls 473 | * - smtp-auth - none, plain, login, cram-md5 474 | * - smtp-flag - debug, no-cert-verify 475 | * - smtp-server - server name or IP address 476 | * - smtp-port - server port number 477 | * - smtp-user - server authentication user name 478 | * - smtp-pass - server authentication user password 479 | * - smtp-from - from email account 480 | * 481 | * The following list shows the default option for -S argument if not provided: 482 | * - smtp-security - none 483 | * - smtp-auth - none 484 | * - smtp-flag - none 485 | * - smtp-server - localhost 486 | * - smtp-port - 25 487 | * - smtp-user - none 488 | * - smtp-pass - none 489 | * - smtp-from - none 490 | * 491 | * @param[in] argc Number of arguments in @p argv. 492 | * @param[in] argv String array containing the program name and any optional 493 | * parameters described above. 494 | * @retval 0 Email has been sent. 495 | * @retval 1 An error occurred while sending email. Although unlikely, an email 496 | * can still get sent even after returning with this error code. 497 | */ 498 | int main(int argc, char *argv[]){ 499 | int rc; 500 | int i; 501 | struct mailx mailx; 502 | 503 | mailx_init_default_values(&mailx); 504 | 505 | while((rc = getopt(argc, argv, "a:s:S:")) != -1){ 506 | switch(rc){ 507 | case 'a': 508 | mailx_append_attachment_arg(&mailx, optarg); 509 | break; 510 | case 's': 511 | mailx.subject = optarg; 512 | break; 513 | case 'S': 514 | mailx_parse_smtp_option(&mailx, optarg); 515 | break; 516 | default: 517 | return 1; 518 | } 519 | } 520 | argc -= optind; 521 | argv += optind; 522 | 523 | if(argc < 1){ 524 | errx(1, "must provide at least one email destination address"); 525 | } 526 | 527 | if(mailx.from == NULL){ 528 | errx(1, "must provide a FROM address"); 529 | } 530 | 531 | if(mailx.server == NULL){ 532 | if((mailx.server = strdup("localhost")) == NULL){ 533 | err(1, "strdup"); 534 | } 535 | } 536 | 537 | if(mailx.port == NULL){ 538 | if((mailx.port = strdup("25")) == NULL){ 539 | err(1, "strdup"); 540 | } 541 | } 542 | 543 | puts("Reading email body from stdin"); 544 | if((mailx.body = smtp_ffile_get_contents(stdin, NULL)) == NULL){ 545 | err(1, "failed to read email body from stdin"); 546 | } 547 | 548 | mailx_address_append(&mailx, SMTP_ADDRESS_FROM, mailx.from); 549 | 550 | for(i = 0; i < argc; i++){ 551 | mailx_address_append(&mailx, SMTP_ADDRESS_TO, argv[i]); 552 | } 553 | 554 | mailx_send(&mailx); 555 | mailx_free(&mailx); 556 | return 0; 557 | } 558 | 559 | -------------------------------------------------------------------------------- /src/smtp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief SMTP client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * This SMTP client library allows the user to send emails to an SMTP server. 8 | * The user can include custom headers and MIME attachments. 9 | * 10 | * This software has been placed into the public domain using CC0. 11 | */ 12 | #ifndef SMTP_H 13 | #define SMTP_H 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef SIZE_MAX 20 | /** 21 | * Maximum value of size_t type. 22 | */ 23 | # define SIZE_MAX ((size_t)(-1)) 24 | #endif /* SIZE_MAX */ 25 | 26 | /** 27 | * Status codes indicating success or failure from calling any of the 28 | * SMTP library functions. 29 | * 30 | * This code gets returned by all functions in this header. 31 | */ 32 | enum smtp_status_code{ 33 | /** 34 | * Successful operation completed. 35 | */ 36 | SMTP_STATUS_OK = 0, 37 | 38 | /** 39 | * Memory allocation failed. 40 | */ 41 | SMTP_STATUS_NOMEM = 1, 42 | 43 | /** 44 | * Failed to connect to the mail server. 45 | */ 46 | SMTP_STATUS_CONNECT = 2, 47 | 48 | /** 49 | * Failed to handshake or negotiate a TLS connection with the server. 50 | */ 51 | SMTP_STATUS_HANDSHAKE = 3, 52 | 53 | /** 54 | * Failed to authenticate with the given credentials. 55 | */ 56 | SMTP_STATUS_AUTH = 4, 57 | 58 | /** 59 | * Failed to send bytes to the server. 60 | */ 61 | SMTP_STATUS_SEND = 5, 62 | 63 | /** 64 | * Failed to receive bytes from the server. 65 | */ 66 | SMTP_STATUS_RECV = 6, 67 | 68 | /** 69 | * Failed to properly close a connection. 70 | */ 71 | SMTP_STATUS_CLOSE = 7, 72 | 73 | /** 74 | * SMTP server sent back an unexpected status code. 75 | */ 76 | SMTP_STATUS_SERVER_RESPONSE = 8, 77 | 78 | /** 79 | * Invalid parameter. 80 | */ 81 | SMTP_STATUS_PARAM = 9, 82 | 83 | /** 84 | * Failed to open or read a local file. 85 | */ 86 | SMTP_STATUS_FILE = 10, 87 | 88 | /** 89 | * Failed to get the local date and time. 90 | */ 91 | SMTP_STATUS_DATE = 11, 92 | 93 | /** 94 | * Indicates the last status code in the enumeration, useful for 95 | * bounds checking. 96 | * 97 | * Not a valid status code. 98 | */ 99 | SMTP_STATUS__LAST 100 | }; 101 | 102 | /** 103 | * Address source and destination types. 104 | */ 105 | enum smtp_address_type{ 106 | /** 107 | * From address. 108 | */ 109 | SMTP_ADDRESS_FROM = 0, 110 | 111 | /** 112 | * To address. 113 | */ 114 | SMTP_ADDRESS_TO = 1, 115 | 116 | /** 117 | * Copy address. 118 | */ 119 | SMTP_ADDRESS_CC = 2, 120 | 121 | /** 122 | * Blind copy address. 123 | * 124 | * Recipients should not see any of the BCC addresses when they receive 125 | * their email. However, some SMTP server implementations may copy this 126 | * information into the mail header, so do not assume that this will 127 | * always get hidden. If the BCC addresses must not get shown to the 128 | * receivers, then send one separate email to each BCC party and add 129 | * the TO and CC addresses manually as a header property using 130 | * @ref smtp_header_add instead of as an address using 131 | * @ref smtp_address_add. 132 | */ 133 | SMTP_ADDRESS_BCC = 3 134 | }; 135 | 136 | /** 137 | * Connect to the SMTP server using either an unencrypted socket or 138 | * TLS encryption. 139 | */ 140 | enum smtp_connection_security{ 141 | #ifdef SMTP_OPENSSL 142 | /** 143 | * First connect without encryption, then negotiate an encrypted connection 144 | * by issuing a STARTTLS command. 145 | * 146 | * Typically used when connecting to a mail server on port 25 or 587. 147 | */ 148 | SMTP_SECURITY_STARTTLS = 0, 149 | 150 | /** 151 | * Use TLS when initially connecting to server. 152 | * 153 | * Typically used when connecting to a mail server on port 465. 154 | */ 155 | SMTP_SECURITY_TLS = 1, 156 | #endif /* SMTP_OPENSSL */ 157 | 158 | /** 159 | * Do not use TLS encryption. 160 | * 161 | * Not recommended unless connecting to the SMTP server locally. 162 | */ 163 | SMTP_SECURITY_NONE = 2 164 | }; 165 | 166 | /** 167 | * List of supported methods for authenticating a mail user account on 168 | * the server. 169 | */ 170 | enum smtp_authentication_method{ 171 | #ifdef SMTP_OPENSSL 172 | /** 173 | * Use HMAC-MD5. 174 | */ 175 | SMTP_AUTH_CRAM_MD5 = 0, 176 | #endif /* SMTP_OPENSSL */ 177 | /** 178 | * No authentication required. 179 | * 180 | * Some servers support this option if connecting locally. 181 | */ 182 | SMTP_AUTH_NONE = 1, 183 | 184 | /** 185 | * Authenticate using base64 user and password. 186 | */ 187 | SMTP_AUTH_PLAIN = 2, 188 | 189 | /** 190 | * Another base64 authentication method, similar to SMTP_AUTH_PLAIN. 191 | */ 192 | SMTP_AUTH_LOGIN = 3 193 | }; 194 | 195 | /** 196 | * Special flags defining certain behaviors for the SMTP client context. 197 | */ 198 | enum smtp_flag{ 199 | /** 200 | * Print client and server communication on stderr. 201 | */ 202 | SMTP_DEBUG = 1 << 0, 203 | 204 | /** 205 | * Do not verify TLS certificate. 206 | * 207 | * By default, the TLS handshake function will check if a certificate 208 | * has expired or if using a self-signed certificate. Either of those 209 | * conditions will cause the connection to fail. This option allows the 210 | * connection to proceed even if those checks fail. 211 | */ 212 | SMTP_NO_CERT_VERIFY = 1 << 1 213 | }; 214 | 215 | struct smtp; 216 | 217 | #ifdef __cplusplus 218 | extern "C" { 219 | #endif /* __cplusplus */ 220 | 221 | /** 222 | * Open a connection to an SMTP server and return the context. 223 | * 224 | * After successfully connecting and performing a handshake with the 225 | * SMTP server, this will return a valid SMTP client context that 226 | * the application can use in the other library functions. 227 | * 228 | * This always returns a valid SMTP client context even if 229 | * the server connection or memory allocation fails. In this scenario, the 230 | * error status will continue to propagate to future library calls for 231 | * the SMTP context while in this failure mode. 232 | * 233 | * This function will ignore the SIGPIPE signal. Applications that require a 234 | * handler for that signal should set it up after calling this function. 235 | * 236 | * @param[in] server Server name or IP address. 237 | * @param[in] port Server port number. 238 | * @param[in] connection_security See @ref smtp_connection_security. 239 | * @param[in] flags See @ref smtp_flag. 240 | * @param[in] cafile Path to certificate file, or NULL to use 241 | * certificates in the default path. 242 | * @param[out] smtp Pointer to a new SMTP context. When 243 | * finished, the caller must free this 244 | * context using @ref smtp_close. 245 | * @return See @ref smtp_status_code. 246 | */ 247 | enum smtp_status_code 248 | smtp_open(const char *const server, 249 | const char *const port, 250 | enum smtp_connection_security connection_security, 251 | enum smtp_flag flags, 252 | const char *const cafile, 253 | struct smtp **smtp); 254 | 255 | /** 256 | * Authenticate the user using one of the methods listed in 257 | * @ref smtp_authentication_method. 258 | * 259 | * @param[in] smtp SMTP client context. 260 | * @param[in] auth_method See @ref smtp_authentication_method. 261 | * @param[in] user SMTP user name. 262 | * @param[in] pass SMTP password. 263 | * @return See @ref smtp_status_code. 264 | */ 265 | enum smtp_status_code 266 | smtp_auth(struct smtp *const smtp, 267 | enum smtp_authentication_method auth_method, 268 | const char *const user, 269 | const char *const pass); 270 | 271 | /** 272 | * Sends an email using the addresses, attachments, and headers defined 273 | * in the current SMTP context. 274 | * 275 | * The caller must call the @ref smtp_open function prior to this. 276 | * 277 | * The 'Date' header will automatically get generated here if it hasn't 278 | * already been set using @ref smtp_header_add. 279 | * 280 | * If the application overrides the default 'Content-Type' header, then 281 | * this function will output the @p body as raw data just below the email 282 | * headers, and it will not output the attachments added using the 283 | * smtp_attachment_add_* functions. In other words, the application must 284 | * create its own MIME sections (if needed) when overriding the 285 | * 'Content-Type' header. 286 | * 287 | * @param[in] smtp SMTP client context. 288 | * @param[in] body Null-terminated string to send in the email body. 289 | * @return See @ref smtp_status_code. 290 | */ 291 | enum smtp_status_code 292 | smtp_mail(struct smtp *const smtp, 293 | const char *const body); 294 | 295 | /** 296 | * Close the SMTP connection and frees all resources held by the 297 | * SMTP context. 298 | * 299 | * @param[in] smtp SMTP client context. 300 | * @return See @ref smtp_status_code. 301 | */ 302 | enum smtp_status_code 303 | smtp_close(struct smtp *smtp); 304 | 305 | /** 306 | * Get the current status/error code. 307 | * 308 | * @param[in] smtp SMTP client context. 309 | * @return See @ref smtp_status_code. 310 | */ 311 | enum smtp_status_code 312 | smtp_status_code_get(const struct smtp *const smtp); 313 | 314 | /** 315 | * Clear the current error code set in the SMTP client context. 316 | * 317 | * @param[in,out] smtp SMTP client context. 318 | * @return Previous error code before clearing. 319 | */ 320 | enum smtp_status_code 321 | smtp_status_code_clear(struct smtp *const smtp); 322 | 323 | /** 324 | * Set the error status of the SMTP client context and return the same code. 325 | * 326 | * This allows the caller to clear an error status to SMTP_STATUS_OK 327 | * so that previous errors will stop propagating. However, this will only 328 | * work correctly for clearing the SMTP_STATUS_PARAM and SMTP_STATUS_FILE 329 | * errors. Do not use this to clear any other error codes. 330 | * 331 | * @deprecated Use @ref smtp_status_code_clear instead. 332 | * 333 | * @param[in] smtp SMTP client context. 334 | * @param[in] new_status_code See @ref smtp_status_code. 335 | * @return See @ref smtp_status_code. 336 | */ 337 | enum smtp_status_code 338 | smtp_status_code_set(struct smtp *const smtp, 339 | enum smtp_status_code new_status_code); 340 | 341 | /** 342 | * Convert a standard SMTP client status code to a descriptive string. 343 | * 344 | * @param[in] status_code Status code returned from one of the other 345 | * library functions. 346 | * @return String containing a description of the @p status_code. The caller 347 | * must not free or modify this string. 348 | */ 349 | const char * 350 | smtp_status_code_errstr(enum smtp_status_code status_code); 351 | 352 | /** 353 | * Add a key/value header to the header list in the SMTP context. 354 | * 355 | * If adding a header with an existing key, this will insert instead of 356 | * replacing the existing header. See @ref smtp_header_clear_all. 357 | * 358 | * See @ref smtp_mail when overriding the default 'Content-Type' header. 359 | * 360 | * @param[in] smtp SMTP client context. 361 | * @param[in] key Key name for new header. It must consist only of 362 | * printable US-ASCII characters except colon. 363 | * @param[in] value Value for new header. It must consist only of printable 364 | * US-ASCII, space, or horizontal tab. If set to NULL, 365 | * this will prevent the header from printing out. 366 | * @return See @ref smtp_status_code. 367 | */ 368 | enum smtp_status_code 369 | smtp_header_add(struct smtp *const smtp, 370 | const char *const key, 371 | const char *const value); 372 | 373 | /** 374 | * Free all memory related to email headers. 375 | * 376 | * @param[in] smtp SMTP client context. 377 | */ 378 | void 379 | smtp_header_clear_all(struct smtp *const smtp); 380 | 381 | /** 382 | * Add a FROM, TO, CC, or BCC address destination to this SMTP context. 383 | * 384 | * @note Some SMTP servers may reject over 100 recipients. 385 | * 386 | * @param[in] smtp SMTP client context. 387 | * @param[in] type See @ref smtp_address_type. 388 | * @param[in] email The email address of the party. Must consist only of 389 | * printable characters excluding the angle brackets 390 | * (<) and (>). 391 | * @param[in] name Name or description of the party. Must consist only of 392 | * printable characters, excluding the quote characters. If 393 | * set to NULL or empty string, no name will get associated 394 | * with this email. 395 | * @return See @ref smtp_status_code. 396 | */ 397 | enum smtp_status_code 398 | smtp_address_add(struct smtp *const smtp, 399 | enum smtp_address_type type, 400 | const char *const email, 401 | const char *const name); 402 | 403 | /** 404 | * Free all memory related to the address list. 405 | * 406 | * @param[in] smtp SMTP client context. 407 | */ 408 | void 409 | smtp_address_clear_all(struct smtp *const smtp); 410 | 411 | /** 412 | * Add a file attachment from a path. 413 | * 414 | * See @ref smtp_attachment_add_mem for more details. 415 | * 416 | * @param[in] smtp SMTP client context. 417 | * @param[in] name Filename of the attachment shown to recipients. Must 418 | * consist only of printable ASCII characters, excluding 419 | * the quote characters (') and ("). 420 | * @param[in] path Path to file. 421 | * @return See @ref smtp_status_code. 422 | */ 423 | enum smtp_status_code 424 | smtp_attachment_add_path(struct smtp *const smtp, 425 | const char *const name, 426 | const char *const path); 427 | 428 | /** 429 | * Add an attachment using a file pointer. 430 | * 431 | * See @ref smtp_attachment_add_mem for more details. 432 | * 433 | * @param[in] smtp SMTP client context. 434 | * @param[in] name Filename of the attachment shown to recipients. Must 435 | * consist only of printable ASCII characters, excluding 436 | * the quote characters (') and ("). 437 | * @param[in] fp File pointer already opened by the caller. 438 | * @return See @ref smtp_status_code. 439 | */ 440 | enum smtp_status_code 441 | smtp_attachment_add_fp(struct smtp *const smtp, 442 | const char *const name, 443 | FILE *fp); 444 | 445 | /** 446 | * Add a MIME attachment to this SMTP context with the data retrieved 447 | * from memory. 448 | * 449 | * The attachment data will get base64 encoded before sending to the server. 450 | * 451 | * @param[in] smtp SMTP client context. 452 | * @param[in] name Filename of the attachment shown to recipients. Must 453 | * consist only of printable ASCII characters, excluding 454 | * the quote characters (') and ("). 455 | * @param[in] data Raw attachment data stored in memory. 456 | * @param[in] datasz Number of bytes in @p data, or -1 if data 457 | * null-terminated. 458 | * @return See @ref smtp_status_code. 459 | */ 460 | enum smtp_status_code 461 | smtp_attachment_add_mem(struct smtp *const smtp, 462 | const char *const name, 463 | const void *const data, 464 | size_t datasz); 465 | 466 | /** 467 | * Remove all attachments from the SMTP client context. 468 | * 469 | * @param[in] smtp SMTP client context. 470 | */ 471 | void 472 | smtp_attachment_clear_all(struct smtp *const smtp); 473 | 474 | 475 | /* 476 | * The SMTP_INTERNAL DEFINE section contains definitions that get used 477 | * internally by the SMTP client library. 478 | */ 479 | #ifdef SMTP_INTERNAL_DEFINE 480 | /** 481 | * SMTP codes returned by the server and parsed by the client. 482 | */ 483 | enum smtp_result_code{ 484 | /** 485 | * Client error code which does not get set by the server. 486 | */ 487 | SMTP_INTERNAL_ERROR = -1, 488 | 489 | /** 490 | * Returned when ready to begin processing next step. 491 | */ 492 | SMTP_READY = 220, 493 | 494 | /** 495 | * Returned in response to QUIT. 496 | */ 497 | SMTP_CLOSE = 221, 498 | 499 | /** 500 | * Returned if client successfully authenticates. 501 | */ 502 | SMTP_AUTH_SUCCESS = 235, 503 | 504 | /** 505 | * Returned when some commands successfully complete. 506 | */ 507 | SMTP_DONE = 250, 508 | 509 | /** 510 | * Returned for some multi-line authentication mechanisms where this code 511 | * indicates the next stage in the authentication step. 512 | */ 513 | SMTP_AUTH_CONTINUE = 334, 514 | 515 | /** 516 | * Returned in response to DATA command. 517 | */ 518 | SMTP_BEGIN_MAIL = 354 519 | }; 520 | 521 | /** 522 | * Used for parsing out the responses from the SMTP server. 523 | * 524 | * For example, if the server sends back '250-STARTTLS', then code would 525 | * get set to 250, more would get set to 1, and text would get set to STARTTLS. 526 | */ 527 | struct smtp_command{ 528 | /** 529 | * Result code converted to an integer. 530 | */ 531 | enum smtp_result_code code; 532 | 533 | /** 534 | * Indicates if more server commands follow. 535 | * 536 | * This will get set to 1 if the fourth character in the response line 537 | * contains a '-', otherwise this will get set to 0. 538 | */ 539 | int more; 540 | 541 | /** 542 | * The text shown after the status code. 543 | */ 544 | const char *text; 545 | }; 546 | 547 | /** 548 | * Return codes for the getdelim interface which allows the caller to check 549 | * if more delimited lines can get processed. 550 | */ 551 | enum str_getdelim_retcode{ 552 | /** 553 | * An error occurred during the getdelim processing. 554 | */ 555 | STRING_GETDELIMFD_ERROR = -1, 556 | 557 | /** 558 | * Found a new line and can process more lines in the next call. 559 | */ 560 | STRING_GETDELIMFD_NEXT = 0, 561 | 562 | /** 563 | * Found a new line and unable to read any more lines at this time. 564 | */ 565 | STRING_GETDELIMFD_DONE = 1 566 | }; 567 | 568 | /** 569 | * Data structure for read buffer and line parsing. 570 | * 571 | * This assists with getting and parsing the server response lines. 572 | */ 573 | struct str_getdelimfd{ 574 | /** 575 | * Read buffer which may include bytes past the delimiter. 576 | */ 577 | char *_buf; 578 | 579 | /** 580 | * Number of allocated bytes in the read buffer. 581 | */ 582 | size_t _bufsz; 583 | 584 | /** 585 | * Number of actual stored bytes in the read buffer. 586 | */ 587 | size_t _buf_len; 588 | 589 | /** 590 | * Current line containing the text up to the delimiter. 591 | */ 592 | char *line; 593 | 594 | /** 595 | * Number of stored bytes in the line buffer. 596 | */ 597 | size_t line_len; 598 | 599 | /** 600 | * Function pointer to a custom read function for the 601 | * @ref smtp_str_getdelimfd interface. 602 | * 603 | * This function prototype has similar semantics to the read function. 604 | * The @p gdfd parameter allows the custom function to pull the user_data 605 | * info from the @ref str_getdelimfd struct which can contain file pointer, 606 | * socket connection, etc. 607 | */ 608 | long (*getdelimfd_read)(struct str_getdelimfd *const gdfd, 609 | void *buf, 610 | size_t count); 611 | 612 | /** 613 | * User data which gets sent to the read handler function. 614 | */ 615 | void *user_data; 616 | 617 | /** 618 | * Character delimiter used for determining line separation. 619 | */ 620 | int delim; 621 | 622 | /** 623 | * Padding structure to align. 624 | */ 625 | char pad[4]; 626 | }; 627 | 628 | #endif /* SMTP_INTERNAL_DEFINE */ 629 | 630 | #ifdef __cplusplus 631 | } 632 | #endif /* __cplusplus */ 633 | 634 | #endif /* SMTP_H */ 635 | 636 | -------------------------------------------------------------------------------- /test/seams.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief Test seams for the smtp-client library. 4 | * @author James Humphrey (mail@somnisoft.com) 5 | * @version 1.00 6 | * 7 | * Used by the smtp-client testing framework to inject specific return values 8 | * by some standard library functions. This makes it possible to test less 9 | * common errors like out of memory conditions and input/output errors. 10 | * 11 | * This software has been placed into the public domain using CC0. 12 | */ 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "test.h" 23 | 24 | /** 25 | * See @ref g_smtp_test_err_bio_new_socket_ctr and 26 | * @ref test_seams_countdown_global. 27 | */ 28 | int g_smtp_test_err_bio_new_socket_ctr = -1; 29 | 30 | /** 31 | * See @ref g_smtp_test_err_bio_should_retry_ctr and 32 | * @ref test_seams_countdown_global. 33 | */ 34 | int g_smtp_test_err_bio_should_retry_ctr = -1; 35 | 36 | /** 37 | * See @ref g_smtp_test_err_bio_should_retry_rc. 38 | */ 39 | int g_smtp_test_err_bio_should_retry_rc = -1; 40 | 41 | /** 42 | * See @ref g_smtp_test_err_calloc_ctr and @ref test_seams_countdown_global. 43 | */ 44 | int g_smtp_test_err_calloc_ctr = -1; 45 | 46 | /** 47 | * See @ref g_smtp_test_err_close_ctr and @ref test_seams_countdown_global. 48 | */ 49 | int g_smtp_test_err_close_ctr = -1; 50 | 51 | /** 52 | * See @ref g_smtp_test_err_connect_ctr and @ref test_seams_countdown_global. 53 | */ 54 | int g_smtp_test_err_connect_ctr = -1; 55 | 56 | /** 57 | * See @ref g_smtp_test_err_err_peek_error_ctr and 58 | * @ref test_seams_countdown_global. 59 | */ 60 | int g_smtp_test_err_err_peek_error_ctr = -1; 61 | 62 | /** 63 | * See @ref g_smtp_test_err_fclose_ctr and @ref test_seams_countdown_global. 64 | */ 65 | int g_smtp_test_err_fclose_ctr = -1; 66 | 67 | /** 68 | * See @ref g_smtp_test_err_ferror_ctr and @ref test_seams_countdown_global. 69 | */ 70 | int g_smtp_test_err_ferror_ctr = -1; 71 | 72 | /** 73 | * See @ref g_smtp_test_err_gmtime_r_ctr @ref test_seams_countdown_global. 74 | */ 75 | int g_smtp_test_err_gmtime_r_ctr = -1; 76 | 77 | /** 78 | * See @ref g_smtp_test_err_hmac_ctr @ref test_seams_countdown_global. 79 | */ 80 | int g_smtp_test_err_hmac_ctr = -1; 81 | 82 | /** 83 | * See @ref g_smtp_test_err_localtime_r_ctr and 84 | * @ref test_seams_countdown_global. 85 | */ 86 | int g_smtp_test_err_localtime_r_ctr = -1; 87 | 88 | /** 89 | * See @ref g_smtp_test_err_malloc_ctr and @ref test_seams_countdown_global. 90 | */ 91 | int g_smtp_test_err_malloc_ctr = -1; 92 | 93 | /** 94 | * See @ref g_smtp_test_err_mktime_ctr and @ref test_seams_countdown_global. 95 | */ 96 | int g_smtp_test_err_mktime_ctr = -1; 97 | 98 | /** 99 | * See @ref g_smtp_test_err_realloc_ctr and @ref test_seams_countdown_global. 100 | */ 101 | int g_smtp_test_err_realloc_ctr = -1; 102 | 103 | /** 104 | * See @ref g_smtp_test_err_recv_ctr and @ref test_seams_countdown_global. 105 | */ 106 | int g_smtp_test_err_recv_ctr = -1; 107 | 108 | /** 109 | * See @ref g_smtp_test_err_recv_rc. 110 | */ 111 | int g_smtp_test_err_recv_rc = -1; 112 | 113 | /** 114 | * See @ref g_smtp_test_err_recv_bytes and @ref test_seams_countdown_global. 115 | */ 116 | char g_smtp_test_err_recv_bytes[90] = {0}; 117 | 118 | /** 119 | * See @ref g_smtp_test_err_select_ctr and @ref test_seams_countdown_global. 120 | */ 121 | int g_smtp_test_err_select_ctr = -1; 122 | 123 | /** 124 | * See @ref g_smtp_test_err_send_ctr and @ref test_seams_countdown_global. 125 | */ 126 | int g_smtp_test_err_send_ctr = -1; 127 | 128 | /** 129 | * See @ref g_smtp_test_send_one_byte. 130 | */ 131 | int g_smtp_test_send_one_byte = 0; 132 | 133 | /** 134 | * See @ref g_smtp_test_err_si_add_size_t_ctr 135 | * and @ref test_seams_countdown_global. 136 | */ 137 | int g_smtp_test_err_si_add_size_t_ctr = -1; 138 | 139 | /** 140 | * See @ref g_smtp_test_err_si_sub_size_t_ctr 141 | * and @ref test_seams_countdown_global. 142 | */ 143 | int g_smtp_test_err_si_sub_size_t_ctr = -1; 144 | 145 | /** 146 | * See @ref g_smtp_test_err_si_mul_size_t_ctr 147 | * and @ref test_seams_countdown_global. 148 | */ 149 | int g_smtp_test_err_si_mul_size_t_ctr = -1; 150 | 151 | /** 152 | * See @ref g_smtp_test_err_socket_ctr and @ref test_seams_countdown_global. 153 | */ 154 | int g_smtp_test_err_socket_ctr = -1; 155 | 156 | /** 157 | * See @ref g_smtp_test_err_ssl_connect_ctr and 158 | * @ref test_seams_countdown_global. 159 | */ 160 | int g_smtp_test_err_ssl_connect_ctr = -1; 161 | 162 | /** 163 | * See @ref g_smtp_test_err_ssl_ctx_new_ctr and 164 | * @ref test_seams_countdown_global. 165 | */ 166 | int g_smtp_test_err_ssl_ctx_new_ctr = -1; 167 | 168 | /** 169 | * See @ref g_smtp_test_err_ssl_do_handshake_ctr and 170 | * @ref test_seams_countdown_global. 171 | */ 172 | int g_smtp_test_err_ssl_do_handshake_ctr = -1; 173 | 174 | /** 175 | * See @ref g_smtp_test_err_ssl_get_peer_certificate_ctr and 176 | * @ref test_seams_countdown_global. 177 | */ 178 | int g_smtp_test_err_ssl_get_peer_certificate_ctr = -1; 179 | 180 | /** 181 | * See @ref g_smtp_test_err_x509_check_host_ctr and 182 | * @ref test_seams_countdown_global. 183 | */ 184 | int g_smtp_test_err_x509_check_host_ctr = -1; 185 | 186 | /** 187 | * See @ref g_smtp_test_err_ssl_new_ctr and @ref test_seams_countdown_global. 188 | */ 189 | int g_smtp_test_err_ssl_new_ctr = -1; 190 | 191 | /** 192 | * See @ref g_smtp_test_err_ssl_read_ctr and @ref test_seams_countdown_global. 193 | */ 194 | int g_smtp_test_err_ssl_read_ctr = -1; 195 | 196 | /** 197 | * See @ref g_smtp_test_err_ssl_write_ctr and @ref test_seams_countdown_global. 198 | */ 199 | int g_smtp_test_err_ssl_write_ctr = -1; 200 | 201 | /** 202 | * See @ref g_smtp_test_err_sprintf_ctr and @ref test_seams_countdown_global. 203 | */ 204 | int g_smtp_test_err_sprintf_ctr = -1; 205 | 206 | /** 207 | * See @ref g_smtp_test_err_sprintf_rc. 208 | */ 209 | int g_smtp_test_err_sprintf_rc = 0; 210 | 211 | /** 212 | * See @ref g_smtp_test_strlen_custom_ret. 213 | */ 214 | int g_smtp_test_strlen_custom_ret = 0; 215 | 216 | /** 217 | * See @ref g_smtp_test_strlen_ret_value. 218 | */ 219 | size_t g_smtp_test_strlen_ret_value = 0; 220 | 221 | /** 222 | * See @ref g_smtp_test_time_custom_ret. 223 | */ 224 | int g_smtp_test_time_custom_ret = 0; 225 | 226 | /** 227 | * See @ref g_smtp_test_time_ret_value. 228 | */ 229 | time_t g_smtp_test_time_ret_value = 0; 230 | 231 | /** 232 | * Decrement an error counter until it reaches -1. 233 | * 234 | * Once a counter reaches -1, it will return a successful response (1). This 235 | * typically gets used to denote when to cause a function to fail. For example, 236 | * the unit test or functional test might need to cause the realloc() function 237 | * to fail after calling it the third time. 238 | * 239 | * @param[in,out] test_err_ctr Integer counter to decrement. 240 | * @retval 0 The counter has been decremented, but did not reach -1 yet. 241 | * @retval 1 The counter has reached -1. 242 | */ 243 | int 244 | smtp_test_seam_dec_err_ctr(int *const test_err_ctr){ 245 | if(*test_err_ctr >= 0){ 246 | *test_err_ctr -= 1; 247 | if(*test_err_ctr < 0){ 248 | return 1; 249 | } 250 | } 251 | return 0; 252 | } 253 | 254 | /** 255 | * Allows the test harness to control when BIO_new_socket() fails. 256 | * 257 | * @param[in] sock Existing socket to attach the BIO to. 258 | * @param[in] close_flag Close flag for new BIO. 259 | * @retval BIO* New BIO created on existing socket. 260 | * @retval NULL Failed to create the new BIO. 261 | */ 262 | BIO * 263 | smtp_test_seam_bio_new_socket(int sock, 264 | int close_flag){ 265 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_bio_new_socket_ctr)){ 266 | return NULL; 267 | } 268 | return BIO_new_socket(sock, close_flag); 269 | } 270 | 271 | /** 272 | * Allows the test harness to control when BIO_should_retry() fails. 273 | * 274 | * @param[in] bio Existing BIO connection. 275 | * @retval 0 The error condition does not allow a retry. 276 | * @retval 1 The error condition allows a retry. 277 | */ 278 | int 279 | smtp_test_seam_bio_should_retry(BIO *bio){ 280 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_bio_should_retry_ctr)){ 281 | return 0; 282 | } 283 | if(g_smtp_test_err_bio_should_retry_rc != -1){ 284 | return g_smtp_test_err_bio_should_retry_rc; 285 | } 286 | return BIO_should_retry(bio); 287 | } 288 | 289 | /** 290 | * Allows the test harness to control when calloc() fails. 291 | * 292 | * @param[in] nelem Number of elements to allocate. 293 | * @param[in] elsize Size of each element to allocate. 294 | * @retval void* Pointer to new allocated memory. 295 | * @retval NULL Memory allocation failed. 296 | */ 297 | void * 298 | smtp_test_seam_calloc(size_t nelem, 299 | size_t elsize){ 300 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_calloc_ctr)){ 301 | errno = ENOMEM; 302 | return NULL; 303 | } 304 | return calloc(nelem, elsize); 305 | } 306 | 307 | /** 308 | * Allows the test harness to control when close() fails. 309 | * 310 | * @param[in] fildes Socket file descriptor to close. 311 | * @retval 0 Successfully closed file descriptor. 312 | * @retval -1 Failed to close file descriptor. 313 | */ 314 | int 315 | smtp_test_seam_close(int fildes){ 316 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_close_ctr)){ 317 | errno = EBADF; 318 | return -1; 319 | } 320 | return close(fildes); 321 | } 322 | 323 | /** 324 | * Allows the test harness to control when connect() fails. 325 | * 326 | * @param[in] socket Socket connection. 327 | * @param[in] address Network address of peer. 328 | * @param[in] address_len Number of bytes in @p address. 329 | * @retval 0 Successfully connected to the peer. 330 | * @retval -1 Failed to connect to the peer. 331 | */ 332 | int 333 | smtp_test_seam_connect(int socket, 334 | const struct sockaddr *address, 335 | socklen_t address_len){ 336 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_connect_ctr)){ 337 | errno = ECONNREFUSED; 338 | return -1; 339 | } 340 | return connect(socket, address, address_len); 341 | } 342 | 343 | /** 344 | * Allows the test harness to control when ERR_peek_error() returns a failure 345 | * code. 346 | * 347 | * @retval 0 No error code on the error queue. 348 | * @retval !0 An error code exists on the error queue. 349 | */ 350 | unsigned long 351 | smtp_test_seam_err_peek_error(void){ 352 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_err_peek_error_ctr)){ 353 | return 1; 354 | } 355 | return ERR_peek_error(); 356 | } 357 | 358 | /** 359 | * Allows the test harness to control when fclose() fails. 360 | * 361 | * @param[in] stream File stream to close. 362 | * @retval 0 Successfully closed the file stream. 363 | * @retval EOF An error occurred while closing the file stream. 364 | */ 365 | int smtp_test_seam_fclose(FILE *stream){ 366 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_fclose_ctr)){ 367 | errno = EBADF; 368 | return EOF; 369 | } 370 | return fclose(stream); 371 | } 372 | 373 | /** 374 | * Allows the test harness to control the file stream error indicator return 375 | * value in ferror(). 376 | * 377 | * @param[in] stream Check for errors on this file stream. 378 | * @retval 0 No errors detected on the file stream. 379 | * @retval 1 An error occurred during a file stream operation. 380 | */ 381 | int 382 | smtp_test_seam_ferror(FILE *stream){ 383 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ferror_ctr)){ 384 | return 1; 385 | } 386 | return ferror(stream); 387 | } 388 | 389 | /** 390 | * Allows the test harness to control when gmtime_r() fails. 391 | * 392 | * @param[in] timep Time value to convert to a struct tm. 393 | * @param[out] result Converts the @p timep value into a UTC tm structure 394 | * value and stores the results in this pointer. 395 | * @retval tm* time_t value converted to a tm structure value. 396 | * @retval NULL An error occurred while converting the time. 397 | */ 398 | struct tm * 399 | smtp_test_seam_gmtime_r(const time_t *timep, 400 | struct tm *result){ 401 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_gmtime_r_ctr)){ 402 | return NULL; 403 | } 404 | return gmtime_r(timep, result); 405 | } 406 | 407 | /** 408 | * Allows the test harness to control when HMAC() fails. 409 | * 410 | * @param[in] evp_md Hash function. 411 | * @param[in] key Hash key. 412 | * @param[in] key_len Number of bytes in @p key. 413 | * @param[in] d Message data. 414 | * @param[in] n Number of bytes in @p d. 415 | * @param[out] md The computed message authentication code. 416 | * @param[in] md_len Number of bytes in @p md. 417 | * @retval uchar* Pointer to @p md. 418 | * @retval NULL An error occurred. 419 | */ 420 | unsigned char * 421 | smtp_test_seam_hmac(const EVP_MD *evp_md, 422 | const void *key, 423 | int key_len, 424 | const unsigned char *d, 425 | size_t n, 426 | unsigned char *md, 427 | unsigned int *md_len){ 428 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_hmac_ctr)){ 429 | return NULL; 430 | } 431 | return HMAC(evp_md, key, key_len, d, n, md, md_len); 432 | } 433 | 434 | /** 435 | * Allows the test harness to control when localtime_r() fails. 436 | * 437 | * @param[in] timep Time value to convert to a struct tm. 438 | * @param[out] result Converts the @p timep value into a local time tm 439 | * structure value and stores the results in this pointer. 440 | * @retval tm* time_t value converted to a tm structure value. 441 | * @retval NULL An error occurred while converting the time. 442 | */ 443 | struct tm * 444 | smtp_test_seam_localtime_r(const time_t *timep, 445 | struct tm *result){ 446 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_localtime_r_ctr)){ 447 | return NULL; 448 | } 449 | return localtime_r(timep, result); 450 | } 451 | 452 | /** 453 | * Allows the test harness to control when malloc() fails. 454 | * 455 | * @param[in] size Number of bytes to allocate. 456 | * @retval void* Pointer to new allocated memory. 457 | * @retval NULL Memory allocation failed. 458 | */ 459 | void * 460 | smtp_test_seam_malloc(size_t size){ 461 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_malloc_ctr)){ 462 | errno = ENOMEM; 463 | return NULL; 464 | } 465 | return malloc(size); 466 | } 467 | 468 | /** 469 | * Allows the test harness to control when mktime() fails. 470 | * 471 | * @param[in] timeptr tm data structure to convert to time_t. 472 | * @retval >=0 Time since the epoch. 473 | * @retval -1 Failed to convert the time. 474 | */ 475 | time_t 476 | smtp_test_seam_mktime(struct tm *timeptr){ 477 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_mktime_ctr)){ 478 | return -1; 479 | } 480 | return mktime(timeptr); 481 | } 482 | 483 | /** 484 | * Allows the test harness to control when realloc() fails. 485 | * 486 | * @param[in] ptr Previously allocated memory or NULL memory has not been 487 | * allocated yet. 488 | * @param[in] size Number of bytes to reallocate. 489 | * @retval void* Pointer to new allocated memory. 490 | * @retval NULL Memory allocation failed. 491 | */ 492 | void * 493 | smtp_test_seam_realloc(void *ptr, 494 | size_t size){ 495 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_realloc_ctr)){ 496 | errno = ENOMEM; 497 | return NULL; 498 | } 499 | return realloc(ptr, size); 500 | } 501 | 502 | /** 503 | * Allows the test harness to control when recv() fails. 504 | * 505 | * @param[in] socket TCP network socket. 506 | * @param[in] buffer Store received data in this buffer. 507 | * @param[in] length Number of bytes in @p buffer. 508 | * @param[in] flags Set this to 0. 509 | * @retval >=0 Number of bytes received. 510 | * @retval -1 Failed to receive bytes over the network. 511 | */ 512 | long 513 | smtp_test_seam_recv(int socket, 514 | void *buffer, 515 | size_t length, 516 | int flags){ 517 | size_t bytes_inject_len; 518 | 519 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_recv_ctr)){ 520 | if(g_smtp_test_err_recv_rc != -1){ 521 | return g_smtp_test_err_recv_rc; 522 | } 523 | if(*g_smtp_test_err_recv_bytes){ 524 | bytes_inject_len = strlen(g_smtp_test_err_recv_bytes); 525 | assert(bytes_inject_len < length && bytes_inject_len < LONG_MAX); 526 | memcpy(buffer, g_smtp_test_err_recv_bytes, bytes_inject_len); 527 | return (long)bytes_inject_len; 528 | } 529 | errno = EBADF; 530 | return -1; 531 | } 532 | return recv(socket, buffer, length, flags); 533 | } 534 | 535 | /** 536 | * Allows the test harness to control when select() fails. 537 | * 538 | * @param[in] nfds Check for file descriptors in range 0 to (@p nfds - 1) 539 | * which have any of the read/write/error conditions. 540 | * @param[in] readfds Checks for file descriptors in fd_set that have bytes 541 | * ready for reading. 542 | * @param[in] writefds Checks for file descriptors in fd_set that have bytes 543 | * ready for writing. 544 | * @param[in] errorfds Checks for file descriptors in fd_set that have errors 545 | * pending. 546 | * @param[in] timeout Wait for the read/write/error conditions in blocking 547 | * mode until this timeout or an interrupt occurs. 548 | * @retval >=0 Number of bits set in the bitmask. 549 | * @retval -1 An error occurred. 550 | */ 551 | int 552 | smtp_test_seam_select(int nfds, 553 | fd_set *readfds, 554 | fd_set *writefds, 555 | fd_set *errorfds, 556 | struct timeval *timeout){ 557 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_select_ctr)){ 558 | errno = EINTR; 559 | return -1; 560 | } 561 | return select(nfds, readfds, writefds, errorfds, timeout); 562 | } 563 | 564 | /** 565 | * Allows the test harness to control when send() fails. 566 | * 567 | * @param[in] socket TCP network socket. 568 | * @param[in] buffer Data to send over the network. 569 | * @param[in] length Number of bytes in @p buffer. 570 | * @param[in] flags Set this to 0. 571 | * @retval >=0 Number of bytes sent. 572 | * @retval -1 Failed to send bytes over the network. 573 | */ 574 | ssize_t 575 | smtp_test_seam_send(int socket, 576 | const void *buffer, 577 | size_t length, 578 | int flags){ 579 | long sent_bytes; 580 | size_t bytes_to_send; 581 | 582 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_send_ctr)){ 583 | errno = EBADF; 584 | sent_bytes = -1; 585 | } 586 | else{ 587 | bytes_to_send = length; 588 | if(g_smtp_test_send_one_byte){ 589 | bytes_to_send = 1; 590 | } 591 | sent_bytes = send(socket, buffer, bytes_to_send, flags); 592 | } 593 | return sent_bytes; 594 | } 595 | 596 | /** 597 | * Allows the test harness to control when socket() fails. 598 | * 599 | * @param[in] domain Socket domain. 600 | * @param[in] type Socket type. 601 | * @param[in] protocol Socket protocol. 602 | * @retval !(-1) The file descriptor for the new socket. 603 | * @retval -1 Failed to create the socket. 604 | */ 605 | int 606 | smtp_test_seam_socket(int domain, 607 | int type, 608 | int protocol){ 609 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_socket_ctr)){ 610 | errno = EINVAL; 611 | return -1; 612 | } 613 | return socket(domain, type, protocol); 614 | } 615 | 616 | /** 617 | * Allows the test harness to control when SSL_connect() fails. 618 | * 619 | * @param[in] ssl OpenSSL handle. 620 | * @retval 1 TLS connection handshake successful. 621 | * @retval <1 TLS connection handshake failed. 622 | */ 623 | int 624 | smtp_test_seam_ssl_connect(SSL *ssl){ 625 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_connect_ctr)){ 626 | return 0; 627 | } 628 | return SSL_connect(ssl); 629 | } 630 | 631 | /** 632 | * Allows the test harness to control when SSL_CTX_new() fails. 633 | * 634 | * @param[in] method TLS connection method. 635 | * @retval SSL_CTX* Pointer to new TLS context. 636 | * @retval NULL Failed to create new TLS context. 637 | */ 638 | SSL_CTX * 639 | smtp_test_seam_ssl_ctx_new(const SSL_METHOD *method){ 640 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_ctx_new_ctr)){ 641 | return NULL; 642 | } 643 | return SSL_CTX_new(method); 644 | } 645 | 646 | /** 647 | * Allows the test harness to control when SSL_do_handshake() fails. 648 | * 649 | * @param[in] ssl OpenSSL handle. 650 | * @retval 1 TLS handshake successful. 651 | * @retval <1 TLS handshake failed. 652 | */ 653 | int 654 | smtp_test_seam_ssl_do_handshake(SSL *ssl){ 655 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_do_handshake_ctr)){ 656 | return 0; 657 | } 658 | return SSL_do_handshake(ssl); 659 | } 660 | 661 | /** 662 | * Allows the test harness to control when SSL_get_peer_certificate() fails. 663 | * 664 | * @param[in] ssl OpenSSL handle. 665 | * @retval X509* Peer certficate which must get freed by using X509_free(). 666 | * @retval NULL Failed to get the peer certificate. 667 | */ 668 | X509 * 669 | smtp_test_seam_ssl_get_peer_certificate(const SSL *ssl){ 670 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_get_peer_certificate_ctr)){ 671 | return NULL; 672 | } 673 | return SSL_get_peer_certificate(ssl); 674 | } 675 | 676 | /** 677 | * Allows the test harness to control when X509_check_host() fails. 678 | * 679 | * @param[in] cert X509 certificate handle. 680 | * @param[in] name Server name. 681 | * @param[in] namelen Number of characters in @p name or 0 if null-terminated. 682 | * @param[in] flags Usually set to 0. 683 | * @param[in] peername Pointer to CN from certificate stored in this buffer 684 | * if not NULL. 685 | * @retval 1 Successful host check. 686 | * @retval 0 Failed host check. 687 | * @retval -1 Internal error. 688 | */ 689 | int 690 | smtp_test_seam_x509_check_host(X509 *cert, 691 | const char *name, 692 | size_t namelen, 693 | unsigned int flags, 694 | char **peername){ 695 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_x509_check_host_ctr)){ 696 | return -1; 697 | } 698 | return X509_check_host(cert, name, namelen, flags, peername); 699 | } 700 | 701 | /** 702 | * Allows the test harness to control when SSL_new() fails. 703 | * 704 | * @param[in] ctx OpenSSL TLS context. 705 | * @retval SSL* Pointer to a new TLS context. 706 | * @retval NULL Failed to create new TLS context. 707 | */ 708 | SSL * 709 | smtp_test_seam_ssl_new(SSL_CTX *ctx){ 710 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_new_ctr)){ 711 | return NULL; 712 | } 713 | return SSL_new(ctx); 714 | } 715 | 716 | /** 717 | * Allows the test harness to control when SSL_read() fails. 718 | * 719 | * @param[in] ssl OpenSSL TLS object. 720 | * @param[in] buf Store received data in this buffer. 721 | * @param[in] num Number of bytes in @p buf. 722 | * @retval >0 Number of bytes successfully read from the TLS connection. 723 | * @retval <=0 Failed to read bytes on the TLS connection. 724 | */ 725 | int 726 | smtp_test_seam_ssl_read(SSL *ssl, 727 | void *buf, 728 | int num){ 729 | size_t bytes_inject_len; 730 | 731 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_read_ctr)){ 732 | if(*g_smtp_test_err_recv_bytes){ 733 | bytes_inject_len = strlen(g_smtp_test_err_recv_bytes); 734 | assert(bytes_inject_len < (size_t)num && bytes_inject_len < INT_MAX); 735 | memcpy(buf, g_smtp_test_err_recv_bytes, bytes_inject_len); 736 | return (int)bytes_inject_len; 737 | } 738 | return -1; 739 | } 740 | return SSL_read(ssl, buf, num); 741 | } 742 | 743 | /** 744 | * Allows the test harness to control when SSL_write() fails. 745 | * 746 | * @param[in] ssl OpenSSL TLS object. 747 | * @param[in] buf Data to write to the TLS connection. 748 | * @param[in] num Number of bytes in @p buf. 749 | * @retval >0 Number of bytes successfully written to the TLS connection. 750 | * @retval <=0 Failed to write bytes to the TLS connection. 751 | */ 752 | int 753 | smtp_test_seam_ssl_write(SSL *ssl, 754 | const void *buf, 755 | int num){ 756 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_ssl_write_ctr)){ 757 | return -1; 758 | } 759 | return SSL_write(ssl, buf, num); 760 | } 761 | 762 | /** 763 | * Allows the test harness to control when sprintf() fails. 764 | * 765 | * @param[in] s Buffer to store the output contents to. 766 | * @param[in] format Format string defined in sprintf(). 767 | * @retval >=0 Number of bytes copied to @p s, excluding the null-terminator. 768 | * @retval <0 Output or formatting error. 769 | */ 770 | int 771 | smtp_test_seam_sprintf(char *s, 772 | const char *format, ...){ 773 | va_list ap; 774 | int rc; 775 | 776 | if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_sprintf_ctr)){ 777 | errno = ENOMEM; 778 | return g_smtp_test_err_sprintf_rc; 779 | } 780 | va_start(ap, format); 781 | rc = vsprintf(s, format, ap); 782 | va_end(ap); 783 | return rc; 784 | } 785 | 786 | /** 787 | * Allows the test harness to control the return value of strlen(). 788 | * 789 | * @param[in] s Null-terminated string. 790 | * @return Length of @p s. 791 | */ 792 | size_t 793 | smtp_test_seam_strlen(const char *s){ 794 | size_t result; 795 | 796 | if(g_smtp_test_strlen_custom_ret){ 797 | result = g_smtp_test_strlen_ret_value; 798 | } 799 | else{ 800 | result = strlen(s); 801 | } 802 | return result; 803 | } 804 | 805 | /** 806 | * Allows the test harness to control when time() fails. 807 | * 808 | * @param[out] tloc Buffer to hold the time_t results. 809 | * @retval >=0 Time in seconds since the Epoch. 810 | * @retval -1 Failed to store the time in @p tloc. 811 | */ 812 | time_t 813 | smtp_test_seam_time(time_t *tloc){ 814 | if(g_smtp_test_time_custom_ret){ 815 | return g_smtp_test_time_ret_value; 816 | } 817 | return time(tloc); 818 | } 819 | 820 | --------------------------------------------------------------------------------