MAILClient.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /*
  2. * @file IMAPClient.h
  3. * @brief libcurl wrapper for IMAP operations
  4. *
  5. * @author Mohamed Amine Mzoughi <mohamed-amine.mzoughi@laposte.net>
  6. * @date 2017-01-02
  7. *
  8. * Updated by acetone in 2023 for QtEmailFetcher
  9. */
  10. #include "MAILClient.h"
  11. // Static members initialization
  12. std::string CMailClient::s_strCertificationAuthorityFile;
  13. #ifdef DEBUG_CURL
  14. std::string CMailClient::s_strCurlTraceLogDirectory;
  15. #endif
  16. /**
  17. * @brief constructor for the mail client object
  18. *
  19. * @param Logger - a callabck to a logger function void(const std::string&)
  20. *
  21. */
  22. CMailClient::CMailClient(LogFnCallback Logger) :
  23. m_oLog(Logger),
  24. m_iCurlTimeout(5),
  25. m_eSettingsFlags(ALL_FLAGS),
  26. m_eSslTlsFlags(SslTlsFlag::NO_SSLTLS),
  27. m_pCurlSession(nullptr),
  28. m_pRecipientslist(nullptr),
  29. m_bProgressCallbackSet(false),
  30. m_bNoSignal(false),
  31. m_curlHandle(CurlHandle::instance())
  32. {
  33. }
  34. /**
  35. * @brief destructor for the mail client object
  36. *
  37. */
  38. CMailClient::~CMailClient()
  39. {
  40. if (m_pCurlSession != nullptr)
  41. {
  42. if (m_eSettingsFlags & ENABLE_LOG)
  43. m_oLog(LOG_WARNING_OBJECT_NOT_CLEANED);
  44. CleanupSession();
  45. }
  46. }
  47. /**
  48. * @brief Starts a new mail session, initializes the cURL API session
  49. *
  50. * If a new session was already started, the method has no effect.
  51. *
  52. * @param [in] strHost server address with or without port number
  53. * @param [in] strLogin username
  54. * @param [in] strPassword password
  55. * @param [in] eSettingsFlags optional use | operator to choose multiple options
  56. * @param [in] eSslTlsFlags optional encryption type
  57. *
  58. * @retval true Successfully initialized the session.
  59. * @retval false The session is already initialized : call CleanupSession()
  60. * before initializing a new one or the Curl API is not initialized.
  61. *
  62. * Example Usage:
  63. * @code
  64. * pMailClient->InitSession("smtp.gnet.tn:25", "amine", "my_password", ENABLE_LOG);
  65. * @endcode
  66. */
  67. bool CMailClient::InitSession(const std::string& strHost, const std::string& strLogin,
  68. const std::string& strPassword,
  69. const SettingsFlag& eSettingsFlags /* = ALL_FLAGS */,
  70. const SslTlsFlag& eSslTlsFlags /* = NO_SSLTLS */)
  71. {
  72. if (strHost.empty())
  73. {
  74. if (m_eSettingsFlags & ENABLE_LOG)
  75. m_oLog(LOG_ERROR_EMPTY_HOST_MSG);
  76. return false;
  77. }
  78. if (m_pCurlSession)
  79. {
  80. if (m_eSettingsFlags & ENABLE_LOG)
  81. m_oLog(LOG_ERROR_CURL_ALREADY_INIT_MSG);
  82. return false;
  83. }
  84. m_pCurlSession = curl_easy_init();
  85. // curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, CURLUSESSL_ALL);
  86. m_eSettingsFlags = eSettingsFlags;
  87. m_eSslTlsFlags = eSslTlsFlags;
  88. m_strURL = strHost;
  89. ParseURL(m_strURL);
  90. m_strUserName = strLogin;
  91. m_strPassword = strPassword;
  92. return (m_pCurlSession != nullptr);
  93. }
  94. /**
  95. * @brief Cleans the current mail session
  96. *
  97. * If a session was not already started, this method has no effect
  98. *
  99. * @retval true Successfully cleaned the current session.
  100. * @retval false The session is not already initialized.
  101. *
  102. * Example Usage:
  103. * @code
  104. * objMailClient.CleanupSession();
  105. * @endcode
  106. */
  107. bool CMailClient::CleanupSession()
  108. {
  109. if (!m_pCurlSession)
  110. {
  111. if (m_eSettingsFlags & ENABLE_LOG)
  112. m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
  113. return false;
  114. }
  115. #ifdef DEBUG_CURL
  116. if (m_ofFileCurlTrace.is_open())
  117. {
  118. m_ofFileCurlTrace.close();
  119. }
  120. #endif
  121. curl_easy_cleanup(m_pCurlSession);
  122. m_pCurlSession = nullptr;
  123. /* Free the list of recipients */
  124. if (m_pRecipientslist)
  125. {
  126. curl_slist_free_all(m_pRecipientslist);
  127. /* Curl won't send the QUIT command until you call cleanup, so you should
  128. * be able to re-use this connection for additional requests or messages
  129. * (setting CURLOPT_MAIL_FROM and CURLOPT_MAIL_RCPT as required, and calling
  130. * curl_easy_perform() again. It may not be a good idea to keep the connection
  131. * open for a very long time though (more than a few minutes may result in the
  132. * server timing out the connection) and you do want to clean up in the end.
  133. */
  134. m_pRecipientslist = nullptr;
  135. }
  136. return true;
  137. }
  138. /**
  139. * @brief sets the progress function callback and the owner of the client
  140. *
  141. * @param [in] pOwner pointer to the object owning the client, nullptr otherwise
  142. * @param [in] fnCallback callback to progress function
  143. *
  144. */
  145. void CMailClient::SetProgressFnCallback(void* pOwner, const ProgressFnCallback& fnCallback)
  146. {
  147. m_ProgressStruct.pOwner = pOwner;
  148. m_fnProgressCallback = fnCallback;
  149. m_ProgressStruct.pCurl = m_pCurlSession;
  150. m_ProgressStruct.dLastRunTime = 0;
  151. m_bProgressCallbackSet = true;
  152. }
  153. /**
  154. * @brief sets the HTTP Proxy address to tunnel the operation through it
  155. *
  156. * @param [in] strProxy URI of the HTTP Proxy
  157. *
  158. */
  159. void CMailClient::SetProxy(const std::string& strProxy)
  160. {
  161. if (strProxy.empty())
  162. return;
  163. std::string strUri = strProxy;
  164. std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);
  165. if (strUri.compare(0, 4, "HTTP") != 0)
  166. m_strProxy = "http://" + strProxy;
  167. else
  168. m_strProxy = strProxy;
  169. };
  170. /**
  171. * @brief performs the request of a mail client
  172. *
  173. *
  174. * @retval true Successfully performed the request.
  175. * @retval false The request couldn't be performed.
  176. *
  177. */
  178. bool CMailClient::Perform()
  179. {
  180. CURLcode res = CURLE_OK;
  181. if (!m_pCurlSession)
  182. {
  183. if (m_eSettingsFlags & ENABLE_LOG)
  184. m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
  185. return false;
  186. }
  187. // Reset is mandatory to avoid bad surprises
  188. curl_easy_reset(m_pCurlSession);
  189. if (!PrePerform())
  190. {
  191. if (m_eSettingsFlags & ENABLE_LOG)
  192. m_oLog(LOG_ERROR_PREPERFORM_FAILED_MSG);
  193. return false;
  194. }
  195. /* Set username and password */
  196. curl_easy_setopt(m_pCurlSession, CURLOPT_USERNAME, m_strUserName.c_str());
  197. curl_easy_setopt(m_pCurlSession, CURLOPT_PASSWORD, m_strPassword.c_str());
  198. if (m_eSslTlsFlags & ENABLE_TLS)
  199. {
  200. /* With TLS, start with a plain text connection, and upgrade
  201. * to Transport Layer Security (TLS) using the STARTTLS (SMTP) or the STLS
  202. * (POP) command. Be careful of using CURLUSESSL_TRY here, because if TLS
  203. * upgrade fails, the transfer will continue anyway - see the security
  204. * discussion in the libcurl tutorial for more details.
  205. *
  206. * If your server doesn't have a valid certificate, then you can disable
  207. * part of the Transport Layer Security protection by setting the
  208. * CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST options to 0 (false).
  209. * curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  210. * curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  211. *
  212. * That is, in general, a bad idea. It is still better than sending your
  213. * authentication details in plain text though. Instead, you should get
  214. * the issuer certificate (or the host certificate if the certificate is
  215. * self-signed) and add it to the set of certificates that are known to
  216. * libcurl using CURLOPT_CAINFO and/or CURLOPT_CAPATH. See docs/SSLCERTS
  217. * for more information. */
  218. curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, static_cast<long>(CURLUSESSL_ALL));
  219. }
  220. if (!s_strCertificationAuthorityFile.empty())
  221. curl_easy_setopt(m_pCurlSession, CURLOPT_CAINFO, s_strCertificationAuthorityFile.c_str());
  222. if (!m_strSSLCertFile.empty())
  223. curl_easy_setopt(m_pCurlSession, CURLOPT_SSLCERT, m_strSSLCertFile.c_str());
  224. if (!m_strSSLKeyFile.empty())
  225. curl_easy_setopt(m_pCurlSession, CURLOPT_SSLKEY, m_strSSLKeyFile.c_str());
  226. if (!m_strSSLKeyPwd.empty())
  227. curl_easy_setopt(m_pCurlSession, CURLOPT_KEYPASSWD, m_strSSLKeyPwd.c_str());
  228. if (!(m_eSettingsFlags & VERIFY_PEER))
  229. {
  230. /* If you want to connect to a site who isn't using a certificate that is
  231. * signed by one of the certs in the CA bundle you have, you can skip the
  232. * verification of the server's certificate. This makes the connection
  233. * A LOT LESS SECURE.
  234. *
  235. * If you have a CA cert for the server stored someplace else than in the
  236. * default bundle, then the CURLOPT_CAPATH option might come handy for
  237. * you. */
  238. curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, 0L);
  239. }
  240. /* If the site you're connecting to uses a different host name that what
  241. * they have mentioned in their server certificate's commonName (or
  242. * subjectAltName) fields, libcurl will refuse to connect. You can skip
  243. * this check, but this will make the connection less secure. */
  244. if (!(m_eSettingsFlags & VERIFY_HOST))
  245. curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYHOST, 0L); // use 2L for strict name check
  246. if (m_bProgressCallbackSet)
  247. {
  248. curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSFUNCTION, *GetProgressFnCallback());
  249. curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSDATA, &m_ProgressStruct);
  250. curl_easy_setopt(m_pCurlSession, CURLOPT_NOPROGRESS, 0L);
  251. }
  252. /* some servers need this */
  253. curl_easy_setopt(m_pCurlSession, CURLOPT_USERAGENT, CLIENT_USERAGENT);
  254. if (m_iCurlTimeout > 0)
  255. {
  256. curl_easy_setopt(m_pCurlSession, CURLOPT_TIMEOUT, m_iCurlTimeout);
  257. // don't want to get a sig alarm on timeout
  258. curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1);
  259. }
  260. if (!m_strProxy.empty())
  261. {
  262. curl_easy_setopt(m_pCurlSession, CURLOPT_PROXY, m_strProxy.c_str());
  263. curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPPROXYTUNNEL, 1L);
  264. }
  265. if (m_bNoSignal)
  266. {
  267. curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1L);
  268. }
  269. curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, 0L);
  270. curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYHOST, 0L);
  271. #ifdef DEBUG_CURL
  272. StartCurlDebug();
  273. #endif
  274. // Perform the requested operation
  275. res = curl_easy_perform(m_pCurlSession);
  276. #ifdef DEBUG_CURL
  277. EndCurlDebug();
  278. #endif
  279. if (!PostPerform(res))
  280. {
  281. if (m_eSettingsFlags & ENABLE_LOG)
  282. m_oLog(LOG_ERROR_POSTPERFORM_FAILED_MSG);
  283. return false;
  284. }
  285. if (res != CURLE_OK)
  286. {
  287. if (m_eSettingsFlags & ENABLE_LOG)
  288. m_oLog(StringFormat(LOG_ERROR_CURL_PEFORM_FAILURE_FORMAT, res, curl_easy_strerror(res)));
  289. return false;
  290. }
  291. return true;
  292. }
  293. /**
  294. * @brief returns a formatted string
  295. *
  296. * @param [in] strFormat string with one or many format specifiers
  297. * @param [in] parameters to be placed in the format specifiers of strFormat
  298. *
  299. * @retval string formatted string
  300. */
  301. std::string CMailClient::StringFormat(const std::string strFormat, ...)
  302. {
  303. int n = (static_cast<int>(strFormat.size())) * 2; // Reserve two times as much as the length of the strFormat
  304. std::unique_ptr<char[]> pFormatted;
  305. va_list ap;
  306. while(true)
  307. {
  308. pFormatted.reset(new char[n]); // Wrap the plain char array into the unique_ptr
  309. strcpy(&pFormatted[0], strFormat.c_str());
  310. va_start(ap, strFormat);
  311. int iFinaln = vsnprintf(&pFormatted[0], n, strFormat.c_str(), ap);
  312. va_end(ap);
  313. if (iFinaln < 0 || iFinaln >= n)
  314. {
  315. n += abs(iFinaln - n + 1);
  316. }
  317. else
  318. {
  319. break;
  320. }
  321. }
  322. return std::string(pFormatted.get());
  323. }
  324. /**
  325. * @brief stores the server response in a string
  326. *
  327. * @param ptr pointer of max size (size*nmemb) to read data from it
  328. * @param size size parameter
  329. * @param nmemb memblock parameter
  330. * @param data pointer to user data (string)
  331. *
  332. * @return (size * nmemb)
  333. */
  334. size_t CMailClient::WriteInStringCallback(void* ptr, size_t size, size_t nmemb, void* data)
  335. {
  336. std::string* strWriteHere = reinterpret_cast<std::string*>(data);
  337. if (strWriteHere != nullptr)
  338. {
  339. strWriteHere->append(reinterpret_cast<char*>(ptr), size * nmemb);
  340. return size * nmemb;
  341. }
  342. return 0;
  343. }
  344. /**
  345. * @brief stores the server response in an already opened file stream
  346. *
  347. * @param buff pointer of max size (size*nmemb) to read data from it
  348. * @param size size parameter
  349. * @param nmemb memblock parameter
  350. * @param userdata pointer to user data (file stream)
  351. *
  352. * @return (size * nmemb)
  353. */
  354. size_t CMailClient::WriteToFileCallback(void* buff, size_t size, size_t nmemb, void* data)
  355. {
  356. if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1) || (data == nullptr))
  357. return 0;
  358. std::fstream* pFileStream = reinterpret_cast<std::fstream*>(data);
  359. if (pFileStream->is_open())
  360. {
  361. pFileStream->write(reinterpret_cast<char*>(buff), size * nmemb);
  362. }
  363. else
  364. {
  365. std::cout.write(reinterpret_cast<char*>(buff), size * nmemb);
  366. }
  367. return size * nmemb;
  368. }
  369. /**
  370. * @brief sends a line from an already opened file stream (text)
  371. *
  372. * @param ptr pointer of max size (size*nmemb) to write data to it
  373. * @param size size parameter
  374. * @param nmemb memblock parameter
  375. * @param stream pointer to user data (file stream)
  376. *
  377. * @return (size * nmemb)
  378. */
  379. size_t CMailClient::ReadLineFromFileStreamCallback(void* ptr, size_t size, size_t nmemb, void* stream)
  380. {
  381. if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1) || (stream == nullptr))
  382. return 0;
  383. std::fstream* pFileStream = reinterpret_cast<std::fstream*>(stream);
  384. std::string strLine;
  385. // what if the memory pointed by ptr was less than strLine.length() ?
  386. if (pFileStream->is_open() && getline(*pFileStream, strLine))
  387. {
  388. strLine.append("\r\n");
  389. std::memcpy(ptr, strLine.c_str(), strLine.length());
  390. return strLine.length();
  391. }
  392. return 0;
  393. }
  394. /**
  395. * @brief sends a line from an input string stream
  396. *
  397. * @param ptr pointer of max size (size*nmemb) to write data to it
  398. * @param size size parameter
  399. * @param nmemb memblock parameter
  400. * @param userp pointer to user data (input string stream)
  401. *
  402. * @return (size * nmemb)
  403. */
  404. size_t CMailClient::ReadLineFromStringStreamCallback(void* ptr, size_t size, size_t nmemb, void* userp)
  405. {
  406. if ((size == 0) || (nmemb == 0) || ((size*nmemb) < 1) || (userp == nullptr))
  407. return 0;
  408. std::istringstream* ssText = reinterpret_cast<std::istringstream*>(userp);
  409. std::string strLine;
  410. // what if the memory pointed by ptr was less than strLine.length() ?
  411. if (std::getline(*ssText, strLine, '\n'))
  412. {
  413. strLine.append("\r\n");
  414. std::memcpy(ptr, strLine.c_str(), strLine.length());
  415. return strLine.length();
  416. }
  417. return 0;
  418. }
  419. /**
  420. * @brief reads the content of an already opened file stream
  421. *
  422. * @param ptr pointer of max size (size*nmemb) to write data to it
  423. * @param size size parameter
  424. * @param nmemb memblock parameter
  425. * @param stream pointer to user data (file stream)
  426. *
  427. * @return (size * nmemb)
  428. */
  429. size_t CMailClient::ReadFromFileCallback(void* ptr, size_t size, size_t nmemb, void* stream)
  430. {
  431. std::fstream* pFileStream = reinterpret_cast<std::fstream*>(stream);
  432. if (pFileStream->is_open())
  433. {
  434. pFileStream->read(reinterpret_cast<char*>(ptr), size * nmemb);
  435. return pFileStream->gcount();
  436. }
  437. return 0;
  438. }
  439. #ifdef DEBUG_CURL
  440. void CMailClient::SetCurlTraceLogDirectory(const std::string& strPath)
  441. {
  442. s_strCurlTraceLogDirectory = strPath;
  443. if (!s_strCurlTraceLogDirectory.empty()
  444. #ifdef WINDOWS
  445. && s_strCurlTraceLogDirectory.at(s_strCurlTraceLogDirectory.length() - 1) != '\\')
  446. {
  447. s_strCurlTraceLogDirectory += '\\';
  448. }
  449. #else
  450. && s_strCurlTraceLogDirectory.at(s_strCurlTraceLogDirectory.length() - 1) != '/')
  451. {
  452. s_strCurlTraceLogDirectory += '/';
  453. }
  454. #endif
  455. }
  456. int CMailClient::DebugCallback(CURL* curl , curl_infotype curl_info_type, char* pszTrace, size_t usSize, void* pFile)
  457. {
  458. std::string strText;
  459. std::string strTrace(pszTrace, usSize);
  460. switch (curl_info_type)
  461. {
  462. case CURLINFO_TEXT:
  463. strText = "# Information : ";
  464. break;
  465. case CURLINFO_HEADER_OUT:
  466. strText = "-> Sending header : ";
  467. break;
  468. case CURLINFO_DATA_OUT:
  469. strText = "-> Sending data : ";
  470. break;
  471. case CURLINFO_SSL_DATA_OUT:
  472. strText = "-> Sending SSL data : ";
  473. break;
  474. case CURLINFO_HEADER_IN:
  475. strText = "<- Receiving header : ";
  476. break;
  477. case CURLINFO_DATA_IN:
  478. strText = "<- Receiving unencrypted data : ";
  479. break;
  480. case CURLINFO_SSL_DATA_IN:
  481. strText = "<- Receiving SSL data : ";
  482. break;
  483. default:
  484. break;
  485. }
  486. std::ofstream* pofTraceFile = reinterpret_cast<std::ofstream*>(pFile);
  487. if (pofTraceFile == nullptr)
  488. {
  489. std::cout << "[DEBUG] cURL debug log [" << curl_info_type << "]: " << " - " << strTrace;
  490. }
  491. else
  492. {
  493. (*pofTraceFile) << strText << strTrace;
  494. }
  495. return 0;
  496. }
  497. void CMailClient::StartCurlDebug()
  498. {
  499. if (!m_ofFileCurlTrace.is_open())
  500. {
  501. curl_easy_setopt(m_pCurlSession, CURLOPT_VERBOSE, 1L);
  502. curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGFUNCTION, &CMailClient::DebugCallback);
  503. std::string strFileCurlTraceFullName(s_strCurlTraceLogDirectory);
  504. if (!strFileCurlTraceFullName.empty())
  505. {
  506. char szDate[32];
  507. memset(szDate, 0, 32);
  508. time_t tNow; time(&tNow);
  509. // new trace file for each hour
  510. strftime(szDate, 32, "%Y%m%d_%H", localtime(&tNow));
  511. strFileCurlTraceFullName += "TraceLog_";
  512. strFileCurlTraceFullName += szDate;
  513. strFileCurlTraceFullName += ".txt";
  514. m_ofFileCurlTrace.open(strFileCurlTraceFullName, std::ifstream::app | std::ifstream::binary);
  515. if (m_ofFileCurlTrace)
  516. curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGDATA, &m_ofFileCurlTrace);
  517. }
  518. }
  519. }
  520. void CMailClient::EndCurlDebug()
  521. {
  522. if (m_ofFileCurlTrace && m_ofFileCurlTrace.is_open())
  523. {
  524. m_ofFileCurlTrace << "###########################################" << std::endl;
  525. m_ofFileCurlTrace.close();
  526. }
  527. }
  528. #endif