// // The developer of the original code and/or files is Tripwire, Inc. // Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire, // Inc. Tripwire is a registered trademark of Tripwire, Inc. All rights // reserved. // // This program is free software. The contents of this file are subject // to the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2 of the License, or (at your // option) any later version. You may redistribute it and/or modify it // only in compliance with the GNU General Public License. // // This program is distributed in the hope that it will be useful. // However, this program is distributed AS-IS WITHOUT ANY // WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS // FOR A PARTICULAR PURPOSE. Please see the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, // USA. // // Nothing in the GNU General Public License or any other license to use // the code or files shall permit you to use Tripwire's trademarks, // service marks, or other intellectual property without Tripwire's // prior written consent. // // If you have any questions, please contact Tripwire, Inc. at either // info@tripwire.org or www.tripwire.org. // #include "stdtripwire.h" #include "core/debug.h" #include "mailmessage.h" #include "tw/configfile.h" #include "tw/twutil.h" #include "tripwirestrings.h" #include "core/stringutil.h" #include #include "core/msystem.h" #include "core/file.h" #include //All the spleck that it takes to run sockets in Unix... #include #if HAVE_SYS_SOCKET_H # include # include # include # include #endif #include #include #if HAVE_SYS_UTSNAME_H # include #endif #if HAVE_SYS_SELECT_H # include #endif /* Some systems like Solaris and AIX don't define * INADDR_NONE, but it's pretty standard. If not, * then the OS _should_ define it for us. */ #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff #endif #include #define INVALID_SOCKET -1 #if IS_AROS #ifndef HAVE_GETHOSTNAME #define HAVE_GETHOSTNAME 1 #endif #endif #ifndef HAVE_GETHOSTNAME static int gethostname( char* name, int namelen ) { name[0] = '\0'; #if HAVE_SYS_UTSNAME_H struct utsname myname; uname( & myname ); if ( strlen( myname.nodename ) < (unsigned int)namelen ) { strncpy( name, myname.nodename, namelen ); return 0; } else { //Not enough room in the buffer for the nodename return -1; // equivalent of SOCKET_ERROR } #else strncpy(name, "localhost", namelen); #endif } #endif //HAVE_GETHOSTNAME // Unix does not require us to go though any silly DLL hoops, so we'll // just #define the pointers to functions needed by Windows to be the // berkely functions. #define mPfnSocket socket #define mPfnInetAddr inet_addr #define mPfnGethostname gethostname #define mPfnGethostbyname gethostbyname #define mPfnConnect connect #define mPfnCloseSocket close #define mPfnSend send #define mPfnRecv recv #define mPfnSelect select #define mPfnNtohl ntohl #define mPfnHtonl htonl #define mPfnNtohs ntohs #define mPfnHtons htons // // TODO - maybe convert this SMTP code to non-blocking socket calls, or use // another thread, or make it fail gracefully when the server fails to // respond at all. // /////////////////////////////////////////////////////////////////////////////// // // Construct the SMTP Mail Message Sender // cSMTPMailMessage::cSMTPMailMessage(TSTRING strServerName, unsigned short portNumber) { mstrServerName = strServerName; mPortNumber = portNumber; mSocket = INVALID_SOCKET; } /////////////////////////////////////////////////////////////////////////////// // // Clean up any thing left over from sending or failing to send the message. // cSMTPMailMessage::~cSMTPMailMessage() { } /////////////////////////////////////////////////////////////////////////////// // // Get the IP address from the the server string. It's OK to have // this function look up a string like "192.34.64.23" or one like // "mail.stinkycheese.com" // long cSMTPMailMessage::GetServerAddress() { bool bIsNumeric = true; // Decide if the string is in the form "127.0.0.1" or "mail.domain.com" // by looking for an character that is not a digit or a period. for (std::vector::size_type i=0; i < mstrServerName.length(); i++) { if (mstrServerName[i] != '.' && (mstrServerName[i]<'0' || mstrServerName[i]>'9')) { bIsNumeric = false; } } std::string sNarrowString = cStringUtil::TstrToStr(mstrServerName); if (bIsNumeric) { #ifndef HAVE_SYS_SOCKET_H return 0; #else // convert the numberic address to a long return mPfnInetAddr(sNarrowString.c_str()); #endif } else { #if IS_SORTIX || !defined(HAVE_SYS_SOCKET_H) return INADDR_NONE; #else // do a DNS lookup of the hostname and get the long hostent *ent = mPfnGethostbyname(sNarrowString.c_str()); if (!ent) return INADDR_NONE; else return *(long *)ent->h_addr_list[0]; #endif } } /////////////////////////////////////////////////////////////////////////////// // // Create and open the socket connection // bool cSMTPMailMessage::OpenConnection() { #ifndef HAVE_SYS_SOCKET_H return false; #else // Initialize the socket structure sockaddr_in sockAddrIn; memset(&sockAddrIn, 0, sizeof(sockaddr)); sockAddrIn.sin_family = AF_INET; sockAddrIn.sin_port = mPfnHtons(mPortNumber); uint32 iServerAddress = GetServerAddress(); sockAddrIn.sin_addr.s_addr = iServerAddress; if ( iServerAddress == INADDR_NONE ) { DecodeError(); TOSTRINGSTREAM estr; estr << TSS_GetString( cTripwire, tripwire::STR_ERR2_MAIL_MESSAGE_SERVER ) << mstrServerName; throw eMailSMTPIPUnresolvable(estr.str()); return false; } // Create the socket mSocket = mPfnSocket(AF_INET, SOCK_STREAM, 0); if (mSocket == INVALID_SOCKET) { DecodeError(); throw eMailSMTPSocket(); return false; } // Make the connection int connectVal = mPfnConnect(mSocket, (struct sockaddr *)&sockAddrIn, sizeof(sockAddrIn)); if (connectVal < 0) { DecodeError(); TOSTRINGSTREAM estr; estr << TSS_GetString( cTripwire, tripwire::STR_ERR2_MAIL_MESSAGE_SERVER ) << mstrServerName; throw eMailSMTPOpenConnection(estr.str()); return false; } return true; #endif } /////////////////////////////////////////////////////////////////////////////// // // Close the socket connection // bool cSMTPMailMessage::CloseConnection() { if ( INVALID_SOCKET != mSocket ) { // close the connection int closeVal = mPfnCloseSocket(mSocket); if (closeVal != 0) { DecodeError(); throw eMailSMTPCloseConnection(); return false; } return true; } else { return true; } } /////////////////////////////////////////////////////////////////////////////// // // Call this function to send the mail message once the requisite // methods have been called to define the mail message. // bool cSMTPMailMessage::Send() { // Be sure that everything that needs to be set has been set if (!Ready()) { // the message has not been adequately defined and cannot be sent. return false; } bool errorOccured = false; if ((errorOccured = !OpenConnection()) == false) { errorOccured |= !MailMessage(); errorOccured |= !CloseConnection(); } return !errorOccured; } /////////////////////////////////////////////////////////////////////////////// // // Once the connection is set, this function conducts the SMTP protocol // to actually send the message. // bool cSMTPMailMessage::MailMessage() { cDebug d("cSMTPMailMessage::MailMessage()"); std::string sNarrowString; // Get the greeting message from the SMTP server if (!GetAcknowledgement()) return false; char sLocalHost[256]; // It's alright for this to be a fixed buffer, since // we will be explicitely passing it's length to // mpfnGethostname (see below). It won't be used // after that. std::ostringstream strmSend; // This should be a stream object, so we don't have // to use nasty calls to sprintf that might overflow // the buffer. Before, we used a fixed buffer of 512 // characters, and there's really no guarantee that any // of the string objects (that we are printing to the buffer // from) will be below this limit. ASSERT( strmSend.str().length() == 0 ); // This bad boy better be empty. // get our hostname for the HELO message if (mPfnGethostname(sLocalHost, 256) < 0 ) { DecodeError(); return false; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Set up connection //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Say hello strmSend.str(""); //Clear the stream buffer. strmSend << "HELO " << sLocalHost << "\r\n"; //Fill the stream buffer. SendString( strmSend.str() ); if (!GetAcknowledgement()) return false; strmSend.str(""); //Clear the stream buffer. strmSend << "MAIL FROM:<" << cStringUtil::TstrToStr(mstrFrom) << ">\r\n"; SendString( strmSend.str() ); if (!GetAcknowledgement()) return false; // Say who all we're sending to strmSend.str(""); //Clear the stream buffer. for (std::vector::size_type i=0; i\r\n"; SendString( strmSend.str() ); if (!GetAcknowledgement()) return false; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start data //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Send start the message process SendString( "DATA\r\n" ); if (!GetAcknowledgement()) return false; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Send Header //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // set up header strmSend.str(""); strmSend << cMailMessage::Create822Header(); SendString( strmSend.str() ); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Get Body and Attachments //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool allOK = true; // get body std::string sNBody = cStringUtil::TstrToStr( mstrBody ); std::string sAttachments; if( ! GetAttachmentsAsString( sAttachments ) ) { sAttachments.erase(); allOK = false; } std::string sSend = sNBody + sAttachments; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Make sure that there's no lone LFs //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - cMailMessageUtil::LFToCRLF( sSend ); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Determine encoding needed for body or attachments //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - std::string sContentType = "Content-Transfer-Encoding: "; if( cMailMessageUtil::HasNonAsciiChars( sSend ) ) { // encode text sSend = iMimeEncoding::GetInstance()->Encode( sSend, cMailMessageUtil::_MAX_RFC822_LINE_LEN ); // identify content type sContentType += iMimeEncoding::GetInstance()->GetContentTypeIdentifier(); } else { // do no encoding // identify content type sContentType += "7bit"; } // send content type sContentType += "\r\n"; SendString( sContentType ); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Send Body and Attachments //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SendString( "\r\n" ); SendString( sSend ); // send the end of message line SendString( "\r\n.\r\n" ); if (!GetAcknowledgement()) return false; // send the quit message SendString( "QUIT" ); return allOK; } /////////////////////////////////////////////////////////////////////////////// // // Get the response code from the server to see if the last command // we sent was accepted. // bool cSMTPMailMessage::GetAcknowledgement() { #ifndef HAVE_SYS_SOCKET_H return false; #else cDebug d( "cSMTPMailMessage::GetAcknowledgement" ); const int bufsize = 512; TCHAR sRecvString[bufsize+1]; // This string is and should be unicode char sTempString[bufsize+1]; // This string is not, and should not be unicode int bytes; int i=0; // make socket array for the call to select fd_set socketSet; // need comment timeval tv; FD_ZERO( &socketSet ); FD_SET( mSocket, &socketSet ); // set the timeout time to sixty seconds tv.tv_sec = 60; tv.tv_usec = 0; // Wait up to sixty seconds fot data to show up on the socket to be read if (mPfnSelect(mSocket+1, &socketSet, NULL, NULL, &tv) == 1) { // Get the reply message bytes = mPfnRecv(mSocket, sTempString, 512, 0); // TODO:BAM -- this should be changed to use 'cStringUtil' for (int j=0; j= 200 && code < 400) { // Error codes in the range of 200-399 indicate success. See RFC 821 return true; } else { // Error codes other than 200-399 indicate an error or a failure. See RFC 821 TOSTRINGSTREAM estr; estr << TSS_GetString( cTripwire, tripwire::STR_ERR2_MAIL_MESSAGE_SERVER_RETURNED_ERROR ) << sRecvString; throw eMailSMTPServer(estr.str()); return false; } #endif } void cSMTPMailMessage::SendString( const std::string& str ) { cDebug d("util_SendString()"); #if HAVE_SYS_SOCKET_H if( str.length() < 800 ) d.TraceDebug( "Sending \"%s\"\n", str.c_str() ); else d.TraceDebug( "Sending (truncated in this debug output)\"%s\"\n", std::string( str.c_str(), 800 ).c_str() ); mPfnSend( mSocket, str.c_str(), str.length(), 0 ); #endif } /////////////////////////////////////////////////////////////////////////////// // // Get debug info when a error is encountered. // void cSMTPMailMessage::DecodeError() { #if defined(_DEBUG) // // TODO - Write what ever error reporting will be needed under unix. // #endif // defined(_DEBUG) }