POP3.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  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 <kopano/platform.h>
  18. #include <memory>
  19. #include <utility>
  20. #include <cstdio>
  21. #include <cstdlib>
  22. #include <iostream>
  23. #include <algorithm>
  24. #include <kopano/memory.hpp>
  25. #include <kopano/tie.hpp>
  26. #include <mapi.h>
  27. #include <mapix.h>
  28. #include <mapicode.h>
  29. #include <mapidefs.h>
  30. #include <mapiutil.h>
  31. #include <kopano/CommonUtil.h>
  32. #include <kopano/Util.h>
  33. #include <kopano/ECTags.h>
  34. #include <inetmapi/inetmapi.h>
  35. #include <kopano/mapiext.h>
  36. #include <kopano/stringutil.h>
  37. #include <kopano/charset/convert.h>
  38. #include <kopano/ecversion.h>
  39. #include <kopano/charset/utf8string.h>
  40. #include "ECFeatures.h"
  41. #include "POP3.h"
  42. using namespace std;
  43. using namespace KCHL;
  44. /**
  45. * @ingroup gateway_pop3
  46. * @{
  47. */
  48. POP3::POP3(const char *szServerPath, ECChannel *lpChannel, ECLogger *lpLogger, ECConfig *lpConfig) : ClientProto(szServerPath, lpChannel, lpLogger, lpConfig) {
  49. imopt_default_sending_options(&sopt);
  50. sopt.no_recipients_workaround = true; // do not stop processing mail on empty recipient table
  51. sopt.add_received_date = true; // add Received header (outlook uses this)
  52. }
  53. POP3::~POP3() {
  54. for (auto &m : lstMails)
  55. delete[] m.sbEntryID.lpb;
  56. if (lpInbox)
  57. lpInbox->Release();
  58. if (lpStore)
  59. lpStore->Release();
  60. if (lpSession)
  61. lpSession->Release();
  62. if (lpAddrBook)
  63. lpAddrBook->Release();
  64. }
  65. /**
  66. * Returns number of minutes to keep connection alive
  67. *
  68. * @return user logged in (true) or not (false)
  69. */
  70. int POP3::getTimeoutMinutes() {
  71. if (lpStore != NULL)
  72. return 5; // 5 minutes when logged in
  73. else
  74. return 1; // 1 minute when not logged in
  75. }
  76. HRESULT POP3::HrSendGreeting(const std::string &strHostString) {
  77. HRESULT hr = hrSuccess;
  78. if (parseBool(lpConfig->GetSetting("server_hostname_greeting")))
  79. hr = HrResponse(POP3_RESP_OK, "POP3 gateway ready" + strHostString);
  80. else
  81. hr = HrResponse(POP3_RESP_OK, "POP3 gateway ready");
  82. return hr;
  83. }
  84. /**
  85. * Send client an error message that the socket will be closed by the server
  86. *
  87. * @param[in] strQuitMsg quit message for client
  88. * @return MAPI error code
  89. */
  90. HRESULT POP3::HrCloseConnection(const std::string &strQuitMsg)
  91. {
  92. return HrResponse(POP3_RESP_ERR, strQuitMsg);
  93. }
  94. /**
  95. * Process the requested command from the POP3 client
  96. *
  97. * @param[in] strIput received input from client
  98. *
  99. * @return MAPI error code
  100. */
  101. HRESULT POP3::HrProcessCommand(const std::string &strInput)
  102. {
  103. HRESULT hr = hrSuccess;
  104. vector<string> vWords;
  105. string strCommand;
  106. vWords = tokenize(strInput, ' ');
  107. if (vWords.empty()) {
  108. lpLogger->Log(EC_LOGLEVEL_WARNING, "Empty line received");
  109. return MAPI_E_CALL_FAILED;
  110. }
  111. if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
  112. lpLogger->Log(EC_LOGLEVEL_DEBUG, "Command received: %s", vWords[0].c_str());
  113. strCommand = strToUpper(vWords[0]);
  114. if (strCommand.compare("CAPA") == 0) {
  115. if (vWords.size() != 1)
  116. return HrResponse(POP3_RESP_ERR,
  117. "CAPA command must have 0 arguments");
  118. return HrCmdCapability();
  119. } else if (strCommand.compare("STLS") == 0) {
  120. if (vWords.size() != 1)
  121. return HrResponse(POP3_RESP_ERR,
  122. "STLS command must have 0 arguments");
  123. if (HrCmdStarttls() != hrSuccess)
  124. // log ?
  125. // let the gateway quit from the socket read loop
  126. return MAPI_E_END_OF_SESSION;
  127. return hr;
  128. } else if (strCommand.compare("USER") == 0) {
  129. if (vWords.size() != 2)
  130. return HrResponse(POP3_RESP_ERR,
  131. "User command must have 1 argument");
  132. return HrCmdUser(vWords[1]);
  133. } else if (strCommand.compare("PASS") == 0) {
  134. if (vWords.size() < 2)
  135. return HrResponse(POP3_RESP_ERR,
  136. "Pass command must have 1 argument");
  137. string strPass = strInput;
  138. strPass.erase(0, strCommand.length()+1);
  139. return HrCmdPass(strPass);
  140. } else if (strCommand.compare("QUIT") == 0) {
  141. HrCmdQuit();
  142. // let the gateway quit from the socket read loop
  143. return MAPI_E_END_OF_SESSION;
  144. } else if (!IsAuthorized()) {
  145. HrResponse(POP3_RESP_ERR, "Invalid command");
  146. lpLogger->Log(EC_LOGLEVEL_ERROR, "Not authorized for command: %s", vWords[0].c_str());
  147. return MAPI_E_CALL_FAILED;
  148. } else if (strCommand.compare("STAT") == 0) {
  149. if (vWords.size() != 1)
  150. return HrResponse(POP3_RESP_ERR,
  151. "Stat command has no arguments");
  152. return HrCmdStat();
  153. } else if (strCommand.compare("LIST") == 0) {
  154. if (vWords.size() > 2)
  155. return HrResponse(POP3_RESP_ERR,
  156. "List must have 0 or 1 arguments");
  157. if (vWords.size() == 2)
  158. return HrCmdList(strtoul(vWords[1].c_str(), NULL, 0));
  159. return HrCmdList();
  160. } else if (strCommand.compare("RETR") == 0) {
  161. if (vWords.size() != 2)
  162. return HrResponse(POP3_RESP_ERR,
  163. "RETR must have 1 argument");
  164. return HrCmdRetr(strtoul(vWords[1].c_str(), NULL, 0));
  165. } else if (strCommand.compare("DELE") == 0) {
  166. if (vWords.size() != 2)
  167. return HrResponse(POP3_RESP_ERR,
  168. "DELE must have 1 argument");
  169. return HrCmdDele(strtoul(vWords[1].c_str(), NULL, 0));
  170. } else if (strCommand.compare("NOOP") == 0) {
  171. if (vWords.size() > 1)
  172. return HrResponse(POP3_RESP_ERR,
  173. "NOOP must have 0 arguments");
  174. return HrCmdNoop();
  175. } else if (strCommand.compare("RSET") == 0) {
  176. if (vWords.size() > 1)
  177. return HrResponse(POP3_RESP_ERR,
  178. "RSET must have 0 arguments");
  179. return HrCmdRset();
  180. } else if (strCommand.compare("TOP") == 0) {
  181. if (vWords.size() != 3)
  182. return HrResponse(POP3_RESP_ERR,
  183. "TOP must have 2 arguments");
  184. return HrCmdTop(strtoul(vWords[1].c_str(), NULL, 0), strtoul(vWords[2].c_str(), NULL, 0));
  185. } else if (strCommand.compare("UIDL") == 0) {
  186. if (vWords.size() > 2)
  187. return HrResponse(POP3_RESP_ERR,
  188. "UIDL must have 0 or 1 arguments");
  189. if (vWords.size() == 2)
  190. return HrCmdUidl(strtoul(vWords[1].c_str(), NULL, 0));
  191. return HrCmdUidl();
  192. }
  193. HrResponse(POP3_RESP_ERR, "Function not (yet) implemented");
  194. lpLogger->Log(EC_LOGLEVEL_ERROR, "non-existing function called: %s", vWords[0].c_str());
  195. return MAPI_E_CALL_FAILED;
  196. }
  197. /**
  198. * Cleanup connection
  199. *
  200. * @return hrSuccess
  201. */
  202. HRESULT POP3::HrDone(bool bSendResponse)
  203. {
  204. // no cleanup for POP3 required
  205. return hrSuccess;
  206. }
  207. /**
  208. * Send a response to the client, either +OK or -ERR
  209. *
  210. * @param[in] strResult +OK or -ERR result (use defines)
  211. * @param[in] strResponse string to send to client with given result
  212. *
  213. * @return MAPI Error code
  214. */
  215. HRESULT POP3::HrResponse(const string &strResult, const string &strResponse) {
  216. if(lpLogger->Log(EC_LOGLEVEL_DEBUG))
  217. lpLogger->Log(EC_LOGLEVEL_DEBUG, "%s%s", strResult.c_str(), strResponse.c_str());
  218. return lpChannel->HrWriteLine(strResult + strResponse);
  219. }
  220. /**
  221. * Returns the CAPA string. In some stages, items can be listed or
  222. * not. This depends on the command received from the client, and
  223. * the logged on status of the user. Last state is autodetected in
  224. * the class.
  225. *
  226. * @return The capabilities string
  227. */
  228. std::string POP3::GetCapabilityString()
  229. {
  230. string strCapabilities;
  231. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  232. // capabilities we always have
  233. strCapabilities = "\r\nCAPA\r\nTOP\r\nUIDL\r\nRESP-CODES\r\nAUTH-RESP-CODE\r\n";
  234. if (lpSession == NULL) {
  235. // authentication capabilities
  236. if (!lpChannel->UsingSsl() && lpChannel->sslctx())
  237. strCapabilities += "STLS\r\n";
  238. if (!(!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0))
  239. strCapabilities += "USER\r\n";
  240. }
  241. strCapabilities += ".";
  242. return strCapabilities;
  243. }
  244. /**
  245. * @brief Handle the CAPA command
  246. *
  247. * Sends all the gateway capabilities to the client, depending on the
  248. * state we're in. Authentication capabilities are skipped when a user
  249. * was already logged in.
  250. *
  251. * @return hrSuccess
  252. */
  253. HRESULT POP3::HrCmdCapability() {
  254. return HrResponse(POP3_RESP_OK, GetCapabilityString());
  255. }
  256. /**
  257. * @brief Handle the STLS command
  258. *
  259. * Tries to set the current connection to use SSL encryption.
  260. *
  261. * @return hrSuccess
  262. */
  263. HRESULT POP3::HrCmdStarttls() {
  264. if (!lpChannel->sslctx())
  265. return HrResponse(POP3_RESP_PERMFAIL, "STLS error in ssl context");
  266. if (lpChannel->UsingSsl())
  267. return HrResponse(POP3_RESP_ERR, "STLS already using SSL/TLS");
  268. HRESULT hr = HrResponse(POP3_RESP_OK, "Begin TLS negotiation now");
  269. if (hr != hrSuccess)
  270. return hr;
  271. hr = lpChannel->HrEnableTLS();
  272. if (hr != hrSuccess) {
  273. HrResponse(POP3_RESP_ERR, "Error switching to secure SSL/TLS connection");
  274. lpLogger->Log(EC_LOGLEVEL_ERROR, "Error switching to SSL in STLS");
  275. return hr;
  276. }
  277. if (lpChannel->UsingSsl())
  278. lpLogger->Log(EC_LOGLEVEL_INFO, "Using SSL now");
  279. return hrSuccess;
  280. }
  281. /**
  282. * @brief Handle the USER command
  283. *
  284. * Stores the username in the class, since the password is in a second call
  285. *
  286. * @param[in] strUser loginname of the user who wants to login
  287. *
  288. * @return MAPI Error code
  289. */
  290. HRESULT POP3::HrCmdUser(const string &strUser) {
  291. HRESULT hr = hrSuccess;
  292. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  293. if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
  294. hr = HrResponse(POP3_RESP_AUTH_ERROR, "Plaintext authentication disallowed on non-secure (SSL/TLS) connections");
  295. lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s with username \"%s\" (tried to use disallowed plaintext auth)",
  296. lpChannel->peer_addr(), strUser.c_str());
  297. return hr;
  298. } else if (lpStore != NULL) {
  299. return HrResponse(POP3_RESP_AUTH_ERROR, "Can't login twice");
  300. } else if (strUser.length() > POP3_MAX_RESPONSE_LENGTH) {
  301. lpLogger->Log(EC_LOGLEVEL_ERROR, "Username too long: %d > %d", (int)strUser.length(), POP3_MAX_RESPONSE_LENGTH);
  302. return HrResponse(POP3_RESP_PERMFAIL, "Username too long");
  303. }
  304. szUser = strUser;
  305. return HrResponse(POP3_RESP_OK, "Waiting for password");
  306. }
  307. /**
  308. * @brief Handle the PASS command
  309. *
  310. * Now that we have the password, we can login.
  311. *
  312. * @param[in] strPass password of the user to login with
  313. *
  314. * @return MAPI Error code
  315. */
  316. HRESULT POP3::HrCmdPass(const string &strPass) {
  317. HRESULT hr;
  318. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  319. if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
  320. hr = HrResponse(POP3_RESP_AUTH_ERROR, "Plaintext authentication disallowed on non-secure (SSL/TLS) connections");
  321. if(szUser.empty())
  322. lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s without username (tried to use disallowed "
  323. "plaintext auth)", lpChannel->peer_addr());
  324. else
  325. lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s with username \"%s\" (tried to use disallowed "
  326. "plaintext auth)", lpChannel->peer_addr(), szUser.c_str());
  327. return hr;
  328. } else if (lpStore != NULL) {
  329. return HrResponse(POP3_RESP_AUTH_ERROR, "Can't login twice");
  330. } else if (strPass.length() > POP3_MAX_RESPONSE_LENGTH) {
  331. lpLogger->Log(EC_LOGLEVEL_ERROR, "Password too long: %d > %d", (int)strPass.length(), POP3_MAX_RESPONSE_LENGTH);
  332. return HrResponse(POP3_RESP_PERMFAIL, "Password too long");
  333. } else if (szUser.empty()) {
  334. return HrResponse(POP3_RESP_ERR, "Give username first");
  335. }
  336. hr = this->HrLogin(szUser, strPass);
  337. if (hr != hrSuccess) {
  338. if (hr == MAPI_E_LOGON_FAILED)
  339. HrResponse(POP3_RESP_AUTH_ERROR, "Wrong username or password");
  340. else
  341. HrResponse(POP3_RESP_TEMPFAIL, "Internal error: HrLogin failed");
  342. return hr;
  343. }
  344. hr = this->HrMakeMailList();
  345. if (hr != hrSuccess) {
  346. HrResponse(POP3_RESP_ERR, "Can't get mail list");
  347. return hr;
  348. }
  349. return HrResponse(POP3_RESP_OK, "Username and password accepted");
  350. }
  351. /**
  352. * @brief Handle the STAT command
  353. *
  354. * STAT displays the number of messages and the total size of the Inbox
  355. *
  356. * @return MAPI Error code
  357. */
  358. HRESULT POP3::HrCmdStat() {
  359. ULONG ulSize = 0;
  360. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  361. for (size_t i = 0; i < lstMails.size(); ++i)
  362. ulSize += lstMails[i].ulSize;
  363. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u %u", (ULONG)lstMails.size(), ulSize);
  364. return HrResponse(POP3_RESP_OK, szResponse);
  365. }
  366. /**
  367. * @brief Handle the LIST command
  368. *
  369. * List shows for every message the number to retrieve the message
  370. * with and the size of the message. Since we don't know a client
  371. * which uses this size exactly, we can use the table version.
  372. *
  373. * @return MAPI Error code
  374. */
  375. HRESULT POP3::HrCmdList() {
  376. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  377. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u messages", (ULONG)lstMails.size());
  378. HRESULT hr = HrResponse(POP3_RESP_OK, szResponse);
  379. if (hr != hrSuccess)
  380. return hr;
  381. for (size_t i = 0; i < lstMails.size(); ++i) {
  382. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u %u", (ULONG)i + 1, lstMails[i].ulSize);
  383. hr = lpChannel->HrWriteLine(szResponse);
  384. if (hr != hrSuccess)
  385. return hr;
  386. }
  387. return lpChannel->HrWriteLine(".");
  388. }
  389. /**
  390. * @brief Handle the LIST <number> command
  391. *
  392. * Shows the size of the given mail number.
  393. *
  394. * @param[in] ulMailNr number of the email, starting at 1
  395. *
  396. * @return MAPI Error code
  397. */
  398. HRESULT POP3::HrCmdList(unsigned int ulMailNr) {
  399. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  400. if (ulMailNr > lstMails.size() || ulMailNr < 1) {
  401. HrResponse(POP3_RESP_ERR, "Wrong mail number");
  402. return MAPI_E_NOT_FOUND;
  403. }
  404. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u %u", ulMailNr, lstMails[ulMailNr - 1].ulSize);
  405. return HrResponse(POP3_RESP_OK, szResponse);
  406. }
  407. /**
  408. * @brief Handle the RETR command
  409. *
  410. * Retrieve the complete mail for a given number
  411. *
  412. * @param[in] ulMailNr number of the email, starting at 1
  413. *
  414. * @return
  415. */
  416. HRESULT POP3::HrCmdRetr(unsigned int ulMailNr) {
  417. HRESULT hr = hrSuccess;
  418. object_ptr<IMessage> lpMessage;
  419. object_ptr<IStream> lpStream;
  420. ULONG ulObjType;
  421. string strMessage;
  422. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  423. if (ulMailNr < 1 || ulMailNr > lstMails.size()) {
  424. HrResponse(POP3_RESP_ERR, "mail nr not found");
  425. return MAPI_E_NOT_FOUND;
  426. }
  427. hr = lpStore->OpenEntry(lstMails[ulMailNr-1].sbEntryID.cb, reinterpret_cast<ENTRYID *>(lstMails[ulMailNr-1].sbEntryID.lpb), &IID_IMessage, MAPI_DEFERRED_ERRORS,
  428. &ulObjType, &~lpMessage);
  429. if (hr != hrSuccess) {
  430. HrResponse(POP3_RESP_ERR, "Failing to open entry");
  431. return hr;
  432. }
  433. hr = lpMessage->OpenProperty(PR_EC_IMAP_EMAIL, &IID_IStream, 0, 0, &~lpStream);
  434. if (hr == hrSuccess) {
  435. hr = Util::HrStreamToString(lpStream, strMessage);
  436. if (hr == hrSuccess)
  437. strMessage = DotFilter(strMessage.c_str());
  438. }
  439. if (hr != hrSuccess) {
  440. // unable to load streamed version, so try full conversion.
  441. std::unique_ptr<char[]> szMessage;
  442. hr = IMToINet(lpSession, lpAddrBook, lpMessage, &unique_tie(szMessage), sopt);
  443. if (hr != hrSuccess) {
  444. lpLogger->Log(EC_LOGLEVEL_ERROR, "Error converting MAPI to MIME: 0x%08x", hr);
  445. HrResponse(POP3_RESP_PERMFAIL, "Converting MAPI to MIME error");
  446. return hr;
  447. }
  448. strMessage = DotFilter(szMessage.get());
  449. }
  450. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u octets", (ULONG)strMessage.length());
  451. HrResponse(POP3_RESP_OK, szResponse);
  452. lpChannel->HrWriteLine(strMessage);
  453. lpChannel->HrWriteLine(".");
  454. return hrSuccess;
  455. }
  456. /**
  457. * @brief Handle the DELE command
  458. *
  459. * Mark an email for deletion after the QUIT command
  460. *
  461. * @param[in] ulMailNr number of the email, starting at 1
  462. *
  463. * @return MAPI Error code
  464. */
  465. HRESULT POP3::HrCmdDele(unsigned int ulMailNr) {
  466. if (ulMailNr < 1 || ulMailNr > lstMails.size()) {
  467. HRESULT hr = HrResponse(POP3_RESP_ERR, "mail nr not found");
  468. if (hr == hrSuccess)
  469. hr = MAPI_E_NOT_FOUND;
  470. return hr;
  471. }
  472. lstMails[ulMailNr - 1].bDeleted = true;
  473. return HrResponse(POP3_RESP_OK, "mail deleted");
  474. }
  475. /**
  476. * @brief Handle the NOOP command
  477. *
  478. * Sends empty OK string to client
  479. * @return MAPI Error code
  480. */
  481. HRESULT POP3::HrCmdNoop() {
  482. return HrResponse(POP3_RESP_OK, string());
  483. }
  484. /**
  485. * @brief Handle the RSET command
  486. *
  487. * Resets the connections, sets every email to not deleted.
  488. *
  489. * @return MAPI Error code
  490. */
  491. HRESULT POP3::HrCmdRset() {
  492. for (auto &m : lstMails)
  493. m.bDeleted = false;
  494. return HrResponse(POP3_RESP_OK, "Undeleted mails");
  495. }
  496. /**
  497. * @brief Handle the QUIT command
  498. *
  499. * Delete all delete marked emails and close the connection.
  500. *
  501. * @return MAPI Error code
  502. */
  503. HRESULT POP3::HrCmdQuit() {
  504. HRESULT hr = hrSuccess;
  505. unsigned int DeleteCount = 0;
  506. SBinaryArray ba = {0, NULL};
  507. for (const auto &m : lstMails)
  508. if (m.bDeleted)
  509. ++DeleteCount;
  510. if (DeleteCount) {
  511. ba.cValues = DeleteCount;
  512. ba.lpbin = new SBinary[DeleteCount];
  513. DeleteCount = 0;
  514. for (const auto &m : lstMails)
  515. if (m.bDeleted) {
  516. ba.lpbin[DeleteCount] = m.sbEntryID;
  517. ++DeleteCount;
  518. }
  519. lpInbox->DeleteMessages(&ba, 0, NULL, 0);
  520. // ignore error, we always send the Bye to the client
  521. }
  522. hr = HrResponse(POP3_RESP_OK, "Bye");
  523. delete[] ba.lpbin;
  524. return hr;
  525. }
  526. /**
  527. * @brief Handle the UIDL command
  528. *
  529. * List all messages by number and Unique ID (EntryID). This ID must
  530. * be valid over different sessions.
  531. *
  532. * @return MAPI Error code
  533. */
  534. HRESULT POP3::HrCmdUidl() {
  535. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  536. string strResponse;
  537. HRESULT hr = lpChannel->HrWriteLine("+OK");
  538. if (hr != hrSuccess)
  539. return hr;
  540. for (size_t i = 0; i < lstMails.size(); ++i) {
  541. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u ", (ULONG)i + 1);
  542. strResponse = szResponse;
  543. strResponse += bin2hex(lstMails[i].sbEntryID.cb, lstMails[i].sbEntryID.lpb);
  544. hr = lpChannel->HrWriteLine(strResponse);
  545. if (hr != hrSuccess)
  546. return hr;
  547. }
  548. return lpChannel->HrWriteLine(".");
  549. }
  550. /**
  551. * @brief Handle the UIDL <number> command
  552. *
  553. * List the given message number by number and Unique ID
  554. * (EntryID). This ID must be valid over different sessions.
  555. *
  556. * @param ulMailNr number of the email to get the Unique ID for
  557. *
  558. * @return MAPI Error code
  559. */
  560. HRESULT POP3::HrCmdUidl(unsigned int ulMailNr) {
  561. HRESULT hr = hrSuccess;
  562. string strResponse;
  563. char szResponse[POP3_MAX_RESPONSE_LENGTH];
  564. if (ulMailNr < 1 || ulMailNr > lstMails.size()) {
  565. hr = HrResponse(POP3_RESP_ERR, "mail nr not found");
  566. if (hr == hrSuccess)
  567. hr = MAPI_E_NOT_FOUND;
  568. return hr;
  569. }
  570. snprintf(szResponse, POP3_MAX_RESPONSE_LENGTH, "%u ", ulMailNr);
  571. strResponse = szResponse;
  572. strResponse += bin2hex(lstMails[ulMailNr - 1].sbEntryID.cb, lstMails[ulMailNr - 1].sbEntryID.lpb);
  573. return HrResponse(POP3_RESP_OK, strResponse);
  574. }
  575. /**
  576. * @brief Handle the TOP command
  577. *
  578. * List the first N body lines of an email. The headers are always sent.
  579. *
  580. * @param[in] ulMailNr The email to list
  581. * @param[in] ulLines The number of lines of the email to send
  582. *
  583. * @return MAPI Error code
  584. */
  585. HRESULT POP3::HrCmdTop(unsigned int ulMailNr, unsigned int ulLines) {
  586. HRESULT hr = hrSuccess;
  587. object_ptr<IMessage> lpMessage;
  588. object_ptr<IStream> lpStream;
  589. ULONG ulObjType;
  590. string strMessage;
  591. string::size_type ulPos;
  592. if (ulMailNr < 1 || ulMailNr > lstMails.size()) {
  593. hr = HrResponse(POP3_RESP_ERR, "mail nr not found");
  594. if (hr == hrSuccess)
  595. return MAPI_E_NOT_FOUND;
  596. return hr;
  597. }
  598. hr = lpStore->OpenEntry(lstMails[ulMailNr-1].sbEntryID.cb, reinterpret_cast<ENTRYID *>(lstMails[ulMailNr-1].sbEntryID.lpb), &IID_IMessage, MAPI_DEFERRED_ERRORS,
  599. &ulObjType, &~lpMessage);
  600. if (hr != hrSuccess) {
  601. HrResponse(POP3_RESP_ERR, "Failing to open entry");
  602. return hr;
  603. }
  604. hr = lpMessage->OpenProperty(PR_EC_IMAP_EMAIL, &IID_IStream, 0, 0, &~lpStream);
  605. if (hr == hrSuccess)
  606. hr = Util::HrStreamToString(lpStream, strMessage);
  607. if (hr != hrSuccess) {
  608. // unable to load streamed version, so try full conversion.
  609. std::unique_ptr<char[]> szMessage;
  610. hr = IMToINet(lpSession, lpAddrBook, lpMessage, &unique_tie(szMessage), sopt);
  611. if (hr != hrSuccess) {
  612. lpLogger->Log(EC_LOGLEVEL_ERROR, "Error converting MAPI to MIME: 0x%08x", hr);
  613. HrResponse(POP3_RESP_PERMFAIL, "Converting MAPI to MIME error");
  614. return hr;
  615. }
  616. strMessage = szMessage.get();
  617. }
  618. ulPos = strMessage.find("\r\n\r\n", 0);
  619. ++ulLines;
  620. while (ulPos != string::npos && ulLines--)
  621. ulPos = strMessage.find("\r\n", ulPos + 1);
  622. if (ulPos != string::npos)
  623. strMessage = strMessage.substr(0, ulPos);
  624. strMessage = DotFilter(strMessage.c_str());
  625. if (HrResponse(POP3_RESP_OK, string()) != hrSuccess ||
  626. lpChannel->HrWriteLine(strMessage) != hrSuccess ||
  627. lpChannel->HrWriteLine(".") != hrSuccess)
  628. return MAPI_E_CALL_FAILED;
  629. return hrSuccess;
  630. }
  631. /**
  632. * Open the Inbox with the given login credentials
  633. *
  634. * @param[in] strUsername Username to login with
  635. * @param[in] strPassword Corresponding password of username
  636. *
  637. * @return MAPI Error code
  638. */
  639. HRESULT POP3::HrLogin(const std::string &strUsername, const std::string &strPassword) {
  640. HRESULT hr = hrSuccess;
  641. ULONG cbEntryID = 0;
  642. memory_ptr<ENTRYID> lpEntryID;
  643. ULONG ulObjType = 0;
  644. wstring strwUsername;
  645. wstring strwPassword;
  646. unsigned int flags;
  647. hr = TryConvert(strUsername, rawsize(strUsername), "windows-1252", strwUsername);
  648. if (hr != hrSuccess) {
  649. lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in username");
  650. goto exit;
  651. }
  652. hr = TryConvert(strPassword, rawsize(strPassword), "windows-1252", strwPassword);
  653. if (hr != hrSuccess) {
  654. lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in password");
  655. goto exit;
  656. }
  657. flags = EC_PROFILE_FLAGS_NO_NOTIFICATIONS | EC_PROFILE_FLAGS_NO_COMPRESSION;
  658. if (!parseBool(lpConfig->GetSetting("bypass_auth")))
  659. flags |= EC_PROFILE_FLAGS_NO_UID_AUTH;
  660. hr = HrOpenECSession(&lpSession, "gateway/pop3", PROJECT_SVN_REV_STR,
  661. strwUsername.c_str(), strwPassword.c_str(), m_strPath.c_str(),
  662. flags, NULL, NULL);
  663. if (hr != hrSuccess) {
  664. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to login from %s with invalid username \"%s\" or wrong password. Error: 0x%X",
  665. lpChannel->peer_addr(), strUsername.c_str(), hr);
  666. ++m_ulFailedLogins;
  667. if (m_ulFailedLogins >= LOGIN_RETRIES)
  668. // disconnect client
  669. hr = MAPI_E_END_OF_SESSION;
  670. goto exit;
  671. }
  672. hr = HrOpenDefaultStore(lpSession, &lpStore);
  673. if (hr != hrSuccess) {
  674. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open default store");
  675. goto exit;
  676. }
  677. hr = lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &lpAddrBook);
  678. if (hr != hrSuccess) {
  679. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open addressbook");
  680. goto exit;
  681. }
  682. // check if pop3 access is disabled
  683. if (isFeatureDisabled("pop3", lpAddrBook, lpStore)) {
  684. lpLogger->Log(EC_LOGLEVEL_ERROR, "POP3 not enabled for user '%s'", strUsername.c_str());
  685. hr = MAPI_E_LOGON_FAILED;
  686. goto exit;
  687. }
  688. hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  689. if (hr != hrSuccess) {
  690. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to find receive folder of store");
  691. goto exit;
  692. }
  693. hr = lpStore->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType, (LPUNKNOWN *) &lpInbox);
  694. if (ulObjType != MAPI_FOLDER)
  695. hr = MAPI_E_NOT_FOUND;
  696. if (hr != hrSuccess) {
  697. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open receive folder");
  698. goto exit;
  699. }
  700. lpLogger->Log(EC_LOGLEVEL_NOTICE, "POP3 Login from %s for user %s", lpChannel->peer_addr(), strUsername.c_str());
  701. exit:
  702. if (hr != hrSuccess) {
  703. if (lpInbox) {
  704. lpInbox->Release();
  705. lpInbox = NULL;
  706. }
  707. if (lpStore) {
  708. lpStore->Release();
  709. lpStore = NULL;
  710. }
  711. }
  712. return hr;
  713. }
  714. /**
  715. * Make a list of all emails in the Opened inbox
  716. *
  717. * @return MAPI Error code
  718. */
  719. HRESULT POP3::HrMakeMailList() {
  720. HRESULT hr = hrSuccess;
  721. object_ptr<IMAPITable> lpTable;
  722. enum { EID, SIZE, NUM_COLS };
  723. static constexpr const SizedSPropTagArray(NUM_COLS, spt) =
  724. {NUM_COLS, {PR_ENTRYID, PR_MESSAGE_SIZE}};
  725. static constexpr const SizedSSortOrderSet(1, tableSort) =
  726. {1, 0, 0, {{PR_CREATION_TIME, TABLE_SORT_ASCEND}}};
  727. hr = lpInbox->GetContentsTable(0, &~lpTable);
  728. if (hr != hrSuccess)
  729. return hr;
  730. hr = lpTable->SetColumns(spt, 0);
  731. if (hr != hrSuccess)
  732. return hr;
  733. hr = lpTable->SortTable(tableSort, 0);
  734. if (hr != hrSuccess)
  735. return hr;
  736. rowset_ptr lpRows;
  737. hr = lpTable->QueryRows(-1, 0, &~lpRows);
  738. if (hr != hrSuccess)
  739. return hr;
  740. lstMails.clear();
  741. for (ULONG i = 0; i < lpRows->cRows; ++i) {
  742. if (PROP_TYPE(lpRows->aRow[i].lpProps[EID].ulPropTag) == PT_ERROR) {
  743. lpLogger->Log(EC_LOGLEVEL_ERROR, "Missing EntryID in message table for message %d", i);
  744. continue;
  745. }
  746. if (PROP_TYPE(lpRows->aRow[i].lpProps[SIZE].ulPropTag) == PT_ERROR) {
  747. lpLogger->Log(EC_LOGLEVEL_ERROR, "Missing size in message table for message %d", i);
  748. continue;
  749. }
  750. MailListItem sMailListItem;
  751. sMailListItem.sbEntryID.cb = lpRows->aRow[i].lpProps[EID].Value.bin.cb;
  752. sMailListItem.sbEntryID.lpb = new BYTE[lpRows->aRow[i].lpProps[EID].Value.bin.cb];
  753. memcpy(sMailListItem.sbEntryID.lpb, lpRows->aRow[i].lpProps[EID].Value.bin.lpb, lpRows->aRow[i].lpProps[EID].Value.bin.cb);
  754. sMailListItem.bDeleted = false;
  755. sMailListItem.ulSize = lpRows->aRow[i].lpProps[SIZE].Value.l;
  756. lstMails.push_back(std::move(sMailListItem));
  757. }
  758. return hrSuccess;
  759. }
  760. /**
  761. * Since a POP3 email stops with one '.' line, we need to escape these lines in the actual email.
  762. *
  763. * @param[in] input input email to escape
  764. *
  765. * @return POP3 escaped email
  766. */
  767. string POP3::DotFilter(const char *input) {
  768. string output;
  769. ULONG i = 0;
  770. while (input[i] != '\0') {
  771. if (input[i] == '.' && input[i-1] == '\n')
  772. output += '.';
  773. output += input[i++];
  774. }
  775. return output;
  776. }
  777. /** @} */