| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- /*
- * Copyright 2010 by egnite GmbH
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * For additional information see http://www.ethernut.de/
- */
- /*
- * \file pro/smtpc.c
- * \brief Simple mail transfer protocol client.
- *
- * \verbatim
- * $Id$
- * \endverbatim
- */
- #include <sys/nutdebug.h>
- #include <sys/confnet.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <pro/rfctime.h>
- #include <memdebug.h>
- #include <string.h>
- #include <pro/smtpc.h>
- /*!
- * \addtogroup xgSMTPC
- */
- /*@{*/
- #ifndef SMTP_TIMEOUT
- #define SMTP_TIMEOUT 600000
- #endif
- /*!
- * \brief BASE64 encoder.
- *
- * The encoder that is available in the gorp library can't be used for
- * sending SMTP authentication strings containing ASCII 0 characters.
- * This local implementation fixes this and takes a user supplied buffer
- * for the encoded string, avoiding costly heap allocation.
- *
- * In opposite to the original gorp routine, this function will not
- * insert any line breaks. Note, that the encoder loop will not release
- * the CPU. Thus, the caller should split large buffers into smaller
- * chunks and add any required line breaks.
- *
- * \param sptr Pointer to the source buffer that contains the data to
- * encode.
- * \param slen Number of bytes available in the source buffer.
- * \param dptr Pointer to the destination buffer that receives the
- * encoded data. A terminating zero byte will be appended.
- * The caller must make sure, that the size of this buffer
- * is large enough to hold the resulting string. A save
- * calculation is (slen / 3) * 4 + 5.
- */
- static void NutBase64Encode(const uint8_t * sptr, size_t slen, char *dptr)
- {
- static const char base64set[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- uint_fast8_t i;
- uint32_t blk;
- uint_fast8_t pad = 0;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(sptr != NULL);
- NUTASSERT(dptr != NULL);
- while (slen) {
- blk = (uint32_t) (*sptr++) << 16;
- if (slen > 1) {
- blk |= (uint32_t) (*sptr++) << 8;
- if (slen > 2) {
- blk |= *sptr++;
- } else {
- pad = 1;
- }
- } else {
- pad = 2;
- }
- for (i = pad; i < 4; i++) {
- *dptr++ = base64set[(blk >> 18) & 63];
- blk <<= 6;
- }
- for (i = 0; i < pad; i++) {
- *dptr++ = '=';
- }
- slen -= 3 - pad;
- }
- *dptr = '\0';
- }
- /*!
- * \brief Read a response line from the server.
- *
- * \return Pointer to a buffer containing the response. In case of a
- * broken connection or a line overflow, a NULL pointer is
- * returned.
- */
- const char *NutSmtpReceiveResponse(SMTPCLIENTSESSION * si)
- {
- char *cp;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- if (fgets(si->smtp_buff, sizeof(si->smtp_buff), si->smtp_stream)) {
- cp = strchr(si->smtp_buff, '\r');
- if (cp == NULL) {
- cp = strchr(si->smtp_buff, '\n');
- }
- if (cp) {
- *cp = '\0';
- return si->smtp_buff;
- }
- /* Line overflow. */
- }
- return NULL;
- }
- /*!
- * \brief Send command to the server and return the first response line.
- *
- * If a multi-line response is expected, the caller may use
- * NutSmtpReceiveResponse() to receive additional response lines.
- *
- * \param fmt Format string containing conversion specifications like
- * printf.
- */
- const char *NutSmtpSendCommand(SMTPCLIENTSESSION * si, const char *fmt, ...)
- {
- va_list ap;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- NUTASSERT(fmt != NULL);
- va_start(ap, fmt);
- vfprintf(si->smtp_stream, (char *) fmt, ap);
- va_end(ap);
- fputs("\r\n", si->smtp_stream);
- fflush(si->smtp_stream);
- return NutSmtpReceiveResponse(si);
- }
- /*!
- * \brief Terminate an SMTP session.
- *
- * Gracefully closes the SMTP connection.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- */
- void NutSmtpDisconnect(SMTPCLIENTSESSION * si)
- {
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- if (si->smtp_sock) {
- if (si->smtp_stream) {
- NutSmtpSendCommand(si, "QUIT");
- fclose(si->smtp_stream);
- }
- NutTcpCloseSocket(si->smtp_sock);
- }
- free(si);
- }
- /*!
- * \brief Start an SMTP session.
- *
- * \param ip IP address of the host to connect.
- * \param port Port number to connect. Typically port 25 is used by SMTP.
- *
- * \return A pointer to a newly create \ref SMTPCLIENTSESSION structure,
- * if the server is connected and ready to accept commands.
- * Otherwise a NULL pointer is returned.
- */
- SMTPCLIENTSESSION *NutSmtpConnect(uint32_t ip, uint16_t port)
- {
- SMTPCLIENTSESSION *si;
- si = calloc(1, sizeof(SMTPCLIENTSESSION));
- if (si) {
- si->smtp_sock = NutTcpCreateSocket();
- if (si->smtp_sock && NutTcpConnect(si->smtp_sock, ip, port) == 0) {
- uint32_t tmo = SMTP_TIMEOUT;
- NutTcpSetSockOpt(si->smtp_sock, SO_RCVTIMEO, &tmo, sizeof(tmo));
- si->smtp_stream = _fdopen((int) ((intptr_t) si->smtp_sock), "r+b");
- if (si->smtp_stream) {
- for (;;) {
- const char *rsp = NutSmtpReceiveResponse(si);
- if (*rsp != '2') {
- break;
- }
- if (*(rsp + 3) != '-') {
- return si;
- }
- }
- }
- }
- NutSmtpDisconnect(si);
- }
- return NULL;
- }
- /*!
- * \brief Send EHLO/HELO command to the SMTP server.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param cmd Either EHLO or HELO.
- * \param host Primary host name, or NULL if the local host has no name.
- *
- * \return
- */
- static const char *SayHello(SMTPCLIENTSESSION * si, char *cmd, char *host)
- {
- if (host) {
- return NutSmtpSendCommand(si, "%s %s", cmd, host);
- }
- return NutSmtpSendCommand(si, "%s [%s]", cmd, inet_ntoa(confnet.cdn_ip_addr));
- }
- /*!
- * \brief Identify the client SMTP to the server.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param host Primary host name, or NULL if the local host has no name.
- * \param user Login name. Set to NULL if authorization is not required.
- * \param pass Login password. Ignored if login name is set to NULL.
- *
- * \return 0 on success, -1 otherwise.
- */
- int NutSmtpLogin(SMTPCLIENTSESSION * si, char *host, char *user, char *pass)
- {
- const char *rsp;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- /* We start an SMTP session by issuing the EHLO command. */
- rsp = SayHello(si, "EHLO", host);
- if (rsp && *rsp == '5') {
- /* If EHLO is not supported, we fall back to HELO. */
- si->smtp_feat |= SMTPFEAT_VINTAGE;
- rsp = SayHello(si, "HELO", host);
- }
- if (rsp && *rsp == '2') {
- if ((si->smtp_feat & SMTPFEAT_VINTAGE) != SMTPFEAT_VINTAGE) {
- for (;;) {
- if (strncmp(rsp + 4, "AUTH ", 5) == 0) {
- if (strstr(rsp + 9, "LOGIN")) {
- si->smtp_feat |= SMTPFEAT_AUTH_LOGIN;
- }
- if (strstr(rsp + 9, "PLAIN")) {
- si->smtp_feat |= SMTPFEAT_AUTH_PLAIN;
- }
- }
- if (*(rsp + 3) != '-') {
- break;
- }
- rsp = NutSmtpReceiveResponse(si);
- if (rsp == NULL) {
- break;
- }
- }
- }
- if (user == NULL) {
- /* Return with success, if authorization not required. */
- return 0;
- }
- if (si->smtp_feat & SMTPFEAT_AUTH_PLAIN) {
- int lu = strlen(user);
- int lp = strlen(pass);
- uint8_t *auth = malloc(lu + lp + 3);
- *auth = '\0';
- memcpy(auth + 1, user, lu + 1);
- memcpy(auth + 1 + lu + 1, pass, lp);
- NutBase64Encode(auth, lu + lp + 2, si->smtp_buff);
- rsp = NutSmtpSendCommand(si, "AUTH PLAIN %s", si->smtp_buff);
- if (rsp && *rsp == '2') {
- return 0;
- }
- } else if (si->smtp_feat & SMTPFEAT_AUTH_LOGIN) {
- }
- }
- return -1;
- }
- /*!
- * \brief Send mail request.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param me Pointer to the \ref MAILENVELOPE structure.
- *
- * \return Number of recipients accepted by the server. If 0, no mail
- * should be sent. In any case the caller should inspect the
- * status of each recipient address in the mail envelope
- * structure.
- */
- int NutSmtpSendMailRequest(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
- {
- int rc = 0;
- const char *rsp;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(me != NULL);
- rsp = NutSmtpSendCommand(si, "MAIL FROM:%s", me->mail_from);
- if (rsp && *rsp == '2') {
- int i;
- for (i = 0; i < MAX_MAIL_RCPTS; i++) {
- /* Request all unprocessed recipients. */
- if (me->mail_rcpt[i] && (me->mail_rcpt_stat[i] & MAIL_RCPT_DONE) == 0) {
- rsp = NutSmtpSendCommand(si, "RCPT TO:%s", me->mail_rcpt[i]);
- if (rsp) {
- if (*rsp == '2') {
- /* Recipient accepted. */
- me->mail_rcpt_stat[i] |= MAIL_RCPT_ACPT;
- rc++;
- } else {
- /* Recipient not accepted. */
- me->mail_rcpt_stat[i] &= ~MAIL_RCPT_ACPT;
- if (*rsp == '5') {
- /* Recipient refused. */
- me->mail_rcpt_stat[i] |= MAIL_RCPT_FAIL;
- }
- }
- }
- }
- }
- }
- return rc;
- }
- /*!
- * \brief Send header line with all recipients of a specified type.
- *
- * \param stream Stream to send to.
- * \param me Pointer to the \ref MAILENVELOPE structure.
- * \param type Tpye of recipients, either MAIL_RCPT_TO or MAIL_RCPT_CC.
- */
- static int SendMailHeaderRecipient(FILE *stream, MAILENVELOPE * me, uint8_t type)
- {
- uint_fast8_t i;
- int cnt;
- /* Process the list of recipients given by this envelope. */
- for (i = 0, cnt = 0; i < MAX_MAIL_RCPTS; i++) {
- /* Check for the recipient's type. */
- if ((me->mail_rcpt_stat[i] & MAIL_RCPT_TYPE) == type) {
- if (cnt) {
- /* Additional recipient. */
- fputs(",\r\n ", stream);
- } else {
- /* First one found. */
- fputs(type == MAIL_RCPT_TO ? "To: " : "CC: ", stream);
- }
- fputs(me->mail_rcpt_header[i], stream);
- cnt++;
- }
- }
- if (cnt) {
- fputs("\r\n", stream);
- }
- return 0;
- }
- /*!
- * \brief Send major mail header lines.
- *
- * This function will send the following header lines:
- *
- * - Date
- * - From
- * - Subject
- * - To
- * - CC
- *
- * The header information must be supplied by the caller in the mail
- * envelope structure.
- *
- * Note, that BCC recipients are not included.
- *
- * The caller may add additional headers using stdio functions with the
- * stream handle available in the \ref SMTPCLIENTSESSION structure.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param me Pointer to the \ref MAILENVELOPE structure.
- *
- * \return Always 0 right now. Later versions may return -1 in case of
- * an error.
- */
- int NutSmtpSendMailHeader(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
- {
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- NUTASSERT(si->smtp_stream != NULL);
- NUTASSERT(me != NULL);
- if (me->mail_date) {
- fprintf(si->smtp_stream, "Date: %s\r\n", Rfc1123TimeString(gmtime(&me->mail_date)));
- }
- fprintf(si->smtp_stream, "From: %s\r\n", me->mail_from_header);
- fprintf(si->smtp_stream, "Subject: %s\r\n", me->mail_subj);
- SendMailHeaderRecipient(si->smtp_stream, me, MAIL_RCPT_TO);
- SendMailHeaderRecipient(si->smtp_stream, me, MAIL_RCPT_CC);
- return 0;
- }
- /*!
- * \brief Send encoded email text lines.
- *
- * Each line will be terminated by a carriage return / linefeed pair.
- * Lines larger than \ref SMTP_BUFSIZ are split. This may occure in
- * the middle of a word. A dot will be put in front of lines that start
- * with a dot.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param text Pointer to a string containing the text lines to send.
- * Lines should be separated by linefeeds and may be
- * optionally prepended by a carriage return. If the pointer
- * points to an empty string, nothing will be sent.
- *
- * \return 0 on success, -1 otherwise. An error typically indicates a
- * broken connection.
- */
- int NutSmtpSendEncodedLines(SMTPCLIENTSESSION * si, const char *text)
- {
- /* Sanity checks. */
- NUTASSERT(si != NULL);
- NUTASSERT(text != NULL);
- while (*text) {
- char *bufp = si->smtp_buff;
- int i;
- /* Collect a line or a complete buffer. */
- for (i = 0; *text && i < SMTP_BUFSIZ; text++) {
- /* Ignore carriage returns. */
- if (*text != '\r') {
- /* Stop at linefeeds or at the end of the message. */
- if (*text == '\n' || *text == '\0') {
- /* Also send newline. */
- i++;
- text++;
- break;
- } else {
- /* Send this character unchanged. */
- *bufp++ = *text;
- i++;
- }
- }
- }
- if (i) {
- *bufp = '\0';
- /* Insert a dot in front of any line that starts with a dot. */
- if (si->smtp_buff[0] == '.') {
- fputc('.', si->smtp_stream);
- }
- /* Send the buffer with CR/LF appended. */
- fputs(si->smtp_buff, si->smtp_stream);
- if (fputs("\r\n", si->smtp_stream) == EOF) {
- /* Connection broken. */
- return -1;
- }
- }
- }
- return 0;
- }
- /*!
- * \brief Send an email via an active SMTP session.
- *
- * Applications may use the following basic sequence to send an email:
- *
- * \code
- * #include <pro/smtpc.h>
- *
- * MAILENVELOPE email = {
- * 0, "<me@ethernut.de>", "Problem",
- * "Be warned.",
- * { "<admin@ethernut.de>", NULL, NULL, NULL },
- * { MAIL_RCPT_TO, 0, 0, 0 }
- * };
- * SMTPCLIENTSESSION *smtp;
- *
- * smtp = NutSmtpConnect(daemon_ip, 25);
- * NutSmtpLogin(smtp, NULL, NULL, NULL);
- * NutSmtpSendMail(smtp, &email);
- * NutSmtpDisconnect(smtp);
- * \endcode
- *
- * More advanced mail transfers may be implemented by using the
- * other routines of this API for sending commands or parts of
- * an email individually. Even the stream in the session structure
- * may be used with stdio calls.
- *
- * \param si Pointer to the \ref SMTPCLIENTSESSION structure, obtained
- * from a previous call to NutSmtpConnect().
- * \param me Pointer to the \ref MAILENVELOPE structure. On return,
- * the caller should inspect the status of each recipient.
- *
- * \return Pointer to a buffer containing the last response. The status
- * of the recipients in the envelope will have been updated. In
- * case of a fatal error or if all recipients had been rejected,
- * a NULL pointer is returned.
- */
- const char *NutSmtpSendMail(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
- {
- const char *rsp = NULL;
- /* Normally we do not check for NULL pointers. */
- NUTASSERT(si != NULL);
- NUTASSERT(me != NULL);
- /* Send a request for sending mail. The function returns the number
- of accepted recipients. */
- if (NutSmtpSendMailRequest(si, me) > 0) {
- /* Start sending the email. */
- rsp = NutSmtpSendCommand(si, "DATA");
- /* Server wants us to go ahead? */
- if (rsp && *rsp == '3') {
- /* Send the major header lines. */
- NutSmtpSendMailHeader(si, me);
- fputs("\r\n", si->smtp_stream);
- /* If available, send the mail body's text. */
- if (me->mail_body) {
- NutSmtpSendEncodedLines(si, me->mail_body);
- }
- fputs(".\r\n", si->smtp_stream);
- fflush(si->smtp_stream);
- /* Check the result of the data transfer. */
- rsp = NutSmtpReceiveResponse(si);
- /* On success, update the status of the previously accepted
- recipients. */
- if (rsp && *rsp == '2') {
- uint_fast8_t i;
- for (i = 0; i < MAX_MAIL_RCPTS; i++) {
- if (me->mail_rcpt[i]) {
- if ((me->mail_rcpt_stat[i] & MAIL_RCPT_ACPT) == MAIL_RCPT_ACPT) {
- me->mail_rcpt_stat[i] |= MAIL_RCPT_SENT;
- }
- }
- }
- }
- }
- }
- return rsp;
- }
- /*@}*/
|