/* Module : SMTP.CPP Purpose: Implementation for a MFC class encapsulation of the SMTP protocol Created: PJN / 22-05-1998 History: PJN / 15-06-1998 1) Fixed the case where a single dot occurs on its own in the body of a message 2) Class now supports Reply-To Header Field 3) Class now supports file attachments PJN / 18-06-1998 1) Fixed a memory overwrite problem which was occurring with the buffer used for encoding base64 attachments PJN / 27-06-1998 1) The case where a line begins with a "." but contains other text is now also catered for. See RFC821, Section 4.5.2 for further details. 2) m_sBody in CSMTPMessage has now been made protected. Client applications now should call AddBody instead. This ensures that FixSingleDot is only called once even if the same message is sent a number of times. 3) Fixed a number of problems with how the MIME boundaries were defined and sent. 4) Got rid of an unreferenced formal parameter compiler warning when doing a release build PJN / 11-09-1998 1) VC 5 project file is now provided 2) Attachment array which the message class contains now uses references instead of pointers. 3) Now uses Sleep(0) to yield our time slice instead of Sleep(100), this is the preferred way of writting polling style code in Win32 without serverly impacting performance. 4) All Trace statements now display the value as returned from GetLastError 5) A number of extra asserts have been added 6) A AddMultipleRecipients function has been added which supports added a number of recipients at one time from a single string 7) Extra trace statements have been added to help in debugging PJN / 12-09-98 1) Removed a couple of unreferenced variable compiler warnings when code was compiled with Visual C++ 6.0 2) Fixed a major bug which was causing an ASSERT when the CSMTPAttachment destructor was being called in the InitInstance of the sample app. This was inadvertingly introduced for the 1.2 release. The fix is to revert fix 2) as done on 11-09-1998. This will also help to reduce the number of attachment images kept in memory at one time. PJN / 18-01-99 1) Full CC & BCC support has been added to the classes PJN / 22-02-99 1) Addition of a Get and SetTitle function which allows a files attachment title to be different that the original filename 2) AddMultipleRecipients now ignores addresses if they are empty. 3) Improved the reading of responses back from the server by implementing a growable receive buffer 4) timeout is now 60 seconds when building for debug PJN / 25-03-99 1) Now sleeps for 250 ms instead of yielding the time slice. This helps reduce CPU usage when waiting for data to arrive in the socket PJN / 14-05-99 1) Fixed a bug with the way the code generates time zone fields in the Date headers. PJN / 10-09-99 1) Improved CSMTPMessage::GetHeader to include mime field even when no attachments are included. PJN / 16-02-00 1) Fixed a problem which was occuring when code was compiled with VC++ 6.0. PJN / 19-03-00 1) Fixed a problem in GetHeader on Non-English Windows machines 2) Now ships with a VC 5 workspace. I accidentally shipped a VC 6 version in one of the previous versions of the code. 3) Fixed a number of UNICODE problems 4) Updated the sample app to deliberately assert before connecting to the author's SMTP server. PJN / 28-03-00 1) Set the release mode timeout to be 10 seconds. 2 seconds was causing problems for slow dial up networking connections. PJN / 07-05-00 1) Addition of some ASSERT's in CSMTPSocket::Connect PP / 16-06-00 The following modifications were done by Puneet Pawaia 1) Removed the base64 encoder from this file 2) Added the base64 encoder/decoder implementation in a separate file. This was done because base64 decoding was not part of the previous implementation 3) Added support for ESMTP connection. The class now attempts to authenticate the user on the ESMTP server using the username and passwords supplied. For this connect now takes the username and passwords as parameters. These can be null in which case ESMTP authentication is not attempted 4) This class can now handle AUTH LOGIN and AUTH LOGIN PLAIN authentication schemes on PP / 19-06-00 The following modifications were done by Puneet Pawaia 1) Added the files md5.* containing the MD5 digest generation code after modifications so that it compiles with VC++ 6 2) Added the CRAM-MD5 login procedure. PJN / 10-07-00 1) Fixed a problem with sending attachments > 1K in size 2) Changed the parameters to CSMTPConnection::Connect PJN / 30-07-00 1) Fixed a bug in AuthLogin which was transmitting the username and password with an extra "=" which was causing the login to failure. Thanks to Victor Vogelpoel for finding this. PJN / 05-09-00 1) Added a CSMTP_NORSA preprocessor macro to allow the CSmtpConnection code to be compiled without the dependence on the RSA code. PJN / 28-12-2000 1) Removed an unused variable from ConnectESMTP. 2) Allowed the hostname as sent in the HELO command to be specified at run time in addition to using the hostname of the client machine 3) Fixed a problem where high ascii characters were not being properly encoded in the quoted-printable version of the body sent. 4) Added support for user definable charset's for the message body. 5) Mime boundaries are now always sent irrespective if whether attachments are included or not. This is required as the body is using quoted-printable. 6) Fixed a bug in sendLines which was causing small message bodies to be sent incorrectly 7) Now fully supports custom headers in the SMTP message 8) Fixed a copy and paste bug where the default port for the SMTP socket class was 110. 9) You can now specify the address on which the socket is bound. This enables the programmer to decide on which NIC data should be sent from. This is especially useful on a machine with multiple IP addresses. 10) Addition of functions in the SMTP connection class to auto dial and auto disconnect to the Internet if you so desire. 11) Sample app has been improved to allow Auto Dial and binding to IP addresses to be configured. Copyright (c) 1998 - 2000 by PJ Naughter. All rights reserved. */ //////////////// Includes //////////////////////////////////////////// #include "stdafx.h" #include #include "smtp.h" #ifndef CSMTP_NORSA //#include "glob-md5.h" #endif #include //////////////// Macros / Locals ///////////////////////////////////// #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define BASE64_MAXLINE 76 //////////////// Implementation ////////////////////////////////////// //Class which handles function which must be constructed at run time //since we cannot absolutely gurantee wininet will be available. To avoid the loader //bringing up a message such as "Failed to load due to missing export...", the //functions are constructed using GetProcAddress. The SMTP functions then checks to //see if the function pointers are NULL and if it is it returns failure and sets the //error code ERROR_CALL_NOT_IMPLEMENTED which is what the OS would have done if it had //implemented a stub for it in the first place !! class _WININET_DATA { public: //Constructors /Destructors _WININET_DATA(); ~_WININET_DATA(); //typedefs of the function pointers typedef BOOL (WINAPI INTERNETGETCONNECTEDSTATE)(LPDWORD, DWORD); typedef INTERNETGETCONNECTEDSTATE* LPINTERNETGETCONNECTEDSTATE; typedef BOOL (WINAPI INTERNETAUTODIALHANGUP)(DWORD); typedef INTERNETAUTODIALHANGUP* LPINTERNETAUTODIALHANGUP; typedef BOOL (WINAPI INTERNETATTEMPCONNECT)(DWORD); typedef INTERNETATTEMPCONNECT* LPINTERNETATTEMPCONNECT; //Member variables HINSTANCE m_hWininet; //Instance handle of the "Wininet.dll" which houses the 2 functions we want LPINTERNETGETCONNECTEDSTATE m_lpfnInternetGetConnectedState; LPINTERNETAUTODIALHANGUP m_lpfnInternetAutoDialHangup; LPINTERNETATTEMPCONNECT m_lpfnInternetAttemptConnect; }; _WININET_DATA::_WININET_DATA() { m_hWininet = LoadLibrary(_T("WININET.DLL")); if (m_hWininet) { m_lpfnInternetGetConnectedState = (LPINTERNETGETCONNECTEDSTATE) GetProcAddress(m_hWininet, "InternetGetConnectedState"); m_lpfnInternetAutoDialHangup = (LPINTERNETAUTODIALHANGUP) GetProcAddress(m_hWininet, "InternetAutodialHangup"); m_lpfnInternetAttemptConnect = (LPINTERNETATTEMPCONNECT) GetProcAddress(m_hWininet, "InternetAttemptConnect"); } } _WININET_DATA::~_WININET_DATA() { if (m_hWininet) { FreeLibrary(m_hWininet); m_hWininet = NULL; } } //The local variable which handle the function pointers _WININET_DATA _WinInetData; CSMTPSocket::CSMTPSocket() { m_hSocket = INVALID_SOCKET; //default to an invalid scoket descriptor } CSMTPSocket::~CSMTPSocket() { Close(); } BOOL CSMTPSocket::Create() { m_hSocket = socket(AF_INET, SOCK_STREAM, 0); return (m_hSocket != INVALID_SOCKET); } BOOL CSMTPSocket::Connect(LPCTSTR pszHostAddress, int nPort, LPCTSTR pszLocalBoundAddress) { ASSERT(pszHostAddress); //Must have a valid host ASSERT(_tcslen(pszHostAddress)); //as above //For correct operation of the T2A macro, see MFC Tech Note 59 USES_CONVERSION; //must have been created first ASSERT(m_hSocket != INVALID_SOCKET); //Bind to the local address if need be if (pszLocalBoundAddress && _tcslen(pszLocalBoundAddress)) { LPSTR lpszAsciiLocalAddress = T2A((LPTSTR)pszLocalBoundAddress); SOCKADDR_IN sockLocalAddress; ZeroMemory(&sockLocalAddress, sizeof(sockLocalAddress)); sockLocalAddress.sin_family = AF_INET; sockLocalAddress.sin_port = htons(0); sockLocalAddress.sin_addr.s_addr = inet_addr(lpszAsciiLocalAddress); //If the address is not dotted notation, then do a DNS //lookup of it. if (sockLocalAddress.sin_addr.s_addr == INADDR_NONE) { LPHOSTENT lphost; lphost = gethostbyname(lpszAsciiLocalAddress); if (lphost != NULL) sockLocalAddress.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; else { WSASetLastError(WSAEINVAL); return FALSE; } } //Finally bind to the address if (bind(m_hSocket, (sockaddr*) &sockLocalAddress, sizeof(sockLocalAddress)) == SOCKET_ERROR) return FALSE; } //Work out the IP address of the machine we want to connect to LPSTR lpszAsciiDestination = T2A((LPTSTR)pszHostAddress); //Determine if the address is in dotted notation SOCKADDR_IN sockDestinationAddr; ZeroMemory(&sockDestinationAddr, sizeof(sockDestinationAddr)); sockDestinationAddr.sin_family = AF_INET; sockDestinationAddr.sin_port = htons((u_short)nPort); sockDestinationAddr.sin_addr.s_addr = inet_addr(lpszAsciiDestination); //If the address is not dotted notation, then do a DNS //lookup of it. if (sockDestinationAddr.sin_addr.s_addr == INADDR_NONE) { LPHOSTENT lphost; lphost = gethostbyname(lpszAsciiDestination); if (lphost != NULL) sockDestinationAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; else { WSASetLastError(WSAEINVAL); return FALSE; } } //Call the protected version which takes an address //in the form of a standard C style struct. return Connect((SOCKADDR*)&sockDestinationAddr, sizeof(sockDestinationAddr)); } BOOL CSMTPSocket::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen) { return (connect(m_hSocket, lpSockAddr, nSockAddrLen) != SOCKET_ERROR); } BOOL CSMTPSocket::Send(LPCSTR pszBuf, int nBuf) { //must have been created first ASSERT(m_hSocket != INVALID_SOCKET); return (send(m_hSocket, pszBuf, nBuf, 0) != SOCKET_ERROR); } int CSMTPSocket::Receive(LPSTR pszBuf, int nBuf) { //must have been created first ASSERT(m_hSocket != INVALID_SOCKET); return recv(m_hSocket, pszBuf, nBuf, 0); } void CSMTPSocket::Close() { if (m_hSocket != INVALID_SOCKET) { VERIFY(SOCKET_ERROR != closesocket(m_hSocket)); m_hSocket = INVALID_SOCKET; } } BOOL CSMTPSocket::IsReadible(BOOL& bReadible) { timeval timeout = {0, 0}; fd_set fds; FD_ZERO(&fds); FD_SET(m_hSocket, &fds); int nStatus = select(0, &fds, NULL, NULL, &timeout); if (nStatus == SOCKET_ERROR) { return FALSE; } else { bReadible = !(nStatus == 0); return TRUE; } } CSMTPAddress::CSMTPAddress() { } CSMTPAddress::CSMTPAddress(const CSMTPAddress& address) { *this = address; } CSMTPAddress::CSMTPAddress(const CString& sAddress) : m_sEmailAddress(sAddress) { ASSERT(m_sEmailAddress.GetLength()); //An empty address is not allowed } CSMTPAddress::CSMTPAddress(const CString& sFriendly, const CString& sAddress) : m_sFriendlyName(sFriendly), m_sEmailAddress(sAddress) { ASSERT(m_sEmailAddress.GetLength()); //An empty address is not allowed } CSMTPAddress& CSMTPAddress::operator=(const CSMTPAddress& r) { m_sFriendlyName = r.m_sFriendlyName; m_sEmailAddress = r.m_sEmailAddress; return *this; } CString CSMTPAddress::GetRegularFormat() const { ASSERT(m_sEmailAddress.GetLength()); //Email Address must be valid CString sAddress; if (m_sFriendlyName.IsEmpty()) sAddress = m_sEmailAddress; //Just transfer the address across directly else sAddress.Format(_T("%s <%s>"), m_sFriendlyName, m_sEmailAddress); return sAddress; } CSMTPMessage::CSMTPMessage() : m_sXMailer(_T("CSMTPConnection v2.04")) { } CSMTPMessage::~CSMTPMessage() { } int CSMTPMessage::GetNumberOfRecipients(RECIPIENT_TYPE RecipientType) const { int nSize = 0; switch (RecipientType) { case TO: nSize = m_ToRecipients.GetSize(); break; case CC: nSize = m_CCRecipients.GetSize(); break; case BCC: nSize = m_BCCRecipients.GetSize(); break; default: ASSERT(FALSE); break; } return nSize; } int CSMTPMessage::AddRecipient(CSMTPAddress& recipient, RECIPIENT_TYPE RecipientType) { int nIndex = -1; switch (RecipientType) { case TO: nIndex = m_ToRecipients.Add(recipient); break; case CC: nIndex = m_CCRecipients.Add(recipient); break; case BCC: nIndex = m_BCCRecipients.Add(recipient); break; default: ASSERT(FALSE); break; } return nIndex; } CSMTPAddress CSMTPMessage::GetRecipient(int nIndex, RECIPIENT_TYPE RecipientType) const { CSMTPAddress address; switch (RecipientType) { case TO: address = m_ToRecipients.GetAt(nIndex); break; case CC: address = m_CCRecipients.GetAt(nIndex); break; case BCC: address = m_BCCRecipients.GetAt(nIndex); break; default: ASSERT(FALSE); break; } return address; } CString CSMTPMessage::GetHeader() const { //Hive away the locale so that we can restore it later. We //require the English locale to ensure the date header is //formed correctly TCHAR* pszOldLocale = _tsetlocale(LC_TIME, NULL); _tsetlocale(LC_TIME, _T("english")); //Form the Timezone info which will form part of the Date header TIME_ZONE_INFORMATION tzi; int nTZBias; if (GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_DAYLIGHT) nTZBias = tzi.Bias + tzi.DaylightBias; else nTZBias = tzi.Bias; CString sTZBias; sTZBias.Format(_T("%+.2d%.2d"), -nTZBias/60, nTZBias%60); //Create the "Date:" part of the header CTime now(CTime::GetCurrentTime()); CString sDate(now.Format(_T("%a, %d %b %Y %H:%M:%S "))); sDate += sTZBias; //Create the "To:" part of the header CString sTo; for (int i=0; i 127) || (c == '=')) //Need to quote if it is not US-Ascii or the character is an equals sign '=' { //Must Quote the text pszOuputBuf[nOutputOffset++] = '='; pszOuputBuf[nOutputOffset++] = HexDigit((pszText[i] & 0xF0) >> 4); pszOuputBuf[nOutputOffset++] = HexDigit(pszText[i] & 0x0F); } else pszOuputBuf[nOutputOffset++] = pszText[i]; } nSize = nOutputOffset; return pszOuputBuf; } BOOL CSMTPConnection::SendMessage(CSMTPMessage& Message) { //For correct operation of the T2A macro, see MFC Tech Note 59 USES_CONVERSION; //paramater validity checking // ASSERT(m_bConnected); //Must be connected to send a message if( !m_bConnected ) return false; //Send the MAIL command ASSERT(Message.m_From.m_sEmailAddress.GetLength()); CString sBuf; sBuf.Format(_T("MAIL FROM:<%s>\r\n"), Message.m_From.m_sEmailAddress); LPCSTR pszMailFrom = T2A((LPTSTR) (LPCTSTR) sBuf); int nCmdLength = strlen(pszMailFrom); if (!m_SMTP.Send(pszMailFrom, nCmdLength)) { TRACE(_T("Failed in call to send MAIL command, GetLastError returns: %d\n"), GetLastError()); return FALSE; } //check the response to the MAIL command if (!ReadCommandResponse(250)) { SetLastError(ERROR_BAD_COMMAND); TRACE(_T("An unexpected MAIL response was received\n")); return FALSE; } //Send the RCPT command, one for each recipient (includes the TO, CC & BCC recipients) //Must be sending to someone ASSERT(Message.GetNumberOfRecipients(CSMTPMessage::TO) + Message.GetNumberOfRecipients(CSMTPMessage::CC) + Message.GetNumberOfRecipients(CSMTPMessage::BCC)); //First the "To" recipients for (int i=0; i\r\n"), recipient.m_sEmailAddress); LPSTR pszRCPT = T2A((LPTSTR) (LPCTSTR) sBuf); int nCmdLength = strlen(pszRCPT); if (!m_SMTP.Send(pszRCPT, nCmdLength)) { TRACE(_T("Failed in call to send RCPT command, GetLastError returns: %d\n"), GetLastError()); return FALSE; } //check the response to the RCPT command if (!ReadCommandResponse(250)) { SetLastError(ERROR_BAD_COMMAND); TRACE(_T("An unexpected RCPT response was received\n")); return FALSE; } return TRUE; } BOOL CSMTPConnection::ReadCommandResponse(int nExpectedCode) { LPSTR pszOverFlowBuffer = NULL; char sBuf[256]; BOOL bSuccess = ReadResponse(sBuf, 256, "\r\n", nExpectedCode, &pszOverFlowBuffer); if (pszOverFlowBuffer) delete [] pszOverFlowBuffer; return bSuccess; } //big function BOOL CSMTPConnection::ReadResponse(LPSTR pszBuffer, int nInitialBufSize, LPSTR pszTerminator, int nExpectedCode, LPSTR* ppszOverFlowBuffer, int nGrowBy) { ASSERT(ppszOverFlowBuffer); //Must have a valid string pointer ASSERT(*ppszOverFlowBuffer == NULL); //Initially it must point to a NULL string //must have been created first ASSERT(m_bConnected); //The local variables which will receive the data LPSTR pszRecvBuffer = pszBuffer; int nBufSize = nInitialBufSize; //retrieve the reponse using until we //get the terminator or a timeout occurs BOOL bFoundTerminator = FALSE; int nReceived = 0; DWORD dwStartTicks = ::GetTickCount(); while (!bFoundTerminator) { //Has the timeout occured if ((::GetTickCount() - dwStartTicks) > m_dwTimeout) { pszRecvBuffer[nReceived] = '\0'; SetLastError(WSAETIMEDOUT); TRACE(_T("Timed Out waiting for response from SMTP server")); m_sLastCommandResponse = pszRecvBuffer; //Hive away the last command reponse return FALSE; } //check the socket for readability BOOL bReadible; if (!m_SMTP.IsReadible(bReadible)) { pszRecvBuffer[nReceived] = '\0'; m_sLastCommandResponse = pszRecvBuffer; //Hive away the last command reponse return FALSE; } else if (!bReadible) //no data to receive, just loop around { Sleep(250); //Sleep for a while before we loop around again continue; } //receive the data from the socket int nBufRemaining = nBufSize-nReceived-1; //Allows allow one space for the NULL terminator if (nBufRemaining<0) nBufRemaining = 0; int nData = m_SMTP.Receive(pszRecvBuffer+nReceived, nBufRemaining); //Reset the idle timeout if data was received if (nData) { dwStartTicks = ::GetTickCount(); //Increment the count of data received nReceived += nData; } //If an error occurred receiving the data if (nData == SOCKET_ERROR) { //NULL terminate the data received if (pszRecvBuffer) pszBuffer[nReceived] = '\0'; m_sLastCommandResponse = pszRecvBuffer; //Hive away the last command reponse return FALSE; } else { //NULL terminate the data received if (pszRecvBuffer) pszRecvBuffer[nReceived] = '\0'; if (nBufRemaining-nData == 0) //No space left in the current buffer { //Allocate the new receive buffer nBufSize += nGrowBy; //Grow the buffer by the specified amount LPSTR pszNewBuf = new char[nBufSize]; //copy the old contents over to the new buffer and assign //the new buffer to the local variable used for retreiving //from the socket if (pszRecvBuffer) strcpy(pszNewBuf, pszRecvBuffer); pszRecvBuffer = pszNewBuf; //delete the old buffer if it was allocated if (*ppszOverFlowBuffer) delete [] *ppszOverFlowBuffer; //Remember the overflow buffer for the next time around *ppszOverFlowBuffer = pszNewBuf; } } //Check to see if the terminator character(s) have been found bFoundTerminator = (strstr(pszRecvBuffer, pszTerminator) != NULL); } //Remove the terminator from the response data pszRecvBuffer[nReceived - strlen(pszTerminator)] = '\0'; //determine if the response is an error char sCode[4]; strncpy(sCode, pszRecvBuffer, 3); sCode[3] = '\0'; sscanf(sCode, "%d", &m_nLastCommandResponseCode); BOOL bSuccess = (m_nLastCommandResponseCode == nExpectedCode); m_sLastCommandResponse = pszRecvBuffer; //Hive away the last command reponse if (!bSuccess) SetLastError(WSAEPROTONOSUPPORT); return bSuccess; } // This function negotiates AUTH LOGIN PLAIN BOOL CSMTPConnection::AuthLoginPlain(LPCTSTR pszUsername, LPCTSTR pszPassword) { //For correct operation of the T2A macro, see MFC Tech Note 59 USES_CONVERSION; //Send the AUTH LOGIN PLAIN command CString sBuf; sBuf.Format(_T("AUTH LOGIN PLAIN\r\n")); LPCSTR pszData = T2A((LPTSTR) (LPCTSTR) sBuf); int nCmdLength = strlen(pszData); if (!m_SMTP.Send(pszData, nCmdLength)) { TRACE(_T("An unexpected error occurred while sending the AUTH command\n")); return FALSE; } if (!ReadCommandResponse(334)) { TRACE(_T("Server does not support AUTH LOGIN PLAIN!\n")); return FALSE; } else { //send username in plain CString sLastCommandString = m_sLastCommandResponse; sLastCommandString = sLastCommandString.Right(sLastCommandString.GetLength() - 4); LPCSTR pszLastCommandString = T2A((LPTSTR) (LPCTSTR) sLastCommandString); if (strcmp(pszLastCommandString, "Username:") == 0) { sBuf.Format(_T("%s\r\n"), pszUsername); pszData = T2A((LPTSTR) (LPCTSTR) sBuf); nCmdLength = strlen(pszData); if (!m_SMTP.Send(pszData, nCmdLength)) { TRACE(_T("An unexpected error occurred while sending the username\n")); return FALSE; } } } //check the response to the username if (!ReadCommandResponse(334)) { TRACE(_T("Server did not response correctly to AUTH LOGIN PLAIN username field!\n")); return FALSE; } else { //send password in plain CString LastCommandString = m_sLastCommandResponse; LastCommandString = LastCommandString.Right(LastCommandString.GetLength() - 4); if (lstrcmp(LastCommandString, _T("Password:")) == 0) { sBuf.Format(_T("%s\r\n"), pszPassword); pszData = T2A((LPTSTR) (LPCTSTR) sBuf); nCmdLength = strlen(pszData); if (!m_SMTP.Send(pszData, nCmdLength)) { TRACE(_T("An unexpected error occurred while sending the password\n")); return FALSE; } // check if authentication is successful if (!ReadCommandResponse(235)) { TRACE(_T("AUTH LOGIN PLAIN authentication was unsuccessful\n")); return FALSE; } } } return TRUE; } int CSMTPConnection::getLine(LPCSTR pszBuf, int nBufLen, int& nCurPos, BOOL& bHasCRLF, int nLineSize) { if (nCurPos >= nBufLen) return FALSE; int nStartPos = nCurPos; // save the starting position int nCurLineSize = 0; // holds the current line size in relation to CRLF while (nCurPos < nBufLen) { if (pszBuf[nCurPos] == '\n' && nCurPos > 0 && pszBuf[nCurPos-1] == '\r') { // CRLF just passed over nCurLineSize = 0; bHasCRLF = TRUE; nCurPos++; } else { nCurLineSize++; bHasCRLF = FALSE; nCurPos++; if (nCurLineSize == nLineSize) { // If we get to a line that has no CRLF and is the max, return break; } } } return nCurPos - nStartPos; } BOOL CSMTPConnection::SendLines(LPCSTR pszBuf, int nBuf, int nLineSize) { // RFC 2045, 821 state certain limits exist for line lengths BOOL bSuccess = TRUE; int nCurPos = 0; BOOL bHasCRLF = FALSE; int nSendBytes = 0; int nStartPos = 0; while ((nSendBytes = getLine(pszBuf, nBuf, nCurPos, bHasCRLF, nLineSize)) > 0) { BOOL bRC = m_SMTP.Send(&pszBuf[nStartPos], nSendBytes); if (!bRC) return FALSE; // Now check if the lines we sent has a CRLF and if not, send it if (!bHasCRLF && nCurPos < nBuf) { if (!m_SMTP.Send("\r\n", 2)) bSuccess = FALSE; } nStartPos = nCurPos; } return bSuccess; }