Dependents:   TimeZoneDemo EthernetJackTestCode MMEx_Challenge ntp_mem ... more

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers SMTPClient.cpp Source File

SMTPClient.cpp

00001 
00002 /*
00003 Copyright (c) 2010 Donatien Garnier (donatiengar [at] gmail [dot] com) y Segundo Equipo
00004 
00005 Permission is hereby granted, free of charge, to any person obtaining a copy
00006 of this software and associated documentation files (the "Software"), to deal
00007 in the Software without restriction, including without limitation the rights
00008 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00009 copies of the Software, and to permit persons to whom the Software is
00010 furnished to do so, subject to the following conditions:
00011 
00012 The above copyright notice and this permission notice shall be included in
00013 all copies or substantial portions of the Software.
00014 
00015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00018 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00019 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00020 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00021 THE SOFTWARE.
00022 */
00023 #include "core/netservice.h"
00024 #include "SMTPClient.h"
00025 #include "../util/base64.h"
00026 
00027 #define __DEBUG
00028 #include "dbg/dbg.h"
00029 
00030 #define CHUNK_SIZE 256 //512
00031 #define BUF_SIZE (CHUNK_SIZE + 1)
00032 
00033 #define SMTP_REQUEST_TIMEOUT 15000
00034 #define SMTP_PORT 25
00035 
00036 SMTPClient::SMTPClient() : NetService(false) /*Not owned by the pool*/,
00037         m_pCbItem(NULL), m_pCbMeth(NULL), m_pCb(NULL),
00038         m_watchdog(), m_timeout(SMTP_REQUEST_TIMEOUT), m_pDnsReq(NULL), m_server(),
00039         m_closed(true), m_state(SMTP_CLOSED), m_pMessage(NULL),
00040         m_posInMsg(0), m_blockingResult(SMTP_PROCESSING),
00041         m_username(""), m_password(""), m_auth(SMTP_AUTH_NONE),
00042         m_heloDomain("localhost") {
00043     DBG("New SMTPClient %p\n", this);
00044 }
00045 
00046 SMTPClient::SMTPClient(const Host& host, const char* heloDomain, const char* user, const char* password, SMTPAuth auth) : NetService(false),
00047         m_pCbItem(NULL), m_pCbMeth(NULL), m_pCb(NULL),
00048         m_watchdog(), m_timeout(SMTP_REQUEST_TIMEOUT), m_pDnsReq(NULL), m_server(host),
00049         m_closed(true), m_state(SMTP_CLOSED), m_pMessage(NULL),
00050         m_posInMsg(0), m_blockingResult(SMTP_PROCESSING),
00051         m_username(string(user)), m_password(string(password)), m_auth(auth),
00052         m_heloDomain(heloDomain) {
00053     DBG("New SMTPClient %p\n", this);
00054 }
00055 
00056 SMTPClient::~SMTPClient() {
00057     close();
00058 }
00059 
00060 void SMTPClient::setAuth(const char* user, const char* password) { // Plain authentication
00061     m_username = string(user);
00062     m_password = string(password);
00063     m_auth = SMTP_AUTH_PLAIN;
00064 }
00065 
00066 void SMTPClient::clearAuth() { // Clear authentication
00067     m_username = "";
00068     m_password = "";
00069     m_auth = SMTP_AUTH_NONE;
00070 }
00071 
00072 string SMTPClient::encodePlainAuth() {
00073     string decStr = m_username;
00074     decStr += '\0';
00075     decStr += m_username;
00076     decStr += '\0';
00077     decStr += m_password;
00078 
00079     string auth = "AUTH PLAIN ";
00080     return auth.append(Base64::encode(decStr));
00081 }
00082 
00083 void SMTPClient::setHeloDomain(const char* heloDomain) {
00084     m_heloDomain = string(heloDomain);
00085 }
00086 
00087 SMTPResult SMTPClient::send(EmailMessage* pMessage) { //Blocking
00088     doSend(pMessage);
00089     return blockingProcess();
00090 }
00091 
00092 SMTPResult SMTPClient::send(EmailMessage* pMessage, void (*pMethod)(SMTPResult)) { //Non blocking
00093     setOnResult(pMethod);
00094     doSend(pMessage);
00095     return SMTP_PROCESSING;
00096 }
00097 
00098 void SMTPClient::doSend(EmailMessage* pMessage) {
00099     setup(pMessage);
00100 }
00101 
00102 void SMTPClient::setOnResult( void (*pMethod)(SMTPResult) ) {
00103     m_pCb = pMethod;
00104     m_pCbItem = NULL;
00105     m_pCbMeth = NULL;
00106 }
00107 
00108 void SMTPClient::setTimeout(int ms) {
00109     m_timeout = ms;
00110 }
00111 
00112 void SMTPClient::poll() { //Called by NetServices
00113     if ( (!m_closed) && (m_watchdog.read_ms() >= m_timeout) ) {
00114         onTimeout();
00115     }
00116 }
00117 
00118 void SMTPClient::resetTimeout() {
00119     m_watchdog.reset();
00120     m_watchdog.start();
00121 }
00122 
00123 void SMTPClient::init() { //Create and setup socket if needed
00124     close(); //Remove previous elements
00125     if (!m_closed) //Already opened
00126         return;
00127     m_state = SMTP_HELLO;
00128     m_pTCPSocket = new TCPSocket;
00129     m_pTCPSocket->setOnEvent(this, &SMTPClient::onTCPSocketEvent);
00130     m_closed = false;
00131     m_posInMsg = 0;
00132     m_posInCRLF = 2;
00133     m_response = "";
00134 }
00135 
00136 void SMTPClient::close() {
00137     if (m_closed)
00138         return;
00139     m_state = SMTP_CLOSED;
00140     m_closed = true; //Prevent recursive calling or calling on an object being destructed by someone else
00141     m_watchdog.stop(); //Stop timeout
00142     m_watchdog.reset();
00143     m_pTCPSocket->resetOnEvent();
00144     m_pTCPSocket->close();
00145     delete m_pTCPSocket;
00146     m_pTCPSocket = NULL;
00147     if ( m_pDnsReq ) {
00148         m_pDnsReq->close();
00149         delete m_pDnsReq;
00150         m_pDnsReq = NULL;
00151     }
00152 }
00153 
00154 void SMTPClient::setServer(const Host& host) { //Setup request, make DNS Req if necessary
00155     m_server = host;
00156 }
00157 
00158 void SMTPClient::setup(EmailMessage* pMessage) { //Setup request, make DNS Req if necessary
00159 
00160     init(); //Initialize client in known state, create socket
00161     m_pMessage = pMessage;
00162     resetTimeout();
00163 
00164     m_To = m_pMessage->m_lTo.begin(); // Point to first to recipient in TO list
00165 
00166     //If port set to zero then use default port
00167     if (!m_server.getPort()) {
00168         m_server.setPort(SMTP_PORT);
00169         DBG("Using default port %d\n", SMTP_PORT);
00170     }
00171 
00172     if (m_server.getIp().isNull()) {
00173         //DNS query required
00174         m_pDnsReq = new DNSRequest();
00175         DBG("DNSRequest %p\r\n", m_pDnsReq);
00176         m_pDnsReq->setOnReply(this, &SMTPClient::onDNSReply);
00177         m_pDnsReq->resolve(&m_server);
00178         return;
00179     } else
00180         connect();
00181 
00182 }
00183 
00184 void SMTPClient::connect() { //Start Connection
00185     resetTimeout();
00186     DBG("Connecting...\n");
00187     m_pTCPSocket->connect(m_server);
00188 }
00189 
00190 void SMTPClient::onTCPSocketEvent(TCPSocketEvent e) {
00191 
00192     DBG("Event %d in SMTPClient::onTCPSocketEvent()\n", e);
00193 
00194     if (m_closed) {
00195         DBG("WARN: Discarded\n");
00196         return;
00197     }
00198 
00199     switch (e) {
00200         case TCPSOCKET_READABLE:
00201             resetTimeout();
00202             process(false);
00203             break;
00204         case TCPSOCKET_WRITEABLE:
00205             resetTimeout();
00206             process(true);
00207             break;
00208         case TCPSOCKET_CONTIMEOUT:
00209         case TCPSOCKET_CONRST:
00210         case TCPSOCKET_CONABRT:
00211         case TCPSOCKET_ERROR:
00212             DBG("Connection error in SMTP Client.\n");
00213             close();
00214             onResult(SMTP_DISC);
00215             break;
00216         case TCPSOCKET_DISCONNECTED:
00217             if (m_state != SMTP_BYE) {
00218                 DBG("Connection error in SMTP Client.\n");
00219                 close();
00220                 onResult(SMTP_DISC);
00221             }
00222             break;
00223     }
00224 }
00225 
00226 void SMTPClient::onDNSReply(DNSReply r) {
00227     if (m_closed) {
00228         DBG("WARN: Discarded\n");
00229         return;
00230     }
00231 
00232     if ( r != DNS_FOUND ) {
00233         DBG("Could not resolve hostname.\n");
00234         close();
00235         onResult(SMTP_DNS);
00236         return;
00237     }
00238 
00239     DBG("DNS Resolved to %d.%d.%d.%d\n", m_server.getIp()[0], m_server.getIp()[1], m_server.getIp()[2], m_server.getIp()[3]);
00240     //If no error, m_server has been updated by m_pDnsReq so we're set to go !
00241     m_pDnsReq->close();
00242     delete m_pDnsReq;
00243     m_pDnsReq = NULL;
00244     connect();
00245 }
00246 
00247 void SMTPClient::onResult(SMTPResult r) { //Called when exchange completed or on failure
00248     if (m_pCbItem && m_pCbMeth)
00249         (m_pCbItem->*m_pCbMeth)(r);
00250     else if (m_pCb)
00251         m_pCb(r);
00252     m_blockingResult = r; //Blocking mode
00253 }
00254 
00255 void SMTPClient::onTimeout() { //Connection has timed out
00256     DBG("Timed out.\n");
00257     close();
00258     onResult(SMTP_TIMEOUT);
00259 }
00260 
00261 SMTPResult SMTPClient::blockingProcess() { //Called in blocking mode, calls Net::poll() until return code is available
00262     //Disable callbacks
00263     m_pCb = NULL;
00264     m_pCbItem = NULL;
00265     m_pCbMeth = NULL;
00266     m_blockingResult = SMTP_PROCESSING;
00267     do {
00268         Net::poll();
00269     } while (m_blockingResult == SMTP_PROCESSING);
00270     Net::poll(); //Necessary for cleanup
00271     return m_blockingResult;
00272 }
00273 
00274 int SMTPClient::rc(char* buf) { //Parse return code
00275     int rc;
00276     int len = sscanf(buf, "%d %*[^\r\n]\r\n", &rc);
00277     if (len != 1)
00278         return -1;
00279     return rc;
00280 }
00281 
00282 bool SMTPClient::okPostAuthentication(int code) {
00283     return (code == 250) || (code == 235);
00284 }
00285 
00286 void SMTPClient::process(bool writeable) { //Main state-machine
00287 
00288     DBG("In state %d, writeable %d\n", m_state, writeable);
00289 
00290     // If writeable but nothing to write then return
00291     if (writeable && (m_state != SMTP_BODYMORE))
00292         return;
00293 
00294     // If not writeable then read
00295     char buf[BUF_SIZE] = {0};
00296     int responseCode = -1;
00297     if (!writeable) {
00298         bool firstBuf = true;
00299         int read;
00300         do { // read until nothing left but only process the response from first buffer
00301             read = m_pTCPSocket->recv(buf, BUF_SIZE - 1);
00302             if (firstBuf) {
00303                 m_response = string(buf);
00304                 responseCode = rc(buf);
00305                 firstBuf = false;
00306                 DBG("Response %d %d | %s", read, responseCode, buf);
00307             }
00308         } while (read > 0);
00309     }
00310 
00311     switch (m_state) {
00312         case SMTP_HELLO:
00313             if ( responseCode != 220 ) {
00314                 close();
00315                 onResult(SMTP_PRTCL);
00316                 return;
00317             }
00318             char* helloCommand;
00319             if (m_auth == SMTP_AUTH_NONE) {
00320                 helloCommand = "HELO";
00321                 m_state = SMTP_FROM;
00322             } else {
00323                 helloCommand = "EHLO";
00324                 m_state = SMTP_AUTH;
00325             }
00326             snprintf(buf, BUF_SIZE, "%s %s\r\n", helloCommand, m_heloDomain.c_str());
00327             break;
00328         case SMTP_AUTH:
00329             if ( responseCode != 250 ) {
00330                 close();
00331                 onResult(SMTP_PRTCL);
00332                 return;
00333             }
00334             snprintf(buf, BUF_SIZE, "%s\r\n", encodePlainAuth().c_str());
00335             m_state = SMTP_FROM;
00336             break;
00337         case SMTP_FROM:
00338             if (!okPostAuthentication(responseCode)) {
00339                 close();
00340                 onResult(SMTP_PRTCL);
00341                 return;
00342             }
00343             snprintf(buf, BUF_SIZE, "MAIL FROM:<%s>\r\n", m_pMessage->m_from.c_str());
00344             m_state = SMTP_TO;
00345             break;
00346         case SMTP_TO:
00347             if ( responseCode != 250 ) {
00348                 close();
00349                 onResult(SMTP_PRTCL);
00350                 return;
00351             }
00352             snprintf(buf, BUF_SIZE, "RCPT TO:<%s>\r\n", (m_To++)->c_str());
00353             if (m_To == m_pMessage->m_lTo.end())
00354                 m_state = SMTP_DATA;
00355             break;
00356         case SMTP_DATA:
00357             if ( responseCode != 250 ) {
00358                 close();
00359                 onResult(SMTP_PRTCL);
00360                 return;
00361             }
00362             snprintf(buf, BUF_SIZE, "DATA\r\n");
00363             m_state = SMTP_BODY;
00364             break;
00365         case SMTP_BODY:
00366             if ( responseCode != 354 ) {
00367                 close();
00368                 onResult(SMTP_PRTCL);
00369                 return;
00370             }
00371             m_state = SMTP_BODYMORE;
00372             buf[0] = '\0'; // clear buffer before carrying on into next state
00373         case SMTP_BODYMORE:
00374             if (strlen(buf) > 0) { // sending interrupted by a server response
00375                 close();
00376                 onResult(SMTP_PRTCL);
00377                 return;
00378             }
00379 
00380             if ( m_posInMsg < m_pMessage->m_content.length() ) { // if still something to send
00381                 int sendLen = 0;
00382                 while (sendLen < BUF_SIZE - 1) { // - 1 to allow room for extra dot or CR or LF
00383                     char c = m_pMessage->m_content.at(m_posInMsg++);
00384                     switch (c) { // thanks ExtraDotOutputStream.java (with extra check for naked CR)
00385                         case '.':
00386                             if (m_posInCRLF == 2) // add extra dot
00387                                 buf[sendLen++] = '.';
00388                             m_posInCRLF = 0;
00389                             break;
00390                         case '\r':
00391                             if (m_posInCRLF == 1) // two CR in a row, so insert an LF first
00392                                 buf[sendLen++] = '\n';
00393                             m_posInCRLF = 1;
00394                             break;
00395                         case '\n':
00396                             if (m_posInCRLF != 1) // convert naked LF to CRLF
00397                                 buf[sendLen++] = '\r';
00398                             m_posInCRLF = 2;
00399                             break;
00400                         default:
00401                             if (m_posInCRLF == 1) { // convert naked CR to CRLF
00402                                 buf[sendLen++] = '\n';
00403                                 m_posInCRLF = 2;
00404                             } else
00405                                 m_posInCRLF = 0; // we're  no longer at the start of a line
00406                             break;
00407                     }
00408                     buf[sendLen++] = c;
00409                     if ( m_posInMsg == m_pMessage->m_content.length() )
00410                         break;
00411                 }
00412                 m_pTCPSocket->send( buf, sendLen );
00413                 DBG("Sending %d bytes of processed message content\n", sendLen);
00414             } else {
00415                 if (m_posInCRLF == 0)
00416                     snprintf(buf, BUF_SIZE, "\r\n.\r\n");
00417                 else if (m_posInCRLF == 1)
00418                     snprintf(buf, BUF_SIZE, "\n.\r\n");
00419                 else
00420                     snprintf(buf, BUF_SIZE, ".\r\n");
00421                 m_state = SMTP_EOF;
00422             }
00423             break;
00424         case SMTP_EOF:
00425             if ( responseCode != 250 ) {
00426                 close();
00427                 onResult(SMTP_PRTCL);
00428                 return;
00429             }
00430             snprintf(buf, BUF_SIZE, "QUIT\r\n");
00431             m_state = SMTP_BYE;
00432             break;
00433         case SMTP_BYE:
00434             if ( responseCode != 221 ) {
00435                 close();
00436                 onResult(SMTP_PRTCL);
00437                 return;
00438             }
00439             close();
00440             onResult(SMTP_OK);
00441             return;
00442     }
00443 
00444     if ( m_state != SMTP_BODYMORE ) {
00445         DBG("Sending | %s", buf);
00446         m_pTCPSocket->send( buf, strlen(buf) );
00447     }
00448 
00449 }
00450 
00451 string& SMTPClient::getLastResponse() { // Return last response set on result
00452     return m_response;
00453 }