12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- /*
- * This is the Kopano spooler.
- *
- *
- * The actual encoding is done by the inetmapi library.
- *
- * The spooler starts up, runs the queue once, and then
- * waits for changes in the outgoing queue. If any changes
- * occur, the whole queue is run again. This is done by having
- * an advise sink which is called when a table change is detected.
- * This advise sink unblocks the main (waiting) thread.
- */
- #include <kopano/platform.h>
- #include <chrono>
- #include <condition_variable>
- #include <mutex>
- #include <new>
- #include "mailer.h"
- #include <climits>
- #include <cstdio>
- #include <cstdlib>
- #include <iostream>
- #include <csignal>
- #include <time.h>
- #define USES_IID_IMAPIFolder
- #define USES_IID_IMessage
- #define USES_IID_IMsgStore
- #include <mapi.h>
- #include <mapix.h>
- #include <mapiutil.h>
- #include <mapidefs.h>
- #include <mapiguid.h>
- #include <kopano/IECUnknown.h>
- #include "IECSpooler.h"
- #include <kopano/IECServiceAdmin.h>
- #include <kopano/IECSecurity.h>
- #include <kopano/MAPIErrors.h>
- #include <kopano/ECGuid.h>
- #include <kopano/EMSAbTag.h>
- #include <kopano/ECTags.h>
- #include <kopano/ECABEntryID.h>
- #include <kopano/CommonUtil.h>
- #include <kopano/ECLogger.h>
- #include <kopano/ECConfig.h>
- #include <kopano/UnixUtil.h>
- #include <kopano/memory.hpp>
- #include <kopano/my_getopt.h>
- #include <kopano/ecversion.h>
- #include <kopano/Util.h>
- #include <kopano/stringutil.h>
- #include <kopano/mapiext.h>
- #include <edkmdb.h>
- #include <edkguid.h>
- #include <kopano/mapiguidext.h>
- #include "mapicontact.h"
- #include <kopano/charset/convert.h>
- #include <kopano/charset/convstring.h>
- #include <kopano/charset/utf8string.h>
- #include <kopano/ECGetText.h>
- #include "StatsClient.h"
- #include "TmpPath.h"
- #include <map>
- #include "spmain.h"
- using namespace std;
- using namespace KCHL;
- static StatsClient *sc = NULL;
- // spooler exit codes
- #define EXIT_OK 0
- #define EXIT_FAILED 1
- #define EXIT_WAIT 2
- #define EXIT_REMOVE 3
- static bool bQuit = false;
- static int nReload = 0;
- static int disconnects = 0;
- static const char *szCommand = NULL;
- static const char *szConfig = ECConfig::GetDefaultPath("spooler.cfg");
- ECConfig *g_lpConfig = NULL;
- ECLogger *g_lpLogger = NULL;
- // notification
- static bool bMessagesWaiting = false;
- static std::mutex hMutexMessagesWaiting;
- static std::condition_variable hCondMessagesWaiting;
- // messages being processed
- struct SendData {
- ULONG cbStoreEntryId;
- BYTE* lpStoreEntryId;
- ULONG cbMessageEntryId;
- BYTE* lpMessageEntryId;
- ULONG ulFlags;
- wstring strUsername;
- };
- static map<pid_t, SendData> mapSendData;
- static map<pid_t, int> mapFinished; // exit status of finished processes
- static std::mutex hMutexFinished; /* mutex for mapFinished */
- static HRESULT running_server(const char *szSMTP, int port, const char *szPath);
- /**
- * Print command line options, only for daemon version, not for mailer fork process
- *
- * @param[in] name name of the command
- */
- static void print_help(const char *name)
- {
- cout << "Usage:\n" << endl;
- cout << name << " [-F] [-h|--host <serverpath>] [-c|--config <configfile>] [smtp server]" << endl;
- cout << " -F\t\tDo not run in the background" << endl;
- cout << " -h path\tUse alternate connect path (e.g. file:///var/run/socket).\n\t\tDefault: file:///var/run/kopano/server.sock" << endl;
- cout << " -V Print version info." << endl;
- cout << " -c filename\tUse alternate config file (e.g. /etc/kopano-spooler.cfg)\n\t\tDefault: /etc/kopano/spooler.cfg" << endl;
- cout << " smtp server: The name or IP-address of the SMTP server, overriding the configuration" << endl;
- cout << endl;
- }
- /**
- * Encode a wide string in UTF-8 and output it in hexadecimal.
- * @param[in] lpszW The wide string to encode
- * @return The encoded string.
- */
- static string encodestring(const wchar_t *lpszW) {
- const utf8string u8 = convstring(lpszW);
- return bin2hex(u8.size(), (const unsigned char*)u8.c_str());
- }
- /**
- * Decode a string previously encoded with encodestring.
- * @param[in] lpszA The string containing the hexadecimal
- * representation of the UTF-8 encoded string.
- * @return The original wide string.
- */
- static wstring decodestring(const char *lpszA) {
- const utf8string u8 = utf8string::from_string(hex2bin(lpszA));
- return convert_to<wstring>(u8);
- }
- /**
- * Notification callback will be called about new messages in the
- * queue. Since this will happen from a different thread, we'll need
- * to use a mutex.
- *
- * @param[in] lpContext context of the callback (?)
- * @param[in] cNotif number of notifications in lpNotif
- * @param[in] lpNotif notification data
- */
- static LONG __stdcall AdviseCallback(void *lpContext, ULONG cNotif,
- LPNOTIFICATION lpNotif)
- {
- std::unique_lock<std::mutex> lk(hMutexMessagesWaiting);
- for (ULONG i = 0; i < cNotif; ++i) {
- if (lpNotif[i].info.tab.ulTableEvent == TABLE_RELOAD) {
- // Table needs a reload - trigger a reconnect with the server
- nReload = true;
- bMessagesWaiting = true;
- hCondMessagesWaiting.notify_one();
- }
- else if (lpNotif[i].info.tab.ulTableEvent != TABLE_ROW_DELETED) {
- bMessagesWaiting = true;
- hCondMessagesWaiting.notify_one();
- break;
- }
- }
- return 0;
- }
- /*
- * starting fork, passes:
- * -c config for all log settings and smtp server and such
- * --log-fd x execpt you should log here through a pipe
- * --entryids the data to send
- * --username the user to send the message as
- * if (szPath) kopano host
- * if (szSMTP) smtp host
- * if (szSMTPPport) smtp port
- */
- /**
- * Starts a forked process which sends the actual mail, and removes it
- * from the queue, in normal situations. On error, the
- * CleanFinishedMessages function will try to remove the failed
- * message if needed, else it will be tried again later (eg. timestamp
- * on sending, or SMTP not responding).
- *
- * @param[in] szUsername The username. This name is in unicode.
- * @param[in] szSMTP SMTP server to use
- * @param[in] ulPort SMTP port to use
- * @param[in] szPath URL to storage server
- * @param[in] cbStoreEntryId Length of lpStoreEntryId
- * @param[in] lpStoreEntryId Entry ID of store of user containing the message to be sent
- * @param[in] cbStoreEntryId Length of lpMsgEntryId
- * @param[in] lpMsgEntryId Entry ID of message to be sent
- * @param[in] ulFlags PR_EC_OUTGOING_FLAGS of message (EC_SUBMIT_DOSENTMAIL flag)
- * @return HRESULT
- */
- static HRESULT StartSpoolerFork(const wchar_t *szUsername, const char *szSMTP,
- int ulSMTPPort, const char *szPath, ULONG cbStoreEntryId,
- BYTE *lpStoreEntryId, ULONG cbMsgEntryId, BYTE *lpMsgEntryId,
- ULONG ulFlags)
- {
- SendData sSendData;
- pid_t pid;
- bool bDoSentMail = ulFlags & EC_SUBMIT_DOSENTMAIL;
- std::string strPort = stringify(ulSMTPPort);
- // place pid with entryid copy in map
- sSendData.cbStoreEntryId = cbStoreEntryId;
- HRESULT hr = MAPIAllocateBuffer(cbStoreEntryId,
- reinterpret_cast<void **>(&sSendData.lpStoreEntryId));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "StartSpoolerFork(): MAPIAllocateBuffer failed(1) %x", hr);
- return hr;
- }
- memcpy(sSendData.lpStoreEntryId, lpStoreEntryId, cbStoreEntryId);
- sSendData.cbMessageEntryId = cbMsgEntryId;
- hr = MAPIAllocateBuffer(cbMsgEntryId, (void**)&sSendData.lpMessageEntryId);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "StartSpoolerFork(): MAPIAllocateBuffer failed(2) %x", hr);
- return hr;
- }
- memcpy(sSendData.lpMessageEntryId, lpMsgEntryId, cbMsgEntryId);
- sSendData.ulFlags = ulFlags;
- sSendData.strUsername = szUsername;
- // execute the new spooler process to send the email
- pid = vfork();
- if (pid < 0) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, string("Unable to start new spooler process: ") + strerror(errno));
- return MAPI_E_CALL_FAILED;
- }
- if (pid == 0) {
- char *bname = strdup(szCommand);
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s NULL",
- szCommand, basename(bname) /* argv[0] */,
- "--send-message-entryid", bin2hex(cbMsgEntryId, lpMsgEntryId).c_str(),
- "--send-username-enc", encodestring(szUsername).c_str(),
- "--log-fd", stringify(g_lpLogger->GetFileDescriptor()).c_str(),
- "--config", szConfig,
- "--host", szPath,
- "--foreground", szSMTP,
- "--port", strPort.c_str(),
- bDoSentMail ? "--do-sentmail" : "");
- #ifdef SPOOLER_FORK_DEBUG
- _exit(EXIT_WAIT);
- #else
- // we execute because of all the mapi memory in use would be duplicated in the child,
- // and there won't be a nice way to clean it all up.
- execlp(szCommand, basename(bname) /* argv[0] */,
- "--send-message-entryid", bin2hex(cbMsgEntryId, lpMsgEntryId).c_str(),
- "--send-username-enc", encodestring(szUsername).c_str(),
- "--log-fd", stringify(g_lpLogger->GetFileDescriptor()).c_str(),
- "--config", szConfig,
- "--host", szPath,
- "--foreground", szSMTP,
- "--port", strPort.c_str(),
- bDoSentMail ? "--do-sentmail" : NULL, NULL);
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, string("Cannot start spooler process `") + szCommand + "`: " + strerror(errno));
- _exit(EXIT_REMOVE);
- #endif
- }
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Spooler process started on pid %d", pid);
- // process is started, place in map
- mapSendData[pid] = sSendData;
- return hrSuccess;
- }
- /**
- * Opens all required objects of the administrator to move an error
- * mail out of the queue.
- *
- * @param[in] sSendData Struct with information about the mail in the queue which caused a fatal error
- * @param[in] lpAdminSession MAPI session of Kopano SYSTEM user
- * @param[out] lppAddrBook MAPI Addressbook object
- * @param[out] lppMailer inetmapi ECSender object, which can generate an error text for the body for the mail
- * @param[out] lppUserStore The store of the user with the error mail, open with admin rights
- * @param[out] lppMessage The message of the user which caused the error, open with admin rights
- * @return HRESULT
- */
- static HRESULT GetErrorObjects(const SendData &sSendData,
- IMAPISession *lpAdminSession, IAddrBook **lppAddrBook,
- ECSender **lppMailer, IMsgStore **lppUserStore, IMessage **lppMessage)
- {
- HRESULT hr = hrSuccess;
- ULONG ulObjType = 0;
- if (*lppAddrBook == NULL) {
- hr = lpAdminSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, lppAddrBook);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open addressbook for error mail, skipping. Error 0x%08X", hr);
- return hr;
- }
- }
- if (*lppMailer == NULL) {
- /*
- * SMTP server does not matter here, we just use the
- * object for the error body.
- */
- *lppMailer = CreateSender("localhost", 25);
- if (! (*lppMailer)) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to create error object for error mail, skipping.");
- return hr;
- }
- }
- if (*lppUserStore == NULL) {
- hr = lpAdminSession->OpenMsgStore(0, sSendData.cbStoreEntryId, (LPENTRYID)sSendData.lpStoreEntryId, NULL, MDB_WRITE | MDB_NO_DIALOG | MDB_NO_MAIL | MDB_TEMPORARY, lppUserStore);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open store of user for error mail, skipping. Error 0x%08X", hr);
- return hr;
- }
- }
- if (*lppMessage == NULL) {
- hr = (*lppUserStore)->OpenEntry(sSendData.cbMessageEntryId, (LPENTRYID)sSendData.lpMessageEntryId, &IID_IMessage, MAPI_BEST_ACCESS, &ulObjType, (IUnknown**)lppMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open message of user for error mail, skipping. Error 0x%08X", hr);
- return hr;
- }
- }
- return hr;
- }
- /**
- * Cleans finished messages. Normally only prints a logmessage. If the
- * mailer completely failed (eg. segfault), this function will try to
- * remove the faulty mail from the queue.
- *
- * @param[in] lpAdminSession MAPI session of the Kopano SYSTEM user
- * @param[in] lpSpooler IECSpooler object
- * @return HRESULT
- */
- static HRESULT CleanFinishedMessages(IMAPISession *lpAdminSession,
- IECSpooler *lpSpooler)
- {
- HRESULT hr = hrSuccess;
- SendData sSendData;
- bool bErrorMail;
- map<pid_t, int> finished; // exit status of finished processes
- int status;
- // error message creation
- object_ptr<IAddrBook> lpAddrBook;
- ECSender *lpMailer = NULL;
- std::unique_lock<std::mutex> lock(hMutexFinished);
- if (mapFinished.empty())
- return hr;
- // copy map contents and clear it, so hMutexFinished can be unlocked again asap
- finished = mapFinished;
- mapFinished.clear();
- lock.unlock();
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Cleaning %d messages from queue", (int)finished.size());
- // process finished entries
- for (const auto &i : finished) {
- sSendData = mapSendData[i.first];
- /* Find exit status, and decide to remove mail from queue or not */
- status = i.second;
- bErrorMail = false;
- bool wasSent = false;
- #ifdef WEXITSTATUS
- if(WIFEXITED(status)) { /* Child exited by itself */
- if (WEXITSTATUS(status) == EXIT_WAIT) {
- // timed message, try again later
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Message for user %ls will be tried again later", sSendData.strUsername.c_str());
- sc -> countInc("Spooler", "exit_wait");
- }
- else if (WEXITSTATUS(status) == EXIT_OK || WEXITSTATUS(status) == EXIT_FAILED) {
- // message was sent, or the user already received an error mail.
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Processed message for user %ls", sSendData.strUsername.c_str());
- wasSent = true;
- }
- else {
- // message was not sent, and could not be removed from queue. Notify user also.
- bErrorMail = true;
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed message for user %ls will be removed from queue, error 0x%x", sSendData.strUsername.c_str(), status);
- }
- }
- else if(WIFSIGNALED(status)) { /* Child was killed by a signal */
- bErrorMail = true;
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Spooler process %d was killed by signal %d", i.first, WTERMSIG(status));
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message for user %ls will be removed from queue", sSendData.strUsername.c_str());
- sc -> countInc("Spooler", "sig_killed");
- }
- else { /* Something strange happened */
- bErrorMail = true;
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Spooler process %d terminated abnormally", i.first);
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message for user %ls will be removed from queue", sSendData.strUsername.c_str());
- sc -> countInc("Spooler", "abnormal_terminate");
- }
- #else
- if (status) {
- bErrorMail = true;
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Spooler process %d exited with status %d", i.first, status);
- }
- #endif
- if (wasSent)
- sc -> countInc("Spooler", "sent");
- else if (bErrorMail)
- sc -> countInc("Spooler", "send_failed");
- if (bErrorMail) {
- object_ptr<IMsgStore> lpUserStore;
- object_ptr<IMessage> lpMessage;
- hr = GetErrorObjects(sSendData, lpAdminSession, &~lpAddrBook, &lpMailer, &~lpUserStore, &~lpMessage);
- if (hr == hrSuccess) {
- lpMailer->setError(_("A fatal error occurred while processing your message, and Kopano is unable to send your email."));
- hr = SendUndeliverable(lpMailer, lpUserStore, lpMessage);
- // TODO: if failed, and we have the lpUserStore, create message?
- }
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to create error message for user %ls: %s (%x)",
- sSendData.strUsername.c_str(), GetMAPIErrorMessage(hr), hr);
- // remove mail from queue
- hr = lpSpooler->DeleteFromMasterOutgoingTable(sSendData.cbMessageEntryId, (LPENTRYID)sSendData.lpMessageEntryId, sSendData.ulFlags);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Could not remove invalid message from queue, error code: 0x%08X", hr);
- // move mail to sent items folder
- if (sSendData.ulFlags & EC_SUBMIT_DOSENTMAIL && lpMessage) {
- hr = DoSentMail(lpAdminSession, lpUserStore, 0, lpMessage);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to move sent mail to sent-items folder: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- }
- }
- MAPIFreeBuffer(sSendData.lpStoreEntryId);
- MAPIFreeBuffer(sSendData.lpMessageEntryId);
- mapSendData.erase(i.first);
- }
- delete lpMailer;
- return hr;
- }
- /**
- * Sends all messages found in lpTable (outgoing queue).
- *
- * Starts forks or threads until maximum number of simultatious
- * messages is reached. Loops until a slot comes free to start a new
- * fork/thread. When a fatal MAPI error has occurred, or the table is
- * empty, this function will return.
- *
- * @param[in] lpAdminSession MAPI Session of Kopano SYSTEM user
- * @param[in] lpSpooler IECSpooler object
- * @param[in] lpTable Outgoing queue table view
- * @param[in] szSMTP SMTP server to use
- * @param[in] ulPort SMTP port
- * @param[in] szPath URI to storage server
- * @return HRESULT
- */
- static HRESULT ProcessAllEntries(IMAPISession *lpAdminSession,
- IECSpooler *lpSpooler, IMAPITable *lpTable, const char *szSMTP, int ulPort,
- const char *szPath)
- {
- HRESULT hr = hrSuccess;
- unsigned int ulMaxThreads = 0;
- unsigned int ulFreeThreads = 0;
- ULONG ulRowCount = 0;
- std::wstring strUsername;
- bool bForceReconnect = false;
- hr = lpTable->GetRowCount(0, &ulRowCount);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get outgoing queue count: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- if (ulRowCount) {
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Number of messages in the queue: %d", ulRowCount);
- sc -> countInc("Spooler", "batch_invokes");
- sc -> countAdd("Spooler", "batch_count", int64_t(ulRowCount));
- }
- ulMaxThreads = atoi(g_lpConfig->GetSetting("max_threads"));
- if (ulMaxThreads == 0)
- ulMaxThreads = 1;
- while(!bQuit) {
- ulFreeThreads = ulMaxThreads - mapSendData.size();
- if (ulFreeThreads == 0) {
- Sleep(100);
- // remove enties from mapSendData which are finished
- CleanFinishedMessages(lpAdminSession, lpSpooler);
- continue; /* Continue looping until threads become available */
- }
- rowset_ptr lpsRowSet;
- hr = lpTable->QueryRows(1, 0, &~lpsRowSet);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to fetch data from table, error code: 0x%08X", hr);
- goto exit;
- }
- if (lpsRowSet->cRows == 0) // All rows done
- goto exit;
- if (lpsRowSet->aRow[0].lpProps[4].ulPropTag == PR_DEFERRED_SEND_TIME) {
- // check time
- time_t now = time(NULL);
- time_t sendat;
-
- FileTimeToUnixTime(lpsRowSet->aRow[0].lpProps[4].Value.ft, &sendat);
- if (now < sendat)
- // if we ever add logging here, it should trigger just once for this mail
- continue;
- }
- // Check whether the row contains the entryid and store id
- if (lpsRowSet->aRow[0].lpProps[0].ulPropTag != PR_EC_MAILBOX_OWNER_ACCOUNT_W ||
- lpsRowSet->aRow[0].lpProps[1].ulPropTag != PR_STORE_ENTRYID ||
- lpsRowSet->aRow[0].lpProps[2].ulPropTag != PR_ENTRYID ||
- lpsRowSet->aRow[0].lpProps[3].ulPropTag != PR_EC_OUTGOING_FLAGS)
- {
- // Client was quick enough to remove message from queue before we could read it
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Empty row in OutgoingQueue");
- if (lpsRowSet->aRow[0].lpProps[2].ulPropTag == PR_ENTRYID && lpsRowSet->aRow[0].lpProps[3].ulPropTag == PR_EC_OUTGOING_FLAGS) {
- // we can remove this message
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Removing invalid entry from OutgoingQueue");
- 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);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Could not remove invalid message from queue, error code: 0x%08X", hr);
- // since we have an error, we will reconnect to the server to fully reload the table
- goto exit;
- }
- }
- else {
- // this error makes the spooler disconnect from the server, and reconnect again (bQuit still false)
- bForceReconnect = true;
- }
- continue;
- }
- strUsername = lpsRowSet->aRow[0].lpProps[0].Value.lpszW;
- // Check if there is already an active process for this message
- bool bMatch = false;
- for (const auto &i : mapSendData)
- if (i.second.cbMessageEntryId == lpsRowSet->aRow[0].lpProps[2].Value.bin.cb &&
- memcmp(i.second.lpMessageEntryId, lpsRowSet->aRow[0].lpProps[2].Value.bin.lpb, i.second.cbMessageEntryId) == 0) {
- bMatch = true;
- break;
- }
- if (bMatch)
- continue;
- // Start new process to send the mail
- 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);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "ProcessAllEntries(): Failed starting spooler: %x", hr);
- goto exit;
- }
- }
- exit:
- return bForceReconnect ? MAPI_E_NETWORK_ERROR : hr;
- }
- /**
- * Opens the IECSpooler object on an admin session.
- *
- * @param[in] lpAdminSession MAPI Session of the Kopano SYSTEM user
- * @param[out] lppSpooler IECSpooler is a Kopano interface to the outgoing queue functions.
- * @return HRESULT
- */
- static HRESULT GetAdminSpooler(IMAPISession *lpAdminSession,
- IECSpooler **lppSpooler)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IECSpooler> lpSpooler;
- object_ptr<IMsgStore> lpMDB;
- memory_ptr<SPropValue> lpsProp;
- hr = HrOpenDefaultStore(lpAdminSession, &~lpMDB);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open default store for system account. Error 0x%08X", hr);
- return hr;
- }
- hr = HrGetOneProp(lpMDB, PR_EC_OBJECT, &~lpsProp);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get Kopano internal object: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- hr = ((IECUnknown *)lpsProp->Value.lpszA)->QueryInterface(IID_IECSpooler, &~lpSpooler);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Spooler interface not supported: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- *lppSpooler = lpSpooler.release();
- return hr;
- }
- /**
- * Opens an admin session and the outgoing queue. If either one
- * produces an error this function will return. If the queue is empty,
- * it will wait for a notification when new data is present in the
- * outgoing queue table.
- *
- * @param[in] szSMTP The SMTP server to send to.
- * @param[in] ulPort The SMTP port to sent to.
- * @param[in] szPath URI of storage server to connect to, must be file:// or https:// with valid SSL certificates.
- * @return HRESULT
- */
- static HRESULT ProcessQueue(const char *szSMTP, int ulPort, const char *szPath)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPISession> lpAdminSession;
- object_ptr<IECSpooler> lpSpooler;
- object_ptr<IMAPITable> lpTable;
- object_ptr<IMAPIAdviseSink> lpAdviseSink;
- ULONG ulConnection = 0;
- static constexpr const SizedSPropTagArray(5, sOutgoingCols) =
- {5, {PR_EC_MAILBOX_OWNER_ACCOUNT_W, PR_STORE_ENTRYID,
- PR_ENTRYID, PR_EC_OUTGOING_FLAGS, PR_DEFERRED_SEND_TIME}};
- static constexpr const SizedSSortOrderSet(1, sSort) =
- {1, 0, 0, {{PR_EC_HIERARCHYID, TABLE_SORT_ASCEND}}};
- hr = HrOpenECAdminSession(&~lpAdminSession, "kopano-spooler:system",
- PROJECT_SVN_REV_STR, szPath, EC_PROFILE_FLAGS_NO_PUBLIC_STORE,
- g_lpConfig->GetSetting("sslkey_file", "", NULL),
- g_lpConfig->GetSetting("sslkey_pass", "", NULL));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open admin session. Error 0x%08X", hr);
- goto exit;
- }
- if (disconnects == 0)
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Connection to storage server succeeded");
- else
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Connection to storage server succeeded after %d retries", disconnects);
- disconnects = 0; // first call succeeded, assume all is well.
- hr = GetAdminSpooler(lpAdminSession, &~lpSpooler);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessQueue: GetAdminSpooler failed %x", hr);
- goto exit;
- }
- // Mark reload as done since we reloaded the outgoing table
- nReload = false;
-
- // Request the master outgoing table
- hr = lpSpooler->GetMasterOutgoingTable(0, &~lpTable);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Master outgoing queue not available: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- hr = lpTable->SetColumns(sOutgoingCols, 0);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to setColumns() on OutgoingQueue: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
-
- // Sort by ascending hierarchyid: first in, first out queue
- hr = lpTable->SortTable(sSort, 0);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to SortTable() on OutgoingQueue: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- hr = HrAllocAdviseSink(AdviseCallback, nullptr, &~lpAdviseSink);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to allocate memory for advise sink: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- // notify on new mail in the outgoing table
- hr = lpTable->Advise(fnevTableModified, lpAdviseSink, &ulConnection);
- while(!bQuit && !nReload) {
- bMessagesWaiting = false;
- lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
- // also checks not to send a message again which is already sending
- hr = ProcessAllEntries(lpAdminSession, lpSpooler, lpTable, szSMTP, ulPort, szPath);
- if(hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "ProcessQueue: ProcessAllEntries failed %x", hr);
- goto exit;
- }
- // Exit signal, break the operation
- if(bQuit)
- break;
-
- if(nReload)
- break;
- std::unique_lock<std::mutex> lk(hMutexMessagesWaiting);
- if(!bMessagesWaiting) {
- while (!bMessagesWaiting) {
- auto s = hCondMessagesWaiting.wait_for(lk, std::chrono::seconds(60));
- if (s == std::cv_status::timeout || bMessagesWaiting || bQuit || nReload)
- break;
- // not timed out, no messages waiting, not quit requested, no table reload required:
- // we were triggered for a cleanup call.
- CleanFinishedMessages(lpAdminSession, lpSpooler);
- }
- }
- lk.unlock();
- // remove any entries that were done during the wait
- CleanFinishedMessages(lpAdminSession, lpSpooler);
- }
- exit:
- // when we exit, we must make sure all forks started are cleaned
- if (bQuit) {
- ULONG ulCount = 0;
- ULONG ulThreads = 0;
- while (ulCount < 60) {
- if ((ulCount % 5) == 0) {
- ulThreads = mapSendData.size();
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Still waiting for %d thread%c to exit.", ulThreads, ulThreads!=1?'s':' ');
- }
- if (lpSpooler != nullptr)
- CleanFinishedMessages(lpAdminSession, lpSpooler);
- if (mapSendData.size() == 0)
- break;
- Sleep(1000);
- ++ulCount;
- }
- if (ulCount == 60)
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "%d threads did not yet exit, closing anyway.", (int)mapSendData.size());
- }
- else if (nReload) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Table reload requested, breaking server connection");
- }
- if (lpTable && ulConnection)
- lpTable->Unadvise(ulConnection);
- return hr;
- }
- /**
- * Segfault signal handler. Prints the backtrace of the crash in the log.
- *
- * @param[in] signr Any signal that can dump core. Mostly SIGSEGV.
- */
- static void sigsegv(int signr, siginfo_t *si, void *uc)
- {
- generic_sigsegv_handler(g_lpLogger, "Spooler",
- PROJECT_VERSION_SPOOLER_STR, signr, si, uc);
- }
- /**
- * actual signal handler, direct entry point if only linuxthreads is available.
- *
- * @param[in] sig signal received
- */
- static void process_signal(int sig)
- {
- ec_log_debug("Received signal %d", sig);
- int stat;
- pid_t pid;
- switch (sig) {
- case SIGTERM:
- case SIGINT: {
- bQuit = true;
- // Trigger condition so we force wakeup the queue thread
- std::lock_guard<std::mutex> lk(hMutexMessagesWaiting);
- hCondMessagesWaiting.notify_one();
- break;
- }
- case SIGCHLD: {
- std::unique_lock<std::mutex> finlock(hMutexFinished);
- while ((pid = waitpid (-1, &stat, WNOHANG)) > 0)
- mapFinished[pid] = stat;
- finlock.unlock();
- // Trigger condition so the messages get cleaned from the queue
- std::lock_guard<std::mutex> mwlock(hMutexMessagesWaiting);
- hCondMessagesWaiting.notify_one();
- break;
- }
- case SIGHUP:
- if (g_lpConfig != nullptr && !g_lpConfig->ReloadSettings() &&
- g_lpLogger != nullptr)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to reload configuration file, continuing with current settings.");
- if (g_lpLogger) {
- if (g_lpConfig) {
- const char *ll = g_lpConfig->GetSetting("log_level");
- int new_ll = ll ? atoi(ll) : EC_LOGLEVEL_WARNING;
- g_lpLogger->SetLoglevel(new_ll);
- }
- g_lpLogger->Reset();
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Log connection was reset");
- }
- break;
- case SIGUSR2: {
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Spooler stats:");
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Running threads: %zu", mapSendData.size());
- std::lock_guard<std::mutex> l(hMutexFinished);
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Finished threads: %zu", mapFinished.size());
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Disconnects: %d", disconnects);
- break;
- }
- default:
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Unknown signal %d received", sig);
- break;
- }
- }
- /**
- * Main program loop. Calls ProcessQueue, which logs in to MAPI. This
- * way, disconnects are solved. After a disconnect from the server,
- * the loop will try again after 3 seconds.
- *
- * @param[in] szSMTP The SMTP server to send to.
- * @param[in] ulPort The SMTP port to send to.
- * @param[in] szPath URI of storage server to connect to, must be file:// or https:// with valid SSL certificates.
- * @return HRESULT
- */
- static HRESULT running_server(const char *szSMTP, int ulPort,
- const char *szPath)
- {
- HRESULT hr = hrSuccess;
- g_lpLogger->Log(EC_LOGLEVEL_ALWAYS, "Starting kopano-spooler version " PROJECT_VERSION_SPOOLER_STR " (" PROJECT_SVN_REV_STR "), pid %d", getpid());
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Using SMTP server: %s, port %d", szSMTP, ulPort);
- disconnects = 0;
- while (1) {
- hr = ProcessQueue(szSMTP, ulPort, szPath);
- if (bQuit)
- break;
- if (disconnects == 0)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Server connection lost. Reconnecting in 3 seconds...");
- ++disconnects;
- Sleep(3000); // wait 3s until retry to connect
- }
- bQuit = true; // make sure the sigchld does not use the lock anymore
- return hr;
- }
- int main(int argc, char *argv[]) {
- HRESULT hr = hrSuccess;
- const char *szPath = NULL;
- const char *szSMTP = NULL;
- int ulPort = 0;
- int c;
- int daemonize = 1;
- int logfd = -1;
- bool bForked = false;
- std::string strMsgEntryId;
- std::wstring strUsername;
- bool bDoSentMail = false;
- bool bIgnoreUnknownConfigOptions = false;
- // options
- enum {
- OPT_HELP = UCHAR_MAX + 1,
- OPT_CONFIG,
- OPT_HOST,
- OPT_FOREGROUND,
- OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS,
- // only called by spooler itself
- OPT_SEND_MESSAGE_ENTRYID,
- OPT_SEND_USERNAME,
- OPT_LOGFD,
- OPT_DO_SENTMAIL,
- OPT_PORT
- };
- static const struct option long_options[] = {
- { "help", 0, NULL, OPT_HELP }, // help text
- { "config", 1, NULL, OPT_CONFIG }, // config file
- { "host", 1, NULL, OPT_HOST }, // kopano host location
- { "foreground", 0, NULL, OPT_FOREGROUND }, // do not daemonize
- // only called by spooler itself
- { "send-message-entryid", 1, NULL, OPT_SEND_MESSAGE_ENTRYID }, // entryid of message to send
- { "send-username-enc", 1, NULL, OPT_SEND_USERNAME }, // owner's username of message to send in hex-utf8
- { "log-fd", 1, NULL, OPT_LOGFD }, // fd where to send log messages to
- { "do-sentmail", 0, NULL, OPT_DO_SENTMAIL },
- { "port", 1, NULL, OPT_PORT },
- { "ignore-unknown-config-options", 0, NULL, OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS },
- { NULL, 0, NULL, 0 }
- };
- // Default settings
- static const configsetting_t lpDefaults[] = {
- { "smtp_server","localhost", CONFIGSETTING_RELOADABLE },
- { "smtp_port","25", CONFIGSETTING_RELOADABLE },
- { "server_socket", "default:" },
- { "run_as_user", "kopano" },
- { "run_as_group", "kopano" },
- { "pid_file", "/var/run/kopano/spooler.pid" },
- { "running_path", "/var/lib/kopano" },
- { "coredump_enabled", "no" },
- { "log_method","file" },
- { "log_file","-" },
- { "log_level", "3", CONFIGSETTING_RELOADABLE },
- { "log_timestamp","1" },
- { "log_buffer_size", "0" },
- { "sslkey_file", "" },
- { "sslkey_pass", "", CONFIGSETTING_EXACT },
- { "max_threads", "5", CONFIGSETTING_RELOADABLE },
- { "fax_domain", "", CONFIGSETTING_RELOADABLE },
- { "fax_international", "+", CONFIGSETTING_RELOADABLE },
- { "always_send_delegates", "no", CONFIGSETTING_RELOADABLE },
- { "always_send_tnef", "no", CONFIGSETTING_RELOADABLE },
- { "always_send_utf8", "no", CONFIGSETTING_RELOADABLE },
- { "charset_upgrade", "windows-1252", CONFIGSETTING_RELOADABLE },
- { "allow_redirect_spoofing", "yes", CONFIGSETTING_RELOADABLE },
- { "allow_delegate_meeting_request", "yes", CONFIGSETTING_RELOADABLE },
- { "allow_send_to_everyone", "yes", CONFIGSETTING_RELOADABLE },
- { "copy_delegate_mails", "yes", CONFIGSETTING_RELOADABLE },
- { "expand_groups", "no", CONFIGSETTING_RELOADABLE },
- { "archive_on_send", "no", CONFIGSETTING_RELOADABLE },
- { "enable_dsn", "yes", CONFIGSETTING_RELOADABLE },
- { "plugin_enabled", "yes" },
- { "plugin_path", "/var/lib/kopano/spooler/plugins" },
- { "plugin_manager_path", "/usr/share/kopano-spooler/python" },
- { "z_statsd_stats", "/var/run/kopano/statsd.sock" },
- { "tmp_path", "/tmp" },
- { "tmp_path", "/tmp" },
- { "tmp_path", "/tmp" },
- { NULL, NULL },
- };
- // SIGSEGV backtrace support
- stack_t st;
- struct sigaction act;
- memset(&st, 0, sizeof(st));
- memset(&act, 0, sizeof(act));
- setlocale(LC_CTYPE, "");
- setlocale(LC_MESSAGES, "");
- while(1) {
- c = my_getopt_long_permissive(argc, argv, "c:h:iuVF", long_options, NULL);
- if(c == -1)
- break;
- switch(c) {
- case OPT_CONFIG:
- case 'c':
- szConfig = optarg;
- break;
- case OPT_HOST:
- case 'h':
- szPath = optarg;
- break;
- case 'i': // Install service
- case 'u': // Uninstall service
- break;
- case 'F':
- case OPT_FOREGROUND:
- daemonize = 0;
- break;
- case OPT_SEND_MESSAGE_ENTRYID:
- bForked = true;
- strMsgEntryId = hex2bin(optarg);
- break;
- case OPT_SEND_USERNAME:
- bForked = true;
- strUsername = decodestring(optarg);
- break;
- case OPT_LOGFD:
- logfd = atoi(optarg);
- break;
- case OPT_DO_SENTMAIL:
- bDoSentMail = true;
- break;
- case OPT_PORT:
- ulPort = atoi(optarg);
- break;
- case OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS:
- bIgnoreUnknownConfigOptions = true;
- break;
- case 'V':
- cout << "Product version:\t" << PROJECT_VERSION_SPOOLER_STR << endl
- << "File version:\t\t" << PROJECT_SVN_REV_STR << endl;
- return 1;
- case OPT_HELP:
- default:
- cout << "Unknown option: " << c << endl;
- print_help(argv[0]);
- return 1;
- }
- }
- if (bForked)
- bIgnoreUnknownConfigOptions = true;
- g_lpConfig = ECConfig::Create(lpDefaults);
- if (szConfig) {
- int argidx = 0;
- if (!g_lpConfig->LoadSettings(szConfig) ||
- (argidx = g_lpConfig->ParseParams(argc - optind, &argv[optind])) < 0 ||
- (!bIgnoreUnknownConfigOptions && g_lpConfig->HasErrors())) {
- /* Create info logger without a timestamp to stderr. */
- g_lpLogger = new(std::nothrow) ECLogger_File(EC_LOGLEVEL_INFO, 0, "-", false);
- if (g_lpLogger == nullptr) {
- hr = MAPI_E_NOT_ENOUGH_MEMORY;
- goto exit;
- }
- ec_log_set(g_lpLogger);
- LogConfigErrors(g_lpConfig);
- hr = E_FAIL;
- goto exit;
- }
-
- // ECConfig::ParseParams returns the index in the passed array,
- // after some shuffling, where it stopped parsing. optind is
- // the index where my_getopt_long_permissive stopped parsing. So
- // adding argidx to optind will result in the index after all
- // options are parsed.
- optind += argidx;
- }
- // commandline overwrites spooler.cfg
- if (optind < argc)
- szSMTP = argv[optind];
- else
- szSMTP = g_lpConfig->GetSetting("smtp_server");
-
- if (!ulPort)
- ulPort = atoui(g_lpConfig->GetSetting("smtp_port"));
- szCommand = argv[0];
- // setup logging, use pipe to log if started in forked mode and using pipe (file) logger, create normal logger for syslog
- if (bForked && logfd != -1)
- g_lpLogger = new ECLogger_Pipe(logfd, 0, atoi(g_lpConfig->GetSetting("log_level")));
- else
- g_lpLogger = CreateLogger(g_lpConfig, argv[0], "KopanoSpooler");
- ec_log_set(g_lpLogger);
- if ((bIgnoreUnknownConfigOptions && g_lpConfig->HasErrors()) || g_lpConfig->HasWarnings())
- LogConfigErrors(g_lpConfig);
- if (!TmpPath::getInstance() -> OverridePath(g_lpConfig))
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Ignoring invalid path-setting!");
- // set socket filename
- if (!szPath)
- szPath = g_lpConfig->GetSetting("server_socket");
- if (bForked) {
- // keep sending mail when we're killed in forked mode
- signal(SIGTERM, SIG_IGN);
- signal(SIGINT, SIG_IGN);
- signal(SIGHUP, SIG_IGN);
- signal(SIGUSR1, SIG_IGN);
- signal(SIGUSR2, SIG_IGN);
- }
- else {
- // notification condition
- act.sa_handler = process_signal;
- act.sa_flags = SA_ONSTACK | SA_RESTART;
- sigemptyset(&act.sa_mask);
- sigaction(SIGHUP, &act, nullptr);
- sigaction(SIGINT, &act, nullptr);
- sigaction(SIGTERM, &act, nullptr);
- sigaction(SIGCHLD, &act, nullptr);
- sigaction(SIGUSR2, &act, nullptr);
- }
- st.ss_sp = malloc(65536);
- st.ss_flags = 0;
- st.ss_size = 65536;
- act.sa_sigaction = sigsegv;
- act.sa_flags = SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
- sigemptyset(&act.sa_mask);
- sigaltstack(&st, NULL);
- sigaction(SIGSEGV, &act, NULL);
- sigaction(SIGBUS, &act, NULL);
- sigaction(SIGABRT, &act, NULL);
- bQuit = bMessagesWaiting = false;
- if (parseBool(g_lpConfig->GetSetting("coredump_enabled")))
- unix_coredump_enable();
- // fork if needed and drop privileges as requested.
- // this must be done before we do anything with pthreads
- if (unix_runas(g_lpConfig)) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): run-as failed");
- goto exit;
- }
- if (daemonize && unix_daemonize(g_lpConfig)) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): failed daemonizing");
- goto exit;
- }
- if (!daemonize)
- setsid();
- if (bForked == false && unix_create_pidfile(argv[0], g_lpConfig, false) < 0) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, "main(): Failed creating PID file");
- goto exit;
- }
- g_lpLogger = StartLoggerProcess(g_lpConfig, g_lpLogger);
- ec_log_set(g_lpLogger);
- g_lpLogger->SetLogprefix(LP_PID);
- hr = MAPIInitialize(NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to initialize MAPI: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- goto exit;
- }
- sc = new StatsClient(g_lpLogger);
- sc->startup(g_lpConfig->GetSetting("z_statsd_stats"));
- if (bForked)
- hr = ProcessMessageForked(strUsername.c_str(), szSMTP, ulPort, szPath, strMsgEntryId.length(), (LPENTRYID)strMsgEntryId.data(), bDoSentMail);
- else
- hr = running_server(szSMTP, ulPort, szPath);
- delete sc;
- if (!bForked)
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Spooler shutdown complete");
- MAPIUninitialize();
- exit:
- delete g_lpConfig;
- DeleteLogger(g_lpLogger);
- free(st.ss_sp);
- switch(hr) {
- case hrSuccess:
- return EXIT_OK;
- case MAPI_E_WAIT: // Timed message
- case MAPI_W_NO_SERVICE: // SMTP server did not react in forked mode, mail should be retried later
- return EXIT_WAIT;
- }
- // forked: failed sending message, but is already removed from the queue
- return EXIT_FAILED;
- }
|