Dependents: TimeZoneDemo EthernetJackTestCode MMEx_Challenge ntp_mem ... more
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 }
Generated on Tue Jul 12 2022 16:54:38 by 1.7.2