inetmapi.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <exception>
  18. #include <mutex>
  19. #include <kopano/platform.h>
  20. #include <kopano/lockhelper.hpp>
  21. #include <kopano/stringutil.h>
  22. #include <string>
  23. #include <fstream>
  24. #include <iostream>
  25. #include <cstdlib>
  26. // vmime
  27. #include <vmime/vmime.hpp>
  28. #include <vmime/textPartFactory.hpp>
  29. #include "mapiTextPart.h"
  30. #include <vmime/platforms/posix/posixHandler.hpp>
  31. // mapi
  32. #include <mapix.h>
  33. #include <mapiutil.h>
  34. #include <kopano/mapiext.h>
  35. #include <kopano/memory.hpp>
  36. #include <edkmdb.h>
  37. #include <kopano/CommonUtil.h>
  38. #include <kopano/charset/convert.h>
  39. // inetmapi
  40. #include <inetmapi/inetmapi.h>
  41. #include "VMIMEToMAPI.h"
  42. #include "MAPIToVMIME.h"
  43. #include "ECVMIMEUtils.h"
  44. #include "ECMapiUtils.h"
  45. #include <kopano/ECLogger.h>
  46. #include <kopano/mapi_ptr.h>
  47. using namespace std;
  48. using namespace KCHL;
  49. namespace KC {
  50. bool ValidateCharset(const char *charset)
  51. {
  52. /*
  53. * iconv does not like to convert wchar_t to wchar_t, so filter that
  54. * one. https://sourceware.org/bugzilla/show_bug.cgi?id=20804
  55. */
  56. if (strcmp(charset, CHARSET_WCHAR) == 0)
  57. return true;
  58. iconv_t cd = iconv_open(CHARSET_WCHAR, charset);
  59. if (cd == (iconv_t)(-1))
  60. return false;
  61. iconv_close(cd);
  62. return true;
  63. }
  64. ECSender::ECSender(const std::string &strSMTPHost, int port)
  65. {
  66. smtpresult = 0;
  67. smtphost = strSMTPHost;
  68. smtpport = port;
  69. }
  70. int ECSender::getSMTPResult() {
  71. return smtpresult;
  72. }
  73. const WCHAR* ECSender::getErrorString() {
  74. return error.c_str();
  75. }
  76. void ECSender::setError(const std::wstring &newError) {
  77. error = newError;
  78. }
  79. void ECSender::setError(const std::string &newError) {
  80. error = convert_to<wstring>(newError);
  81. }
  82. bool ECSender::haveError() {
  83. return ! error.empty();
  84. }
  85. static std::mutex vmInitLock;
  86. static bool vmimeInitialized = false;
  87. static void InitializeVMime()
  88. {
  89. scoped_lock l_vm(vmInitLock);
  90. try {
  91. vmime::platform::getHandler();
  92. }
  93. catch (vmime::exceptions::no_platform_handler &) {
  94. vmime::platform::setHandler<vmime::platforms::posix::posixHandler>();
  95. }
  96. if (vmimeInitialized)
  97. return;
  98. vmime::generationContext::getDefaultContext().setWrapMessageId(false);
  99. // need to have a unique indentifier in the mediaType
  100. vmime::textPartFactory::getInstance()->registerType<vmime::mapiTextPart>(vmime::mediaType(vmime::mediaTypes::TEXT, "mapi"));
  101. // init our random engine for random message id generation
  102. rand_init();
  103. vmimeInitialized = true;
  104. }
  105. static string generateRandomMessageId()
  106. {
  107. #define IDLEN 38
  108. char id[IDLEN] = {0};
  109. // the same format as the vmime generator, but with more randomness
  110. snprintf(id, IDLEN, "kcim.%lx.%x.%08x%08x",
  111. static_cast<unsigned long>(time(NULL)), getpid(),
  112. rand_mt(), rand_mt());
  113. return string(id, strlen(id));
  114. #undef IDLEN
  115. }
  116. ECSender *CreateSender(const std::string &smtp, int port)
  117. {
  118. return new ECVMIMESender(smtp, port);
  119. }
  120. /*
  121. * Because it calls iconv_open() with @s in at least one of iconv_open's two
  122. * argument, this function also implicitly checks whether @s is valid.
  123. */
  124. static bool vtm_ascii_compatible(const char *s)
  125. {
  126. static const char in[] = {
  127. 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,
  128. 24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,
  129. 45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,
  130. 66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,
  131. 87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,
  132. 106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,
  133. 121,122,123,124,125,126,127,
  134. };
  135. char out[sizeof(in)];
  136. iconv_t cd = iconv_open(s, "us-ascii");
  137. if (cd == reinterpret_cast<iconv_t>(-1))
  138. return false;
  139. auto inbuf = const_cast<char *>(in), outbuf = out;
  140. size_t insize = sizeof(in), outsize = sizeof(out);
  141. bool mappable = iconv(cd, &inbuf, &insize, &outbuf, &outsize) != static_cast<size_t>(-1);
  142. iconv_close(cd);
  143. return mappable && memcmp(in, out, sizeof(in)) == 0;
  144. }
  145. // parse rfc822 input, and set props in lpMessage
  146. HRESULT IMToMAPI(IMAPISession *lpSession, IMsgStore *lpMsgStore,
  147. IAddrBook *lpAddrBook, IMessage *lpMessage, const string &input,
  148. delivery_options dopt)
  149. {
  150. // Sanitize options
  151. if (dopt.ascii_upgrade == nullptr || *dopt.ascii_upgrade == '\0') {
  152. dopt.ascii_upgrade = "us-ascii";
  153. } else if (!vtm_ascii_compatible(dopt.ascii_upgrade)) {
  154. ec_log_warn("Configured default_charset \"%s\" is unknown, or not ASCII compatible. "
  155. "Disabling forced charset upgrades.",
  156. dopt.ascii_upgrade);
  157. dopt.ascii_upgrade = "us-ascii";
  158. }
  159. InitializeVMime();
  160. // fill mapi object from buffer
  161. return VMIMEToMAPI(lpAddrBook, dopt).convertVMIMEToMAPI(input, lpMessage);
  162. }
  163. // Read properties from lpMessage object and fill a buffer with internet rfc822 format message
  164. HRESULT IMToINet(IMAPISession *lpSession, IAddrBook *lpAddrBook,
  165. IMessage *lpMessage, char **lppbuf, sending_options sopt)
  166. {
  167. std::ostringstream oss;
  168. char *lpszData = NULL;
  169. HRESULT hr = IMToINet(lpSession, lpAddrBook, lpMessage, oss, sopt);
  170. if (hr != hrSuccess)
  171. return hr;
  172. lpszData = new char[oss.str().size()+1];
  173. strcpy(lpszData, oss.str().c_str());
  174. *lppbuf = lpszData;
  175. return hr;
  176. }
  177. HRESULT IMToINet(IMAPISession *lpSession, IAddrBook *lpAddrBook,
  178. IMessage *lpMessage, std::ostream &os, sending_options sopt)
  179. {
  180. HRESULT hr = hrSuccess;
  181. memory_ptr<SPropValue> lpTime, lpMessageId;
  182. MAPIToVMIME mToVM(lpSession, lpAddrBook, sopt);
  183. vmime::shared_ptr<vmime::message> lpVMMessage;
  184. vmime::utility::outputStreamAdapter adapter(os);
  185. InitializeVMime();
  186. hr = mToVM.convertMAPIToVMIME(lpMessage, &lpVMMessage);
  187. if (hr != hrSuccess)
  188. return hr;
  189. try {
  190. // vmime messageBuilder has set Date header to now(), so we overwrite it.
  191. if (HrGetOneProp(lpMessage, PR_CLIENT_SUBMIT_TIME, &~lpTime) == hrSuccess)
  192. lpVMMessage->getHeader()->Date()->setValue(FiletimeTovmimeDatetime(lpTime->Value.ft));
  193. // else, try PR_MESSAGE_DELIVERY_TIME, maybe other timestamps?
  194. if (HrGetOneProp(lpMessage, PR_INTERNET_MESSAGE_ID_A, &~lpMessageId) == hrSuccess)
  195. lpVMMessage->getHeader()->MessageId()->setValue(lpMessageId->Value.lpszA);
  196. lpVMMessage->generate(adapter);
  197. }
  198. catch (vmime::exception&) {
  199. return MAPI_E_NOT_FOUND;
  200. }
  201. catch (std::exception&) {
  202. return MAPI_E_NOT_FOUND;
  203. }
  204. return hrSuccess;
  205. }
  206. // Read properties from lpMessage object and to internet rfc2822 format message
  207. // then send it using the provided ECSender object
  208. HRESULT IMToINet(IMAPISession *lpSession, IAddrBook *lpAddrBook,
  209. IMessage *lpMessage, ECSender *mailer_base, sending_options sopt)
  210. {
  211. HRESULT hr = hrSuccess;
  212. MAPIToVMIME mToVM(lpSession, lpAddrBook, sopt);
  213. vmime::shared_ptr<vmime::message> vmMessage;
  214. auto mailer = dynamic_cast<ECVMIMESender *>(mailer_base);
  215. wstring wstrError;
  216. SPropArrayPtr ptrProps;
  217. static constexpr const SizedSPropTagArray(2, sptaForwardProps) =
  218. {2, {PR_AUTO_FORWARDED, PR_INTERNET_MESSAGE_ID_A}};
  219. ULONG cValues = 0;
  220. if (mailer == nullptr)
  221. return MAPI_E_INVALID_PARAMETER;
  222. InitializeVMime();
  223. hr = mToVM.convertMAPIToVMIME(lpMessage, &vmMessage, MTV_SPOOL);
  224. if (hr != hrSuccess) {
  225. wstrError = mToVM.getConversionError();
  226. if (wstrError.empty())
  227. wstrError = L"No error details specified";
  228. mailer->setError(L"Conversion error: " + wstringify(hr, true) + L". " + wstrError + L". Your email is not sent at all and cannot be retried.");
  229. return hr;
  230. }
  231. try {
  232. vmime::messageId msgId;
  233. hr = lpMessage->GetProps(sptaForwardProps, 0, &cValues, &~ptrProps);
  234. if (!FAILED(hr) && ptrProps[0].ulPropTag == PR_AUTO_FORWARDED && ptrProps[0].Value.b == TRUE && ptrProps[1].ulPropTag == PR_INTERNET_MESSAGE_ID_A)
  235. // only allow mapi programs to set a messageId for an outgoing message when it comes from rules processing
  236. msgId = ptrProps[1].Value.lpszA;
  237. else
  238. // vmime::messageId::generateId() is not random enough since we use forking in the spooler
  239. msgId = vmime::messageId(generateRandomMessageId(), vmime::platform::getHandler()->getHostName());
  240. vmMessage->getHeader()->MessageId()->setValue(msgId);
  241. ec_log_debug("Sending message with Message-ID: " + msgId.getId());
  242. }
  243. catch (vmime::exception& e) {
  244. mailer->setError(e.what());
  245. return MAPI_E_NOT_FOUND;
  246. }
  247. catch (std::exception& e) {
  248. mailer->setError(e.what());
  249. return MAPI_E_NOT_FOUND;
  250. }
  251. catch (...) {
  252. return MAPI_E_NOT_FOUND;
  253. }
  254. return mailer->sendMail(lpAddrBook, lpMessage, vmMessage,
  255. sopt.allow_send_to_everyone, sopt.always_expand_distr_list);
  256. }
  257. /**
  258. * Create BODY and BODYSTRUCTURE strings for IMAP.
  259. *
  260. * @param[in] input an RFC 2822 email
  261. * @param[out] lpSimple optional BODY result
  262. * @param[out] lpExtended optional BODYSTRUCTURE result
  263. *
  264. * @return MAPI Error code
  265. */
  266. HRESULT createIMAPProperties(const std::string &input, std::string *lpEnvelope,
  267. std::string *lpBody, std::string *lpBodyStructure)
  268. {
  269. InitializeVMime();
  270. return VMIMEToMAPI().createIMAPProperties(input, lpEnvelope, lpBody, lpBodyStructure);
  271. }
  272. } /* namespace */