// File: mail.cpp #include "precomp.h" #include "resource.h" #include #include #include #include "mail.h" #include "ConfUtil.h" typedef struct _tagMAIL_ADDRESS { LPTSTR pszAddress; LPTSTR pszDisplayName; } MAIL_ADDRESS, *LPMAIL_ADDRESS; // Send an e-mail message using the default mail provider HRESULT SendMailMessage(LPMAIL_ADDRESS pmaTo, LPCTSTR pcszSubject, LPCTSTR pcszText, LPCTSTR pcszFile); /* Given an existing Conference Shortcut, bring up a mail message with */ /* it included as an attachment. The Conference Shortcut should have */ /* been saved to disk prior to this call. */ BOOL SendConfLinkMail(LPMAIL_ADDRESS pmaTo, IConferenceLink* pconflink, LPCTSTR pcszNoteText); ///////////////////////////////////////////////////////////////////////////////////// // These variables are only used in this module, so we will make them static... // This keeps it out of the global namespace but more importantly it tells // the person reading this code that they don't have to worry about any othur // source file changing the variables directly... static HANDLE s_hSendMailThread = NULL; static const TCHAR s_cszWinIniMail[] = _TEXT("Mail"); static const TCHAR s_cszWinIniMAPI[] = _TEXT("MAPI"); // MAPISendMail: typedef ULONG (FAR PASCAL *LPMSM)(LHANDLE,ULONG,lpMapiMessage,FLAGS,ULONG); BOOL IsSimpleMAPIInstalled() { return (BOOL) GetProfileInt(s_cszWinIniMail, s_cszWinIniMAPI, 0); } BOOL CreateInvitationMail(LPCTSTR pszMailAddr, LPCTSTR pszMailName, LPCTSTR pcszName, LPCTSTR pcszAddress, DWORD dwTransport, BOOL fMissedYou) { BOOL bRet = FALSE; TCHAR szTempFile[MAX_PATH]; WCHAR wszTempFile[MAX_PATH]; ASSERT(IS_VALID_STRING_PTR(pcszName, CSTR)); ASSERT(IS_VALID_STRING_PTR(pcszAddress, CSTR)); // password not supported yet // ASSERT(IS_VALID_STRING_PTR(pcszPassword, CSTR)); LPTSTR pszFileName = NULL; if (0 == GetTempPath(MAX_PATH, szTempFile)) { ERROR_OUT(("GetTempPath failed!")); return FALSE; } pszFileName = szTempFile + lstrlen(szTempFile); // the +3 is for null terminators // append the conference name and the shortcut extension to the temp directory if (((lstrlen(pcszName) + lstrlen(szTempFile) + lstrlen(g_cszConfLinkExt) + 3) > sizeof(szTempFile)) || (0 == lstrcat(szTempFile, pcszName)) || (0 == lstrcat(szTempFile, g_cszConfLinkExt))) { ERROR_OUT(("Could not create temp file name!")); return FALSE; } // Filter names to allow only legal filename characters SanitizeFileName(pszFileName); // convert to UNICODE because IPersistFile interface expects UNICODE if (0 == MultiByteToWideChar(CP_ACP, 0L, szTempFile, sizeof(szTempFile), wszTempFile, sizeof(wszTempFile))) { ERROR_OUT(("Could not create wide temp file string!")); return FALSE; } IUnknown* punk = NULL; // Create a ConfLink object - try to obtain an IUnknown pointer HRESULT hr = CoCreateInstance( CLSID_ConfLink, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_IUnknown, (LPVOID*) &punk); if (FAILED(hr)) { ERROR_OUT(("CoCreateInstance ret %lx", (DWORD)hr)); return FALSE; } ASSERT(IS_VALID_INTERFACE_PTR(punk, IUnknown)); // Try to obtain a IConferenceLink pointer IConferenceLink* pcl = NULL; hr = punk->QueryInterface(IID_IConferenceLink, (LPVOID*) &pcl); if (SUCCEEDED(hr)) { ASSERT(IS_VALID_INTERFACE_PTR(pcl, IConferenceLink)); // Set the conference name and address pcl->SetAddress(pcszAddress); pcl->SetName(pcszName); pcl->SetTransport(dwTransport); pcl->SetCallFlags(CRPCF_DEFAULT); // Try to obtain a IPersistFile pointer IPersistFile* ppf = NULL; hr = punk->QueryInterface(IID_IPersistFile, (LPVOID*) &ppf); if (SUCCEEDED(hr)) { ASSERT(IS_VALID_INTERFACE_PTR(ppf, IPersistFile)); // Save the object using the filename generated above hr = ppf->Save(wszTempFile, TRUE); // Release the IPersistFile pointer ppf->Release(); ppf = NULL; TCHAR szNoteText[512]; if (fMissedYou) { TCHAR szFormat[MAX_PATH]; if (FLoadString(IDS_MISSED_YOU_FORMAT, szFormat, CCHMAX(szFormat))) { RegEntry reULS(ISAPI_CLIENT_KEY, HKEY_CURRENT_USER); wsprintf(szNoteText, szFormat, reULS.GetString(REGVAL_ULS_NAME)); } } else { FLoadString(IDS_SEND_MAIL_NOTE_TEXT, szNoteText, CCHMAX(szNoteText)); } MAIL_ADDRESS maDestAddress; maDestAddress.pszAddress = (LPTSTR) pszMailAddr; maDestAddress.pszDisplayName = (LPTSTR) pszMailName; // Send it using MAPI bRet = SendConfLinkMail(&maDestAddress, pcl, szNoteText); } // Release the IConferenceLink pointer pcl->Release(); pcl = NULL; } // Release the IUnknown pointer punk->Release(); punk = NULL; return bRet; } // SendConfLinkMail creates a mail message using Simple MAPI and attaches one // file to it - a Conference Shortcut which is passed in via the IConferenceLink // interface pointer. BOOL SendConfLinkMail(LPMAIL_ADDRESS pmaTo, IConferenceLink* pconflink, LPCTSTR pcszNoteText) { ASSERT(IS_VALID_INTERFACE_PTR((PCIConferenceLink)pconflink, IConferenceLink)); HRESULT hr = E_FAIL; // File TCHAR szFile[MAX_PATH]; LPOLESTR pwszFile = NULL; IPersistFile* pPersistFile = NULL; if (SUCCEEDED(pconflink->QueryInterface(IID_IPersistFile, (LPVOID*) &pPersistFile))) { if (SUCCEEDED(pPersistFile->GetCurFile(&pwszFile))) { #ifndef _UNICODE WideCharToMultiByte(CP_ACP, 0L, pwszFile, -1, szFile, sizeof(szFile), NULL, NULL); // Free the string using the Shell Allocator LPMALLOC pMalloc = NULL; if (SUCCEEDED(SHGetMalloc(&pMalloc))) { pMalloc->Free(pwszFile); pwszFile = NULL; pMalloc->Release(); pMalloc = NULL; } #else // ndef _UNICODE #error Unicode not handled here! #endif // ndef _UNICODE hr = SendMailMessage(pmaTo, NULL, pcszNoteText, szFile); // BUGBUG: need unique ret val for this case // BUGBUG: should we move error UI out of this function? if (FAILED(hr)) { ::PostConfMsgBox(IDS_CANT_SEND_SENDMAIL_IN_PROGRESS); } } else { ERROR_OUT(("GetCurFile failed - can't send message!")); pPersistFile->Release(); return FALSE; } pPersistFile->Release(); } else { ERROR_OUT(("Did not get IPersistFile pointer - can't send message!")); return FALSE; } return SUCCEEDED(hr); } // // BEGIN STOLEN CODE FROM IE 3.0 (sendmail.c) ------------------------------- // const TCHAR g_cszAthenaV1Name[] = _TEXT("Internet Mail and News"); const TCHAR g_cszAthenaV2Name[] = _TEXT("Outlook Express"); const TCHAR g_cszAthenaV1DLLPath[] = _TEXT("mailnews.dll"); BOOL IsAthenaDefault() { TCHAR szAthena[80]; LONG cb = ARRAY_ELEMENTS(szAthena); return (ERROR_SUCCESS == RegQueryValue(HKEY_LOCAL_MACHINE, REGVAL_IE_CLIENTS_MAIL, szAthena, &cb)) && ((lstrcmpi(szAthena, g_cszAthenaV1Name) == 0) || (lstrcmpi(szAthena, g_cszAthenaV2Name) == 0)); } HMODULE LoadMailProvider() { TCHAR szMAPIDLL[MAX_PATH]; if (IsAthenaDefault()) { RegEntry reMailClient(REGVAL_IE_CLIENTS_MAIL, HKEY_LOCAL_MACHINE); PTSTR pszMailClient = reMailClient.GetString(NULL); if ((NULL != pszMailClient) && (_T('\0') != pszMailClient[0])) { reMailClient.MoveToSubKey(pszMailClient); PTSTR pszDllPath = reMailClient.GetString(REGVAL_MAIL_DLLPATH); if ((NULL == pszDllPath) || (_T('\0') == pszDllPath[0])) { pszDllPath = (PTSTR) g_cszAthenaV1DLLPath; } return ::LoadLibraryEx(pszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); } else { ERROR_OUT(("No e-mail client in registry but IsAthenaDefault() returned TRUE")); } } // read win.ini (bogus hu!) for mapi dll provider if (GetProfileString( TEXT("Mail"), TEXT("CMCDLLName32"), TEXT(""), szMAPIDLL, ARRAY_ELEMENTS(szMAPIDLL)) <= 0) lstrcpy(szMAPIDLL, TEXT("mapi32.dll")); return ::LoadLibraryEx(szMAPIDLL, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); } typedef struct { TCHAR szToAddress[MAX_PATH]; TCHAR szToDisplayName[MAX_PATH]; TCHAR szSubject[MAX_PATH]; TCHAR szText[MAX_PATH]; TCHAR szFile[MAX_PATH]; BOOL fDeleteFile; MapiMessage mm; MapiRecipDesc mrd; MapiFileDesc mfd; } MAPI_FILES; MAPI_FILES* _AllocMapiFiles(LPMAIL_ADDRESS pmaTo, LPCTSTR pcszSubject, LPCTSTR pcszText, LPCTSTR pcszFile, BOOL fDeleteFile) { MAPI_FILES* pmf = new MAPI_FILES; if (pmf) { ::ZeroMemory(pmf, sizeof(MAPI_FILES)); pmf->fDeleteFile = fDeleteFile; if (NULL != pcszSubject) { lstrcpyn(pmf->szSubject, pcszSubject, CCHMAX(pmf->szSubject)); } else { pmf->szSubject[0] = _T('\0'); } if (NULL != pcszText) { lstrcpyn(pmf->szText, pcszText, CCHMAX(pmf->szText)); } else { pmf->szText[0] = _T('\0'); } pmf->mm.nFileCount = (NULL != pcszFile) ? 1 : 0; if (pmf->mm.nFileCount) { lstrcpyn(pmf->szFile, pcszFile, CCHMAX(pmf->szFile)); pmf->mm.lpFiles = &(pmf->mfd); pmf->mfd.lpszPathName = pmf->szFile; pmf->mfd.lpszFileName = pmf->szFile; pmf->mfd.nPosition = (UINT)-1; } pmf->mm.lpszSubject = pmf->szSubject; pmf->mm.lpszNoteText = pmf->szText; if( ( NULL != pmaTo ) && !FEmptySz(pmaTo->pszAddress ) ) { pmf->mm.lpRecips = &(pmf->mrd); pmf->mrd.ulRecipClass = MAPI_TO; pmf->mm.nRecipCount = 1; // If we're sending via Athena and a friendly name is specified, // we pass both the friendly name and address. If we're sending // via Simple MAPI, we pass just the address in the name field. // This is necessary so that the email client can do the address // resolution as appropriate for the installed mail system. This // is not necessary for Athena since it assumes that all addresses // are SMTP addresses. if (IsAthenaDefault() && NULL != pmaTo->pszDisplayName && _T('\0') != pmaTo->pszDisplayName[0]) { lstrcpyn( pmf->szToDisplayName, pmaTo->pszDisplayName, CCHMAX(pmf->szToDisplayName)); pmf->mrd.lpszName = pmf->szToDisplayName; lstrcpyn( pmf->szToAddress, pmaTo->pszAddress, CCHMAX(pmf->szToAddress)); pmf->mrd.lpszAddress = pmf->szToAddress; } else { lstrcpyn( pmf->szToDisplayName, pmaTo->pszAddress, CCHMAX(pmf->szToDisplayName)); pmf->mrd.lpszName = pmf->szToDisplayName; pmf->mrd.lpszAddress = NULL; } } else { // No recepients pmf->mm.lpRecips = NULL; } } return pmf; } VOID _FreeMapiFiles(MAPI_FILES *pmf) { if (pmf->fDeleteFile) { ::DeleteFile(pmf->szFile); } delete pmf; } STDAPI_(DWORD) MailRecipientThreadProc(LPVOID pv) { DebugEntry(MailRecipientThreadProc); MAPI_FILES* pmf = (MAPI_FILES*) pv; DWORD dwRet = S_OK; if (pmf) { HMODULE hmodMail = LoadMailProvider(); if (hmodMail) { LPMSM pfnSendMail; if (pfnSendMail = (LPMSM) ::GetProcAddress(hmodMail, "MAPISendMail")) { dwRet = pfnSendMail(0, 0, &pmf->mm, MAPI_LOGON_UI | MAPI_DIALOG, 0); } ::FreeLibrary(hmodMail); } _FreeMapiFiles(pmf); } // s_hSendMailThread can't be NULL because we don't resume this thread // until s_hSendMailThread is set to a non-null value, so this is a sanity check ASSERT(s_hSendMailThread); HANDLE hThread = s_hSendMailThread; s_hSendMailThread = NULL; ::CloseHandle(hThread); DebugExitULONG(MailRecipientThreadProc, dwRet); return dwRet; } // // END STOLEN CODE FROM IE 3.0 (sendmail.c) --------------------------------- // VOID SendMailMsg(LPTSTR pszAddr, LPTSTR pszName) { // Create Send Mail structure to pass on MAIL_ADDRESS maDestAddress; maDestAddress.pszAddress = pszAddr; maDestAddress.pszDisplayName = pszName; // We are adding the callto://pszName link // to the body of the e-mail message TCHAR sz[MAX_PATH]; lstrcpy( sz, RES2T(IDS_NMCALLTOMAILTEXT) ); lstrcat( sz, pszAddr ); // Only send the text part if pszName is not a NULL string HRESULT hr = SendMailMessage(&maDestAddress, NULL, ( *pszAddr ) ? sz : NULL, NULL); if (FAILED(hr)) { ::PostConfMsgBox(IDS_CANT_SEND_SENDMAIL_IN_PROGRESS); } } const int MESSAGE_THREAD_SHUTDOWN_TIMEOUT = 5000; // milliseconds HRESULT SendMailMessage(LPMAIL_ADDRESS pmaTo, LPCTSTR pcszSubject, LPCTSTR pcszText, LPCTSTR pcszFile) { DebugEntry(SendMailMessage); HRESULT hr = E_FAIL; if (NULL != s_hSendMailThread) { // Athena takes a while to get out of MAPISendMail after the message is closed, // so we wait around a few seconds in case you just finished sending a message.. HCURSOR hCurPrev = ::SetCursor(::LoadCursor(NULL, IDC_WAIT)); ::WaitForSingleObject(s_hSendMailThread, MESSAGE_THREAD_SHUTDOWN_TIMEOUT); ::SetCursor(hCurPrev); } if (NULL == s_hSendMailThread) { MAPI_FILES* pmf = _AllocMapiFiles( pmaTo, pcszSubject, pcszText, pcszFile, TRUE); if (NULL != pmf) { DWORD dwThreadID; // We create the thread suspended because in the thread fn // we call closehandle on s_hSendMailThread...if we create // the thread not suspended there is a race condition where // s_hSendMailThread may not have been assigned the return // value of CreateThread before it is checked in the thread fn s_hSendMailThread = ::CreateThread( NULL, 0, MailRecipientThreadProc, pmf, CREATE_SUSPENDED, &dwThreadID); // If the thread was created, we have to call Resume Thread... if( s_hSendMailThread ) { if( 0xFFFFFFFF != ResumeThread( s_hSendMailThread ) ) { hr = S_OK; } else { // This would indicate an error... hr = HRESULT_FROM_WIN32(GetLastError()); } } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } } else { WARNING_OUT(("can't send mail - mail thread already in progress")); } DebugExitHRESULT(SendMailMessage, hr); return hr; } BOOL IsSendMailInProgress() { return (NULL != s_hSendMailThread); }