tripwire-open-source/src/tripwire/mailmessage.cpp

885 lines
23 KiB
C++

//
// 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 <locale.h>
#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<TSTRING>::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 "<CRLF>.<CRLF>" 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 ( &current_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