Spooler.cpp 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199
  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. /*
  18. * This is the Kopano spooler.
  19. *
  20. *
  21. * The actual encoding is done by the inetmapi library.
  22. *
  23. * The spooler starts up, runs the queue once, and then
  24. * waits for changes in the outgoing queue. If any changes
  25. * occur, the whole queue is run again. This is done by having
  26. * an advise sink which is called when a table change is detected.
  27. * This advise sink unblocks the main (waiting) thread.
  28. */
  29. #include <kopano/platform.h>
  30. #include <chrono>
  31. #include <condition_variable>
  32. #include <mutex>
  33. #include <new>
  34. #include "mailer.h"
  35. #include <climits>
  36. #include <cstdio>
  37. #include <cstdlib>
  38. #include <iostream>
  39. #include <csignal>
  40. #include <time.h>
  41. #define USES_IID_IMAPIFolder
  42. #define USES_IID_IMessage
  43. #define USES_IID_IMsgStore
  44. #include <mapi.h>
  45. #include <mapix.h>
  46. #include <mapiutil.h>
  47. #include <mapidefs.h>
  48. #include <mapiguid.h>
  49. #include <kopano/IECUnknown.h>
  50. #include "IECSpooler.h"
  51. #include <kopano/IECServiceAdmin.h>
  52. #include <kopano/IECSecurity.h>
  53. #include <kopano/MAPIErrors.h>
  54. #include <kopano/ECGuid.h>
  55. #include <kopano/EMSAbTag.h>
  56. #include <kopano/ECTags.h>
  57. #include <kopano/ECABEntryID.h>
  58. #include <kopano/CommonUtil.h>
  59. #include <kopano/ECLogger.h>
  60. #include <kopano/ECConfig.h>
  61. #include <kopano/UnixUtil.h>
  62. #include <kopano/memory.hpp>
  63. #include <kopano/my_getopt.h>
  64. #include <kopano/ecversion.h>
  65. #include <kopano/Util.h>
  66. #include <kopano/stringutil.h>
  67. #include <kopano/mapiext.h>
  68. #include <edkmdb.h>
  69. #include <edkguid.h>
  70. #include <kopano/mapiguidext.h>
  71. #include "mapicontact.h"
  72. #include <kopano/charset/convert.h>
  73. #include <kopano/charset/convstring.h>
  74. #include <kopano/charset/utf8string.h>
  75. #include <kopano/ECGetText.h>
  76. #include "StatsClient.h"
  77. #include "TmpPath.h"
  78. #include <map>
  79. #include "spmain.h"
  80. using namespace std;
  81. using namespace KCHL;
  82. static StatsClient *sc = NULL;
  83. // spooler exit codes
  84. #define EXIT_OK 0
  85. #define EXIT_FAILED 1
  86. #define EXIT_WAIT 2
  87. #define EXIT_REMOVE 3
  88. static bool bQuit = false;
  89. static int nReload = 0;
  90. static int disconnects = 0;
  91. static const char *szCommand = NULL;
  92. static const char *szConfig = ECConfig::GetDefaultPath("spooler.cfg");
  93. ECConfig *g_lpConfig = NULL;
  94. ECLogger *g_lpLogger = NULL;
  95. // notification
  96. static bool bMessagesWaiting = false;
  97. static std::mutex hMutexMessagesWaiting;
  98. static std::condition_variable hCondMessagesWaiting;
  99. // messages being processed
  100. struct SendData {
  101. ULONG cbStoreEntryId;
  102. BYTE* lpStoreEntryId;
  103. ULONG cbMessageEntryId;
  104. BYTE* lpMessageEntryId;
  105. ULONG ulFlags;
  106. wstring strUsername;
  107. };
  108. static map<pid_t, SendData> mapSendData;
  109. static map<pid_t, int> mapFinished; // exit status of finished processes
  110. static std::mutex hMutexFinished; /* mutex for mapFinished */
  111. static HRESULT running_server(const char *szSMTP, int port, const char *szPath);
  112. /**
  113. * Print command line options, only for daemon version, not for mailer fork process
  114. *
  115. * @param[in] name name of the command
  116. */
  117. static void print_help(const char *name)
  118. {
  119. cout << "Usage:\n" << endl;
  120. cout << name << " [-F] [-h|--host <serverpath>] [-c|--config <configfile>] [smtp server]" << endl;
  121. cout << " -F\t\tDo not run in the background" << endl;
  122. cout << " -h path\tUse alternate connect path (e.g. file:///var/run/socket).\n\t\tDefault: file:///var/run/kopano/server.sock" << endl;
  123. cout << " -V Print version info." << endl;
  124. cout << " -c filename\tUse alternate config file (e.g. /etc/kopano-spooler.cfg)\n\t\tDefault: /etc/kopano/spooler.cfg" << endl;
  125. cout << " smtp server: The name or IP-address of the SMTP server, overriding the configuration" << endl;
  126. cout << endl;
  127. }
  128. /**
  129. * Encode a wide string in UTF-8 and output it in hexadecimal.
  130. * @param[in] lpszW The wide string to encode
  131. * @return The encoded string.
  132. */
  133. static string encodestring(const wchar_t *lpszW) {
  134. const utf8string u8 = convstring(lpszW);
  135. return bin2hex(u8.size(), (const unsigned char*)u8.c_str());
  136. }
  137. /**
  138. * Decode a string previously encoded with encodestring.
  139. * @param[in] lpszA The string containing the hexadecimal
  140. * representation of the UTF-8 encoded string.
  141. * @return The original wide string.
  142. */
  143. static wstring decodestring(const char *lpszA) {
  144. const utf8string u8 = utf8string::from_string(hex2bin(lpszA));
  145. return convert_to<wstring>(u8);
  146. }
  147. /**
  148. * Notification callback will be called about new messages in the
  149. * queue. Since this will happen from a different thread, we'll need
  150. * to use a mutex.
  151. *
  152. * @param[in] lpContext context of the callback (?)
  153. * @param[in] cNotif number of notifications in lpNotif
  154. * @param[in] lpNotif notification data
  155. */
  156. static LONG __stdcall AdviseCallback(void *lpContext, ULONG cNotif,
  157. LPNOTIFICATION lpNotif)
  158. {
  159. std::unique_lock<std::mutex> lk(hMutexMessagesWaiting);
  160. for (ULONG i = 0; i < cNotif; ++i) {
  161. if (lpNotif[i].info.tab.ulTableEvent == TABLE_RELOAD) {
  162. // Table needs a reload - trigger a reconnect with the server
  163. nReload = true;
  164. bMessagesWaiting = true;
  165. hCondMessagesWaiting.notify_one();
  166. }
  167. else if (lpNotif[i].info.tab.ulTableEvent != TABLE_ROW_DELETED) {
  168. bMessagesWaiting = true;
  169. hCondMessagesWaiting.notify_one();
  170. break;
  171. }
  172. }
  173. return 0;
  174. }
  175. /*
  176. * starting fork, passes:
  177. * -c config for all log settings and smtp server and such
  178. * --log-fd x execpt you should log here through a pipe
  179. * --entryids the data to send
  180. * --username the user to send the message as
  181. * if (szPath) kopano host
  182. * if (szSMTP) smtp host
  183. * if (szSMTPPport) smtp port
  184. */
  185. /**
  186. * Starts a forked process which sends the actual mail, and removes it
  187. * from the queue, in normal situations. On error, the
  188. * CleanFinishedMessages function will try to remove the failed
  189. * message if needed, else it will be tried again later (eg. timestamp
  190. * on sending, or SMTP not responding).
  191. *
  192. * @param[in] szUsername The username. This name is in unicode.
  193. * @param[in] szSMTP SMTP server to use
  194. * @param[in] ulPort SMTP port to use
  195. * @param[in] szPath URL to storage server
  196. * @param[in] cbStoreEntryId Length of lpStoreEntryId
  197. * @param[in] lpStoreEntryId Entry ID of store of user containing the message to be sent
  198. * @param[in] cbStoreEntryId Length of lpMsgEntryId
  199. * @param[in] lpMsgEntryId Entry ID of message to be sent
  200. * @param[in] ulFlags PR_EC_OUTGOING_FLAGS of message (EC_SUBMIT_DOSENTMAIL flag)
  201. * @return HRESULT
  202. */
  203. static HRESULT StartSpoolerFork(const wchar_t *szUsername, const char *szSMTP,
  204. int ulSMTPPort, const char *szPath, ULONG cbStoreEntryId,
  205. BYTE *lpStoreEntryId, ULONG cbMsgEntryId, BYTE *lpMsgEntryId,
  206. ULONG ulFlags)
  207. {
  208. SendData sSendData;
  209. pid_t pid;
  210. bool bDoSentMail = ulFlags & EC_SUBMIT_DOSENTMAIL;
  211. std::string strPort = stringify(ulSMTPPort);
  212. // place pid with entryid copy in map
  213. sSendData.cbStoreEntryId = cbStoreEntryId;
  214. HRESULT hr = MAPIAllocateBuffer(cbStoreEntryId,
  215. reinterpret_cast<void **>(&sSendData.lpStoreEntryId));
  216. if (hr != hrSuccess) {
  217. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "StartSpoolerFork(): MAPIAllocateBuffer failed(1) %x", hr);
  218. return hr;
  219. }
  220. memcpy(sSendData.lpStoreEntryId, lpStoreEntryId, cbStoreEntryId);
  221. sSendData.cbMessageEntryId = cbMsgEntryId;
  222. hr = MAPIAllocateBuffer(cbMsgEntryId, (void**)&sSendData.lpMessageEntryId);
  223. if (hr != hrSuccess) {
  224. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "StartSpoolerFork(): MAPIAllocateBuffer failed(2) %x", hr);
  225. return hr;
  226. }
  227. memcpy(sSendData.lpMessageEntryId, lpMsgEntryId, cbMsgEntryId);
  228. sSendData.ulFlags = ulFlags;
  229. sSendData.strUsername = szUsername;
  230. // execute the new spooler process to send the email
  231. pid = vfork();
  232. if (pid < 0) {
  233. g_lpLogger->Log(EC_LOGLEVEL_FATAL, string("Unable to start new spooler process: ") + strerror(errno));
  234. return MAPI_E_CALL_FAILED;
  235. }
  236. if (pid == 0) {
  237. char *bname = strdup(szCommand);
  238. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s NULL",
  239. szCommand, basename(bname) /* argv[0] */,
  240. "--send-message-entryid", bin2hex(cbMsgEntryId, lpMsgEntryId).c_str(),
  241. "--send-username-enc", encodestring(szUsername).c_str(),
  242. "--log-fd", stringify(g_lpLogger->GetFileDescriptor()).c_str(),
  243. "--config", szConfig,
  244. "--host", szPath,
  245. "--foreground", szSMTP,
  246. "--port", strPort.c_str(),
  247. bDoSentMail ? "--do-sentmail" : "");
  248. #ifdef SPOOLER_FORK_DEBUG
  249. _exit(EXIT_WAIT);
  250. #else
  251. // we execute because of all the mapi memory in use would be duplicated in the child,
  252. // and there won't be a nice way to clean it all up.
  253. execlp(szCommand, basename(bname) /* argv[0] */,
  254. "--send-message-entryid", bin2hex(cbMsgEntryId, lpMsgEntryId).c_str(),
  255. "--send-username-enc", encodestring(szUsername).c_str(),
  256. "--log-fd", stringify(g_lpLogger->GetFileDescriptor()).c_str(),
  257. "--config", szConfig,
  258. "--host", szPath,
  259. "--foreground", szSMTP,
  260. "--port", strPort.c_str(),
  261. bDoSentMail ? "--do-sentmail" : NULL, NULL);
  262. g_lpLogger->Log(EC_LOGLEVEL_FATAL, string("Cannot start spooler process `") + szCommand + "`: " + strerror(errno));
  263. _exit(EXIT_REMOVE);
  264. #endif
  265. }
  266. g_lpLogger->Log(EC_LOGLEVEL_INFO, "Spooler process started on pid %d", pid);
  267. // process is started, place in map
  268. mapSendData[pid] = sSendData;
  269. return hrSuccess;
  270. }
  271. /**
  272. * Opens all required objects of the administrator to move an error
  273. * mail out of the queue.
  274. *
  275. * @param[in] sSendData Struct with information about the mail in the queue which caused a fatal error
  276. * @param[in] lpAdminSession MAPI session of Kopano SYSTEM user
  277. * @param[out] lppAddrBook MAPI Addressbook object
  278. * @param[out] lppMailer inetmapi ECSender object, which can generate an error text for the body for the mail
  279. * @param[out] lppUserStore The store of the user with the error mail, open with admin rights
  280. * @param[out] lppMessage The message of the user which caused the error, open with admin rights
  281. * @return HRESULT
  282. */
  283. static HRESULT GetErrorObjects(const SendData &sSendData,
  284. IMAPISession *lpAdminSession, IAddrBook **lppAddrBook,
  285. ECSender **lppMailer, IMsgStore **lppUserStore, IMessage **lppMessage)
  286. {
  287. HRESULT hr = hrSuccess;
  288. ULONG ulObjType = 0;
  289. if (*lppAddrBook == NULL) {
  290. hr = lpAdminSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, lppAddrBook);
  291. if (hr != hrSuccess) {
  292. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open addressbook for error mail, skipping. Error 0x%08X", hr);
  293. return hr;
  294. }
  295. }
  296. if (*lppMailer == NULL) {
  297. /*
  298. * SMTP server does not matter here, we just use the
  299. * object for the error body.
  300. */
  301. *lppMailer = CreateSender("localhost", 25);
  302. if (! (*lppMailer)) {
  303. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to create error object for error mail, skipping.");
  304. return hr;
  305. }
  306. }
  307. if (*lppUserStore == NULL) {
  308. hr = lpAdminSession->OpenMsgStore(0, sSendData.cbStoreEntryId, (LPENTRYID)sSendData.lpStoreEntryId, NULL, MDB_WRITE | MDB_NO_DIALOG | MDB_NO_MAIL | MDB_TEMPORARY, lppUserStore);
  309. if (hr != hrSuccess) {
  310. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open store of user for error mail, skipping. Error 0x%08X", hr);
  311. return hr;
  312. }
  313. }
  314. if (*lppMessage == NULL) {
  315. hr = (*lppUserStore)->OpenEntry(sSendData.cbMessageEntryId, (LPENTRYID)sSendData.lpMessageEntryId, &IID_IMessage, MAPI_BEST_ACCESS, &ulObjType, (IUnknown**)lppMessage);
  316. if (hr != hrSuccess) {
  317. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open message of user for error mail, skipping. Error 0x%08X", hr);
  318. return hr;
  319. }
  320. }
  321. return hr;
  322. }
  323. /**
  324. * Cleans finished messages. Normally only prints a logmessage. If the
  325. * mailer completely failed (eg. segfault), this function will try to
  326. * remove the faulty mail from the queue.
  327. *
  328. * @param[in] lpAdminSession MAPI session of the Kopano SYSTEM user
  329. * @param[in] lpSpooler IECSpooler object
  330. * @return HRESULT
  331. */
  332. static HRESULT CleanFinishedMessages(IMAPISession *lpAdminSession,
  333. IECSpooler *lpSpooler)
  334. {
  335. HRESULT hr = hrSuccess;
  336. SendData sSendData;
  337. bool bErrorMail;
  338. map<pid_t, int> finished; // exit status of finished processes
  339. int status;
  340. // error message creation
  341. object_ptr<IAddrBook> lpAddrBook;
  342. ECSender *lpMailer = NULL;
  343. std::unique_lock<std::mutex> lock(hMutexFinished);
  344. if (mapFinished.empty())
  345. return hr;
  346. // copy map contents and clear it, so hMutexFinished can be unlocked again asap
  347. finished = mapFinished;
  348. mapFinished.clear();
  349. lock.unlock();
  350. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Cleaning %d messages from queue", (int)finished.size());
  351. // process finished entries
  352. for (const auto &i : finished) {
  353. sSendData = mapSendData[i.first];
  354. /* Find exit status, and decide to remove mail from queue or not */
  355. status = i.second;
  356. bErrorMail = false;
  357. bool wasSent = false;
  358. #ifdef WEXITSTATUS
  359. if(WIFEXITED(status)) { /* Child exited by itself */
  360. if (WEXITSTATUS(status) == EXIT_WAIT) {
  361. // timed message, try again later
  362. g_lpLogger->Log(EC_LOGLEVEL_INFO, "Message for user %ls will be tried again later", sSendData.strUsername.c_str());
  363. sc -> countInc("Spooler", "exit_wait");
  364. }
  365. else if (WEXITSTATUS(status) == EXIT_OK || WEXITSTATUS(status) == EXIT_FAILED) {
  366. // message was sent, or the user already received an error mail.
  367. g_lpLogger->Log(EC_LOGLEVEL_INFO, "Processed message for user %ls", sSendData.strUsername.c_str());
  368. wasSent = true;
  369. }
  370. else {
  371. // message was not sent, and could not be removed from queue. Notify user also.
  372. bErrorMail = true;
  373. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed message for user %ls will be removed from queue, error 0x%x", sSendData.strUsername.c_str(), status);
  374. }
  375. }
  376. else if(WIFSIGNALED(status)) { /* Child was killed by a signal */
  377. bErrorMail = true;
  378. g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Spooler process %d was killed by signal %d", i.first, WTERMSIG(status));
  379. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message for user %ls will be removed from queue", sSendData.strUsername.c_str());
  380. sc -> countInc("Spooler", "sig_killed");
  381. }
  382. else { /* Something strange happened */
  383. bErrorMail = true;
  384. g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Spooler process %d terminated abnormally", i.first);
  385. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message for user %ls will be removed from queue", sSendData.strUsername.c_str());
  386. sc -> countInc("Spooler", "abnormal_terminate");
  387. }
  388. #else
  389. if (status) {
  390. bErrorMail = true;
  391. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Spooler process %d exited with status %d", i.first, status);
  392. }
  393. #endif
  394. if (wasSent)
  395. sc -> countInc("Spooler", "sent");
  396. else if (bErrorMail)
  397. sc -> countInc("Spooler", "send_failed");
  398. if (bErrorMail) {
  399. object_ptr<IMsgStore> lpUserStore;
  400. object_ptr<IMessage> lpMessage;
  401. hr = GetErrorObjects(sSendData, lpAdminSession, &~lpAddrBook, &lpMailer, &~lpUserStore, &~lpMessage);
  402. if (hr == hrSuccess) {
  403. lpMailer->setError(_("A fatal error occurred while processing your message, and Kopano is unable to send your email."));
  404. hr = SendUndeliverable(lpMailer, lpUserStore, lpMessage);
  405. // TODO: if failed, and we have the lpUserStore, create message?
  406. }
  407. if (hr != hrSuccess)
  408. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to create error message for user %ls: %s (%x)",
  409. sSendData.strUsername.c_str(), GetMAPIErrorMessage(hr), hr);
  410. // remove mail from queue
  411. hr = lpSpooler->DeleteFromMasterOutgoingTable(sSendData.cbMessageEntryId, (LPENTRYID)sSendData.lpMessageEntryId, sSendData.ulFlags);
  412. if (hr != hrSuccess)
  413. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Could not remove invalid message from queue, error code: 0x%08X", hr);
  414. // move mail to sent items folder
  415. if (sSendData.ulFlags & EC_SUBMIT_DOSENTMAIL && lpMessage) {
  416. hr = DoSentMail(lpAdminSession, lpUserStore, 0, lpMessage);
  417. if (hr != hrSuccess)
  418. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to move sent mail to sent-items folder: %s (%x)",
  419. GetMAPIErrorMessage(hr), hr);
  420. }
  421. }
  422. MAPIFreeBuffer(sSendData.lpStoreEntryId);
  423. MAPIFreeBuffer(sSendData.lpMessageEntryId);
  424. mapSendData.erase(i.first);
  425. }
  426. delete lpMailer;
  427. return hr;
  428. }
  429. /**
  430. * Sends all messages found in lpTable (outgoing queue).
  431. *
  432. * Starts forks or threads until maximum number of simultatious
  433. * messages is reached. Loops until a slot comes free to start a new
  434. * fork/thread. When a fatal MAPI error has occurred, or the table is
  435. * empty, this function will return.
  436. *
  437. * @param[in] lpAdminSession MAPI Session of Kopano SYSTEM user
  438. * @param[in] lpSpooler IECSpooler object
  439. * @param[in] lpTable Outgoing queue table view
  440. * @param[in] szSMTP SMTP server to use
  441. * @param[in] ulPort SMTP port
  442. * @param[in] szPath URI to storage server
  443. * @return HRESULT
  444. */
  445. static HRESULT ProcessAllEntries(IMAPISession *lpAdminSession,
  446. IECSpooler *lpSpooler, IMAPITable *lpTable, const char *szSMTP, int ulPort,
  447. const char *szPath)
  448. {
  449. HRESULT hr = hrSuccess;
  450. unsigned int ulMaxThreads = 0;
  451. unsigned int ulFreeThreads = 0;
  452. ULONG ulRowCount = 0;
  453. std::wstring strUsername;
  454. bool bForceReconnect = false;
  455. hr = lpTable->GetRowCount(0, &ulRowCount);
  456. if (hr != hrSuccess) {
  457. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get outgoing queue count: %s (%x)",
  458. GetMAPIErrorMessage(hr), hr);
  459. goto exit;
  460. }
  461. if (ulRowCount) {
  462. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Number of messages in the queue: %d", ulRowCount);
  463. sc -> countInc("Spooler", "batch_invokes");
  464. sc -> countAdd("Spooler", "batch_count", int64_t(ulRowCount));
  465. }
  466. ulMaxThreads = atoi(g_lpConfig->GetSetting("max_threads"));
  467. if (ulMaxThreads == 0)
  468. ulMaxThreads = 1;
  469. while(!bQuit) {
  470. ulFreeThreads = ulMaxThreads - mapSendData.size();
  471. if (ulFreeThreads == 0) {
  472. Sleep(100);
  473. // remove enties from mapSendData which are finished
  474. CleanFinishedMessages(lpAdminSession, lpSpooler);
  475. continue; /* Continue looping until threads become available */
  476. }
  477. rowset_ptr lpsRowSet;
  478. hr = lpTable->QueryRows(1, 0, &~lpsRowSet);
  479. if (hr != hrSuccess) {
  480. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to fetch data from table, error code: 0x%08X", hr);
  481. goto exit;
  482. }
  483. if (lpsRowSet->cRows == 0) // All rows done
  484. goto exit;
  485. if (lpsRowSet->aRow[0].lpProps[4].ulPropTag == PR_DEFERRED_SEND_TIME) {
  486. // check time
  487. time_t now = time(NULL);
  488. time_t sendat;
  489. FileTimeToUnixTime(lpsRowSet->aRow[0].lpProps[4].Value.ft, &sendat);
  490. if (now < sendat)
  491. // if we ever add logging here, it should trigger just once for this mail
  492. continue;
  493. }
  494. // Check whether the row contains the entryid and store id
  495. if (lpsRowSet->aRow[0].lpProps[0].ulPropTag != PR_EC_MAILBOX_OWNER_ACCOUNT_W ||
  496. lpsRowSet->aRow[0].lpProps[1].ulPropTag != PR_STORE_ENTRYID ||
  497. lpsRowSet->aRow[0].lpProps[2].ulPropTag != PR_ENTRYID ||
  498. lpsRowSet->aRow[0].lpProps[3].ulPropTag != PR_EC_OUTGOING_FLAGS)
  499. {
  500. // Client was quick enough to remove message from queue before we could read it
  501. g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Empty row in OutgoingQueue");
  502. if (lpsRowSet->aRow[0].lpProps[2].ulPropTag == PR_ENTRYID && lpsRowSet->aRow[0].lpProps[3].ulPropTag == PR_EC_OUTGOING_FLAGS) {
  503. // we can remove this message
  504. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Removing invalid entry from OutgoingQueue");
  505. hr = lpSpooler->DeleteFromMasterOutgoingTable(lpsRowSet->aRow[0].lpProps[2].Value.bin.cb, (LPENTRYID)lpsRowSet->aRow[0].lpProps[2].Value.bin.lpb, lpsRowSet->aRow[0].lpProps[3].Value.ul);
  506. if (hr != hrSuccess) {
  507. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Could not remove invalid message from queue, error code: 0x%08X", hr);
  508. // since we have an error, we will reconnect to the server to fully reload the table
  509. goto exit;
  510. }
  511. }
  512. else {
  513. // this error makes the spooler disconnect from the server, and reconnect again (bQuit still false)
  514. bForceReconnect = true;
  515. }
  516. continue;
  517. }
  518. strUsername = lpsRowSet->aRow[0].lpProps[0].Value.lpszW;
  519. // Check if there is already an active process for this message
  520. bool bMatch = false;
  521. for (const auto &i : mapSendData)
  522. if (i.second.cbMessageEntryId == lpsRowSet->aRow[0].lpProps[2].Value.bin.cb &&
  523. memcmp(i.second.lpMessageEntryId, lpsRowSet->aRow[0].lpProps[2].Value.bin.lpb, i.second.cbMessageEntryId) == 0) {
  524. bMatch = true;
  525. break;
  526. }
  527. if (bMatch)
  528. continue;
  529. // Start new process to send the mail
  530. hr = StartSpoolerFork(strUsername.c_str(), szSMTP, ulPort, szPath, lpsRowSet->aRow[0].lpProps[1].Value.bin.cb, lpsRowSet->aRow[0].lpProps[1].Value.bin.lpb, lpsRowSet->aRow[0].lpProps[2].Value.bin.cb, lpsRowSet->aRow[0].lpProps[2].Value.bin.lpb, lpsRowSet->aRow[0].lpProps[3].Value.ul);
  531. if (hr != hrSuccess) {
  532. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "ProcessAllEntries(): Failed starting spooler: %x", hr);
  533. goto exit;
  534. }
  535. }
  536. exit:
  537. return bForceReconnect ? MAPI_E_NETWORK_ERROR : hr;
  538. }
  539. /**
  540. * Opens the IECSpooler object on an admin session.
  541. *
  542. * @param[in] lpAdminSession MAPI Session of the Kopano SYSTEM user
  543. * @param[out] lppSpooler IECSpooler is a Kopano interface to the outgoing queue functions.
  544. * @return HRESULT
  545. */
  546. static HRESULT GetAdminSpooler(IMAPISession *lpAdminSession,
  547. IECSpooler **lppSpooler)
  548. {
  549. HRESULT hr = hrSuccess;
  550. object_ptr<IECSpooler> lpSpooler;
  551. object_ptr<IMsgStore> lpMDB;
  552. memory_ptr<SPropValue> lpsProp;
  553. hr = HrOpenDefaultStore(lpAdminSession, &~lpMDB);
  554. if (hr != hrSuccess) {
  555. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open default store for system account. Error 0x%08X", hr);
  556. return hr;
  557. }
  558. hr = HrGetOneProp(lpMDB, PR_EC_OBJECT, &~lpsProp);
  559. if (hr != hrSuccess) {
  560. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get Kopano internal object: %s (%x)",
  561. GetMAPIErrorMessage(hr), hr);
  562. return hr;
  563. }
  564. hr = ((IECUnknown *)lpsProp->Value.lpszA)->QueryInterface(IID_IECSpooler, &~lpSpooler);
  565. if (hr != hrSuccess) {
  566. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Spooler interface not supported: %s (%x)",
  567. GetMAPIErrorMessage(hr), hr);
  568. return hr;
  569. }
  570. *lppSpooler = lpSpooler.release();
  571. return hr;
  572. }
  573. /**
  574. * Opens an admin session and the outgoing queue. If either one
  575. * produces an error this function will return. If the queue is empty,
  576. * it will wait for a notification when new data is present in the
  577. * outgoing queue table.
  578. *
  579. * @param[in] szSMTP The SMTP server to send to.
  580. * @param[in] ulPort The SMTP port to sent to.
  581. * @param[in] szPath URI of storage server to connect to, must be file:// or https:// with valid SSL certificates.
  582. * @return HRESULT
  583. */
  584. static HRESULT ProcessQueue(const char *szSMTP, int ulPort, const char *szPath)
  585. {
  586. HRESULT hr = hrSuccess;
  587. object_ptr<IMAPISession> lpAdminSession;
  588. object_ptr<IECSpooler> lpSpooler;
  589. object_ptr<IMAPITable> lpTable;
  590. object_ptr<IMAPIAdviseSink> lpAdviseSink;
  591. ULONG ulConnection = 0;
  592. static constexpr const SizedSPropTagArray(5, sOutgoingCols) =
  593. {5, {PR_EC_MAILBOX_OWNER_ACCOUNT_W, PR_STORE_ENTRYID,
  594. PR_ENTRYID, PR_EC_OUTGOING_FLAGS, PR_DEFERRED_SEND_TIME}};
  595. static constexpr const SizedSSortOrderSet(1, sSort) =
  596. {1, 0, 0, {{PR_EC_HIERARCHYID, TABLE_SORT_ASCEND}}};
  597. hr = HrOpenECAdminSession(&~lpAdminSession, "kopano-spooler:system",
  598. PROJECT_SVN_REV_STR, szPath, EC_PROFILE_FLAGS_NO_PUBLIC_STORE,
  599. g_lpConfig->GetSetting("sslkey_file", "", NULL),
  600. g_lpConfig->GetSetting("sslkey_pass", "", NULL));
  601. if (hr != hrSuccess) {
  602. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open admin session. Error 0x%08X", hr);
  603. goto exit;
  604. }
  605. if (disconnects == 0)
  606. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Connection to storage server succeeded");
  607. else
  608. g_lpLogger->Log(EC_LOGLEVEL_INFO, "Connection to storage server succeeded after %d retries", disconnects);
  609. disconnects = 0; // first call succeeded, assume all is well.
  610. hr = GetAdminSpooler(lpAdminSession, &~lpSpooler);
  611. if (hr != hrSuccess) {
  612. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessQueue: GetAdminSpooler failed %x", hr);
  613. goto exit;
  614. }
  615. // Mark reload as done since we reloaded the outgoing table
  616. nReload = false;
  617. // Request the master outgoing table
  618. hr = lpSpooler->GetMasterOutgoingTable(0, &~lpTable);
  619. if (hr != hrSuccess) {
  620. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Master outgoing queue not available: %s (%x)",
  621. GetMAPIErrorMessage(hr), hr);
  622. goto exit;
  623. }
  624. hr = lpTable->SetColumns(sOutgoingCols, 0);
  625. if (hr != hrSuccess) {
  626. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to setColumns() on OutgoingQueue: %s (%x)",
  627. GetMAPIErrorMessage(hr), hr);
  628. goto exit;
  629. }
  630. // Sort by ascending hierarchyid: first in, first out queue
  631. hr = lpTable->SortTable(sSort, 0);
  632. if (hr != hrSuccess) {
  633. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to SortTable() on OutgoingQueue: %s (%x)",
  634. GetMAPIErrorMessage(hr), hr);
  635. goto exit;
  636. }
  637. hr = HrAllocAdviseSink(AdviseCallback, nullptr, &~lpAdviseSink);
  638. if (hr != hrSuccess) {
  639. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to allocate memory for advise sink: %s (%x)",
  640. GetMAPIErrorMessage(hr), hr);
  641. goto exit;
  642. }
  643. // notify on new mail in the outgoing table
  644. hr = lpTable->Advise(fnevTableModified, lpAdviseSink, &ulConnection);
  645. while(!bQuit && !nReload) {
  646. bMessagesWaiting = false;
  647. lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
  648. // also checks not to send a message again which is already sending
  649. hr = ProcessAllEntries(lpAdminSession, lpSpooler, lpTable, szSMTP, ulPort, szPath);
  650. if(hr != hrSuccess) {
  651. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "ProcessQueue: ProcessAllEntries failed %x", hr);
  652. goto exit;
  653. }
  654. // Exit signal, break the operation
  655. if(bQuit)
  656. break;
  657. if(nReload)
  658. break;
  659. std::unique_lock<std::mutex> lk(hMutexMessagesWaiting);
  660. if(!bMessagesWaiting) {
  661. while (!bMessagesWaiting) {
  662. auto s = hCondMessagesWaiting.wait_for(lk, std::chrono::seconds(60));
  663. if (s == std::cv_status::timeout || bMessagesWaiting || bQuit || nReload)
  664. break;
  665. // not timed out, no messages waiting, not quit requested, no table reload required:
  666. // we were triggered for a cleanup call.
  667. CleanFinishedMessages(lpAdminSession, lpSpooler);
  668. }
  669. }
  670. lk.unlock();
  671. // remove any entries that were done during the wait
  672. CleanFinishedMessages(lpAdminSession, lpSpooler);
  673. }
  674. exit:
  675. // when we exit, we must make sure all forks started are cleaned
  676. if (bQuit) {
  677. ULONG ulCount = 0;
  678. ULONG ulThreads = 0;
  679. while (ulCount < 60) {
  680. if ((ulCount % 5) == 0) {
  681. ulThreads = mapSendData.size();
  682. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Still waiting for %d thread%c to exit.", ulThreads, ulThreads!=1?'s':' ');
  683. }
  684. if (lpSpooler != nullptr)
  685. CleanFinishedMessages(lpAdminSession, lpSpooler);
  686. if (mapSendData.size() == 0)
  687. break;
  688. Sleep(1000);
  689. ++ulCount;
  690. }
  691. if (ulCount == 60)
  692. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "%d threads did not yet exit, closing anyway.", (int)mapSendData.size());
  693. }
  694. else if (nReload) {
  695. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Table reload requested, breaking server connection");
  696. }
  697. if (lpTable && ulConnection)
  698. lpTable->Unadvise(ulConnection);
  699. return hr;
  700. }
  701. /**
  702. * Segfault signal handler. Prints the backtrace of the crash in the log.
  703. *
  704. * @param[in] signr Any signal that can dump core. Mostly SIGSEGV.
  705. */
  706. static void sigsegv(int signr, siginfo_t *si, void *uc)
  707. {
  708. generic_sigsegv_handler(g_lpLogger, "Spooler",
  709. PROJECT_VERSION_SPOOLER_STR, signr, si, uc);
  710. }
  711. /**
  712. * actual signal handler, direct entry point if only linuxthreads is available.
  713. *
  714. * @param[in] sig signal received
  715. */
  716. static void process_signal(int sig)
  717. {
  718. ec_log_debug("Received signal %d", sig);
  719. int stat;
  720. pid_t pid;
  721. switch (sig) {
  722. case SIGTERM:
  723. case SIGINT: {
  724. bQuit = true;
  725. // Trigger condition so we force wakeup the queue thread
  726. std::lock_guard<std::mutex> lk(hMutexMessagesWaiting);
  727. hCondMessagesWaiting.notify_one();
  728. break;
  729. }
  730. case SIGCHLD: {
  731. std::unique_lock<std::mutex> finlock(hMutexFinished);
  732. while ((pid = waitpid (-1, &stat, WNOHANG)) > 0)
  733. mapFinished[pid] = stat;
  734. finlock.unlock();
  735. // Trigger condition so the messages get cleaned from the queue
  736. std::lock_guard<std::mutex> mwlock(hMutexMessagesWaiting);
  737. hCondMessagesWaiting.notify_one();
  738. break;
  739. }
  740. case SIGHUP:
  741. if (g_lpConfig != nullptr && !g_lpConfig->ReloadSettings() &&
  742. g_lpLogger != nullptr)
  743. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to reload configuration file, continuing with current settings.");
  744. if (g_lpLogger) {
  745. if (g_lpConfig) {
  746. const char *ll = g_lpConfig->GetSetting("log_level");
  747. int new_ll = ll ? atoi(ll) : EC_LOGLEVEL_WARNING;
  748. g_lpLogger->SetLoglevel(new_ll);
  749. }
  750. g_lpLogger->Reset();
  751. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Log connection was reset");
  752. }
  753. break;
  754. case SIGUSR2: {
  755. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Spooler stats:");
  756. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Running threads: %zu", mapSendData.size());
  757. std::lock_guard<std::mutex> l(hMutexFinished);
  758. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Finished threads: %zu", mapFinished.size());
  759. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Disconnects: %d", disconnects);
  760. break;
  761. }
  762. default:
  763. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Unknown signal %d received", sig);
  764. break;
  765. }
  766. }
  767. /**
  768. * Main program loop. Calls ProcessQueue, which logs in to MAPI. This
  769. * way, disconnects are solved. After a disconnect from the server,
  770. * the loop will try again after 3 seconds.
  771. *
  772. * @param[in] szSMTP The SMTP server to send to.
  773. * @param[in] ulPort The SMTP port to send to.
  774. * @param[in] szPath URI of storage server to connect to, must be file:// or https:// with valid SSL certificates.
  775. * @return HRESULT
  776. */
  777. static HRESULT running_server(const char *szSMTP, int ulPort,
  778. const char *szPath)
  779. {
  780. HRESULT hr = hrSuccess;
  781. g_lpLogger->Log(EC_LOGLEVEL_ALWAYS, "Starting kopano-spooler version " PROJECT_VERSION_SPOOLER_STR " (" PROJECT_SVN_REV_STR "), pid %d", getpid());
  782. g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Using SMTP server: %s, port %d", szSMTP, ulPort);
  783. disconnects = 0;
  784. while (1) {
  785. hr = ProcessQueue(szSMTP, ulPort, szPath);
  786. if (bQuit)
  787. break;
  788. if (disconnects == 0)
  789. g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Server connection lost. Reconnecting in 3 seconds...");
  790. ++disconnects;
  791. Sleep(3000); // wait 3s until retry to connect
  792. }
  793. bQuit = true; // make sure the sigchld does not use the lock anymore
  794. return hr;
  795. }
  796. int main(int argc, char *argv[]) {
  797. HRESULT hr = hrSuccess;
  798. const char *szPath = NULL;
  799. const char *szSMTP = NULL;
  800. int ulPort = 0;
  801. int c;
  802. int daemonize = 1;
  803. int logfd = -1;
  804. bool bForked = false;
  805. std::string strMsgEntryId;
  806. std::wstring strUsername;
  807. bool bDoSentMail = false;
  808. bool bIgnoreUnknownConfigOptions = false;
  809. // options
  810. enum {
  811. OPT_HELP = UCHAR_MAX + 1,
  812. OPT_CONFIG,
  813. OPT_HOST,
  814. OPT_FOREGROUND,
  815. OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS,
  816. // only called by spooler itself
  817. OPT_SEND_MESSAGE_ENTRYID,
  818. OPT_SEND_USERNAME,
  819. OPT_LOGFD,
  820. OPT_DO_SENTMAIL,
  821. OPT_PORT
  822. };
  823. static const struct option long_options[] = {
  824. { "help", 0, NULL, OPT_HELP }, // help text
  825. { "config", 1, NULL, OPT_CONFIG }, // config file
  826. { "host", 1, NULL, OPT_HOST }, // kopano host location
  827. { "foreground", 0, NULL, OPT_FOREGROUND }, // do not daemonize
  828. // only called by spooler itself
  829. { "send-message-entryid", 1, NULL, OPT_SEND_MESSAGE_ENTRYID }, // entryid of message to send
  830. { "send-username-enc", 1, NULL, OPT_SEND_USERNAME }, // owner's username of message to send in hex-utf8
  831. { "log-fd", 1, NULL, OPT_LOGFD }, // fd where to send log messages to
  832. { "do-sentmail", 0, NULL, OPT_DO_SENTMAIL },
  833. { "port", 1, NULL, OPT_PORT },
  834. { "ignore-unknown-config-options", 0, NULL, OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS },
  835. { NULL, 0, NULL, 0 }
  836. };
  837. // Default settings
  838. static const configsetting_t lpDefaults[] = {
  839. { "smtp_server","localhost", CONFIGSETTING_RELOADABLE },
  840. { "smtp_port","25", CONFIGSETTING_RELOADABLE },
  841. { "server_socket", "default:" },
  842. { "run_as_user", "kopano" },
  843. { "run_as_group", "kopano" },
  844. { "pid_file", "/var/run/kopano/spooler.pid" },
  845. { "running_path", "/var/lib/kopano" },
  846. { "coredump_enabled", "no" },
  847. { "log_method","file" },
  848. { "log_file","-" },
  849. { "log_level", "3", CONFIGSETTING_RELOADABLE },
  850. { "log_timestamp","1" },
  851. { "log_buffer_size", "0" },
  852. { "sslkey_file", "" },
  853. { "sslkey_pass", "", CONFIGSETTING_EXACT },
  854. { "max_threads", "5", CONFIGSETTING_RELOADABLE },
  855. { "fax_domain", "", CONFIGSETTING_RELOADABLE },
  856. { "fax_international", "+", CONFIGSETTING_RELOADABLE },
  857. { "always_send_delegates", "no", CONFIGSETTING_RELOADABLE },
  858. { "always_send_tnef", "no", CONFIGSETTING_RELOADABLE },
  859. { "always_send_utf8", "no", CONFIGSETTING_RELOADABLE },
  860. { "charset_upgrade", "windows-1252", CONFIGSETTING_RELOADABLE },
  861. { "allow_redirect_spoofing", "yes", CONFIGSETTING_RELOADABLE },
  862. { "allow_delegate_meeting_request", "yes", CONFIGSETTING_RELOADABLE },
  863. { "allow_send_to_everyone", "yes", CONFIGSETTING_RELOADABLE },
  864. { "copy_delegate_mails", "yes", CONFIGSETTING_RELOADABLE },
  865. { "expand_groups", "no", CONFIGSETTING_RELOADABLE },
  866. { "archive_on_send", "no", CONFIGSETTING_RELOADABLE },
  867. { "enable_dsn", "yes", CONFIGSETTING_RELOADABLE },
  868. { "plugin_enabled", "yes" },
  869. { "plugin_path", "/var/lib/kopano/spooler/plugins" },
  870. { "plugin_manager_path", "/usr/share/kopano-spooler/python" },
  871. { "z_statsd_stats", "/var/run/kopano/statsd.sock" },
  872. { "tmp_path", "/tmp" },
  873. { "tmp_path", "/tmp" },
  874. { "tmp_path", "/tmp" },
  875. { NULL, NULL },
  876. };
  877. // SIGSEGV backtrace support
  878. stack_t st;
  879. struct sigaction act;
  880. memset(&st, 0, sizeof(st));
  881. memset(&act, 0, sizeof(act));
  882. setlocale(LC_CTYPE, "");
  883. setlocale(LC_MESSAGES, "");
  884. while(1) {
  885. c = my_getopt_long_permissive(argc, argv, "c:h:iuVF", long_options, NULL);
  886. if(c == -1)
  887. break;
  888. switch(c) {
  889. case OPT_CONFIG:
  890. case 'c':
  891. szConfig = optarg;
  892. break;
  893. case OPT_HOST:
  894. case 'h':
  895. szPath = optarg;
  896. break;
  897. case 'i': // Install service
  898. case 'u': // Uninstall service
  899. break;
  900. case 'F':
  901. case OPT_FOREGROUND:
  902. daemonize = 0;
  903. break;
  904. case OPT_SEND_MESSAGE_ENTRYID:
  905. bForked = true;
  906. strMsgEntryId = hex2bin(optarg);
  907. break;
  908. case OPT_SEND_USERNAME:
  909. bForked = true;
  910. strUsername = decodestring(optarg);
  911. break;
  912. case OPT_LOGFD:
  913. logfd = atoi(optarg);
  914. break;
  915. case OPT_DO_SENTMAIL:
  916. bDoSentMail = true;
  917. break;
  918. case OPT_PORT:
  919. ulPort = atoi(optarg);
  920. break;
  921. case OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS:
  922. bIgnoreUnknownConfigOptions = true;
  923. break;
  924. case 'V':
  925. cout << "Product version:\t" << PROJECT_VERSION_SPOOLER_STR << endl
  926. << "File version:\t\t" << PROJECT_SVN_REV_STR << endl;
  927. return 1;
  928. case OPT_HELP:
  929. default:
  930. cout << "Unknown option: " << c << endl;
  931. print_help(argv[0]);
  932. return 1;
  933. }
  934. }
  935. if (bForked)
  936. bIgnoreUnknownConfigOptions = true;
  937. g_lpConfig = ECConfig::Create(lpDefaults);
  938. if (szConfig) {
  939. int argidx = 0;
  940. if (!g_lpConfig->LoadSettings(szConfig) ||
  941. (argidx = g_lpConfig->ParseParams(argc - optind, &argv[optind])) < 0 ||
  942. (!bIgnoreUnknownConfigOptions && g_lpConfig->HasErrors())) {
  943. /* Create info logger without a timestamp to stderr. */
  944. g_lpLogger = new(std::nothrow) ECLogger_File(EC_LOGLEVEL_INFO, 0, "-", false);
  945. if (g_lpLogger == nullptr) {
  946. hr = MAPI_E_NOT_ENOUGH_MEMORY;
  947. goto exit;
  948. }
  949. ec_log_set(g_lpLogger);
  950. LogConfigErrors(g_lpConfig);
  951. hr = E_FAIL;
  952. goto exit;
  953. }
  954. // ECConfig::ParseParams returns the index in the passed array,
  955. // after some shuffling, where it stopped parsing. optind is
  956. // the index where my_getopt_long_permissive stopped parsing. So
  957. // adding argidx to optind will result in the index after all
  958. // options are parsed.
  959. optind += argidx;
  960. }
  961. // commandline overwrites spooler.cfg
  962. if (optind < argc)
  963. szSMTP = argv[optind];
  964. else
  965. szSMTP = g_lpConfig->GetSetting("smtp_server");
  966. if (!ulPort)
  967. ulPort = atoui(g_lpConfig->GetSetting("smtp_port"));
  968. szCommand = argv[0];
  969. // setup logging, use pipe to log if started in forked mode and using pipe (file) logger, create normal logger for syslog
  970. if (bForked && logfd != -1)
  971. g_lpLogger = new ECLogger_Pipe(logfd, 0, atoi(g_lpConfig->GetSetting("log_level")));
  972. else
  973. g_lpLogger = CreateLogger(g_lpConfig, argv[0], "KopanoSpooler");
  974. ec_log_set(g_lpLogger);
  975. if ((bIgnoreUnknownConfigOptions && g_lpConfig->HasErrors()) || g_lpConfig->HasWarnings())
  976. LogConfigErrors(g_lpConfig);
  977. if (!TmpPath::getInstance() -> OverridePath(g_lpConfig))
  978. g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Ignoring invalid path-setting!");
  979. // set socket filename
  980. if (!szPath)
  981. szPath = g_lpConfig->GetSetting("server_socket");
  982. if (bForked) {
  983. // keep sending mail when we're killed in forked mode
  984. signal(SIGTERM, SIG_IGN);
  985. signal(SIGINT, SIG_IGN);
  986. signal(SIGHUP, SIG_IGN);
  987. signal(SIGUSR1, SIG_IGN);
  988. signal(SIGUSR2, SIG_IGN);
  989. }
  990. else {
  991. // notification condition
  992. act.sa_handler = process_signal;
  993. act.sa_flags = SA_ONSTACK | SA_RESTART;
  994. sigemptyset(&act.sa_mask);
  995. sigaction(SIGHUP, &act, nullptr);
  996. sigaction(SIGINT, &act, nullptr);
  997. sigaction(SIGTERM, &act, nullptr);
  998. sigaction(SIGCHLD, &act, nullptr);
  999. sigaction(SIGUSR2, &act, nullptr);
  1000. }
  1001. st.ss_sp = malloc(65536);
  1002. st.ss_flags = 0;
  1003. st.ss_size = 65536;
  1004. act.sa_sigaction = sigsegv;
  1005. act.sa_flags = SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
  1006. sigemptyset(&act.sa_mask);
  1007. sigaltstack(&st, NULL);
  1008. sigaction(SIGSEGV, &act, NULL);
  1009. sigaction(SIGBUS, &act, NULL);
  1010. sigaction(SIGABRT, &act, NULL);
  1011. bQuit = bMessagesWaiting = false;
  1012. if (parseBool(g_lpConfig->GetSetting("coredump_enabled")))
  1013. unix_coredump_enable();
  1014. // fork if needed and drop privileges as requested.
  1015. // this must be done before we do anything with pthreads
  1016. if (unix_runas(g_lpConfig)) {
  1017. g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): run-as failed");
  1018. goto exit;
  1019. }
  1020. if (daemonize && unix_daemonize(g_lpConfig)) {
  1021. g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): failed daemonizing");
  1022. goto exit;
  1023. }
  1024. if (!daemonize)
  1025. setsid();
  1026. if (bForked == false && unix_create_pidfile(argv[0], g_lpConfig, false) < 0) {
  1027. g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): Failed creating PID file");
  1028. goto exit;
  1029. }
  1030. g_lpLogger = StartLoggerProcess(g_lpConfig, g_lpLogger);
  1031. ec_log_set(g_lpLogger);
  1032. g_lpLogger->SetLogprefix(LP_PID);
  1033. hr = MAPIInitialize(NULL);
  1034. if (hr != hrSuccess) {
  1035. g_lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to initialize MAPI: %s (%x)",
  1036. GetMAPIErrorMessage(hr), hr);
  1037. goto exit;
  1038. }
  1039. sc = new StatsClient(g_lpLogger);
  1040. sc->startup(g_lpConfig->GetSetting("z_statsd_stats"));
  1041. if (bForked)
  1042. hr = ProcessMessageForked(strUsername.c_str(), szSMTP, ulPort, szPath, strMsgEntryId.length(), (LPENTRYID)strMsgEntryId.data(), bDoSentMail);
  1043. else
  1044. hr = running_server(szSMTP, ulPort, szPath);
  1045. delete sc;
  1046. if (!bForked)
  1047. g_lpLogger->Log(EC_LOGLEVEL_INFO, "Spooler shutdown complete");
  1048. MAPIUninitialize();
  1049. exit:
  1050. delete g_lpConfig;
  1051. DeleteLogger(g_lpLogger);
  1052. free(st.ss_sp);
  1053. switch(hr) {
  1054. case hrSuccess:
  1055. return EXIT_OK;
  1056. case MAPI_E_WAIT: // Timed message
  1057. case MAPI_W_NO_SERVICE: // SMTP server did not react in forked mode, mail should be retried later
  1058. return EXIT_WAIT;
  1059. }
  1060. // forked: failed sending message, but is already removed from the queue
  1061. return EXIT_FAILED;
  1062. }