// // WebCheck Mail Agent // // A user specifies that they are to be notified via email when a web checked // object (usually a page) changes. // // When the subscription is the delivery agent will call the mail agent upon completion // with a temporary ISubscriptionItem // // Julian Jiggins (julianj), January 8, 1997 // #include "private.h" #include "mapi.h" #include "smtp.h" #include "mlang.h" #include #undef TF_THISMODULE #define TF_THISMODULE TF_MAILAGENT // // Global strings // REVIEW move to a better place // #define MAIL_HANDLER TEXT("Software\\Clients\\Mail") #define MAIL_ATHENA TEXT("Internet Mail and News") #define SUBJECT_LINE TEXT("Subscription delivered") #define MESSAGE_PREFIX TEXT(" \r\nThe website you requested ") #define MESSAGE_SUFFIX TEXT(" has been delivered") #define ATHENA_SMTP_SERVER \ TEXT("Software\\Microsoft\\Internet Mail and News\\Mail\\SMTP") #define NETSCAPE_SMTP_SERVER \ TEXT("Software\\Netscape\\netscape Navigator\\Services\\SMTP_Server") #define EUDORA_COMMANDLINE TEXT("Software\\Qualcomm\\Eudora\\CommandLine") #define NOTE_TEXT_LENGTH 4096 #define ENCODING_STRLEN 32 ////////////////////////////////////////////////////////////////////////// // // Email helper functions // ////////////////////////////////////////////////////////////////////////// // // Returns a MemAlloc'd string with HTMLBreak inserted in place of '\d'. // void AddHTMLBreakText(LPSTR szText, LPSTR szHTMLBreak, LPSTR *lpHTMLText) { ASSERT(szText); ASSERT(szHTMLBreak); ASSERT(!*lpHTMLText); LPSTR lpTmp = NULL, lpTmp2 = NULL, lpHTMLAbstract = NULL; int cbCRs = 0; int cbLFs = 0; DWORD dwExtra = 0; // // Count number of carriage returns // for (lpTmp = szText; *lpTmp; lpTmp++) { if (*lpTmp == 0x0d) cbCRs++; if (*lpTmp == 0x0a) cbLFs++; } dwExtra = lstrlenA(szText) - cbCRs - cbLFs + cbCRs * lstrlenA(szHTMLBreak) + 1; // // Allocate appropriate size string // *lpHTMLText = lpHTMLAbstract = (LPSTR)MemAlloc(LPTR, dwExtra); if (!lpHTMLAbstract) return; // // Create new HTML abstract string. // for (lpTmp = szText; *lpTmp; lpTmp++) { if (*lpTmp == 0x0d) { for (lpTmp2 = szHTMLBreak; *lpTmp2; lpTmp2++, lpHTMLAbstract++) *lpHTMLAbstract = *lpTmp2; } else if (*lpTmp != 0x0a) { *lpHTMLAbstract = *lpTmp; lpHTMLAbstract++; } } *lpHTMLAbstract = '\0'; } #ifdef DEBUG void DBG_OUTPUT_MAPI_ERROR(ULONG ul) { switch(ul) { case MAPI_E_LOGON_FAILURE: DBG("MailAgent: MAPI LOGON FAILURE"); break; case MAPI_E_FAILURE: DBG("MailAgent: MAPI_E_FAILURE"); break; default: DBG("MailAgent: Failed to send mail message"); break; } } #else #define DBG_OUTPUT_MAPI_ERROR(ul) #endif // // Build an HTML message containing a frameset that effectively inlines // the requested URL // BOOL BuildHTMLMessage(LPSTR szEmailAddress, LPSTR szName, LPSTR szURL, CHAR **ppHTMLMessage, LPSTR szTitle, LPSTR szAbstract, LPSTR szSrcCharset) { *ppHTMLMessage = NULL; // clear out parameter CHAR * lpBuffer = NULL; CHAR szWrapper[NOTE_TEXT_LENGTH]; CHAR szMessageFormat[NOTE_TEXT_LENGTH]; CHAR szMessageFormat2[NOTE_TEXT_LENGTH]; CHAR szMessageText[NOTE_TEXT_LENGTH]; CHAR szMessageHTML[NOTE_TEXT_LENGTH]; CHAR szTextBreak[10]; CHAR szHTMLBreak[10]; // // Load the wrapper for the HTML message. This is the header stuff // and multipart MIME and HTML goop // int iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGEWRAPPER, szWrapper, NOTE_TEXT_LENGTH); ASSERT(iRet > 0); if (szTitle != NULL) { // NOTE: Size is probably slightly larger than necessary due to %1's. LPSTR lpHTMLAbstract = NULL, lpNewAbstract = NULL; DWORD dwTotalSize = 0; // // load string for single HTML line break as well as tag on for custom email // MLLoadStringA(IDS_AGNT_EMAILMESSAGE, szMessageText, ARRAYSIZE(szMessageText)); MLLoadStringA(IDS_AGNT_HTMLBREAKSINGLE, szHTMLBreak, ARRAYSIZE(szHTMLBreak)); // // Create new abstract string (szAbstract + email tagger) // dwTotalSize = lstrlenA(szAbstract) + lstrlenA(szMessageText) + 1; LPSTR szNewAbstract = (LPSTR)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR)); if (!szNewAbstract) return FALSE; lstrcpyA(szNewAbstract, szAbstract); StrCatA(szNewAbstract, szMessageText); AddHTMLBreakText(szNewAbstract, szHTMLBreak, &lpHTMLAbstract); if (!lpHTMLAbstract) { MemFree(szNewAbstract); return FALSE; } dwTotalSize = lstrlenA(szWrapper) + lstrlenA(szEmailAddress) + 2*lstrlenA(szTitle) + lstrlenA(szNewAbstract) + lstrlenA(szSrcCharset) + lstrlenA(lpHTMLAbstract) + lstrlenA(szURL) + 1; lpBuffer = (CHAR *)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR)); if (!lpBuffer) return FALSE; LPSTR lpArguments[6]; lpArguments[0] = szEmailAddress; lpArguments[1] = szTitle; lpArguments[2] = szNewAbstract; lpArguments[3] = szSrcCharset; // the charset of the HTML page lpArguments[4] = szURL; lpArguments[5] = lpHTMLAbstract; // // Reason for FormatMessage is that wsprintf is limited up to 1024 bytes // FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szWrapper, 0, 0, lpBuffer, dwTotalSize, (va_list *)&lpArguments[0]); MemFree(szNewAbstract); MemFree(lpHTMLAbstract); } else { // // Load line breaks for the plaintext and html messages // iRet = MLLoadStringA(IDS_AGNT_TEXTBREAK, szTextBreak, ARRAYSIZE(szTextBreak)); ASSERT(iRet > 0); iRet = MLLoadStringA(IDS_AGNT_HTMLBREAK, szHTMLBreak, ARRAYSIZE(szHTMLBreak)); ASSERT(iRet > 0); // // Load the actual text message to put sent // iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGETEXT, szMessageFormat, NOTE_TEXT_LENGTH); ASSERT(iRet > 0); iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGETEXT2, szMessageFormat2, NOTE_TEXT_LENGTH); ASSERT(iRet > 0); // // Insert the text messages into the wrapper. Note two message get // Once in the mime section for text/ascii and once in the // noframes section of the text/html frameset. This is a work around // for clients (like Outlook) that think they can render HTML // but cannot really. // The second message IDS_AGNT_HTMLMESSAGETEXT2 should NOT be localized // this is only going to be seen by Exchange users. In the future exchange // will handle html mail correct, so it acceptable that for example // Japanese Exchange users see english in this message. Most Japanese // users will user Outlook Express and so will just see the html message // // First we format 2 text messages, one for text and one for HTML, // since message itself is relatively small we know its < 1024 bytes iRet = wnsprintfA(szMessageText, ARRAYSIZE(szMessageText), szMessageFormat, szName, szTextBreak, szURL, szTextBreak); ASSERT(iRet > lstrlenA(szMessageFormat)); iRet = wnsprintfA(szMessageHTML, ARRAYSIZE(szMessageHTML), szMessageFormat2, szName, szHTMLBreak, szURL, szHTMLBreak); ASSERT(iRet > lstrlenA(szMessageFormat2)); DWORD dwTotalSize = lstrlenA(szWrapper) + lstrlenA(szEmailAddress) + lstrlenA(szName) + lstrlenA(szMessageText) + lstrlenA(szSrcCharset) + lstrlenA(szMessageHTML) + lstrlenA(szURL) + 1; lpBuffer = (CHAR *)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR)); if (!lpBuffer) return FALSE; LPSTR lpArguments[6]; lpArguments[0] = szEmailAddress; // target email address lpArguments[1] = szName; // the name of the page that goes in the subject line lpArguments[2] = szMessageText; // the plain text message lpArguments[3] = szSrcCharset; // the charset of the HTML page lpArguments[4] = szURL; // the href of the page that goes in the frame set lpArguments[5] = szMessageHTML; // the plain text message that goes in the // noframes part of the frameset DWORD dwRet; dwRet = FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szWrapper, 0, 0, lpBuffer, dwTotalSize, (va_list *)&lpArguments[0]); ASSERT(dwRet); } *ppHTMLMessage = lpBuffer; return TRUE; } // // Build the actual text of the message to be sent via SMTP, // load format string from resource and insert URL and URL's friently name. // void BuildSMTPMessage(LPSTR szName, LPSTR szURL, LPSTR *szMessage, LPSTR szTitle, LPSTR szAbstract) { CHAR szFormatText[NOTE_TEXT_LENGTH]; int i; ASSERT(szMessage); if (!szMessage) return; *szMessage = NULL; if (szTitle != NULL) { i = MLLoadStringA(IDS_AGNT_SMTPMESSAGE_OTHER, szFormatText, NOTE_TEXT_LENGTH); ASSERT(i != 0); DWORD dwLen = lstrlenA(szFormatText) + lstrlenA(szTitle) + lstrlenA(szAbstract) + 1; *szMessage = (LPSTR) MemAlloc(LPTR, dwLen * sizeof(CHAR)); if (!*szMessage) return; LPSTR lpArgs[2]; lpArgs[0] = szTitle; lpArgs[1] = szAbstract; FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFormatText, 0, 0, *szMessage, dwLen, (va_list *)&lpArgs[0]); } else { i = MLLoadStringA(IDS_AGNT_SMTPMESSAGE, szFormatText, NOTE_TEXT_LENGTH); ASSERT(i != 0); DWORD dwLen = lstrlenA(szFormatText) + 2*lstrlenA(szName) + lstrlenA(szURL) + 1; *szMessage = (LPSTR) MemAlloc(LPTR, dwLen * sizeof(CHAR)); if (!*szMessage) return; LPSTR lpArgs[3]; lpArgs[0] = lpArgs[1] = szName; lpArgs[2] = szURL; FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szFormatText, 0, 0, *szMessage, dwLen, (va_list *)&lpArgs[0]); } } #if 0 // // Build MAPI message structure // void BuildMAPIMessage( LPTSTR lpszName, LPTSTR lpszURL, LPTSTR lpszEmailAddress, MapiMessage * lpMessage, MapiRecipDesc * lpRecipient, LPTSTR lpszNoteText, // [out] LPTSTR lpszSubject, // [out] LPTSTR lpszTitle, // [in] title, NULL if not custom LPTSTR lpszAbstract) // [in] abstract, NULL if not custom { TCHAR szFmtNoteText[NOTE_TEXT_LENGTH]; TCHAR szFmtSubject[INTERNET_MAX_URL_LENGTH]; int i; // // zero out the passed in mapi structures // ZeroMemory(lpMessage, sizeof(MapiMessage)); ZeroMemory(lpRecipient, sizeof(MapiRecipDesc)); #error The wsprintf's below need to be converted to wnsprintf // // Load the strings containing the bulk of the email message // if (lpszTitle != NULL) { MLLoadString(IDS_AGNT_MAPIMESSAGE_OTHER, szFmtNoteText, NOTE_TEXT_LENGTH); MLLoadString(IDS_AGNT_MAPISUBJECT_OTHER, szFmtSubject, INTERNET_MAX_URL_LENGTH); i = wsprintf(lpszNoteText, szFmtNoteText, lpszAbstract); ASSERT(i < NOTE_TEXT_LENGTH); i = wsprintf(lpszSubject, szFmtSubject, lpszTitle); ASSERT(i < INTERNET_MAX_URL_LENGTH); } else { MLLoadString(IDS_AGNT_MAPIMESSAGE, szFmtNoteText, NOTE_TEXT_LENGTH); MLLoadString(IDS_AGNT_MAPISUBJECT, szFmtSubject, INTERNET_MAX_URL_LENGTH); i = wsprintf(lpszNoteText, szFmtNoteText, lpszName, lpszURL); ASSERT(i < NOTE_TEXT_LENGTH); i = wsprintf(lpszSubject, szFmtSubject, lpszName); ASSERT(i < INTERNET_MAX_URL_LENGTH); } // // Build a mapi mail recipient structure // lpRecipient->ulRecipClass = MAPI_TO; lpRecipient->lpszName = lpszEmailAddress; // // Fill in the message subject line, recipient and note text // lpMessage->nRecipCount = 1; lpMessage->lpRecips = lpRecipient; lpMessage->lpszNoteText = lpszNoteText; lpMessage->lpszSubject = lpszSubject; } #endif // // Use the MLANG apis to translate the string // // Returns success if translation occurred, fails otherwise // // Note if lpszSrcCharSet is NULL then use CP_ACP as the codepage // HRESULT TranslateCharset( LPSTR lpszSrcString, LPSTR lpszDstString, UINT uiDstSize, LPSTR lpszSrcCharset, LPSTR lpszDstCharset ) { HRESULT hr = E_FAIL; WCHAR wszSrcCharset[ENCODING_STRLEN]; WCHAR wszDstCharset[ENCODING_STRLEN]; if (lpszSrcString == NULL || lpszDstString == NULL || lpszDstCharset == NULL) { return E_INVALIDARG; } SHAnsiToUnicode(lpszDstCharset, wszDstCharset, ARRAYSIZE(wszDstCharset)); if (lpszSrcCharset) SHAnsiToUnicode(lpszSrcCharset, wszSrcCharset, ARRAYSIZE(wszSrcCharset)); LPMULTILANGUAGE2 pIML2 = NULL; // // Create the MLANG object // if (SUCCEEDED(CoCreateInstance (CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, IID_IMultiLanguage2, (void**)&pIML2))) { UINT srcCodePage = (UINT)-1, dstCodePage; MIMECSETINFO mcsi = {0}; // // First get the source code page either from the passed in string // name of source Charset or from the default one if null if passed in // if (lpszSrcCharset == NULL) { srcCodePage = GetACP(); hr = S_OK; } else { // // Use the mlang object to get the codepages // hr = pIML2->GetCharsetInfo(wszSrcCharset, &mcsi); if (SUCCEEDED(hr)) { srcCodePage = mcsi.uiInternetEncoding; } } if (SUCCEEDED(hr)) { hr = pIML2->GetCharsetInfo(wszDstCharset, &mcsi); if (SUCCEEDED(hr)) { dstCodePage = mcsi.uiInternetEncoding; if (srcCodePage != dstCodePage) { // // To work around a bug in the Mlang::ConvertString api // have to pass in a ptr to length of the src string // UINT uiSrcSize = lstrlenA(lpszSrcString) + 1; DWORD dwMode = 0; hr = pIML2->ConvertString( &dwMode, srcCodePage, dstCodePage, (LPBYTE)lpszSrcString, &uiSrcSize, (LPBYTE)lpszDstString, &uiDstSize); } else { lstrcpynA(lpszDstString, lpszSrcString, uiDstSize); } } } pIML2->Release(); } return hr; } ////////////////////////////////////////////////////////////////////////// // // Mail notification implementation // ////////////////////////////////////////////////////////////////////////// // // Notify via email that the pszURL has changed // // There are 3 ways to send via email - // // Use straight MAPI (IE Exchange or Outlook) // Most people don't have Exchange in the real world. // // Use Athena's MAPI implementation // It's broken and doesn't handle UI'less mode // // Use straight SMTP, // Need to get the name of an SMTP server // HRESULT NotifyViaEMail( LPSTR lpszURL, // url that was downloaded LPSTR lpszEmailAddress, // email address to send notification to LPSTR lpszSMTPServer, // SMTP server to use to deliver email LPSTR &lpszName, // friendly name of url (probably page title) LPSTR lpszTitle, // optional: NULL if not custom message LPSTR lpszAbstract, // optional: NULL if not custom message LPSTR lpszCharSet, // optional: charset of html page BOOL fSendHTMLEmail ) // TRUE if registry allows it and check mode // supports it. { BOOL b; LPSTR lpszSMTPMessage; // // lpszName comes from the title of the web page. If the charset of the page // is not the same as the one that this version of IE has been localized to // then we need to use the MLANG api's to coerce the string into the correct // charset // CHAR szTargetEncoding[ENCODING_STRLEN]; MLLoadStringA(IDS_TARGET_CHARSET_EMAIL, szTargetEncoding, ARRAYSIZE(szTargetEncoding)); // // Allocate buffer for new name. This is a conversion from one dbcs charset // to another so size shouldn't but to be safe use *2 multiplier. // UINT uiSize = lstrlenA(lpszName) * 2; LPSTR lpszNewName = (LPSTR) MemAlloc(LMEM_FIXED, uiSize * sizeof(CHAR)); if (lpszNewName) { // // Note check for S_OK as will return S_FALSE if there is no appropriate // translation installed on this machine // if (S_OK == TranslateCharset(lpszName, lpszNewName, uiSize, lpszCharSet, szTargetEncoding)) { // // if translation occurred alias new name to old name // SAFELOCALFREE(lpszName); lpszName = lpszNewName; } else { SAFELOCALFREE(lpszNewName); // don't need newname after all } } // // If we are requested to HTML mail and we successfully built the html // if (!(fSendHTMLEmail && BuildHTMLMessage(lpszEmailAddress, lpszName, lpszURL, &lpszSMTPMessage, lpszTitle, lpszAbstract, lpszCharSet))) { // // If sending a simple notification or BuildHTMLMessage failed // force fSendHTMLEmail to false and build simple smtp message // fSendHTMLEmail = FALSE; BuildSMTPMessage(lpszName, lpszURL, &lpszSMTPMessage, lpszTitle, lpszAbstract); } // // Disable MAPI for now // //----------------------------------------- //BUG BUG: If this is enabled then wsprintf on message text should be changed to FormatMessage // due to 1KB message limit. #if 0 // // First try and load a full mapi implementation (exchange or outlook) // HMODULE hmodMail = LoadNormalMapi(); if (hmodMail != NULL) { // // Get mapi function entry points // LPMAPISENDMAIL pfnSendMail; LPMAPILOGON pfnLogon; MapiMessage message; MapiRecipDesc recipient; pfnSendMail = (LPMAPISENDMAIL)GetProcAddress(hmodMail, "MAPISendMail"); pfnLogon = (LPMAPILOGON) GetProcAddress(hmodMail, "MAPILogon"); if (pfnSendMail != NULL && pfnLogon != NULL) { // // Logon to mapi provider // LHANDLE hSession = 0; LPSTR lpszProfileName = NULL; // for now logon on with NULL LPSTR lpszPassword = NULL; // credentials ULONG ul = pfnLogon(0, lpszProfileName, lpszPassword, 0, 0, &hSession); if (ul != SUCCESS_SUCCESS) { DBG_OUTPUT_MAPI_ERROR(ul); // // Fall through to try other mail delivery methods // } else { TCHAR szSubject[INTERNET_MAX_URL_LENGTH]; // // Fill in the text, subject line and recipient in mapi message // BuildMAPIMessage(lpszName, lpszURL, lpszEmailAddress, &message, &recipient, szSubject, lpszSMTPMessage, szTitle, szAbstract); // // Actually send the message via MAPI with no UI // ul = pfnSendMail(0, 0, &message, 0, 0); if (ul != SUCCESS_SUCCESS) { DBG_OUTPUT_MAPI_ERROR(ul); } else { FreeLibrary(hmodMail); return S_OK; } } } FreeLibrary(hmodMail); } #endif // // Send message to given address and from given address // if (lpszSMTPMessage) { b = SMTPSendMessage(lpszSMTPServer, lpszEmailAddress, lpszEmailAddress, lpszSMTPMessage); MemFree(lpszSMTPMessage); } else { b = FALSE; } if (b) return S_OK; return E_FAIL; } ////////////////////////////////////////////////////////////////////////// // // Helper function to send email // ////////////////////////////////////////////////////////////////////////// HRESULT SendEmailFromItem(ISubscriptionItem *pItem) { HRESULT hr = E_FAIL; LPSTR pszURL = NULL; LPSTR pszName = NULL; LPSTR pszTitle = NULL; LPSTR pszAbstract = NULL; LPSTR pszCharSet = NULL; // Get the Email URL to send. Fall back to the download URL. ReadAnsiSTR(pItem, c_szPropEmailURL, &pszURL); if (!pszURL) ReadAnsiSTR(pItem, c_szPropURL, &pszURL); ASSERT(pszURL); // Get the friendly name. Fall back to the download URL. ReadAnsiSTR(pItem, c_szPropName, &pszName); ASSERT(pszName); if (!pszName) ReadAnsiSTR(pItem, c_szPropURL, &pszName); // Get Email Title and Abstract if flag is set. DWORD dwEmailFlags = 0; ReadDWORD(pItem, c_szPropEmailFlags, &dwEmailFlags); if (dwEmailFlags & MAILAGENT_FLAG_CUSTOM_MSG) { ReadAnsiSTR(pItem, c_szPropEmailTitle, &pszTitle); ASSERT(pszTitle); ReadAnsiSTR(pItem, c_szPropEmailAbstract, &pszAbstract); ASSERT(pszAbstract); } // // Get the charset in the notification // ReadAnsiSTR(pItem, c_szPropCharSet, &pszCharSet); // Get Email address and SMTP server TCHAR tszBuf[MAX_PATH]; CHAR szEmailAddress[MAX_PATH]; CHAR szSMTPServer[MAX_PATH]; ReadDefaultEmail(tszBuf, ARRAYSIZE(tszBuf)); SHTCharToAnsi(tszBuf, szEmailAddress, ARRAYSIZE(szEmailAddress)); ReadDefaultSMTPServer(tszBuf, ARRAYSIZE(tszBuf)); SHTCharToAnsi(tszBuf, szSMTPServer, ARRAYSIZE(szSMTPServer)); // Send the email if (pszURL && pszName) { // // Check if HTML Mail notification is enabled or disabled thru the registry // BOOL fSendHTMLEmail = FALSE; if (!ReadRegValue(HKEY_CURRENT_USER, c_szRegKey, TEXT("EnableHTMLMailNotification"), &fSendHTMLEmail, sizeof(DWORD))) { fSendHTMLEmail = TRUE; // default to on if not read from registry } // Now make sure our crawling mode supports HTML mail. We don't // want to send HTML if we're in check-for-change only. DWORD dwTemp = 0; ReadDWORD(pItem, c_szPropCrawlChangesOnly, &dwTemp); if (dwTemp != 0) { fSendHTMLEmail = FALSE; } // else, leave fSendHTMLEmail in its reg-based setting. hr = NotifyViaEMail(pszURL, szEmailAddress, szSMTPServer, pszName, pszTitle, pszAbstract, pszCharSet, fSendHTMLEmail ); } // Clean up. SAFELOCALFREE(pszURL); SAFELOCALFREE(pszName); SAFELOCALFREE(pszTitle); SAFELOCALFREE(pszAbstract); return hr; }