00001 // DOCUMENT: SMTPConnection-Class 00002 // VERSION: $Revision: 1.4 $ 00003 // DATE: $Date: 2007-10-26 23:09:19 $ 00004 // AUTHOR: M.Beranek 00005 // COPYRIGHT: M.Beranek 00006 00007 #include <iostream> 00008 #include <stdio.h> 00009 #include <stdlib.h> 00010 #include <unistd.h> 00011 #include <string> 00012 #include <errno.h> 00013 #include <sys/socket.h> 00014 #include <sys/types.h> 00015 #include <netinet/in.h> 00016 #include <arpa/inet.h> 00017 #include <pthread.h> 00018 #include "Thread.h" 00019 #include "SMTPConnection.h" 00020 00021 using namespace std; 00022 00023 namespace SmtpThread 00024 { 00025 // -------------------------------------------------------------------------- 00026 /** 00027 * Default-Constructor. 00028 */ 00029 SMTPConnection::SMTPConnection() 00030 { 00031 // We are not connected. 00032 _smtp = NOT_CONNECTED; 00033 00034 // We are using the loopback-interface. 00035 _host = "127.0.0.1"; 00036 00037 // We use the default port. 00038 _port = 25; 00039 00040 // We are the "localhost". 00041 _myname = "localhost"; 00042 00043 // We do not have a socket-structure yet. 00044 _remoteEnd = NULL; 00045 00046 // There is no value returned by the SMTP-Server yet. 00047 _state[0] = 0x0; 00048 _state[1] = 0x0; 00049 _state[2] = 0x0; 00050 _state[3] = 0x0; 00051 00052 00053 // We havn't received a single character yet. 00054 _buf2 = 0x0; 00055 00056 } 00057 00058 00059 // -------------------------------------------------------------------------- 00060 /** 00061 * Destructor. 00062 */ 00063 SMTPConnection::~SMTPConnection() 00064 { 00065 // Be sure, to be disconnected: 00066 disconnect(); 00067 00068 // If socket-structure exists: 00069 if( _remoteEnd ) 00070 // Clean up socket-structure: 00071 delete _remoteEnd; 00072 00073 } 00074 00075 00076 // -------------------------------------------------------------------------- 00077 /** 00078 * Gets the current status of the SMTP-Dialog 00079 * @return SMTPConnection::SMTPState Status of the SMTP-Dialog. 00080 */ 00081 SMTPConnection::SMTPState SMTPConnection::getSMTPState() 00082 { 00083 return _smtp; 00084 } 00085 00086 00087 // -------------------------------------------------------------------------- 00088 /** 00089 * Sends the email to the server. 00090 * The actual mail-delivery happens here. 00091 * @return int error-code: 0 = success, -1 = error 00092 */ 00093 int SMTPConnection::sendSMTPMessage() 00094 { 00095 // status-codes: 00096 int okay; 00097 int code; 00098 00099 // counter for attempts: 00100 int attempts_conn = 0; 00101 int attempts_init = 0; 00102 int attempts_from = 0; 00103 int attempts_to = 0; 00104 int attempts_data = 0; 00105 00106 // limits for attempts: 00107 int max_attempts_conn = 20; 00108 int max_attempts_init = 5; 00109 int max_attempts_from = 5; 00110 int max_attempts_to = 5; 00111 int max_attempts_data = 5; 00112 00113 // initialize the connection. If we are connected, this won't do anything. 00114 initConnection(); 00115 00116 00117 /* 00118 ********************************************************** 00119 * 00120 * A typical SMTP-dialog: 00121 * 00122 * 220 voyager.beranek.de ESMTP Postfix 00123 * HELO beranek.de 00124 * 250 voyager.beranek.de 00125 * MAIL From: marcus@beranek.de 00126 * 250 Ok 00127 * RCPT To: marcus@voyager.beranek.de 00128 * 250 Ok 00129 * DATA 00130 * 354 End data with <CR><LF>.<CR><LF> 00131 * bla-blahh... 00132 * . 00133 * 250 Ok: queued as ACA6A37B86 00134 * QUIT 00135 * 221 Bye 00136 00137 ********************************************************** 00138 */ 00139 00140 00141 /* 00142 * Note about the coding-style: 00143 * 00144 * The following code looks more like assembler-code, 00145 * caused by a heavy usage of the "goto"-command. 00146 * 00147 * I know a lot of peaople do not like goto's in C/C++, 00148 * but this is a lot more conveinient than an endless-loop 00149 * with a very big switch-case-statement... 00150 * 00151 */ 00152 00153 00154 // initialize SMTP-Dialog: 00155 init: 00156 00157 // cout << "SMTPConnection.sendSMTPMessage: init" << endl; 00158 00159 // Send Reset: 00160 sendSMTP_CRLF( "RSET"); 00161 00162 // Read SMTP-Status-Code: 00163 code = readSMTPState(); 00164 00165 // Say hello: 00166 sendSMTP( "HELO "); 00167 00168 // Ssend myname: 00169 sendSMTP_CRLF( _myname ); 00170 00171 // Read SMTP-Status-Code: 00172 code = readSMTPState(); 00173 00174 // If SMTP-Statud-Code is not 2xx: 00175 if( code < 200 || code >= 300) 00176 { 00177 00178 // We are still connected: 00179 _smtp = CONNECTED; 00180 00181 // We wait two seconds: 00182 sleep( 2 ); 00183 00184 // We count the attemts: 00185 attempts_init++; 00186 00187 // If we are within our limits: 00188 if( attempts_init < max_attempts_init ) 00189 { 00190 // retry: 00191 goto init; 00192 } 00193 // If we are out of the limits: 00194 else 00195 { 00196 00197 // Close the socket: 00198 closeSocket(); 00199 00200 // Wait a few seconds: 00201 sleep(5); 00202 00203 // Open socket again: 00204 initConnection(); 00205 00206 // Count the attempts: 00207 attempts_conn++; 00208 00209 // If we are within our limits: 00210 if( attempts_conn < max_attempts_conn ) 00211 { 00212 // Set state: 00213 _smtp = CONNECTED; 00214 00215 // Go back to HELO: 00216 goto init; 00217 } 00218 // If we are out of the limits: 00219 else 00220 { 00221 // We give up and set state to not-connected: 00222 _smtp = NOT_CONNECTED; 00223 00224 // Close the socket: 00225 closeSocket(); 00226 00227 // Return an error-code: 00228 return -1; 00229 } 00230 } 00231 } 00232 00233 // We got here, so the HELO-command succeded. 00234 // We change the state into HELO: 00235 _smtp = HELO; 00236 00237 // We have to give our email-address: 00238 mailfrom: 00239 00240 // cout << "SMTPConnection.sendSMTPMessage: mailfrom" << endl; 00241 00242 /* 00243 00244 Command to send: 00245 "MAIL From: <email@domain.com>" 00246 00247 */ 00248 00249 00250 // Send "Mail from <" 00251 sendSMTP( "MAIL From: <"); 00252 00253 // Send the email-address: 00254 sendSMTP( _from ); 00255 00256 // send ">" + CRLF: 00257 sendSMTP_CRLF( ">" ); 00258 00259 // Read SMTP-Status-Code: 00260 code = readSMTPState(); 00261 00262 // If SMTP-state is not 2xx: 00263 if( code < 200 || code >= 300) 00264 { 00265 00266 // Wait a second: 00267 sleep( 1 ); 00268 00269 // Count the attempts: 00270 attempts_from++; 00271 00272 // If we are within out limits: 00273 if( attempts_from < max_attempts_from ) 00274 { 00275 00276 // We are still in the state HELO:: 00277 _smtp = HELO; 00278 00279 // Try again: 00280 goto mailfrom; 00281 00282 } 00283 // If we are out of the limits: 00284 else 00285 { 00286 00287 // We are still connected: 00288 _smtp = CONNECTED; 00289 00290 // Go back to "HELO": 00291 goto init; 00292 00293 } 00294 } 00295 00296 // If we got here, we have send our email-address successfully. 00297 // We change the state to MAILFROM: 00298 _smtp = MAILFROM; 00299 00300 00301 // Send email-recipient-address: 00302 rcptto: 00303 00304 // cout << "SMTPConnection.sendSMTPMessage: rcpto" << endl; 00305 00306 /* 00307 00308 Command to send: 00309 "RCPT To: <email@domain>" 00310 00311 */ 00312 00313 // Send "RCPT To: <" 00314 sendSMTP( "RCPT To: <" ); 00315 00316 // Send recipients address: 00317 sendSMTP( _to ); 00318 00319 // Send ">" + CRLF: 00320 sendSMTP_CRLF( ">" ); 00321 00322 // Read SMTP-Status-Code: 00323 code = readSMTPState(); 00324 00325 // If SMTP-state is not 2xx: 00326 if( code < 200 || code >= 300) 00327 { 00328 00329 // Wait a second: 00330 sleep( 1 ); 00331 00332 // Count the attempts: 00333 attempts_to++; 00334 00335 // If we are within our limits: 00336 if( attempts_to < max_attempts_to ) 00337 { 00338 00339 // We are still in the state MAILFROM: 00340 _smtp = MAILFROM; 00341 00342 // Go backt to MAILFROM 00343 goto mailfrom; 00344 00345 } 00346 // If we are out of limits: 00347 else 00348 { 00349 // We are in the state HELO: 00350 _smtp = HELO; 00351 00352 // Go back to HELO: 00353 goto init; 00354 00355 } 00356 } 00357 00358 // If we got here, we have sent the recipoents address successfully: 00359 _smtp = RCPTTO; 00360 00361 00362 // Start sending data: 00363 data: 00364 // cout << "SMTPConnection.sendSMTPMessage: data" << endl; 00365 00366 // Command to send: "DATA" 00367 sendSMTP_CRLF( "DATA" ); 00368 00369 // Read SMTP-Status-Code: 00370 code = readSMTPState(); 00371 00372 // The server should return 354: 00373 if( code != 354 ) 00374 { 00375 00376 // Wait a second: 00377 sleep( 1 ); 00378 00379 // Count the attempts: 00380 attempts_data++; 00381 00382 // If we are within our limits: 00383 if( attempts_data < max_attempts_data ) 00384 { 00385 00386 // We are still in the state RCPTTO: 00387 _smtp = RCPTTO; 00388 00389 // Try again 00390 goto data; 00391 00392 } 00393 // If we are out of our limits: 00394 else 00395 { 00396 // We are in the state HELO:: 00397 _smtp = HELO; 00398 00399 // Go back to HELO: 00400 goto init; 00401 } 00402 00403 } 00404 00405 // If we got here, the DATA command succeded. 00406 // Now we are allowed to send our message. 00407 00408 // Chang state to DATA: 00409 _smtp = DATA; 00410 00411 // send the mailbody: 00412 body: 00413 00414 // cout << "SMTPConnection.sendSMTPMessage: body" << endl; 00415 00416 // Change the state to DATA_SENDING: 00417 _smtp = DATA_SENDING; 00418 00419 // Send the mail-body: 00420 sendSMTP( _body ); 00421 00422 // End the mail-body with a <CRLF><DOT><CRLF>: 00423 sendSMTP_CRLF( "\r\n." ); 00424 00425 // Read SMTP-Status-Code: 00426 code = readSMTPState(); 00427 00428 // If SMTP-state is not 2xx: 00429 if( code <200 || code >= 300) 00430 { 00431 00432 // Wait a second: 00433 sleep( 1 ); 00434 00435 // Count the attempts: 00436 attempts_data++; 00437 00438 // If we are within our limits: 00439 if( attempts_data < max_attempts_data ) 00440 { 00441 00442 // Change state to DATA 00443 _smtp = DATA; 00444 00445 // Try again: 00446 goto body; 00447 00448 } 00449 // If we are out of our limits: 00450 else 00451 { 00452 00453 // Chang the state to HELO: 00454 _smtp = HELO; 00455 00456 // Go back to HELO: 00457 goto init; 00458 } 00459 } 00460 00461 // cout << "SMTPConnection.sendSMTPMessage: finished" << endl; 00462 00463 00464 // If we got here, everything went well. 00465 // So, we send a reset to start again with 00466 // the next message: 00467 sendSMTP_CRLF( "RSET" ); 00468 00469 // Read SMTP-Status-Code: 00470 code = readSMTPState(); 00471 00472 // Change the state to CONNECTED: 00473 _smtp = CONNECTED; 00474 00475 // Return success-code: 00476 return 0; 00477 00478 } 00479 00480 00481 // -------------------------------------------------------------------------- 00482 /** 00483 * Sets the SMTP-Host (IP). 00484 * @param string host IP-Address of the SMTP-host. 00485 */ 00486 void SMTPConnection::setSMTPHost( string host ) 00487 { 00488 _host = host; 00489 } 00490 00491 00492 // -------------------------------------------------------------------------- 00493 /** 00494 * Sets the Port to connect to, defaults to 25. 00495 * @param int port Port 00496 */ 00497 void SMTPConnection::setSMTPPort( int port ) 00498 { 00499 _port = port; 00500 } 00501 00502 00503 // -------------------------------------------------------------------------- 00504 /** 00505 * Initializes a connection to the SMTP-server. 00506 * If a connection is already established, initConnection will 00507 * silently return and not reset/reinitialize the current connection. 00508 */ 00509 void SMTPConnection::initConnection() 00510 { 00511 // If we don not have a socket-structure: 00512 if( _remoteEnd == NULL ) 00513 // Initialize a socket-structure. 00514 initSocket(); 00515 00516 // Status of the tcp-connection: 00517 int connectState = 0; 00518 00519 // If the SMTP-Connection is established, we are not going to re-initialize 00520 // the socket-structure, because that has happend already. 00521 if( _smtp == NOT_CONNECTED ) 00522 { 00523 00524 // Initialize socket: 00525 _tcpSocket = socket(AF_INET, SOCK_STREAM, 0); 00526 00527 // Connect the socket: 00528 connectState = connect( _tcpSocket, (SA *)_remoteEnd, sizeof (SA) ); 00529 00530 // If connection failed: 00531 if( connectState < 0 ) 00532 { 00533 00534 // Close the socket: 00535 closeSocket(); 00536 00537 } 00538 00539 else 00540 { 00541 // Connection succeeded, change the state to CONNECTED: 00542 _smtp = CONNECTED; 00543 00544 // Read a line from the SMTP-Server and save the SMTP-Status. 00545 int smtpCode = readSMTPLine(); 00546 00547 } 00548 00549 00550 } 00551 00552 // If we already have a connection. 00553 else 00554 { 00555 // Nothing to do, so we return. 00556 return; 00557 } 00558 00559 00560 } 00561 00562 00563 // -------------------------------------------------------------------------- 00564 /** 00565 * Initializes the socket-structure. 00566 */ 00567 void SMTPConnection::initSocket() 00568 { 00569 // Initialize Socket-Structure: 00570 _remoteEnd = (SAI *) new SA; 00571 _remoteEnd->sin_family = AF_INET; 00572 00573 // Set IP-Address of Server for the socket-structure: 00574 _remoteEnd->sin_addr.s_addr = inet_addr( _host.c_str() ); 00575 00576 // Set the SMTP-Port for the socket-structure: 00577 _remoteEnd->sin_port = htons( _port ); 00578 00579 // Clear the rest of the socket-structure: 00580 memset( &(_remoteEnd->sin_zero), '\0', 8 ); 00581 00582 } 00583 00584 00585 // -------------------------------------------------------------------------- 00586 /** 00587 * Closes the connection-socket. 00588 */ 00589 void SMTPConnection::closeSocket() 00590 { 00591 // If we have a socket: 00592 if( _tcpSocket ) 00593 { 00594 // close it: 00595 shutdown( _tcpSocket, 2 ); 00596 } 00597 00598 // Change state to NOT_CONNECTED: 00599 _smtp = NOT_CONNECTED; 00600 00601 } 00602 00603 00604 // -------------------------------------------------------------------------- 00605 /** 00606 * Reads the SMTP-Status from the socket. 00607 * 00608 * This method reads the next 3 bytes out of the socket and 00609 * interprets them as an integer. 00610 * 00611 * This method should be called only after sending an SMTP-Command 00612 * followed by an CRLF to the socket. 00613 */ 00614 int SMTPConnection::readSMTPState() 00615 { 00616 00617 /* 00618 * 'c1' and 'c2' are used as buffer for two following 00619 * characters of the SMTP-Response. They are initialized 00620 * with a space. We are reading from the socket untill 00621 * 'c1' and 'c2' are equal to <CR><LF>. 00622 */ 00623 char c1 = ' '; 00624 char c2 = ' '; 00625 00626 // Status-Array: 00627 char state[4]; 00628 00629 // QUESTION: Why not reading into _state directly instead of state? 00630 00631 // Read three characters from the socket and write them to the Status-Array. 00632 recv( _tcpSocket, state, 1, 0x0 ); 00633 recv( _tcpSocket, state+1, 1, 0x0 ); 00634 recv( _tcpSocket, state+2, 1, 0x0 ); 00635 00636 // The 4th entry is a terminating zero. 00637 state[3] = 0x0; 00638 00639 // Copy the status to class-member. 00640 _state[0] = state[0]; 00641 _state[1] = state[1]; 00642 _state[2] = state[2]; 00643 _state[3] = state[3]; 00644 00645 // Read the rest from the socket, but ignore it. 00646 00647 // As long as we do not have a <CR><LF> (i.e. 0x0D 0x0A: 00648 while( ! ( ( c1 == 0x0D ) && ( c2 == 0x0A ) ) ) 00649 { 00650 // Save last character: 00651 c1 = c2; 00652 00653 // Get next character: 00654 recv( _tcpSocket, &c2, 1, 0x0 ); 00655 00656 } 00657 00658 00659 // TODO: Check contents of the state-array for valid characters! 00660 00661 // Calculate status. 00662 // 'state' contains ASCII-characters, 00663 // ASCII( '0' ) = 48 00664 // To get the actual code, we have to remove the 00665 // ASCII-offset and multiply with 100, 10 and 1... 00666 00667 int code = 100 * (state[0] -48) // first character (eg. 354: 3xx => 3 x 100) 00668 + 10 * (state[1] -48) // second character (eg. 354: x5x => 5 x 10) 00669 + (state[2] -48); // third character (eg. 354: xx4 => 4 x 1) 00670 00671 // Return the status-code: 00672 return code; 00673 00674 00675 } 00676 00677 00678 // -------------------------------------------------------------------------- 00679 /** 00680 * Sends a command to the SMTP-server (without the terminating CRLF). 00681 */ 00682 inline int SMTPConnection::sendSMTP( const string msg ) 00683 { 00684 00685 // Send the command: 00686 return send( _tcpSocket, msg.c_str(), msg.length(), 0x0 ); 00687 00688 } 00689 00690 00691 // -------------------------------------------------------------------------- 00692 /** 00693 * Sends a command to the SMTP-server followed by the terminating CRLF. 00694 */ 00695 inline int SMTPConnection::sendSMTP_CRLF( const string msg ) 00696 { 00697 00698 // Send in two stages: first send command, then send <CR><LF>. 00699 // We cannot send a msg.append( "\r\n" ), because we have a const string! 00700 00701 // Return-values of each stage: 00702 int ret1 = 0; 00703 int ret2 = 0; 00704 00705 // Send the command: 00706 ret1 = sendSMTP( msg ); 00707 00708 // Send a <CR><LF>: 00709 ret2 = sendSMTP( "\r\n" ); 00710 00711 // Return result of both return-values: 00712 return ret1 | ret2; 00713 00714 00715 } 00716 00717 00718 // -------------------------------------------------------------------------- 00719 /** 00720 * Reads a line from the socket, stores it into "_buf" 00721 * and returns the SMTP-Status-Code. 00722 */ 00723 int SMTPConnection::readSMTPLine() 00724 { 00725 00726 /* 00727 * 'c1' and 'c2' are used as buffer for two following 00728 * characters of the SMTP-Response. They are initialized 00729 * with a space. We are reading from the socket untill 00730 * 'c1' and 'c2' are equal to <CR><LF>. 00731 */ 00732 char c1 = ' '; 00733 char c2 = ' '; 00734 00735 // Position within our response-line. 00736 int pos = 0; 00737 00738 00739 // As long as we do not have a terminating <CR><LF>: 00740 while( ! ( (c1 == 0x0D) && (c2 == 0x0A) ) ) 00741 { 00742 00743 // TODO: eleminate _buf2 and use "_buf1 + pos" ... 00744 00745 // Read a character from the socket and store it into _buf2. 00746 recv( _tcpSocket, &_buf2, 1, 0x0 ); 00747 00748 // Save previous last character: 00749 c1 = c2; 00750 00751 // Save last character: 00752 c2=_buf2; 00753 00754 // Write last character into buffer: 00755 _buf1[pos] = c2; 00756 00757 // TODO: Boundary-Check: 00758 00759 // Go one step foreward: 00760 pos++; 00761 00762 // If we have 999 characters read, we stop reading characters. 00763 if (pos == 1000) 00764 { 00765 // TODO: Get the rest of the date from the socket. 00766 00767 /* 00768 c1 = 0x0d; 00769 c2 = 0x0a; 00770 */ 00771 00772 // Stop processing: 00773 break; 00774 } 00775 } 00776 00777 // Insert a terminating zero-byte into our response-buffer: 00778 _buf1[pos] = 0x0; 00779 00780 00781 // Copy the first three characters into the status-code-buffer: 00782 _state[0] = _buf1[0]; 00783 _state[1] = _buf1[1]; 00784 _state[2] = _buf1[2]; 00785 _state[3] = 0x0; 00786 00787 // Return the status-code: 00788 return strtol( _state, NULL, 10 ); 00789 00790 00791 } 00792 00793 00794 // -------------------------------------------------------------------------- 00795 /** 00796 * Accepts a new email to be send via SMTP. 00797 * 00798 * The mailbody should contain all additional headers (like "To", "From", 00799 * "Subject". "Content-Type" etc.). 00800 * 00801 * @param string* from sender-email 00802 * @param string* to receiverr-email 00803 * @param string* msg email-body 00804 */ 00805 int SMTPConnection::sendMail( string& from, string& to, string& msg ) 00806 { 00807 _from = from; 00808 _to = to; 00809 _body = msg; 00810 return sendSMTPMessage(); 00811 } 00812 00813 00814 // -------------------------------------------------------------------------- 00815 /** 00816 * Sets the name, which will be sent within the "HELO"-command. 00817 */ 00818 void SMTPConnection::setMyHostname( string myname ) 00819 { 00820 _myname = myname; 00821 } 00822 00823 00824 // -------------------------------------------------------------------------- 00825 /** 00826 * Disconnects from the SMTP-Server. 00827 * 00828 * If the socket is connected to a server, we will send a "QUIT"-command to 00829 * the server, read the SMTP-Status-Code and then close the socket. 00830 * 00831 * If the socket is not connected, we do nothing. 00832 * 00833 */ 00834 void SMTPConnection::disconnect() 00835 { 00836 // If we are connected: 00837 if( _smtp != NOT_CONNECTED ) 00838 { 00839 // Be polite and quit the smtp-dialog: 00840 sendSMTP_CRLF( "QUIT" ); 00841 00842 // We clear the socket buffer: 00843 readSMTPState(); 00844 00845 // And finally we close the socket: 00846 closeSocket(); 00847 } 00848 } 00849 00850 }
1.5.1