// // 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 #include "core/stringutil.h" #include "core/timeconvert.h" #include "tw/systeminfo.h" #include "core/file.h" #include "core/ntmbs.h" /////////////////////////////////////////////////////////////////////////////// cMailMessage::cMailMessage() { } /////////////////////////////////////////////////////////////////////////////// cMailMessage::~cMailMessage() { } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::SetBody(const TSTRING& strBody) { mstrBody = strBody; } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::SetFrom(const TSTRING& strFrom) { mstrFrom = strFrom; } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::SetFromName(const TSTRING& strFromName) { mstrFromName = strFromName; } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::SetSubject(const TSTRING& strSubject) { mstrSubject = strSubject; } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::AddRecipient(const TSTRING& strRecipient) { mvstrRecipients.push_back(strRecipient); } /////////////////////////////////////////////////////////////////////////////// void cMailMessage::AttachFile(const TSTRING& strFullPath) { mvstrAttachments.push_back(strFullPath); } /////////////////////////////////////////////////////////////////////////////// bool cMailMessage::Send() { // this is a pure virtual method. You should never end up here. ASSERT(false); return false; } /////////////////////////////////////////////////////////////////////////////// // // Returns true if enough things have been set so that a useful email message can be sent // bool cMailMessage::Ready() { // You must specify 'from', 'to', the subject if( mstrFrom.length() == 0 || mvstrRecipients.size() == 0 || mstrSubject.length() == 0 ) { return false; } // make sure we can at least send something... if( mstrBody.empty() ) { mstrBody = _T("\r\n"); } return true; } bool cMailMessage::GetAttachmentsAsString( std::string& s ) { s.erase(); bool allOK = true; for( std::vector::const_iterator at = mvstrAttachments.begin(); at != mvstrAttachments.end(); ++at ) { s += "\r\n"; cFile file; try { file.Open( at->c_str(), cFile::OPEN_READ); // Simply stream the file into the socket. // This will append the file as additional text. const cFile::File_t cBufSize = 2048; char buf[cBufSize]; cFile::File_t bytes; while ((bytes = file.Read(buf, cBufSize)) != 0) { ASSERT( bytes > 0 && bytes <= cBufSize ); // ensures typecast below ASSERT( (std::string::size_type)bytes < std::string().max_size() ); s += std::string( buf, (size_t)bytes ); } file.Close(); } catch (eFile&) { // TODO: There is no method of reporting detailed information on // errors if they occur. Perhaps someday there will be. At the moment // it does not seem necessary as the only attached files that will // be sent are temporary files that tripwire has just created. Thus // the likelyhood of failure is low. file.Close(); allOK = false; } } // Send a new line to be sure the file ends properly // Failure to do this could result in the "." begin acknowledged as // the end of the email message. s += "\r\n"; return allOK; } std::string cMailMessage::Create822Header() { std::ostringstream ss; std::string strToList; for( std::vector< TSTRING >::size_type i = 0; i < mvstrRecipients.size(); i++ ) { if( strToList.length() > 0 ) strToList += ", "; strToList += cStringUtil::TstrToStr( mvstrRecipients[i] ); } ss << cMailMessageUtil::FormatAddressHeader( "MIME-Version", "1.0" ); TSTRING strDate; if( cMailMessageUtil::ReadDate( strDate ) ) ss << cMailMessageUtil::FormatAddressHeader( "Date", cStringUtil::TstrToStr( strDate ) ); std::string fromAddress; if( ! mstrFromName.empty() ) { fromAddress = "\""; fromAddress += cStringUtil::TstrToStr(mstrFromName); fromAddress += "\" <"; fromAddress += cStringUtil::TstrToStr(mstrFrom); fromAddress += ">"; } else { fromAddress = cStringUtil::TstrToStr(mstrFrom); } ss << cMailMessageUtil::FormatAddressHeader( "From", fromAddress); ss << cMailMessageUtil::FormatAddressHeader( "To", strToList ); ss << cMailMessageUtil::FormatNonAddressHeader( "Subject", cStringUtil::TstrToStr(mstrSubject) ); ss << cMailMessageUtil::FormatAddressHeader( "Content-Type", "text/plain" ); return ss.str(); } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // cMailMessageUtil //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // gets the date in the RFC822 compliant form "Tue, 16 Mar 1999 10:53:17 -0800 (PST)" // TODO:BAM -- make windows and unix use same function bool cMailMessageUtil::ReadDate( TSTRING& strDateBuf ) { bool fGotDate = false; #if HAVE_STRFTIME TCHAR szDate[1024]; time_t current_time = time(NULL); struct tm* tm = localtime ( ¤t_time ); const TCHAR* szFormat = _T("%a, %d %b %Y %H:%M:%S %z"); size_t numChars = _tcsftime( szDate, countof( szDate ), szFormat, tm ); if ( numChars != 0 ) { strDateBuf = szDate; fGotDate = true; } #else int64 now = cSystemInfo::GetExeStartTime(); strDateBuf = cTimeUtil::GetRFC822Date(cTimeUtil::TimeToDateGMT(now)); fGotDate = true; #endif // HAVE_STRFTIME return fGotDate; } static const char* util_Get_IANA_CharSet() { const char* pCP = setlocale( LC_CTYPE, NULL ); if( pCP && pCP[0] ) { std::string s = pCP; // TODO:BAM -- change this to ctype::tolower when all compilers // have a proper locale impl. or better yet move to core/twlocale.cpp std::transform( s.begin(), s.end(), s.begin(), tolower ); if( std::string::npos != s.find( "jp" ) || std::string::npos != s.find( "jap" ) ) { return "ISO-2022-JP"; } } // default return "US-ASCII"; } static bool NeedsEncoding( char ch ) { return( ( ch < 33 ) || ( ch > 60 && ch < 62 ) || ( ch > 126 ) ); } static std::string EncodeChar( char ch ) { std::ostringstream ss; ss.imbue( std::locale::classic() ); ss.fill( '0' ); ss.setf( std::ios_base::hex, std::ios_base::basefield ); ss.width( 2 ); ss << (unsigned int)(unsigned char)ch; ASSERT( ss.str().length() == 2 ); // Make sure the hex is uppercase std::string s = ss.str(); std::transform( s.begin(), s.end(), s.begin(), toupper ); return s; } /* static */ iMimeEncoding* iMimeEncoding::GetInstance() { #define TSS_USE_QUOTED_PRINTABLE_MIME static iMimeEncoding* pME= #if defined( TSS_USE_BASE64_MIME ) new cBase64Encoding; #elif defined( TSS_USE_QUOTED_PRINTABLE_MIME ) new cQuotedPrintableEncoding; #else #error what is iMimeEncoding? #endif return pME; } // TODO:BAM -- line breaks // TODO:BAM -- ToEncoding( const std::string& sIn, int maxLineLen = -1, int maxLen = -1 ) std::string cQuotedPrintableEncoding::Encode( const std::string& sIn, int maxLineLen, int maxLen ) { static const size_t _CHAR_AS_HEX_LEN = 2; static const size_t _ENCODED_CHAR_LEN = _CHAR_AS_HEX_LEN + sizeof( '=' ); std::string sOut; sOut.resize( sIn.size() * _ENCODED_CHAR_LEN ); // optimize. one char can turn into 3 std::string::const_iterator at; std::string::size_type i = 0; std::string::size_type lineLen = 0; for( at = sIn.begin(); at != sIn.end(); ++at, lineLen += _ENCODED_CHAR_LEN ) { if( NeedsEncoding( *at ) ) { ASSERT( (unsigned char)*at <= 0xFF ); std::string sTmp = EncodeChar( *at ); ASSERT( sTmp.length() == _CHAR_AS_HEX_LEN ); sOut[i++] = '='; std::copy( &sTmp[0], &sTmp[_CHAR_AS_HEX_LEN], &sOut[i] ); ASSERT( _CHAR_AS_HEX_LEN > 0 ); i += _CHAR_AS_HEX_LEN; } else { sOut[i++] = *at; } // // if string is too long, just quit // if( maxLen != -1 && i >= maxLen - _ENCODED_CHAR_LEN ) { break; } // // check line len // if( maxLineLen != -1 && lineLen >= maxLineLen - _ENCODED_CHAR_LEN ) { // append EOL sOut[i++] = '='; sOut[i++] = '\r'; sOut[i++] = '\n'; lineLen = 0; } } sOut.resize( i ); return sOut; } std::string cMailMessageUtil::CreateEncodedText( const std::string& text ) { cDebug d("cMailMessageUtil::CreateEncodedText"); d.TraceDebug( "came in as: %s\n", text.c_str() ); std::string s; s += "=?"; s += util_Get_IANA_CharSet(); s += "?"; s += iMimeEncoding::GetInstance()->GetEncodedWordIdentifier(); s += "?"; s += iMimeEncoding::GetInstance()->Encode( text, -1, _MAX_RFC822_HEADER_LEN ); s += "?="; ASSERT( s.length() < _MAX_RFC822_LINE_LEN - _EOL_LEN ); d.TraceDebug( "came out as: %s\n", s.c_str() ); return s; } // TODO:BAM -- note that address headers can only have usascii chars // TODO:BAM -- lines can only be 76 chars long -- enforce that! std::string cMailMessageUtil::FormatAddressHeader( const std::string& name, const std::string& addr ) { cDebug d("cMailMessageUtil::FormatAddressHeader"); d.TraceDebug( "name: %s, value: %s\n", name.c_str(), addr.c_str() ); std::string s; s += name; s += ": "; s += addr; s += "\r\n"; d.TraceDebug( "came out as: %s\n", s.c_str() ); return s; } // TODO:BAM -- lines can only be 76 chars long -- enforce that! std::string cMailMessageUtil::FormatNonAddressHeader( const std::string& name, const std::string& valC ) { cDebug d("cMailMessageUtil::FormatNonAddressHeader"); d.TraceDebug( "name: %s, value: %s\n", name.c_str(), valC.c_str() ); std::string val = valC; ASSERT( val != "To" ); ASSERT( val != "From" ); ASSERT( val != "Sender" ); ASSERT( val != "cc" ); ASSERT( val != "bcc" ); // ... std::string s; s += name; s += ": "; if( HasNonAsciiChars( val ) ) s += CreateEncodedText( val ); else s += val; s += "\r\n"; d.TraceDebug( "came out as: %s\n", s.c_str() ); ASSERT( s.length() < _MAX_RFC822_LINE_LEN - _EOL_LEN ); return s; } bool cMailMessageUtil::HasNonAsciiChars( const std::string& s ) { for( std::string::const_iterator at = s.begin(); at != s.end(); ++at ) { if( (unsigned char)*at > (unsigned char)0x7F ) return true; } return false; } // converts lone \n's to \r\n std::string& cMailMessageUtil::LFToCRLF( std::string& sIn ) { cDebug d("cMailMessageUtil::LFToCRLF"); if( sIn.empty() ) return sIn; std::string sOut; // worst case: could be just '\n's // in which case we'd double the length sOut.resize( sIn.size() * 2 ); bool lastCharCR = false; std::string::size_type stringSize = 0; std::string::const_iterator at; std::string::difference_type charSize; for( at = sIn.begin(), charSize = ( (*at ? at+1 : at) - at ); at != sIn.end(); at += charSize, charSize = ( (*at ? at+1 : at) - at ) ) { ASSERT( charSize > 0 ); if( *at == '\n' ) { if( ! lastCharCR ) { d.TraceDebug( "Adding LF\n"); sOut[stringSize++] = '\r'; } lastCharCR = false; } else if( *at == '\r') { lastCharCR = true; } else { lastCharCR = false; } std::copy( at, at + charSize, &sOut[stringSize] ); stringSize += charSize; } sOut.resize( stringSize ); sIn = sOut; return sIn; } // #define RDIFALCO_BASE64_IMPLEMENTATION #ifndef RDIFALCO_BASE64_IMPLEMENTATION /* static std::string util_Base64Encode( const byte b[3], int size ) { // TODO:BAM -- what about endianness? ASSERT( size > 0 && size <= 3 ); static char v64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string s; if( size >= 1 ) { // encode A s += v64[ b[0] >> 2 ]; } if( size >= 2 ) { // encode B,C s += v64[ ( ( b[0] & (byte)0x3 ) << 4 ) | ( b[1] >> 4 ) ]; if( size == 3 ) s += v64[ ( ( b[1] & (byte)0xF ) << 2 ) | ( b[2] >> 6 ) ]; else s += v64[ ( ( b[1] & (byte)0xF ) << 2 ) ]; } if( size >= 3 ) { // encode D s += v64[ b[2] & (byte)0x3F ]; } // padding if( size == 1 ) s += "=="; else if( size == 2 ) s += "="; return s; } // MS _defines_ 'min' and 'max', so I can't use std::min(). template< class T > const T& MS_SUCKS_min( const T& a, const T& b ) { return( b < a ? b : a ); } /// More consistent with the rest of our Convert interfaces // NOTE: Works when we don't want to allocate anything new const std::string::value_type* cMailMessageUtil::ConvertBase64( std::string& sEncode, const byte* pchSrc, size_t nchSrc ) { sEncode.assign( ToBase64( pchSrc, nchSrc ) ); return sEncode.c_str(); } std::string cMailMessageUtil::ToBase64( const byte* p, size_t size ) { ASSERT( sizeof( uint8 ) == sizeof( byte ) ); // everything breaks otherwise std::string s; const int MAX_WORKING_BYTES = 3; const int CHARS_PER_WORKING_BYTES = 4; const int MAX_CHARS_PER_BYTE = 2; // should be ASSERT( MAX_CHARS_PER_BYTE > CHARS_PER_WORKING_BYTES/MAX_WORKING_BYTES ); const int NERVOUS_LINE_BUFFER = 10; const byte* at = p; byte buf[ MAX_WORKING_BYTES ]; int nbLeft; int nbWorking; int nchCurLine; std::string::size_type i = 0; // where we are in the output string s.resize( size * MAX_CHARS_PER_BYTE ); for ( nchCurLine = 0, nbLeft = size, nbWorking = MS_SUCKS_min( MAX_WORKING_BYTES, nbLeft ); nbLeft > 0; nchCurLine += CHARS_PER_WORKING_BYTES, at += nbWorking, nbLeft -= nbWorking, nbWorking = MS_SUCKS_min( MAX_WORKING_BYTES, nbLeft ) ) { ASSERT( nbWorking > 0 ); // fill out the working buffer std::copy( at, at + nbWorking, &buf[0] ); // encode from working buffer to 'enc' string std::string enc = util_Base64Encode( buf, nbWorking ); // append 'enc' string to output string for( std::string::const_iterator at = enc.begin(); at != enc.end(); at++ ) { s[i++] = *at; } // make sure we aren't over the max line length if( nchCurLine > ( _MAX_RFC822_LINE_LEN - NERVOUS_LINE_BUFFER ) ) // NERVOUS_LINE_BUFFER for a little margin of error { s[i++] = '\r'; s[i++] = '\n'; nchCurLine = 0; } } s.resize( i ); return s; } */ #else//RDIFALCO_BASE64_IMPLEMENTATION //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // RAD:10311999 -- A Different Try //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - namespace /*Unique*/ { typedef byte byte_t; #ifdef TSS_MAKE_EOL_CRLF const byte _aszEoL[] = "\r\n"; size_t _EOL_LEN = 2; #else const byte _aszEoL[] = "\n"; size_t _EOL_LEN = 1; #endif const byte_t _abBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; inline size_t tss_base64_amplitude() // TODO: Make a Constant { /* * NOTE:RAD -- Calculate Max Incoming Bytes: * * eol_bytes = ( UINT_MAX / _MAX_RFC822_LINE_LEN ) * _EOL_LEN; * max_bytes = UINT_MAX - eol_bytes; * cvt_bytes = max_bytes / 4 * 3; */ return (((UINT_MAX - (UINT_MAX / _MAX_RFC822_LINE_LEN * _EOL_LEN)) / 4) * 3) + 1; } inline size_t tss_encode_base64_size( size_t nchSource ) { ASSERT( nchSource < tss_base64_amplitude() ); // NOTE: /* NOTE: The formula is -- + bring source bytes to an even divisible of 3 (the padding bytes) + number of source bytes to converted (3:4 conversion ratio) + ( number of lines ) * ( size of end of line string ) */ size_t nchPadding = nchSource % 3; size_t nchOut = ((nchSource + nchPadding) / 3) * 4; size_t nchEol = (nchOut / (_MAX_RFC822_LINE_LEN - _EOL_LEN)) * _EOL_LEN; return nchOut + nchEol + 2; } void tss_encode_digit_base64( const byte_t*& pchSrc, size_t& nchSrcLeft, byte_t*& pchBuf, size_t& nchBufLeft ) { // NOTE:RAD -- Redundant and ugly but very fast!! ASSERT( nchBufLeft > 4 ); // One for NULL switch ( nchSrcLeft ) { case 0: { ASSERTMSG( false, "Invalid call to encode_base64_digit!" ); break; } case 1: { int ch1 = int( *pchSrc++ & 0xFF ); *pchBuf++ = _abBase64[ (ch1 & 0xFC) >> 2 ]; *pchBuf++ = _abBase64[ (ch1 & 0x03) << 4 ]; *pchBuf++ = '='; *pchBuf++ = '='; nchSrcLeft = 0; // NOTE: break; } case 2: { int ch1 = int( *pchSrc++ & 0xFF ); int ch2 = int( *pchSrc++ & 0xFF ); *pchBuf++ = _abBase64[ ( (ch1 & 0xFC) >> 2 ) ]; *pchBuf++ = _abBase64[ ( (ch1 & 0x03) << 4 ) | ( (ch2 & 0xF0) >> 4 ) ]; *pchBuf++ = _abBase64[ ( (ch2 & 0x0F) << 2 ) ]; *pchBuf++ = '='; nchSrcLeft = 0; // NOTE: break; } default: { int ch1 = int( *pchSrc++ & 0xFF ); int ch2 = int( *pchSrc++ & 0xFF ); int ch3 = int( *pchSrc++ & 0xFF ); *pchBuf++ = _abBase64[ ( (ch1 & 0xFC) >> 2 ) ]; *pchBuf++ = _abBase64[ ( (ch1 & 0x03) << 4 ) | ( (ch2 & 0xF0) >> 4 ) ]; *pchBuf++ = _abBase64[ ( (ch2 & 0x0F) << 2 ) | ( (ch3 & 0xC0) >> 6 ) ]; *pchBuf++ = _abBase64[ ( ch3 & 0x3F ) ]; nchSrcLeft -= 3; // NOTE: Probably greater than 3 break; } } nchBufLeft -= 4; // Always the same due to padding! }; size_t tss_encode_base64( const byte_t* pchSource, size_t nchSource, byte_t* pchBuffer, size_t nchBuffer ) { const size_t _ERROR = std::string::npos; // -1; //--Assert Preconditions if ( pchSource == 0 || nchSource == 0 ) return _ERROR; //--If pchBuffer == 0, User is requesting Size size_t nchBuf = tss_encode_base64_size( nchSource ); if ( pchBuffer == 0 ) // Requesting Size return nchBuf; if ( nchBuf > nchBuffer ) // DOH!! return _ERROR; //--Get three characters at a time and encode them (watching linelen) byte_t* pchBuf = ( pchBuffer + 0 ); const byte_t* pchSrc = ( pchSource + 0 ); const size_t _max_linelen = ( _MAX_RFC822_LINE_LEN - _EOL_LEN ); size_t nLineLen = 0; for ( ; nchSource; ) { // Like iconv: Increment pointers and decrement sizes tss_encode_digit_base64( pchSrc, nchSource, pchBuf, nchBuf ); nLineLen += 4; if ( nchSource == 0 || nLineLen > _max_linelen ) { nLineLen = 0; // RESET: const byte_t* pchEol = &_aszEoL[0]; while ( *pchEol ) *pchBuf++ = *pchEol++; } } *pchBuf = 0x00; // CAUTION: Null Terminate!!! return ( pchBuf - pchBuffer ); } }//Unique:: const std::string::value_type* cMailMessageUtil::ConvertBase64( std::string& sEncode, const byte_t* pchSrc, size_t nchSrc ) { size_t nch = tss_encode_base64( pchSrc, nchSrc, 0, 0 ); // Like mbstowcs sEncode.reserve( nch ); // Make it big enough but don't resize sEncode.resize( 0 ); // String must be clean in case of exception! const char* pch = sEncode.c_str(); // Get Pointer (won't change) size_t nLength = // Action tss_encode_base64( pchSrc, nchSrc, (byte_t*)pch, nch ); if ( nLength == std::string::npos ) throw std::bad_alloc(); sEncode.resize( nLength ); // Now it's okay to resize return pch; // So user can do stuff like "out << ConvertBase64()"; } std::string cMailMessageUtil::ToBase64( const byte_t* pchSrc, size_t nchSrc ) { // NOTE: It sucks to use std::string this way! Should be // passed in by reference. std::string sucks; cMailMessageUtil::ConvertBase64( sucks, pchSrc, nchSrc ); return sucks; } #endif//RDIFALCO_BASE64_IMPLEMENTATION