Nut/OS  4.10.3
API Reference
smtpc.c
Go to the documentation of this file.
00001 /*
00002  * Copyright 2010 by egnite GmbH
00003  *
00004  * All rights reserved.
00005  *
00006  * Redistribution and use in source and binary forms, with or without
00007  * modification, are permitted provided that the following conditions
00008  * are met:
00009  *
00010  * 1. Redistributions of source code must retain the above copyright
00011  *    notice, this list of conditions and the following disclaimer.
00012  * 2. Redistributions in binary form must reproduce the above copyright
00013  *    notice, this list of conditions and the following disclaimer in the
00014  *    documentation and/or other materials provided with the distribution.
00015  * 3. Neither the name of the copyright holders nor the names of
00016  *    contributors may be used to endorse or promote products derived
00017  *    from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00020  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00021  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00022  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00023  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00024  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00025  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
00026  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
00027  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00028  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
00029  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00030  * SUCH DAMAGE.
00031  *
00032  * For additional information see http://www.ethernut.de/
00033  */
00034 
00035 /*
00036  * \file pro/smtpc.c
00037  * \brief Simple mail transfer protocol client.
00038  *
00039  * \verbatim
00040  * $Id$
00041  * \endverbatim
00042  */
00043 
00044 #include <sys/nutdebug.h>
00045 #include <sys/confnet.h>
00046 #include <sys/socket.h>
00047 
00048 #include <arpa/inet.h>
00049 #include <pro/rfctime.h>
00050 
00051 #include <memdebug.h>
00052 #include <string.h>
00053 
00054 #include <pro/smtpc.h>
00055 
00060 
00061 #ifndef SMTP_TIMEOUT
00062 #define SMTP_TIMEOUT    600000
00063 #endif
00064 
00087 static void NutBase64Encode(CONST uint8_t * sptr, size_t slen, char *dptr)
00088 {
00089     static CONST char base64set[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
00090     uint_fast8_t i;
00091     uint32_t blk;
00092     uint_fast8_t pad = 0;
00093 
00094     /* Normally we do not check for NULL pointers. */
00095     NUTASSERT(sptr != NULL);
00096     NUTASSERT(dptr != NULL);
00097 
00098     while (slen) {
00099         blk = (uint32_t) (*sptr++) << 16;
00100         if (slen > 1) {
00101             blk |= (uint32_t) (*sptr++) << 8;
00102             if (slen > 2) {
00103                 blk |= *sptr++;
00104             } else {
00105                 pad = 1;
00106             }
00107         } else {
00108             pad = 2;
00109         }
00110         for (i = pad; i < 4; i++) {
00111             *dptr++ = base64set[(blk >> 18) & 63];
00112             blk <<= 6;
00113         }
00114         for (i = 0; i < pad; i++) {
00115             *dptr++ = '=';
00116         }
00117         slen -= 3 - pad;
00118     }
00119     *dptr = '\0';
00120 }
00121 
00129 CONST char *NutSmtpReceiveResponse(SMTPCLIENTSESSION * si)
00130 {
00131     char *cp;
00132 
00133     /* Normally we do not check for NULL pointers. */
00134     NUTASSERT(si != NULL);
00135 
00136     if (fgets(si->smtp_buff, sizeof(si->smtp_buff), si->smtp_stream)) {
00137         cp = strchr(si->smtp_buff, '\r');
00138         if (cp == NULL) {
00139             cp = strchr(si->smtp_buff, '\n');
00140         }
00141         if (cp) {
00142             *cp = '\0';
00143             return si->smtp_buff;
00144         }
00145         /* Line overflow. */
00146     }
00147     return NULL;
00148 }
00149 
00159 CONST char *NutSmtpSendCommand(SMTPCLIENTSESSION * si, CONST char *fmt, ...)
00160 {
00161     va_list ap;
00162 
00163     /* Normally we do not check for NULL pointers. */
00164     NUTASSERT(si != NULL);
00165     NUTASSERT(fmt != NULL);
00166 
00167     va_start(ap, fmt);
00168     vfprintf(si->smtp_stream, (char *) fmt, ap);
00169     va_end(ap);
00170     fputs("\r\n", si->smtp_stream);
00171     fflush(si->smtp_stream);
00172 
00173     return NutSmtpReceiveResponse(si);
00174 }
00175 
00184 void NutSmtpDisconnect(SMTPCLIENTSESSION * si)
00185 {
00186     /* Normally we do not check for NULL pointers. */
00187     NUTASSERT(si != NULL);
00188 
00189     if (si->smtp_sock) {
00190         if (si->smtp_stream) {
00191             NutSmtpSendCommand(si, "QUIT");
00192             fclose(si->smtp_stream);
00193         }
00194         NutTcpCloseSocket(si->smtp_sock);
00195     }
00196     free(si);
00197 }
00198 
00209 SMTPCLIENTSESSION *NutSmtpConnect(uint32_t ip, uint16_t port)
00210 {
00211     SMTPCLIENTSESSION *si;
00212 
00213     si = calloc(1, sizeof(SMTPCLIENTSESSION));
00214     if (si) {
00215         si->smtp_sock = NutTcpCreateSocket();
00216         if (si->smtp_sock && NutTcpConnect(si->smtp_sock, ip, port) == 0) {
00217             uint32_t tmo = SMTP_TIMEOUT;
00218             NutTcpSetSockOpt(si->smtp_sock, SO_RCVTIMEO, &tmo, sizeof(tmo));
00219             si->smtp_stream = _fdopen((int) ((intptr_t) si->smtp_sock), "r+b");
00220             if (si->smtp_stream && *NutSmtpReceiveResponse(si) == '2') {
00221                 return si;
00222             }
00223         }
00224         NutSmtpDisconnect(si);
00225     }
00226     return NULL;
00227 }
00228 
00239 static CONST char *SayHello(SMTPCLIENTSESSION * si, char *cmd, char *host)
00240 {
00241     if (host) {
00242         return NutSmtpSendCommand(si, "%s %s", cmd, host);
00243     }
00244     return NutSmtpSendCommand(si, "%s [%s]", cmd, inet_ntoa(confnet.cdn_ip_addr));
00245 }
00246 
00258 int NutSmtpLogin(SMTPCLIENTSESSION * si, char *host, char *user, char *pass)
00259 {
00260     CONST char *rsp;
00261 
00262     /* Normally we do not check for NULL pointers. */
00263     NUTASSERT(si != NULL);
00264 
00265     /* We start an SMTP session by issuing the EHLO command. */
00266     rsp = SayHello(si, "EHLO", host);
00267     if (rsp && *rsp == '5') {
00268         /* If EHLO is not supported, we fall back to HELO. */
00269         si->smtp_feat |= SMTPFEAT_VINTAGE;
00270         rsp = SayHello(si, "HELO", host);
00271     }
00272     if (rsp && *rsp == '2') {
00273         if ((si->smtp_feat & SMTPFEAT_VINTAGE) != SMTPFEAT_VINTAGE) {
00274             for (;;) {
00275                 if (strncmp(rsp + 4, "AUTH ", 5) == 0) {
00276                     if (strstr(rsp + 9, "LOGIN")) {
00277                         si->smtp_feat |= SMTPFEAT_AUTH_LOGIN;
00278                     }
00279                     if (strstr(rsp + 9, "PLAIN")) {
00280                         si->smtp_feat |= SMTPFEAT_AUTH_PLAIN;
00281                     }
00282                 }
00283                 if (*(rsp + 3) != '-') {
00284                     break;
00285                 }
00286                 rsp = NutSmtpReceiveResponse(si);
00287                 if (rsp == NULL) {
00288                     break;
00289                 }
00290             }
00291         }
00292         if (user == NULL) {
00293             /* Return with success, if authorization not required. */
00294             return 0;
00295         }
00296         if (si->smtp_feat & SMTPFEAT_AUTH_PLAIN) {
00297             int lu = strlen(user);
00298             int lp = strlen(pass);
00299             uint8_t *auth = malloc(lu + lp + 3);
00300 
00301             *auth = '\0';
00302             memcpy(auth + 1, user, lu + 1);
00303             memcpy(auth + 1 + lu + 1, pass, lp);
00304 
00305             NutBase64Encode(auth, lu + lp + 2, si->smtp_buff);
00306             rsp = NutSmtpSendCommand(si, "AUTH PLAIN %s", si->smtp_buff);
00307             if (rsp && *rsp == '2') {
00308                 return 0;
00309             }
00310         } else if (si->smtp_feat & SMTPFEAT_AUTH_LOGIN) {
00311         }
00312     }
00313     return -1;
00314 }
00315 
00328 int NutSmtpSendMailRequest(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
00329 {
00330     int rc = 0;
00331     CONST char *rsp;
00332 
00333     /* Normally we do not check for NULL pointers. */
00334     NUTASSERT(me != NULL);
00335 
00336     rsp = NutSmtpSendCommand(si, "MAIL FROM:%s", me->mail_from);
00337     if (rsp && *rsp == '2') {
00338         int i;
00339 
00340         for (i = 0; i < MAX_MAIL_RCPTS; i++) {
00341             /* Request all unprocessed recipients. */
00342             if (me->mail_rcpt[i] && (me->mail_rcpt_stat[i] & MAIL_RCPT_DONE) == 0) {
00343                 rsp = NutSmtpSendCommand(si, "RCPT TO:%s", me->mail_rcpt[i]);
00344                 if (rsp) {
00345                     if (*rsp == '2') {
00346                         /* Recipient accepted. */
00347                         me->mail_rcpt_stat[i] |= MAIL_RCPT_ACPT;
00348                         rc++;
00349                     } else {
00350                         /* Recipient not accepted. */
00351                         me->mail_rcpt_stat[i] &= ~MAIL_RCPT_ACPT;
00352                         if (*rsp == '5') {
00353                             /* Recipient refused. */
00354                             me->mail_rcpt_stat[i] |= MAIL_RCPT_FAIL;
00355                         }
00356                     }
00357                 }
00358             }
00359         }
00360     }
00361     return rc;
00362 }
00363 
00371 static int SendMailHeaderRecipient(FILE *stream, MAILENVELOPE * me, uint8_t type)
00372 {
00373     uint_fast8_t i;
00374     int cnt;
00375 
00376     /* Process the list of recipients given by this envelope. */
00377     for (i = 0, cnt = 0; i < MAX_MAIL_RCPTS; i++) {
00378         /* Check for the recipient's type. */
00379         if ((me->mail_rcpt_stat[i] & MAIL_RCPT_TYPE) == type) {
00380             if (cnt) {
00381                 /* Additional recipient. */
00382                 fputs(",\r\n    ", stream);
00383             } else {
00384                 /* First one found. */
00385                 fputs(type == MAIL_RCPT_TO ? "To: " : "CC: ", stream);
00386             }
00387             fputs(me->mail_rcpt_header[i], stream);
00388             cnt++;
00389         }
00390     }
00391     if (cnt) {
00392         fputs("\r\n", stream);
00393     }
00394     return 0;
00395 }
00396 
00423 int NutSmtpSendMailHeader(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
00424 {
00425     /* Normally we do not check for NULL pointers. */
00426     NUTASSERT(si != NULL);
00427     NUTASSERT(si->smtp_stream != NULL);
00428     NUTASSERT(me != NULL);
00429 
00430     if (me->mail_date) {
00431         fprintf(si->smtp_stream, "Date: %s\r\n", Rfc1123TimeString(gmtime(&me->mail_date)));
00432     }
00433     fprintf(si->smtp_stream, "From: %s\r\n", me->mail_from_header);
00434     fprintf(si->smtp_stream, "Subject: %s\r\n", me->mail_subj);
00435     SendMailHeaderRecipient(si->smtp_stream, me, MAIL_RCPT_TO);
00436     SendMailHeaderRecipient(si->smtp_stream, me, MAIL_RCPT_CC);
00437 
00438     return 0;
00439 }
00440 
00459 int NutSmtpSendEncodedLines(SMTPCLIENTSESSION * si, CONST char *text)
00460 {
00461     /* Sanity checks. */
00462     NUTASSERT(si != NULL);
00463     NUTASSERT(text != NULL);
00464 
00465     while (*text) {
00466         char *bufp = si->smtp_buff;
00467         int i;
00468 
00469         /* Collect a line or a complete buffer. */
00470         for (i = 0; *text && i < SMTP_BUFSIZ; text++) {
00471             /* Ignore carriage returns. */
00472             if (*text != '\r') {
00473                 /* Stop at linefeeds or at the end of the message. */
00474                 if (*text == '\n' || *text == '\0') {
00475                     /* Also send newline. */
00476                     i++;
00477                     text++;
00478                     break;
00479                 } else {
00480                     /* Send this character unchanged. */
00481                     *bufp++ = *text;
00482                     i++;
00483                 }
00484             }
00485         }
00486         if (i) {
00487             *bufp = '\0';
00488             /* Insert a dot in front of any line that starts with a dot. */
00489             if (si->smtp_buff[0] == '.') {
00490                 fputc('.', si->smtp_stream);
00491             }
00492             /* Send the buffer with CR/LF appended. */
00493             fputs(si->smtp_buff, si->smtp_stream);
00494             if (fputs("\r\n", si->smtp_stream) == EOF) {
00495                 /* Connection broken. */
00496                 return -1;
00497             }
00498         }
00499     }
00500     return 0;
00501 }
00502 
00540 CONST char *NutSmtpSendMail(SMTPCLIENTSESSION * si, MAILENVELOPE * me)
00541 {
00542     CONST char *rsp = NULL;
00543 
00544     /* Normally we do not check for NULL pointers. */
00545     NUTASSERT(si != NULL);
00546     NUTASSERT(me != NULL);
00547 
00548     /* Send a request for sending mail. The function returns the number
00549        of accepted recipients. */
00550     if (NutSmtpSendMailRequest(si, me) > 0) {
00551         /* Start sending the email. */
00552         rsp = NutSmtpSendCommand(si, "DATA");
00553         /* Server wants us to go ahead? */
00554         if (rsp && *rsp == '3') {
00555             /* Send the major header lines. */
00556             NutSmtpSendMailHeader(si, me);
00557             fputs("\r\n", si->smtp_stream);
00558             /* If available, send the mail body's text. */
00559             if (me->mail_body) {
00560                 NutSmtpSendEncodedLines(si, me->mail_body);
00561             }
00562             fputs(".\r\n", si->smtp_stream);
00563             fflush(si->smtp_stream);
00564             /* Check the result of the data transfer. */
00565             rsp = NutSmtpReceiveResponse(si);
00566             /* On success, update the status of the previously accepted
00567                recipients. */
00568             if (rsp && *rsp == '2') {
00569                 uint_fast8_t i;
00570                 for (i = 0; i < MAX_MAIL_RCPTS; i++) {
00571                     if (me->mail_rcpt[i]) {
00572                         if ((me->mail_rcpt_stat[i] & MAIL_RCPT_ACPT) == MAIL_RCPT_ACPT) {
00573                             me->mail_rcpt_stat[i] |= MAIL_RCPT_SENT;
00574                         }
00575                     }
00576                 }
00577             }
00578         }
00579     }
00580     return rsp;
00581 }
00582