smtpthread/SMTPConnection.cc

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 }

Generated on Thu Nov 1 09:51:21 2007 for libSmtpThread by  doxygen 1.5.1