12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899 |
- /*
- * 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/>.
- *
- */
- /*
- * E-mail is delivered to the client through this program; it is invoked
- * by the MTA with the username and rfc822 e-mail message, and the delivery
- * agent parses the rfc822 message, setting properties on the MAPI object
- * as it goes along.
- *
- * The delivery agent should be called with sufficient privileges to be
- * able to open other users' inboxes.
- *
- * The actual decoding is done by the inetmapi library.
- */
- /*
- * An LMTP reply contains:
- * <SMTP code> <ESMTP code> <message>
- * See RFC 1123 and 2821 for normal codes, 1893 and 2034 for enhanced codes.
- *
- * Enhanced Delivery status codes: Class.Subject.Detail
- *
- * Classes:
- * 2 = Success, 4 = Persistent Transient Failure, 5 = Permanent Failure
- *
- * Subjects:
- * 0 = Other/Unknown, 1 = Addressing, 2 = Mailbox, 3 = Mail system
- * 4 = Network/Routing, 5 = Mail delivery, 6 = Message content, 7 = Security
- *
- * Detail:
- * see rfc.
- */
- #include <kopano/platform.h>
- #include <memory>
- #include <unordered_set>
- #include <climits>
- #include <cstdio>
- #include <cstdlib>
- #include <iostream>
- #include <algorithm>
- #include <map>
- #include <poll.h>
- #include <kopano/ECRestriction.h>
- #include <kopano/MAPIErrors.h>
- #include <kopano/mapi_ptr.h>
- #include <kopano/memory.hpp>
- #include <kopano/tie.hpp>
- #include "fileutil.h"
- #include "PyMapiPlugin.h"
- #include <cerrno>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <unistd.h>
- #include <sys/mman.h>
- #include <pwd.h>
- #include "spmain.h"
- #include "TmpPath.h"
- /*
- This is actually from sysexits.h
- but since those windows lamers don't have it ..
- let's copy some defines here..
- */
- #define EX_OK 0 /* successful termination */
- #define EX__BASE 64 /* base value for error messages */
- #define EX_USAGE 64 /* command line usage error */
- #define EX_SOFTWARE 70 /* internal software error */
- #define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */
- #define USES_IID_IMAPIFolder
- #define USES_IID_IExchangeManageStore
- #define USES_IID_IMsgStore
- #include <kopano/ECGuid.h>
- #include <inetmapi/inetmapi.h>
- #include <mapi.h>
- #include <mapix.h>
- #include <mapiutil.h>
- #include <mapidefs.h>
- #include <kopano/mapiext.h>
- #include <mapiguid.h>
- #include <edkguid.h>
- #include <edkmdb.h>
- #include <kopano/EMSAbTag.h>
- #include <cctype>
- #include <ctime>
- #include <kopano/stringutil.h>
- #include <kopano/CommonUtil.h>
- #include <kopano/Util.h>
- #include <kopano/ECLogger.h>
- #include <kopano/my_getopt.h>
- #include "rules.h"
- #include "archive.h"
- #include "helpers/MAPIPropHelper.h"
- #include <inetmapi/options.h>
- #include <kopano/charset/convert.h>
- #include <kopano/IECServiceAdmin.h>
- #include <kopano/IECUnknown.h>
- #include <kopano/ECTags.h>
- #include "ECFeatures.h"
- #include <kopano/ECChannel.h>
- #include <kopano/UnixUtil.h>
- #include "LMTP.h"
- #include <kopano/ecversion.h>
- #include <csignal>
- #include "SSLUtil.h"
- #include "StatsClient.h"
- #include <execinfo.h>
- using namespace std;
- using namespace KC;
- using namespace KCHL;
- static StatsClient *sc = NULL;
- enum _dt {
- DM_STORE=0,
- DM_JUNK,
- DM_PUBLIC
- };
- typedef _dt delivery_mode;
- class DeliveryArgs {
- public:
- DeliveryArgs(void)
- {
- imopt_default_delivery_options(&sDeliveryOpts);
- }
- ~DeliveryArgs()
- {
- delete lpChannel;
- }
- /* Channel for communication from MTA */
- ECChannel *lpChannel = nullptr;
- /* Connection path to storage server */
- std::string strPath;
- /* Path to autorespond handler */
- std::string strAutorespond;
- /* Options for delivery into special subfolder */
- bool bCreateFolder = false;
- std::wstring strDeliveryFolder;
- WCHAR szPathSeperator = '\\';
- /* Delivery options */
- delivery_mode ulDeliveryMode = DM_STORE;
- delivery_options sDeliveryOpts;
- /* Generate notifications regarding the new email */
- bool bNewmailNotify = false;
- /* Username is email address, resolve it to get username */
- bool bResolveAddress = false;
- };
- /**
- * ECRecipient contains an email address from LMTP, or from
- * commandline an email address or username.
- */
- class ECRecipient {
- public:
- ECRecipient(const std::wstring &wstrName)
- {
- /* strRCPT much match recipient string from LMTP caller */
- wstrRCPT = wstrName;
- vwstrRecipients.push_back(wstrName);
- sEntryId.cb = 0;
- sEntryId.lpb = NULL;
- sSearchKey.cb = 0;
- sSearchKey.lpb = NULL;
- }
- ~ECRecipient()
- {
- MAPIFreeBuffer(sEntryId.lpb);
- MAPIFreeBuffer(sSearchKey.lpb);
- }
- void combine(ECRecipient *lpRecip) {
- vwstrRecipients.push_back(lpRecip->wstrRCPT);
- }
- // sort recipients on imap data flag, then on username so find() for combine() works correctly.
- bool operator <(const ECRecipient &r) const {
- if (this->bHasIMAP == r.bHasIMAP)
- return this->wstrUsername < r.wstrUsername;
- else
- return this->bHasIMAP && !r.bHasIMAP;
- }
- ULONG ulResolveFlags = MAPI_UNRESOLVED;
- /* Information from LMTP caller */
- std::wstring wstrRCPT;
- std::vector<std::wstring> vwstrRecipients;
- /* User properties */
- std::wstring wstrUsername;
- std::wstring wstrFullname;
- std::wstring wstrCompany;
- std::wstring wstrEmail;
- std::wstring wstrServerDisplayName;
- std::wstring wstrDeliveryStatus;
- ULONG ulDisplayType = 0;
- ULONG ulAdminLevel = 0;
- std::string strAddrType;
- std::string strSMTP;
- SBinary sEntryId;
- SBinary sSearchKey;
- bool bHasIMAP = false;
- };
- class kc_icase_hash {
- public:
- size_t operator()(const std::string &i) const
- {
- return std::hash<std::string>()(strToLower(i));
- }
- };
- class kc_icase_equal {
- public:
- bool operator()(const std::string &a, const std::string &b) const
- {
- return strcasecmp(a.c_str(), b.c_str()) == 0;
- }
- };
- //Global variables
- static bool g_bQuit = false;
- static bool g_bTempfail = true; // Most errors are tempfails
- static unsigned int g_nLMTPThreads = 0;
- ECLogger *g_lpLogger = NULL;
- ECConfig *g_lpConfig = NULL;
- class sortRecipients {
- public:
- bool operator()(const ECRecipient *left, const ECRecipient *right) const
- {
- return *left < *right;
- }
- };
- typedef std::set<ECRecipient *, sortRecipients> recipients_t;
- // we group by server to correctly single-instance the email data on each server
- typedef std::map<std::wstring, recipients_t, wcscasecmp_comparison> serverrecipients_t;
- // then we group by company to minimize re-opening the addressbook
- typedef std::map<std::wstring, serverrecipients_t, wcscasecmp_comparison> companyrecipients_t;
- static void sigterm(int)
- {
- g_bQuit = true;
- }
- static void sighup(int sig)
- {
- 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");
- }
- }
- static void sigchld(int)
- {
- int stat;
- while (waitpid (-1, &stat, WNOHANG) > 0)
- --g_nLMTPThreads;
- }
- // Look for segmentation faults
- static void sigsegv(int signr, siginfo_t *si, void *uc)
- {
- generic_sigsegv_handler(g_lpLogger, "Spooler/DAgent",
- PROJECT_VERSION_SPOOLER_STR, signr, si, uc);
- }
- /**
- * Check if the message should be processed with the autoaccepter
- *
- * This function returns TRUE if the message passed is a meeting request or meeting cancellation AND
- * the store being delivered in is marked for auto-accepting of meeting requests.
- *
- * @param lpStore Store that the message will be delivered in
- * @param lpMessage Message that will be delivered
- * @return TRUE if the message needs to be autoresponded
- */
- static bool FNeedsAutoAccept(IMsgStore *lpStore, LPMESSAGE lpMessage)
- {
- HRESULT hr = hrSuccess;
- static constexpr const SizedSPropTagArray(2, sptaProps) =
- {2, {PR_RESPONSE_REQUESTED, PR_MESSAGE_CLASS}};
- memory_ptr<SPropValue> lpProps;
- ULONG cValues = 0;
- bool bAutoAccept = false, bDeclineConflict = false, bDeclineRecurring = false;
-
- hr = lpMessage->GetProps(sptaProps, 0, &cValues, &~lpProps);
- if (FAILED(hr)) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FNeedsAutoAccept(): GetProps failed %x", hr);
- return false; /* hr */
- }
- if (PROP_TYPE(lpProps[1].ulPropTag) == PT_ERROR)
- return false; /* MAPI_E_NOT_FOUND */
- if (wcscasecmp(lpProps[1].Value.lpszW, L"IPM.Schedule.Meeting.Request") != 0 && wcscasecmp(lpProps[1].Value.lpszW, L"IPM.Schedule.Meeting.Canceled") != 0)
- return false; /* MAPI_E_NOT_FOUND */
- if ((PROP_TYPE(lpProps[0].ulPropTag) == PT_ERROR || !lpProps[0].Value.b) &&
- wcscasecmp(lpProps[1].Value.lpszW, L"IPM.Schedule.Meeting.Request") == 0)
- // PR_RESPONSE_REQUESTED must be true for requests to start the auto accepter
- return false; /* MAPI_E_NOT_FOUND */
-
- hr = GetAutoAcceptSettings(lpStore, &bAutoAccept, &bDeclineConflict, &bDeclineRecurring);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FNeedsAutoAccept(): GetAutoAcceptSettings failed %x", hr);
- return false; /* hr */
- }
-
- if (!bAutoAccept)
- return false; /* MAPI_E_NOT_FOUND */
- return true;
- }
- /**
- * Checks whether the message needs auto-processing
- */
- static bool FNeedsAutoProcessing(IMessage *lpMessage)
- {
- HRESULT hr = hrSuccess;
- static constexpr const SizedSPropTagArray(1, sptaProps) = {1, {PR_MESSAGE_CLASS}};
- memory_ptr<SPropValue> lpProps;
- ULONG cValues = 0;
- hr = lpMessage->GetProps(sptaProps, 0, &cValues, &~lpProps);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FNeedsAutoProcessing(): GetProps failed %x", hr);
- return false; /* hr */
- }
- if (wcsncasecmp(lpProps[0].Value.lpszW, L"IPM.Schedule.Meeting.", wcslen(L"IPM.Schedule.Meeting.")) != 0)
- return false; /* MAPI_E_NOT_FOUND */
- return true;
- }
- /**
- * Auto-respond to the passed message
- *
- * This function starts the external autoresponder. Since the external autoresponder needs to access a message,
- * we first copy (save) the message into the root folder of the store, then run the script on that message, and remove
- * the message afterwards (if the item was accepted, then it will have been moved to the calendar, which will cause
- * the delete to fail, but that's expected).
- *
- * @param lpRecip Recipient for whom lpMessage is being delivered
- * @param lpStore Store in which lpMessage is being delivered
- * @param lpMessage Message being delivered, should be a meeting request
- *
- * @return result
- */
- static HRESULT HrAutoAccept(ECRecipient *lpRecip, IMsgStore *lpStore,
- IMessage *lpMessage)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpRootFolder;
- object_ptr<IMessage> lpMessageCopy;
- const char *autoresponder = g_lpConfig->GetSetting("mr_autoaccepter");
- std::string strEntryID, strCmdLine;
- memory_ptr<SPropValue> lpEntryID;
- ULONG ulType = 0;
- ENTRYLIST sEntryList;
- sc -> countInc("DAgent", "AutoAccept");
- // Our autoaccepter is an external script. This means that the message it is working on must be
- // saved so that it can find the message to open. Since we can't save the passed lpMessage (it
- // must be processed by the rules engine first), we make a copy, and let the autoaccept script
- // work on the copy.
- hr = lpStore->OpenEntry(0, nullptr, nullptr, MAPI_MODIFY, &ulType, &~lpRootFolder);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoAccept(): OpenEntry failed %x", hr);
- return hr;
- }
- hr = lpRootFolder->CreateMessage(nullptr, 0, &~lpMessageCopy);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoAccept(): CreateMessage failed %x", hr);
- return hr;
- }
-
- hr = lpMessage->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, (LPVOID)lpMessageCopy, 0, NULL);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoAccept(): CopyTo failed %x", hr);
- return hr;
- }
-
- hr = lpMessageCopy->SaveChanges(0);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoAccept(): SaveChanges failed %x", hr);
- return hr;
- }
- hr = HrGetOneProp(lpMessageCopy, PR_ENTRYID, &~lpEntryID);
- if (hr != hrSuccess) {
- ec_log_err("HrAutoAccept(): HrGetOneProp failed %x", hr);
- return hr;
- }
-
- strEntryID = bin2hex(lpEntryID->Value.bin.cb, lpEntryID->Value.bin.lpb);
- // We cannot rely on the 'current locale' to be able to represent the username in wstrUsername. We therefore
- // force UTF-8 output on the username. This means that the autoaccept script must also interpret the username
- // in UTF-8, *not* in the current locale.
- strCmdLine = (std::string)autoresponder + " \"" + convert_to<string>("UTF-8", lpRecip->wstrUsername, rawsize(lpRecip->wstrUsername), CHARSET_WCHAR) + "\" \"" + g_lpConfig->GetSettingsPath() + "\" \"" + strEntryID + "\"";
- ec_log_debug("Starting autoaccept with command line %s", strCmdLine.c_str());
- if (!unix_system(autoresponder, strCmdLine.c_str(), const_cast<const char **>(environ))) {
- hr = MAPI_E_CALL_FAILED;
- ec_log_err("HrAutoAccept(): invoking autoaccept script failed %x", hr);
- }
-
- // Delete the copy, irrespective of the outcome of the script.
- sEntryList.cValues = 1;
- sEntryList.lpbin = &lpEntryID->Value.bin;
-
- lpRootFolder->DeleteMessages(&sEntryList, 0, NULL, 0);
- // ignore error during delete; the autoaccept script may have already (re)moved the message
- return hr;
- }
- /**
- * Auto-process the passed message
- *
- * @param lpRecip Recipient for whom lpMessage is being delivered
- * @param lpStore Store in which lpMessage is being delivered
- * @param lpMessage Message being delivered, should be a meeting request
- *
- * @return result
- */
- static HRESULT HrAutoProcess(ECRecipient *lpRecip, IMsgStore *lpStore,
- IMessage *lpMessage)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPIFolder> lpRootFolder;
- object_ptr<IMessage> lpMessageCopy;
- const char *autoprocessor = g_lpConfig->GetSetting("mr_autoprocessor");
- std::string strEntryID, strCmdLine;
- memory_ptr<SPropValue> lpEntryID;
- ULONG ulType = 0;
- ENTRYLIST sEntryList;
- sc -> countInc("DAgent", "AutoProcess");
- // Pass a copy to the external script
- hr = lpStore->OpenEntry(0, nullptr, nullptr, MAPI_MODIFY, &ulType, &~lpRootFolder);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoProcess(): OpenEntry failed %x", hr);
- return hr;
- }
- hr = lpRootFolder->CreateMessage(nullptr, 0, &~lpMessageCopy);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoProcess(): CreateMessage failed %x", hr);
- return hr;
- }
- hr = lpMessage->CopyTo(0, NULL, NULL, 0, NULL, &IID_IMessage, (LPVOID)lpMessageCopy, 0, NULL);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoProcess(): CopyTo failed %x", hr);
- return hr;
- }
- hr = lpMessageCopy->SaveChanges(0);
- if(hr != hrSuccess) {
- ec_log_err("HrAutoProcess(): SaveChanges failed %x", hr);
- return hr;
- }
- hr = HrGetOneProp(lpMessageCopy, PR_ENTRYID, &~lpEntryID);
- if (hr != hrSuccess) {
- ec_log_err("HrAutoProcess(): HrGetOneProp failed %x", hr);
- return hr;
- }
- strEntryID = bin2hex(lpEntryID->Value.bin.cb, lpEntryID->Value.bin.lpb);
- // We cannot rely on the 'current locale' to be able to represent the username in wstrUsername. We therefore
- // force UTF-8 output on the username. This means that the autoaccept script must also interpret the username
- // in UTF-8, *not* in the current locale.
- strCmdLine = (std::string)autoprocessor + " \"" + convert_to<string>("UTF-8", lpRecip->wstrUsername, rawsize(lpRecip->wstrUsername), CHARSET_WCHAR) + "\" \"" + g_lpConfig->GetSettingsPath() + "\" \"" + strEntryID + "\"";
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Starting autoaccept with command line %s", strCmdLine.c_str());
- if (!unix_system(autoprocessor, strCmdLine.c_str(), const_cast<const char **>(environ)))
- hr = MAPI_E_CALL_FAILED;
- // Delete the copy, irrespective of the outcome of the script.
- sEntryList.cValues = 1;
- sEntryList.lpbin = &lpEntryID->Value.bin;
- lpRootFolder->DeleteMessages(&sEntryList, 0, NULL, 0);
- // ignore error during delete; the autoaccept script may have already (re)moved the message
- return hr;
- }
- static bool kc_recip_in_list(const char *s, const char *recip)
- {
- auto l = tokenize(s, ' ', true);
- return std::find(l.cbegin(), l.cend(), std::string(recip)) != l.cend();
- }
- /**
- * Save copy of the raw message
- *
- * @param[in] fp File pointer to the email data
- * @param[in] lpRecipient Pointer to a recipient name
- */
- static void SaveRawMessage(FILE *fp, const char *lpRecipient)
- {
- if (!g_lpConfig || !g_lpLogger || !fp || !lpRecipient)
- return;
- std::string strFileName = g_lpConfig->GetSetting("log_raw_message_path");
- auto rawmsg = g_lpConfig->GetSetting("log_raw_message");
- /*
- * Either rawmsg contains:
- * - no: do not save messages (default)
- * - all|yes: save for all users (yes for backward compatibility)
- * - space-separated user list
- */
- if (parseBool(rawmsg) && (strcasecmp(rawmsg, "all") == 0 ||
- strcasecmp(rawmsg, "yes") == 0 ||
- kc_recip_in_list(rawmsg, lpRecipient))) {
- char szBuff[64];
- tm tmResult;
- time_t now = time(NULL);
- gmtime_safe(&now, &tmResult);
- // @todo fix windows path!
- if (strFileName.empty()) {
- g_lpLogger->Log(EC_LOGLEVEL_FATAL, "Unable to save raw message. Wrong configuration: field 'log_raw_message_path' is empty.");
- return;
- }
- if (strFileName[strFileName.size()-1] != '/')
- strFileName += '/';
- strFileName += lpRecipient;
- sprintf(szBuff, "_%04d%02d%02d%02d%02d%02d_%08x.eml", tmResult.tm_year+1900, tmResult.tm_mon+1, tmResult.tm_mday, tmResult.tm_hour, tmResult.tm_min, tmResult.tm_sec, rand_mt());
- strFileName += szBuff;
- if (DuplicateFile(fp, strFileName))
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Raw message saved to '%s'", strFileName.c_str());
- }
- }
- /**
- * Opens the default addressbook container on the given addressbook.
- *
- * @param[in] lpAdrBook The IAddrBook interface
- * @param[out] lppAddrDir The default addressbook container.
- * @return MAPI error code
- */
- static HRESULT OpenResolveAddrFolder(LPADRBOOK lpAdrBook,
- IABContainer **lppAddrDir)
- {
- HRESULT hr = hrSuccess;
- memory_ptr<ENTRYID> lpEntryId;
- ULONG cbEntryId = 0;
- ULONG ulObj = 0;
- if (lpAdrBook == nullptr || lppAddrDir == nullptr)
- return MAPI_E_INVALID_PARAMETER;
- hr = lpAdrBook->GetDefaultDir(&cbEntryId, &~lpEntryId);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to find default resolve directory: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- hr = lpAdrBook->OpenEntry(cbEntryId, lpEntryId, NULL, 0, &ulObj, (LPUNKNOWN*)lppAddrDir);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open default resolve directory: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Opens the addressbook on the given session, and optionally opens
- * the default addressbook container of the addressbook.
- *
- * @param[in] lpSession The IMAPISession interface of the logged in user.
- * @param[out] lpAdrBook The Global Addressbook.
- * @param[out] lppAddrDir The default addressbook container, may be NULL if not wanted.
- * @return MAPI error code.
- */
- static HRESULT OpenResolveAddrFolder(IMAPISession *lpSession,
- LPADRBOOK *lppAdrBook, IABContainer **lppAddrDir)
- {
- if (lpSession == NULL || lppAdrBook == NULL)
- return MAPI_E_INVALID_PARAMETER;
- HRESULT hr = lpSession->OpenAddressBook(0, NULL, 0, lppAdrBook);
- if(hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open addressbook: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- if (lppAddrDir) {
- hr = OpenResolveAddrFolder(*lppAdrBook, lppAddrDir);
- if(hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "OpenResolveAddrFolder() OpenResolveAddrFolder failed %x", hr);
- return hr;
- }
- }
- return hrSuccess;
- }
- /**
- * Resolve usernames/email addresses to Kopano users.
- *
- * @param[in] lpAddrFolder resolve users from this addressbook container
- * @param[in,out] lRCPT the list of recipients to resolve in Kopano
- *
- * @return MAPI Error code
- */
- static HRESULT ResolveUsers(IABContainer *lpAddrFolder, recipients_t *lRCPT)
- {
- HRESULT hr = hrSuccess;
- adrlist_ptr lpAdrList;
- memory_ptr<FlagList> lpFlagList;
- static constexpr const SizedSPropTagArray(13, sptaAddress) = {13,
- { PR_ENTRYID, PR_DISPLAY_NAME_W, PR_ACCOUNT_W, PR_SMTP_ADDRESS_A,
- PR_ADDRTYPE_A, PR_EMAIL_ADDRESS_W, PR_DISPLAY_TYPE, PR_SEARCH_KEY,
- PR_EC_COMPANY_NAME_W, PR_EC_HOMESERVER_NAME_W, PR_EC_ADMINISTRATOR,
- PR_EC_ENABLED_FEATURES_W, PR_OBJECT_TYPE }
- };
- // pointers into the row data, non-free
- /*
- LPSPropValue lpEntryIdProp = NULL;
- LPSPropValue lpFullNameProp = NULL;
- LPSPropValue lpCompanyProp = NULL;
- LPSPropValue lpAccountProp = NULL;
- LPSPropValue lpSMTPProp = NULL;
- LPSPropValue lpServerProp = NULL;
- LPSPropValue lpDisplayProp = NULL;
- LPSPropValue lpAdminProp = NULL;
- LPSPropValue lpAddrTypeProp = NULL;
- LPSPropValue lpEmailProp = NULL;
- LPSPropValue lpSearchKeyProp = NULL;
- LPSPropValue lpFeatureList = NULL;
- LPSPropValue lpObjectProp = NULL;
- */
- ULONG ulRCPT = lRCPT->size();
- hr = MAPIAllocateBuffer(CbNewADRLIST(ulRCPT), &~lpAdrList);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveUsers(): MAPIAllocateBuffer failed(1) %x", hr);
- return hr;
- }
- lpAdrList->cEntries = ulRCPT;
- hr = MAPIAllocateBuffer(CbNewFlagList(ulRCPT), &~lpFlagList);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveUsers(): MAPIAllocateBuffer failed(2) %x", hr);
- return hr;
- }
- lpFlagList->cFlags = ulRCPT;
- ulRCPT = 0;
- for (const auto &recip : *lRCPT) {
- lpAdrList->aEntries[ulRCPT].cValues = 1;
- hr = MAPIAllocateBuffer(sizeof(SPropValue), (void **) &lpAdrList->aEntries[ulRCPT].rgPropVals);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveUsers(): MAPIAllocateBuffer failed(3) %x", hr);
- return hr;
- }
- /* szName can either be the email address or username, it doesn't really matter */
- lpAdrList->aEntries[ulRCPT].rgPropVals[0].ulPropTag = PR_DISPLAY_NAME_W;
- lpAdrList->aEntries[ulRCPT].rgPropVals[0].Value.lpszW = const_cast<wchar_t *>(recip->wstrRCPT.c_str());
- lpFlagList->ulFlag[ulRCPT] = MAPI_UNRESOLVED;
- ++ulRCPT;
- }
- // MAPI_UNICODE flag here doesn't have any effect, since we give all proptags ourself
- hr = lpAddrFolder->ResolveNames(sptaAddress,
- MAPI_UNICODE | EMS_AB_ADDRESS_LOOKUP, lpAdrList, lpFlagList);
- if (hr != hrSuccess)
- return hr;
- ulRCPT = 0;
- for (const auto &recip : *lRCPT) {
- recip->ulResolveFlags = lpFlagList->ulFlag[ulRCPT];
- ULONG temp = lpFlagList->ulFlag[ulRCPT];
- if (temp != MAPI_RESOLVED) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to resolve recipient %ls (%x)", recip->wstrRCPT.c_str(), temp);
- continue;
- }
- /* Yay, resolved the address, get it */
- auto lpEntryIdProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_ENTRYID);
- auto lpFullNameProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_DISPLAY_NAME_W);
- auto lpAccountProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_ACCOUNT_W);
- auto lpSMTPProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_SMTP_ADDRESS_A);
- auto lpObjectProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_OBJECT_TYPE);
- // the only property that is allowed NULL in this list
- auto lpDisplayProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_DISPLAY_TYPE);
- if(!lpEntryIdProp || !lpFullNameProp || !lpAccountProp || !lpSMTPProp || !lpObjectProp) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Not all properties found for %ls", recip->wstrRCPT.c_str());
- continue;
- }
- if (lpObjectProp->Value.ul != MAPI_MAILUSER) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Resolved recipient %ls is not a user", recip->wstrRCPT.c_str());
- continue;
- } else if (lpDisplayProp && lpDisplayProp->Value.ul == DT_REMOTE_MAILUSER) {
- // allowed are DT_MAILUSER, DT_ROOM and DT_EQUIPMENT. all other DT_* defines are no MAPI_MAILUSER
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Resolved recipient %ls is a contact address, unable to deliver", recip->wstrRCPT.c_str());
- continue;
- }
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Resolved recipient %ls as user %ls", recip->wstrRCPT.c_str(), lpAccountProp->Value.lpszW);
- /* The following are allowed to be NULL */
- auto lpCompanyProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_EC_COMPANY_NAME_W);
- auto lpServerProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_EC_HOMESERVER_NAME_W);
- auto lpAdminProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_EC_ADMINISTRATOR);
- auto lpAddrTypeProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_ADDRTYPE_A);
- auto lpEmailProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_EMAIL_ADDRESS_W);
- auto lpSearchKeyProp = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_SEARCH_KEY);
- recip->wstrUsername.assign(lpAccountProp->Value.lpszW);
- recip->wstrFullname.assign(lpFullNameProp->Value.lpszW);
- recip->strSMTP.assign(lpSMTPProp->Value.lpszA);
- if (Util::HrCopyBinary(lpEntryIdProp->Value.bin.cb, lpEntryIdProp->Value.bin.lpb, &recip->sEntryId.cb, &recip->sEntryId.lpb) != hrSuccess)
- continue;
- /* Only when multi-company has been enabled will we have the companyname. */
- if (lpCompanyProp)
- recip->wstrCompany.assign(lpCompanyProp->Value.lpszW);
- /* Only when distributed has been enabled will we have the servername. */
- if (lpServerProp)
- recip->wstrServerDisplayName.assign(lpServerProp->Value.lpszW);
- if (lpDisplayProp)
- recip->ulDisplayType = lpDisplayProp->Value.ul;
- if (lpAdminProp)
- recip->ulAdminLevel = lpAdminProp->Value.ul;
- if (lpAddrTypeProp)
- recip->strAddrType.assign(lpAddrTypeProp->Value.lpszA);
- else
- recip->strAddrType.assign("SMTP");
- if (lpEmailProp)
- recip->wstrEmail.assign(lpEmailProp->Value.lpszW);
- if (lpSearchKeyProp) {
- if (Util::HrCopyBinary(lpSearchKeyProp->Value.bin.cb, lpSearchKeyProp->Value.bin.lpb, &recip->sSearchKey.cb, &recip->sSearchKey.lpb) != hrSuccess)
- continue;
- } else {
- std::string key = "SMTP:" + recip->strSMTP;
- key = strToUpper(key);
- recip->sSearchKey.cb = key.size() + 1; // + terminating 0
- if (MAPIAllocateBuffer(recip->sSearchKey.cb, reinterpret_cast<void **>(&recip->sSearchKey.lpb)) != hrSuccess) {
- ++ulRCPT;
- continue;
- }
- memcpy(recip->sSearchKey.lpb, key.c_str(), recip->sSearchKey.cb);
- }
- auto lpFeatureList = PCpropFindProp(lpAdrList->aEntries[ulRCPT].rgPropVals, lpAdrList->aEntries[ulRCPT].cValues, PR_EC_ENABLED_FEATURES_W);
- recip->bHasIMAP = lpFeatureList && hasFeature(L"imap", lpFeatureList) == hrSuccess;
- ++ulRCPT;
- }
- return hrSuccess;
- }
- /**
- * Resolve a single recipient as Kopano user
- *
- * @param[in] lpAddrFolder resolve users from this addressbook container
- * @param[in,out] lpRecip recipient to resolve in Kopano
- *
- * @return MAPI Error code
- */
- static HRESULT ResolveUser(IABContainer *lpAddrFolder, ECRecipient *lpRecip)
- {
- HRESULT hr = hrSuccess;
- recipients_t list;
- /* Simple wrapper around ResolveUsers */
- list.insert(lpRecip);
- hr = ResolveUsers(lpAddrFolder, &list);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveUser(): ResolveUsers failed %x", hr);
- else if (lpRecip->ulResolveFlags != MAPI_RESOLVED)
- hr = MAPI_E_NOT_FOUND;
- return hr;
- }
- /**
- * Free a list of recipients
- *
- * @param[in] lpCompanyRecips list to free memory of, and clear.
- *
- * @return MAPI Error code
- */
- static HRESULT FreeServerRecipients(companyrecipients_t *lpCompanyRecips)
- {
- if (lpCompanyRecips == NULL)
- return MAPI_E_INVALID_PARAMETER;
- for (const auto &cmp : *lpCompanyRecips)
- for (const auto &srv : cmp.second)
- for (const auto &rcpt : srv.second)
- delete rcpt;
- lpCompanyRecips->clear();
- return hrSuccess;
- }
- /**
- * Add a recipient to a delivery list, grouped by companies and
- * servers. If recipient is added to the container, it will be set to
- * NULL so you can't free it anymore. It will be freed when the
- * container is freed.
- *
- * @param[in,out] lpCompanyRecips container to add recipient in
- * @param[in,out] lppRecipient Recipient to add to the container
- *
- * @return MAPI Error code
- */
- static HRESULT AddServerRecipient(companyrecipients_t *lpCompanyRecips,
- ECRecipient **lppRecipient)
- {
- ECRecipient *lpRecipient = *lppRecipient;
- if (lpCompanyRecips == NULL)
- return MAPI_E_INVALID_PARAMETER;
- // Find or insert
- auto iterCMP = lpCompanyRecips->insert(companyrecipients_t::value_type(lpRecipient->wstrCompany, serverrecipients_t())).first;
- // Find or insert
- auto iterSRV = iterCMP->second.insert(serverrecipients_t::value_type(lpRecipient->wstrServerDisplayName, recipients_t())).first;
- // insert into sorted set
- auto iterRecip = iterSRV->second.find(lpRecipient);
- if (iterRecip == iterSRV->second.cend()) {
- iterSRV->second.insert(lpRecipient);
- // The recipient is in the list, and no longer belongs to the caller
- *lppRecipient = NULL;
- } else {
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Combining recipient %ls and %ls, delivering only once", lpRecipient->wstrRCPT.c_str(), (*iterRecip)->wstrUsername.c_str());
- (*iterRecip)->combine(lpRecipient);
- }
- return hrSuccess;
- }
- /**
- * Make a map of recipients grouped by url instead of server name
- *
- * @param[in] lpSession MAPI admin session
- * @param[in] lpServerNameRecips recipients grouped by server name
- * @param[in] strDefaultPath default connection url to kopano
- * @param[out] lpServerPathRecips recipients grouped by server url
- *
- * @return MAPI Error code
- */
- static HRESULT ResolveServerToPath(IMAPISession *lpSession,
- const serverrecipients_t *lpServerNameRecips,
- const std::string &strDefaultPath, serverrecipients_t *lpServerPathRecips)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMsgStore> lpAdminStore;
- object_ptr<IECServiceAdmin> lpServiceAdmin;
- memory_ptr<SPropValue> lpsObject;
- memory_ptr<ECSVRNAMELIST> lpSrvNameList;
- memory_ptr<ECSERVERLIST> lpSrvList;
- if (lpServerNameRecips == nullptr || lpServerPathRecips == nullptr)
- return MAPI_E_INVALID_PARAMETER;
- /* Single server environment, use default path */
- if (lpServerNameRecips->size() == 1 && lpServerNameRecips->begin()->first.empty()) {
- lpServerPathRecips->insert(serverrecipients_t::value_type(convert_to<wstring>(strDefaultPath), lpServerNameRecips->begin()->second));
- return hrSuccess;
- }
- hr = HrOpenDefaultStore(lpSession, &~lpAdminStore);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open default store for system account, error code: 0x%08X", hr);
- // HrLogon() failed .. try again later
- return hr;
- }
- hr = HrGetOneProp(lpAdminStore, PR_EC_OBJECT, &~lpsObject);
- if(hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get internal object, error code: 0x%08X", hr);
- return hr;
- }
- // NOTE: object is placed in Value.lpszA, not Value.x
- hr = ((IECUnknown *)lpsObject->Value.lpszA)->QueryInterface(IID_IECServiceAdmin, &~lpServiceAdmin);
- if(hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get service admin, error code: 0x%08X", hr);
- return hr;
- }
- hr = MAPIAllocateBuffer(sizeof(ECSVRNAMELIST), &~lpSrvNameList);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveServerToPath(): MAPIAllocateBuffer failed %x", hr);
- return hr;
- }
- hr = MAPIAllocateMore(sizeof(WCHAR *) * lpServerNameRecips->size(), lpSrvNameList, (LPVOID *)&lpSrvNameList->lpszaServer);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveServerToPath(): MAPIAllocateMore failed(1) %x", hr);
- return hr;
- }
- lpSrvNameList->cServers = 0;
- for (const auto &iter : *lpServerNameRecips) {
- if (iter.first.empty())
- // recipient doesn't have a home server.
- // don't try to resolve since that will break the GetServerDetails call
- // and thus fail all recipients, not just this one
- continue;
- hr = MAPIAllocateMore((iter.first.size() + 1) * sizeof(wchar_t),
- lpSrvNameList, reinterpret_cast<LPVOID *>(&lpSrvNameList->lpszaServer[lpSrvNameList->cServers]));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveServerToPath(): MAPIAllocateMore failed(2) %x", hr);
- return hr;
- }
- wcscpy(reinterpret_cast<LPWSTR>(lpSrvNameList->lpszaServer[lpSrvNameList->cServers]), iter.first.c_str());
- ++lpSrvNameList->cServers;
- }
- hr = lpServiceAdmin->GetServerDetails(lpSrvNameList, EC_SERVERDETAIL_PREFEREDPATH | MAPI_UNICODE, &~lpSrvList);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ResolveServerToPath(): GetServerDetails failed %x", hr);
- return hr;
- }
- for (ULONG i = 0; i < lpSrvList->cServers; ++i) {
- auto iter = lpServerNameRecips->find((LPWSTR)lpSrvList->lpsaServer[i].lpszName);
- if (iter == lpServerNameRecips->cend()) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Server '%s' not found", (char*)lpSrvList->lpsaServer[i].lpszName);
- return MAPI_E_NOT_FOUND;
- }
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "%d recipient(s) on server '%ls' (url %ls)", (int)iter->second.size(),
- lpSrvList->lpsaServer[i].lpszName, lpSrvList->lpsaServer[i].lpszPreferedPath);
- lpServerPathRecips->insert(serverrecipients_t::value_type((LPWSTR)lpSrvList->lpsaServer[i].lpszPreferedPath, iter->second));
- }
- return hrSuccess;
- }
- /**
- * For a given recipient, open its store, inbox and delivery folder.
- *
- * @param[in] lpSession MAPI Admin session
- * @param[in] lpAdminStore Store of the admin
- * @param[in] lpRecip Resolved Kopano recipient to open folders for
- * @param[in] lpArgs Use these delivery options to open correct folders etc.
- * @param[out] lppStore Store of the recipient
- * @param[out] lppInbox Inbox of the recipient
- * @param[out] lppFolder Delivery folder of the recipient
- *
- * @return MAPI Error code
- */
- static HRESULT HrGetDeliveryStoreAndFolder(IMAPISession *lpSession,
- IMsgStore *lpAdminStore, ECRecipient *lpRecip, DeliveryArgs *lpArgs,
- LPMDB *lppStore, IMAPIFolder **lppInbox, IMAPIFolder **lppFolder)
- {
- HRESULT hr = hrSuccess;
- IMAPIFolder *lpDeliveryFolder;
- LPMDB lpDeliveryStore = NULL;
- object_ptr<IMsgStore> lpUserStore, lpPublicStore;
- object_ptr<IMAPIFolder> lpInbox, lpSubFolder, lpJunkFolder;
- object_ptr<IExchangeManageStore> lpIEMS;
- memory_ptr<SPropValue> lpJunkProp, lpWritePerms;
- ULONG cbUserStoreEntryId = 0;
- memory_ptr<ENTRYID> lpUserStoreEntryId, lpEntryId;
- ULONG cbEntryId = 0;
- ULONG ulObjType = 0;
- std::wstring strDeliveryFolder = lpArgs->strDeliveryFolder;
- bool bPublicStore = false;
- hr = lpAdminStore->QueryInterface(IID_IExchangeManageStore, &~lpIEMS);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrGetDeliveryStoreAndFolder(): QueryInterface failed %x", hr);
- return hr;
- }
- hr = lpIEMS->CreateStoreEntryID((LPTSTR)L"", (LPTSTR)lpRecip->wstrUsername.c_str(), MAPI_UNICODE | OPENSTORE_HOME_LOGON, &cbUserStoreEntryId, &~lpUserStoreEntryId);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrGetDeliveryStoreAndFolder(): CreateStoreEntry failed %x", hr);
- return hr;
- }
- hr = lpSession->OpenMsgStore(0, cbUserStoreEntryId, lpUserStoreEntryId, nullptr, MDB_WRITE | MDB_NO_DIALOG | MDB_NO_MAIL | MDB_TEMPORARY, &~lpUserStore);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrGetDeliveryStoreAndFolder(): OpenMsgStore failed %x", hr);
- return hr;
- }
- hr = lpUserStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryId, &~lpEntryId, NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to resolve incoming folder, error code: 0x%08X", hr);
- return hr;
- }
-
- // Open the inbox
- hr = lpUserStore->OpenEntry(cbEntryId, lpEntryId, &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType, &~lpInbox);
- if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open inbox folder, error code: 0x%08X", hr);
- return MAPI_E_NOT_FOUND;
- }
- // set default delivery to inbox, and default entryid for notify
- lpDeliveryFolder = lpInbox;
- lpDeliveryStore = lpUserStore;
- switch (lpArgs->ulDeliveryMode) {
- case DM_STORE:
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Mail will be delivered in Inbox");
- sc -> countInc("DAgent", "deliver_inbox");
- break;
- case DM_JUNK:
- sc -> countInc("DAgent", "deliver_junk");
- hr = HrGetOneProp(lpInbox, PR_ADDITIONAL_REN_ENTRYIDS, &~lpJunkProp);
- if (hr != hrSuccess || lpJunkProp->Value.MVbin.lpbin[4].cb == 0) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to resolve junk folder, using normal Inbox: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- break;
- }
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Mail will be delivered in junkmail folder");
- // Open the Junk folder
- hr = lpUserStore->OpenEntry(lpJunkProp->Value.MVbin.lpbin[4].cb, reinterpret_cast<ENTRYID *>(lpJunkProp->Value.MVbin.lpbin[4].lpb),
- &IID_IMAPIFolder, MAPI_MODIFY, &ulObjType, &~lpJunkFolder);
- if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to open junkmail folder, using normal Inbox: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- break;
- }
- // set new delivery folder
- lpDeliveryFolder = lpJunkFolder;
- break;
- case DM_PUBLIC:
- sc -> countInc("DAgent", "deliver_public");
- hr = HrOpenECPublicStore(lpSession, &~lpPublicStore);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open public store, error code 0x%08X", hr);
- // revert to normal inbox delivery
- strDeliveryFolder.clear();
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Mail will be delivered in Inbox");
- } else {
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Mail will be delivered in Public store subfolder");
- lpDeliveryStore = lpPublicStore;
- bPublicStore = true;
- }
- break;
- };
- if (!strDeliveryFolder.empty() && lpArgs->ulDeliveryMode != DM_JUNK) {
- hr = OpenSubFolder(lpDeliveryStore, strDeliveryFolder.c_str(),
- lpArgs->szPathSeperator, bPublicStore,
- lpArgs->bCreateFolder, &~lpSubFolder);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Subfolder not found, using normal Inbox. Error code 0x%08X", hr);
- // folder not found, use inbox
- lpDeliveryFolder = lpInbox;
- lpDeliveryStore = lpUserStore;
- } else {
- lpDeliveryFolder = lpSubFolder;
- }
- }
- // check if we may write in the selected folder
- hr = HrGetOneProp(lpDeliveryFolder, PR_ACCESS_LEVEL, &~lpWritePerms);
- if (FAILED(hr)) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to read folder properties, error code: 0x%08X", hr);
- return hr;
- }
- if ((lpWritePerms->Value.ul & MAPI_MODIFY) == 0) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "No write access in folder, using regular inbox");
- lpDeliveryStore = lpUserStore;
- lpDeliveryFolder = lpInbox;
- }
- lpDeliveryStore->AddRef();
- *lppStore = lpDeliveryStore;
- lpInbox->AddRef();
- *lppInbox = lpInbox;
- lpDeliveryFolder->AddRef();
- *lppFolder = lpDeliveryFolder;
- return hrSuccess;
- }
- /**
- * Make the message a fallback message.
- *
- * @param[in,out] lpMessage Message to place fallback data in
- * @param[in] msg original rfc2822 received message
- *
- * @return MAPI Error code
- */
- static HRESULT FallbackDelivery(LPMESSAGE lpMessage, const string &msg)
- {
- HRESULT hr;
- memory_ptr<SPropValue> lpPropValue, lpAttPropValue;
- unsigned int ulPropPos;
- FILETIME ft;
- object_ptr<IAttach> lpAttach;
- ULONG ulAttachNum;
- object_ptr<IStream> lpStream;
- unsigned int ulAttPropPos;
- string newbody;
- sc -> countInc("DAgent", "FallbackDelivery");
- // set props
- hr = MAPIAllocateBuffer(sizeof(SPropValue) * 8, &~lpPropValue);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): MAPIAllocateBuffer failed %x", hr);
- return hr;
- }
- ulPropPos = 0;
- // Subject
- lpPropValue[ulPropPos].ulPropTag = PR_SUBJECT_W;
- lpPropValue[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"Fallback delivery");
- // Message flags
- lpPropValue[ulPropPos].ulPropTag = PR_MESSAGE_FLAGS;
- lpPropValue[ulPropPos++].Value.ul = 0;
- // Message class
- lpPropValue[ulPropPos].ulPropTag = PR_MESSAGE_CLASS_W;
- lpPropValue[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"IPM.Note");
- GetSystemTimeAsFileTime(&ft);
- // Submit time
- lpPropValue[ulPropPos].ulPropTag = PR_CLIENT_SUBMIT_TIME;
- lpPropValue[ulPropPos++].Value.ft = ft;
- // Delivery time
- lpPropValue[ulPropPos].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
- lpPropValue[ulPropPos++].Value.ft = ft;
- newbody = "An e-mail sent to you could not be delivered correctly.\n\n";
- newbody += "The original message is attached to this e-mail (the one you're reading right now).\n";
- lpPropValue[ulPropPos].ulPropTag = PR_BODY_A;
- lpPropValue[ulPropPos++].Value.lpszA = (char*)newbody.c_str();
- // Add the original message into the errorMessage
- hr = lpMessage->CreateAttach(nullptr, 0, &ulAttachNum, &~lpAttach);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to create attachment, error code: 0x%08X", hr);
- return hr;
- }
- hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, STGM_WRITE | STGM_TRANSACTED, MAPI_CREATE | MAPI_MODIFY, &~lpStream);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): lpAttach->OpenProperty failed %x", hr);
- return hr;
- }
- hr = lpStream->Write(msg.c_str(), msg.size(), NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): lpStream->Write failed %x", hr);
- return hr;
- }
- hr = lpStream->Commit(0);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): lpStream->Commit failed %x", hr);
- return hr;
- }
- // Add attachment properties
- hr = MAPIAllocateBuffer(sizeof(SPropValue) * 4, &~lpAttPropValue);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): MAPIAllocateBuffer failed %x", hr);
- return hr;
- }
- ulAttPropPos = 0;
- // Attach method .. ?
- lpAttPropValue[ulAttPropPos].ulPropTag = PR_ATTACH_METHOD;
- lpAttPropValue[ulAttPropPos++].Value.ul = ATTACH_BY_VALUE;
- lpAttPropValue[ulAttPropPos].ulPropTag = PR_ATTACH_LONG_FILENAME_W;
- lpAttPropValue[ulAttPropPos++].Value.lpszW = const_cast<wchar_t *>(L"original.eml");
- lpAttPropValue[ulAttPropPos].ulPropTag = PR_ATTACH_FILENAME_W;
- lpAttPropValue[ulAttPropPos++].Value.lpszW = const_cast<wchar_t *>(L"original.eml");
- lpAttPropValue[ulAttPropPos].ulPropTag = PR_ATTACH_CONTENT_ID_W;
- lpAttPropValue[ulAttPropPos++].Value.lpszW = const_cast<wchar_t *>(L"dagent-001@localhost");
- // Add attachment properties
- hr = lpAttach->SetProps(ulAttPropPos, lpAttPropValue, NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): SetProps failed(1) %x", hr);
- return hr;
- }
- hr = lpAttach->SaveChanges(0);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): SaveChanges failed %x", hr);
- return hr;
- }
- // Add message properties
- hr = lpMessage->SetProps(ulPropPos, lpPropValue, NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): SetProps failed(2) %x", hr);
- return hr;
- }
- hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "FallbackDelivery(): lpMessage->SaveChanges failed %x", hr);
- return hr;
- }
- /**
- * Write into the given fd, and if that fails log an error.
- *
- * @param[in] fd file descriptor to write to
- * @param[in] buffer buffer to write
- * @param[in] len length of buffer to write
- * @param[in] wrap optional wrapping, inserts a \r\n at the point of the wrapping point
- *
- * @return MAPI Error code
- */
- static HRESULT WriteOrLogError(int fd, const char *buffer, size_t len,
- size_t wrap = 0)
- {
- if (!wrap)
- wrap = len;
- while (len > 0) {
- ssize_t n = min(len, wrap);
- if (write_retry(fd, buffer, n) != n) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR,
- "Write error to temp file for out of office "
- "mail: %s", strerror(errno));
- return MAPI_E_CALL_FAILED;
- }
- buffer += n;
- len -= n;
- if (len > 0 && write_retry(fd, "\r\n", 2) != 2) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR,
- "Write error to temp file for out of office "
- "mail: %s", strerror(errno));
- return MAPI_E_CALL_FAILED;
- }
- }
- return hrSuccess;
- }
- static bool dagent_oof_active(const SPropValue *prop)
- {
- bool a = prop[0].ulPropTag == PR_EC_OUTOFOFFICE && prop[0].Value.b;
- if (!a)
- return false;
- time_t ts, now = time(nullptr);
- if (prop[3].ulPropTag == PR_EC_OUTOFOFFICE_FROM) {
- FileTimeToUnixTime(prop[3].Value.ft, &ts);
- a &= ts <= now;
- }
- if (prop[4].ulPropTag == PR_EC_OUTOFOFFICE_UNTIL) {
- FileTimeToUnixTime(prop[4].Value.ft, &ts);
- a &= now <= ts;
- }
- return a;
- }
- /**
- * Contains all the exact-match header names that will inhibit autoreplies.
- */
- static const std::unordered_set<std::string, kc_icase_hash, kc_icase_equal> kc_stopreply_hdr = {
- /* Kopano - Vacation header already present, do not send vacation reply. */
- "X-Kopano-Vacation",
- /* RFC 3834 - Precedence: list/bulk/junk, do not reply to these mails. */
- "Auto-Submitted",
- "Precedence",
- /* RFC 2919 */
- "List-Id",
- /* RFC 2369 */
- "List-Help",
- "List-Subscribe",
- "List-Unsubscribe",
- "List-Post",
- "List-Owner",
- "List-Archive",
- };
- /* A list of prefix searches for entire header-value lines */
- static const std::unordered_set<std::string, kc_icase_hash, kc_icase_equal> kc_stopreply_hdr2 = {
- /* From the package "vacation" */
- "X-Spam-Flag: YES",
- /* From openSUSE's vacation package */
- "X-Is-Junk: YES",
- "X-AMAZON",
- "X-LinkedIn",
- };
- /**
- * Determines from a set of lines from internet headers (can be wrapped or
- * not) whether to inhibit autoreplies.
- */
- static bool dagent_avoid_autoreply(const std::vector<std::string> &hl)
- {
- for (const auto &line : hl) {
- if (isspace(line[0]))
- continue;
- size_t pos = line.find_first_of(':');
- if (pos == std::string::npos || pos == 0)
- continue;
- if (kc_stopreply_hdr.find(line.substr(0, pos)) != kc_stopreply_hdr.cend())
- return true;
- for (const auto &elem : kc_stopreply_hdr2)
- if (kc_stopreply_hdr2.find(line.substr(0, elem.size())) != kc_stopreply_hdr2.cend())
- return true;
- }
- return false;
- }
- /**
- * Create an out-of-office mail, and start the script to trigger its
- * optional sending.
- *
- * @param[in] lpAdrBook Addressbook for email address rewrites
- * @param[in] lpMDB Store of the user that triggered the oof email
- * @param[in] lpMessage delivery message that triggered the oof email
- * @param[in] lpRecip delivery recipient sending the oof email from
- * @param[in] strBaseCommand Command to use to start the oof mailer (kopano-autorespond)
- *
- * @return MAPI Error code
- */
- static HRESULT SendOutOfOffice(LPADRBOOK lpAdrBook, LPMDB lpMDB,
- LPMESSAGE lpMessage, ECRecipient *lpRecip,
- const std::string &strBaseCommand)
- {
- HRESULT hr = hrSuccess;
- static constexpr const SizedSPropTagArray(5, sptaStoreProps) = {5, {
- PR_EC_OUTOFOFFICE, PR_EC_OUTOFOFFICE_MSG_W,
- PR_EC_OUTOFOFFICE_SUBJECT_W,
- PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL,
- }};
- static constexpr const SizedSPropTagArray(5, sptaMessageProps) = {5, {
- PR_TRANSPORT_MESSAGE_HEADERS_A, PR_MESSAGE_TO_ME,
- PR_MESSAGE_CC_ME, PR_SUBJECT_W, PR_EC_MESSAGE_BCC_ME,
- }};
- memory_ptr<SPropValue> lpStoreProps, lpMessageProps;
- ULONG cValues;
- const wchar_t *szSubject = L"Out of office";
- char szHeader[PATH_MAX] = {0};
- wchar_t szwHeader[PATH_MAX] = {0};
- char szTemp[PATH_MAX] = {0};
- int fd = -1;
- wstring strFromName, strFromType, strFromEmail, strBody;
- string unquoted, quoted;
- string command = strBaseCommand;
- // Environment
- const char *env[5];
- std::string strToMe;
- std::string strCcMe, strBccMe;
- std::string strTmpFile;
- std::string strTmpFileEnv;
- sc -> countInc("DAgent", "OutOfOffice");
- // @fixme need to stream PR_TRANSPORT_MESSAGE_HEADERS_A and PR_EC_OUTOFOFFICE_MSG_W if they're > 8Kb
- hr = lpMDB->GetProps(sptaStoreProps, 0, &cValues, &~lpStoreProps);
- if (FAILED(hr)) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): GetProps failed(1) %x", hr);
- goto exit;
- }
- hr = hrSuccess;
- // Check for autoresponder
- if (!dagent_oof_active(lpStoreProps)) {
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Target user has OOF inactive\n");
- goto exit;
- }
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Target user has OOF active\n");
- // Check for presence of PR_EC_OUTOFOFFICE_MSG_W
- if (lpStoreProps[1].ulPropTag == PR_EC_OUTOFOFFICE_MSG_W) {
- strBody = lpStoreProps[1].Value.lpszW;
- } else {
- StreamPtr ptrStream;
- hr = lpMDB->OpenProperty(PR_EC_OUTOFOFFICE_MSG_W, &IID_IStream, 0, 0, &~ptrStream);
- if (hr == MAPI_E_NOT_FOUND) {
- /* no message is ok */
- } else if (hr != hrSuccess || (hr = Util::HrStreamToString(ptrStream, strBody)) != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to download out of office message: %s", GetMAPIErrorMessage(hr));
- hr = MAPI_E_FAILURE;
- goto exit;
- }
- }
- // Possibly override default subject
- if (lpStoreProps[2].ulPropTag == PR_EC_OUTOFOFFICE_SUBJECT_W)
- szSubject = lpStoreProps[2].Value.lpszW;
- hr = lpMessage->GetProps(sptaMessageProps, 0, &cValues, &~lpMessageProps);
- if (FAILED(hr)) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): GetProps failed(2) %x", hr);
- goto exit;
- }
- hr = hrSuccess;
- // See if we're looping
- if (lpMessageProps[0].ulPropTag == PR_TRANSPORT_MESSAGE_HEADERS_A) {
- if (dagent_avoid_autoreply(tokenize(lpMessageProps[0].Value.lpszA, "\n"))) {
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Avoiding OOF reply to an automated message.");
- goto exit;
- }
- // save headers to a file so they can also be tested from the script we're runing
- snprintf(szTemp, PATH_MAX, "%s/autorespond-headers.XXXXXX", TmpPath::getInstance() -> getTempPath().c_str());
- fd = mkstemp(szTemp);
- if (fd >= 0) {
- hr = WriteOrLogError(fd, lpMessageProps[0].Value.lpszA, strlen(lpMessageProps[0].Value.lpszA));
- if (hr == hrSuccess)
- strTmpFile = szTemp; // pass to script
- else
- unlink(szTemp); // ignore headers, but still try oof script
- close(fd);
- fd = -1;
- }
- }
-
- hr = HrGetAddress(lpAdrBook, lpMessage, PR_SENDER_ENTRYID, PR_SENDER_NAME, PR_SENDER_ADDRTYPE, PR_SENDER_EMAIL_ADDRESS, strFromName, strFromType, strFromEmail);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get sender e-mail address for autoresponder, error code: 0x%08X",hr);
- goto exit;
- }
- snprintf(szTemp, PATH_MAX, "%s/autorespond.XXXXXX", getenv("TEMP") == NULL ? "/tmp" : getenv("TEMP"));
- fd = mkstemp(szTemp);
- if (fd < 0) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to create temp file for out of office mail: %s", strerror(errno));
- hr = MAPI_E_FAILURE;
- goto exit;
- }
- // \n is on the beginning of the next header line because of snprintf and the requirement of the \n
- // PATH_MAX should never be reached though.
- quoted = ToQuotedBase64Header(lpRecip->wstrFullname);
- snprintf(szHeader, PATH_MAX, "From: %s <%s>", quoted.c_str(), lpRecip->strSMTP.c_str());
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(1) %x", hr);
- goto exit;
- }
- snprintf(szHeader, PATH_MAX, "\nTo: %ls", strFromEmail.c_str());
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(2) %x", hr);
- goto exit;
- }
- // add anti-loop header for Kopano
- snprintf(szHeader, PATH_MAX, "\nX-Kopano-Vacation: autorespond");
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(3) %x", hr);
- goto exit;
- }
- /*
- * Add anti-loop header for Exchange, see
- * http://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx
- */
- snprintf(szHeader, PATH_MAX, "\nX-Auto-Response-Suppress: All");
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(4) %x", hr);
- goto exit;
- }
- /*
- * Add anti-loop header for vacation(1) compatible implementations,
- * see book "Sendmail" (ISBN 0596555342), section 10.9.
- * RFC 3834 §3.1.8.
- */
- snprintf(szHeader, PATH_MAX, "\nPrecedence: bulk");
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(5) %x", hr);
- goto exit;
- }
- if (lpMessageProps[3].ulPropTag == PR_SUBJECT_W)
- // convert as one string because of [] characters
- swprintf(szwHeader, PATH_MAX, L"%ls [%ls]", szSubject, lpMessageProps[3].Value.lpszW);
- else
- swprintf(szwHeader, PATH_MAX, L"%ls", szSubject);
- quoted = ToQuotedBase64Header(szwHeader);
- snprintf(szHeader, PATH_MAX, "\nSubject: %s", quoted.c_str());
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(4) %x", hr);
- goto exit;
- }
- {
- locale_t timelocale = createlocale(LC_TIME, "C");
- time_t now = time(NULL);
- tm local;
- localtime_r(&now, &local);
- strftime_l(szHeader, PATH_MAX, "\nDate: %a, %d %b %Y %T %z", &local, timelocale);
- freelocale(timelocale);
- }
- if (WriteOrLogError(fd, szHeader, strlen(szHeader)) != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(5) %x", hr);
- goto exit;
- }
- snprintf(szHeader, PATH_MAX, "\nContent-Type: text/plain; charset=utf-8; format=flowed");
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(6) %x", hr);
- goto exit;
- }
- snprintf(szHeader, PATH_MAX, "\nContent-Transfer-Encoding: base64");
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(7) %x", hr);
- goto exit;
- }
- snprintf(szHeader, PATH_MAX, "\nMime-Version: 1.0"); // add mime-version header, so some clients show high-characters correctly
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(8) %x", hr);
- goto exit;
- }
- snprintf(szHeader, PATH_MAX, "\n\n"); // last header line has double \n
- hr = WriteOrLogError(fd, szHeader, strlen(szHeader));
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(9) %x", hr);
- goto exit;
- }
- // write body
- unquoted = convert_to<string>("UTF-8", strBody, rawsize(strBody), CHARSET_WCHAR);
- quoted = base64_encode((const unsigned char*)unquoted.c_str(), unquoted.length());
- hr = WriteOrLogError(fd, quoted.c_str(), quoted.length(), 76);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SendOutOfOffice(): WriteOrLogError failed(10) %x", hr);
- goto exit;
- }
- close(fd);
- fd = -1;
- // Args: From, To, Subject, Username, Msg_Filename
- // Should run in UTF-8 to get correct strings in UTF-8 from shell_escape(wstring)
- command += string(" '") + shell_escape(lpRecip->strSMTP) + string("' '") +
- shell_escape(strFromEmail) + string("' '") + shell_escape(szSubject) + string("' '") + shell_escape(lpRecip->wstrUsername) + string("' '") + shell_escape(szTemp) + string("'");
- // Set MESSAGE_TO_ME and MESSAGE_CC_ME in environment
- strToMe = (std::string)"MESSAGE_TO_ME=" + (lpMessageProps[1].ulPropTag == PR_MESSAGE_TO_ME && lpMessageProps[1].Value.b ? "1" : "0");
- strCcMe = (std::string)"MESSAGE_CC_ME=" + (lpMessageProps[2].ulPropTag == PR_MESSAGE_CC_ME && lpMessageProps[2].Value.b ? "1" : "0");
- strBccMe = std::string("MESSAGE_BCC_ME=") + (lpMessageProps[4].ulPropTag == PR_EC_MESSAGE_BCC_ME && lpMessageProps[4].Value.b ? "1" : "0");
- env[0] = strToMe.c_str();
- env[1] = strCcMe.c_str();
- strTmpFileEnv = "MAILHEADERS=" + strTmpFile;
- env[2] = strTmpFileEnv.c_str();
- env[3] = strBccMe.c_str();
- env[4] = NULL;
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Starting autoresponder for out-of-office message");
- command += " 2>&1";
- if (!unix_system(strBaseCommand.c_str(), command.c_str(), env))
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Autoresponder failed");
- exit:
- if (fd != -1)
- close(fd);
- if (szTemp[0] != 0)
- unlink(szTemp);
- if (!strTmpFile.empty())
- unlink(strTmpFile.c_str());
- return hr;
- }
- /**
- * Create an empty message for delivery
- *
- * @param[in] lpFolder Create the message in this folder
- * @param[in] lpFallbackFolder If write access forbids the creation, fallback to this folder
- * @param[out] lppDeliveryFolder The folder where the message was created
- * @param[out] lppMessage The newly created message
- *
- * @return MAPI Error code
- */
- static HRESULT HrCreateMessage(IMAPIFolder *lpFolder,
- IMAPIFolder *lpFallbackFolder, IMAPIFolder **lppDeliveryFolder,
- IMessage **lppMessage)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMessage> lpMessage;
- hr = lpFolder->CreateMessage(nullptr, 0, &~lpMessage);
- if (hr != hrSuccess && lpFallbackFolder) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to create new message in subfolder, using regular inbox. Error code: %08X", hr);
- lpFolder = lpFallbackFolder;
- hr = lpFolder->CreateMessage(nullptr, 0, &~lpMessage);
- }
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to create new message, error code: %08X", hr);
- return hr;
- }
- hr = lpMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCreateMessage() QueryInterface:message failed %x", hr);
- return hr;
- }
- hr = lpFolder->QueryInterface(IID_IMAPIFolder, (void**)lppDeliveryFolder);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCreateMessage() QueryInterface:folder failed %x", hr);
- return hr;
- }
- /**
- * Convert the received rfc2822 email into a MAPI message
- *
- * @param[in] strMail the received email
- * @param[in] lpSession a MAPI Session
- * @param[in] lpMsgStore The store of the delivery
- * @param[in] lpAdrBook The Global Addressbook
- * @param[in] lpDeliveryFolder Folder to create a new message in when the conversion fails
- * @param[in] lpMessage The message to write the conversion data in
- * @param[in] lpArgs delivery options
- * @param[out] lppMessage The delivered message
- * @param[out] lpbFallbackDelivery indicating if the message is a fallback message or not
- *
- * @return MAPI Error code
- */
- static HRESULT HrStringToMAPIMessage(const string &strMail,
- IMAPISession *lpSession, IMsgStore *lpMsgStore, LPADRBOOK lpAdrBook,
- IMAPIFolder *lpDeliveryFolder, IMessage *lpMessage, ECRecipient *lpRecip,
- DeliveryArgs *lpArgs, IMessage **lppMessage, bool *lpbFallbackDelivery)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMessage> lpFallbackMessage;
- bool bFallback = false;
- lpArgs->sDeliveryOpts.add_imap_data = lpRecip->bHasIMAP;
- // Set the properties on the object
- hr = IMToMAPI(lpSession, lpMsgStore, lpAdrBook, lpMessage, strMail, lpArgs->sDeliveryOpts);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "E-mail parsing failed: 0x%08X. Starting fallback delivery.", hr);
- // create new message
- hr = lpDeliveryFolder->CreateMessage(nullptr, 0, &~lpFallbackMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to create fallback message, error code: 0x%08X", hr);
- goto exit;
- }
- hr = FallbackDelivery(lpFallbackMessage, strMail);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to deliver fallback message, error code: 0x%08X", hr);
- goto exit;
- }
- // override original message with fallback version to return
- lpMessage = lpFallbackMessage;
- bFallback = true;
- }
- // return the filled (real or fallback) message
- hr = lpMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrStringToMAPIMessage(): QueryInterface failed %x", hr);
- goto exit;
- }
- *lpbFallbackDelivery = bFallback;
- exit:
- sc->countInc("DAgent", "string_to_mapi");
- // count attachments
- object_ptr<IMAPITable> lppAttTable;
- if (lpMessage->GetAttachmentTable(0, &~lppAttTable) == hrSuccess &&
- lppAttTable != nullptr) {
- ULONG countAtt = 0;
- if (lppAttTable->GetRowCount(0, &countAtt) == hrSuccess &&
- countAtt > 0) {
- sc -> countInc("DAgent", "n_with_attachment");
- sc -> countAdd("DAgent", "attachment_count", int64_t(countAtt));
- }
- }
- // count recipients
- object_ptr<IMAPITable> lppRecipTable;
- if (lpMessage->GetRecipientTable(0, &~lppRecipTable) == hrSuccess &&
- lppRecipTable != nullptr) {
- ULONG countRecip = 0;
- if (lppRecipTable->GetRowCount(0, &countRecip) == hrSuccess)
- sc->countAdd("DAgent", "recipients", int64_t(countRecip));
- }
- return hr;
- }
- /**
- * Check if the message was expired (delivery limit, header: Expiry-Time)
- *
- * @param[in] lpMessage message for delivery
- * @param[out] bExpired message is expired or not
- *
- * @return always hrSuccess
- */
- static HRESULT HrMessageExpired(IMessage *lpMessage, bool *bExpired)
- {
- HRESULT hr = hrSuccess;
- memory_ptr<SPropValue> lpsExpiryTime;
- /*
- * If the message has an expiry date, and it is past that time,
- * skip delivering the email.
- */
- if (HrGetOneProp(lpMessage, PR_EXPIRY_TIME, &~lpsExpiryTime) == hrSuccess) {
- time_t now = time(NULL);
- time_t expire;
- FileTimeToUnixTime(lpsExpiryTime->Value.ft, &expire);
- if (now > expire) {
- // exit with no errors
- hr = hrSuccess;
- *bExpired = true;
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message was expired, not delivering");
- // TODO: if a read-receipt was requested, we need to send a non-read read-receipt
- goto exit;
- }
- }
- *bExpired = false;
- exit:
- sc -> countInc("DAgent", *bExpired ? "msg_expired" : "msg_not_expired");
- return hr;
- }
- /**
- * Replace To recipient data in message with new recipient
- *
- * @param[in] lpMessage delivery message to set new recipient data in
- * @param[in] lpRecip new recipient to deliver same message for
- *
- * @return MAPI Error code
- */
- static HRESULT HrOverrideRecipProps(IMessage *lpMessage, ECRecipient *lpRecip)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPITable> lpRecipTable;
- memory_ptr<SRestriction> lpRestrictRecipient;
- SPropValue sPropRecip[4];
- SPropValue sCmp[2];
- bool bToMe = false;
- bool bCcMe = false, bBccMe = false;
- bool bRecipMe = false;
- static constexpr const SizedSPropTagArray(2, sptaColumns) =
- {2, {PR_RECIPIENT_TYPE, PR_ENTRYID}};
- hr = lpMessage->GetRecipientTable (0, &~lpRecipTable);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrOverrideRecipProps(): GetRecipientTable failed %x", hr);
- return hr;
- }
- hr = lpRecipTable->SetColumns(sptaColumns, 0);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrOverrideRecipProps(): SetColumns failed %x", hr);
- return hr;
- }
- sCmp[0].ulPropTag = PR_ADDRTYPE_A;
- sCmp[0].Value.lpszA = const_cast<char *>("ZARAFA");
- sCmp[1].ulPropTag = PR_SMTP_ADDRESS_A;
- sCmp[1].Value.lpszA = (char*)lpRecip->strSMTP.c_str();
- hr = ECAndRestriction(
- ECExistRestriction(PR_RECIPIENT_TYPE) +
- ECPropertyRestriction(RELOP_EQ, PR_ADDRTYPE_A, &sCmp[0], ECRestriction::Cheap) +
- ECPropertyRestriction(RELOP_EQ, PR_SMTP_ADDRESS_A, &sCmp[1], ECRestriction::Cheap)
- ).CreateMAPIRestriction(&~lpRestrictRecipient, ECRestriction::Cheap);
- if (hr != hrSuccess)
- return hr;
- hr = lpRecipTable->FindRow(lpRestrictRecipient, BOOKMARK_BEGINNING, 0);
- if (hr == hrSuccess) {
- rowset_ptr lpsRows;
- hr = lpRecipTable->QueryRows(1, 0, &~lpsRows);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrOverrideRecipProps(): QueryRows failed %x", hr);
- return hr;
- }
- bRecipMe = (lpsRows->cRows == 1);
- if (bRecipMe) {
- auto lpProp = PCpropFindProp(lpsRows->aRow[0].lpProps, lpsRows->aRow[0].cValues, PR_RECIPIENT_TYPE);
- if (lpProp) {
- bToMe = (lpProp->Value.ul == MAPI_TO);
- bCcMe = (lpProp->Value.ul == MAPI_CC);
- bBccMe = lpProp->Value.ul == MAPI_BCC;
- }
- }
- } else {
- /*
- * No recipients were found, message was not to me.
- * Don't report error to caller, since we should set
- * the properties to indicate this message is not for us.
- */
- hr = hrSuccess;
- }
- sPropRecip[0].ulPropTag = PR_MESSAGE_RECIP_ME;
- sPropRecip[0].Value.b = bRecipMe;
- sPropRecip[1].ulPropTag = PR_MESSAGE_TO_ME;
- sPropRecip[1].Value.b = bToMe;
- sPropRecip[2].ulPropTag = PR_MESSAGE_CC_ME;
- sPropRecip[2].Value.b = bCcMe;
- sPropRecip[3].ulPropTag = PR_EC_MESSAGE_BCC_ME;
- sPropRecip[3].Value.b = bBccMe;
- hr = lpMessage->SetProps(4, sPropRecip, NULL);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrOverrideRecipProps(): SetProps failed %x", hr);
- return hr;
- }
- /**
- * Replace To and From recipient data in fallback message with new recipient
- *
- * @param[in] lpMessage fallback message to set new recipient data in
- * @param[in] lpRecip new recipient to deliver same message for
- *
- * @return MAPI Error code
- */
- static HRESULT HrOverrideFallbackProps(IMessage *lpMessage,
- ECRecipient *lpRecip)
- {
- HRESULT hr = hrSuccess;
- memory_ptr<ENTRYID> lpEntryIdSender;
- ULONG cbEntryIdSender;
- SPropValue sPropOverride[17];
- ULONG ulPropPos = 0;
- // Set From: and To: to the receiving party, reply will be to yourself...
- // Too much information?
- sPropOverride[ulPropPos].ulPropTag = PR_SENDER_NAME_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"System Administrator");
- sPropOverride[ulPropPos].ulPropTag = PR_SENT_REPRESENTING_NAME_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"System Administrator");
- sPropOverride[ulPropPos].ulPropTag = PR_RECEIVED_BY_NAME_W;
- sPropOverride[ulPropPos++].Value.lpszW = (WCHAR *)lpRecip->wstrEmail.c_str();
- // PR_SENDER_EMAIL_ADDRESS
- sPropOverride[ulPropPos].ulPropTag = PR_SENDER_EMAIL_ADDRESS_A;
- sPropOverride[ulPropPos++].Value.lpszA = (char *)lpRecip->strSMTP.c_str();
- // PR_SENT_REPRESENTING_EMAIL_ADDRESS
- sPropOverride[ulPropPos].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS_A;
- sPropOverride[ulPropPos++].Value.lpszA = (char *)lpRecip->strSMTP.c_str();
- // PR_RECEIVED_BY_EMAIL_ADDRESS
- sPropOverride[ulPropPos].ulPropTag = PR_RECEIVED_BY_EMAIL_ADDRESS_A;
- sPropOverride[ulPropPos++].Value.lpszA = (char *)lpRecip->strSMTP.c_str();
- sPropOverride[ulPropPos].ulPropTag = PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A;
- sPropOverride[ulPropPos++].Value.lpszA = (char *)lpRecip->strSMTP.c_str();
- // PR_SENDER_ADDRTYPE
- sPropOverride[ulPropPos].ulPropTag = PR_SENDER_ADDRTYPE_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
- // PR_SENT_REPRESENTING_ADDRTYPE
- sPropOverride[ulPropPos].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
- // PR_RECEIVED_BY_ADDRTYPE
- sPropOverride[ulPropPos].ulPropTag = PR_RECEIVED_BY_ADDRTYPE_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
- sPropOverride[ulPropPos].ulPropTag = PR_RCVD_REPRESENTING_ADDRTYPE_W;
- sPropOverride[ulPropPos++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
- // PR_SENDER_SEARCH_KEY
- sPropOverride[ulPropPos].ulPropTag = PR_SENDER_SEARCH_KEY;
- sPropOverride[ulPropPos].Value.bin.cb = lpRecip->sSearchKey.cb;
- sPropOverride[ulPropPos++].Value.bin.lpb = lpRecip->sSearchKey.lpb;
- // PR_RECEIVED_BY_SEARCH_KEY (set as previous)
- sPropOverride[ulPropPos].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;
- sPropOverride[ulPropPos].Value.bin.cb = lpRecip->sSearchKey.cb;
- sPropOverride[ulPropPos++].Value.bin.lpb = lpRecip->sSearchKey.lpb;
- // PR_SENT_REPRESENTING_SEARCH_KEY (set as previous)
- sPropOverride[ulPropPos].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
- sPropOverride[ulPropPos].Value.bin.cb = lpRecip->sSearchKey.cb;
- sPropOverride[ulPropPos++].Value.bin.lpb = lpRecip->sSearchKey.lpb;
- hr = ECCreateOneOff((LPTSTR)lpRecip->wstrFullname.c_str(), (LPTSTR)L"SMTP", (LPTSTR)convert_to<wstring>(lpRecip->strSMTP).c_str(),
- MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbEntryIdSender, &~lpEntryIdSender);
- if (hr == hrSuccess) {
- // PR_SENDER_ENTRYID
- sPropOverride[ulPropPos].ulPropTag = PR_SENDER_ENTRYID;
- sPropOverride[ulPropPos].Value.bin.cb = cbEntryIdSender;
- sPropOverride[ulPropPos++].Value.bin.lpb = reinterpret_cast<BYTE *>(lpEntryIdSender.get());
- // PR_RECEIVED_BY_ENTRYID
- sPropOverride[ulPropPos].ulPropTag = PR_RECEIVED_BY_ENTRYID;
- sPropOverride[ulPropPos].Value.bin.cb = cbEntryIdSender;
- sPropOverride[ulPropPos++].Value.bin.lpb = reinterpret_cast<BYTE *>(lpEntryIdSender.get());
- // PR_SENT_REPRESENTING_ENTRYID
- sPropOverride[ulPropPos].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
- sPropOverride[ulPropPos].Value.bin.cb = cbEntryIdSender;
- sPropOverride[ulPropPos++].Value.bin.lpb = reinterpret_cast<BYTE *>(lpEntryIdSender.get());
- } else {
- hr = hrSuccess;
- }
- hr = lpMessage->SetProps(ulPropPos, sPropOverride, NULL);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to set fallback delivery properties: 0x%08X", hr);
- return hr;
- }
- /**
- * Set new To recipient data in message
- *
- * @param[in] lpMessage message to update recipient data in
- * @param[in] lpRecip recipient data to use
- *
- * @return MAPI error code
- */
- static HRESULT HrOverrideReceivedByProps(IMessage *lpMessage,
- ECRecipient *lpRecip)
- {
- SPropValue sPropReceived[5];
- /* First set the PR_RECEIVED_BY_* properties */
- sPropReceived[0].ulPropTag = PR_RECEIVED_BY_ADDRTYPE_A;
- sPropReceived[0].Value.lpszA = (char *)lpRecip->strAddrType.c_str();
- sPropReceived[1].ulPropTag = PR_RECEIVED_BY_EMAIL_ADDRESS_W;
- sPropReceived[1].Value.lpszW = (WCHAR *)lpRecip->wstrUsername.c_str();
- sPropReceived[2].ulPropTag = PR_RECEIVED_BY_ENTRYID;
- sPropReceived[2].Value.bin.cb = lpRecip->sEntryId.cb;
- sPropReceived[2].Value.bin.lpb = lpRecip->sEntryId.lpb;
- sPropReceived[3].ulPropTag = PR_RECEIVED_BY_NAME_W;
- sPropReceived[3].Value.lpszW = (WCHAR *)lpRecip->wstrFullname.c_str();
- sPropReceived[4].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;
- sPropReceived[4].Value.bin.cb = lpRecip->sSearchKey.cb;
- sPropReceived[4].Value.bin.lpb = lpRecip->sSearchKey.lpb;
- HRESULT hr = lpMessage->SetProps(5, sPropReceived, NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to set RECEIVED_BY properties: 0x%08X", hr);
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Copy a delivered message to another recipient
- *
- * @param[in] lpOrigMessage The original delivered message
- * @param[in] lpDeliverFolder The delivery folder of the new message
- * @param[in] lpRecip recipient data to use
- * @param[in] lpFallbackFolder Fallback folder incase lpDeliverFolder cannot be delivered to
- * @param[in] bFallbackDelivery lpOrigMessage is a fallback delivery message
- * @param[out] lppFolder folder the new message was created in
- * @param[out] lppMessage the newly copied message
- *
- * @return MAPI Error code
- */
- static HRESULT HrCopyMessageForDelivery(IMessage *lpOrigMessage,
- IMAPIFolder *lpDeliverFolder, ECRecipient *lpRecip,
- IMAPIFolder *lpFallbackFolder, bool bFallbackDelivery,
- IMAPIFolder **lppFolder = NULL, IMessage **lppMessage = NULL)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMessage> lpMessage;
- object_ptr<IMAPIFolder> lpFolder;
- helpers::MAPIPropHelperPtr ptrArchiveHelper;
- static constexpr const SizedSPropTagArray(13, sptaReceivedBy) = {
- 13, {
- /* Overriden by HrOverrideRecipProps() */
- PR_MESSAGE_RECIP_ME,
- PR_MESSAGE_TO_ME,
- PR_MESSAGE_CC_ME,
- /* HrOverrideReceivedByProps() */
- PR_RECEIVED_BY_ADDRTYPE,
- PR_RECEIVED_BY_EMAIL_ADDRESS,
- PR_RECEIVED_BY_ENTRYID,
- PR_RECEIVED_BY_NAME,
- PR_RECEIVED_BY_SEARCH_KEY,
- /* Written by rules */
- PR_LAST_VERB_EXECUTED,
- PR_LAST_VERB_EXECUTION_TIME,
- PR_ICON_INDEX,
- }
- };
- static constexpr const SizedSPropTagArray(12, sptaFallback) = {
- 12, {
- /* Overriden by HrOverrideFallbackProps() */
- PR_SENDER_ADDRTYPE,
- PR_SENDER_EMAIL_ADDRESS,
- PR_SENDER_ENTRYID,
- PR_SENDER_NAME,
- PR_SENDER_SEARCH_KEY,
- PR_SENT_REPRESENTING_ADDRTYPE,
- PR_SENT_REPRESENTING_EMAIL_ADDRESS,
- PR_SENT_REPRESENTING_ENTRYID,
- PR_SENT_REPRESENTING_NAME,
- PR_SENT_REPRESENTING_SEARCH_KEY,
- PR_RCVD_REPRESENTING_ADDRTYPE,
- PR_RCVD_REPRESENTING_EMAIL_ADDRESS,
- }
- };
- hr = HrCreateMessage(lpDeliverFolder, lpFallbackFolder, &~lpFolder, &~lpMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCopyMessageForDelivery(): HrCreateMessage failed %x", hr);
- return hr;
- }
- /* Copy message, exclude all previously set properties (Those are recipient dependent) */
- hr = lpOrigMessage->CopyTo(0, NULL, sptaReceivedBy, 0, NULL,
- &IID_IMessage, lpMessage, 0, NULL);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCopyMessageForDelivery(): CopyTo failed %x", hr);
- return hr;
- }
-
- // For a fallback, remove some more properties
- if (bFallbackDelivery)
- lpMessage->DeleteProps(sptaFallback, 0);
-
- // Make sure the message is not attached to an archive
- hr = helpers::MAPIPropHelper::Create(MAPIPropPtr(lpMessage, true), &ptrArchiveHelper);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCopyMessageForDelivery(): helpers::MAPIPropHelper::Create failed %x", hr);
- return hr;
- }
-
- hr = ptrArchiveHelper->DetachFromArchives();
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCopyMessageForDelivery(): DetachFromArchives failed %x", hr);
- return hr;
- }
- if (lpRecip->bHasIMAP)
- hr = Util::HrCopyIMAPData(lpOrigMessage, lpMessage);
- else
- hr = Util::HrDeleteIMAPData(lpMessage); // make sure the imap data is not set for this user.
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HrCopyMessageForDelivery(): IMAP handling failed %x", hr);
- return hr;
- }
- if (lppFolder)
- lpFolder->QueryInterface(IID_IMAPIFolder, (void**)lppFolder);
- if (lppMessage)
- lpMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- return hrSuccess;
- }
- /**
- * Make a new MAPI session under a specific username
- *
- * @param[in] lpArgs delivery options
- * @param[in] szUsername username to create mapi session for
- * @param[out] lppSession new MAPI session for user
- * @param[in] bSuppress suppress logging (default: false)
- *
- * @return MAPI Error code
- */
- static HRESULT HrGetSession(const DeliveryArgs *lpArgs,
- const WCHAR *szUsername, IMAPISession **lppSession, bool bSuppress = false)
- {
- HRESULT hr = hrSuccess;
- struct passwd *pwd = NULL;
- string strUnixUser;
- hr = HrOpenECSession(lppSession, "spooler/dagent", PROJECT_SVN_REV_STR,
- szUsername, L"", lpArgs->strPath.c_str(), 0,
- g_lpConfig->GetSetting("sslkey_file", "", NULL),
- g_lpConfig->GetSetting("sslkey_pass", "", NULL));
- if (hr == hrSuccess)
- return hrSuccess;
- // if connecting fails, the mailer should try to deliver again.
- switch (hr) {
- case MAPI_E_NETWORK_ERROR:
- if (!bSuppress)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to connect to storage server for user %ls, using socket: '%s'", szUsername, lpArgs->strPath.c_str());
- break;
- // MAPI_E_NO_ACCESS or MAPI_E_LOGON_FAILED are fatal (user does not exist)
- case MAPI_E_LOGON_FAILED:
- // running dagent as Unix user != lpRecip->strUsername and ! listed in local_admin_user, which gives this error too
- if (!bSuppress)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Access denied or connection failed for user %ls, using socket: '%s', error code: 0x%08X", szUsername, lpArgs->strPath.c_str(), hr);
- // so also log userid we're running as
- pwd = getpwuid(getuid());
- strUnixUser = (pwd != NULL && pwd->pw_name != NULL) ? pwd->pw_name : stringify(getuid());
- if (!bSuppress)
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Current uid:%d username:%s", getuid(), strUnixUser.c_str());
- break;
- default:
- if (!bSuppress)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to login for user %ls, error code: 0x%08X", szUsername, hr);
- break;
- }
- return hr;
- }
- /**
- * Run rules on a message and/or send an oof email before writing it
- * to the server.
- *
- * @param[in] lpAdrBook Addressbook to use during rules
- * @param[in] lpStore Store the message will be written too
- * @param[in] lpInbox Inbox of the user message is being delivered to
- * @param[in] lpFolder Actual delivery folder of message
- * @param[in,out] lppMessage message being delivered, can return another message due to rules
- * @param[in] lpRecip recipient that is delivered to
- * @param[in] lpArgs delivery options
- *
- * @return MAPI Error code
- */
- static HRESULT HrPostDeliveryProcessing(pym_plugin_intf *lppyMapiPlugin,
- LPADRBOOK lpAdrBook, LPMDB lpStore, IMAPIFolder *lpInbox,
- IMAPIFolder *lpFolder, IMessage **lppMessage, ECRecipient *lpRecip,
- DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPISession> lpUserSession;
- SPropValuePtr ptrProp;
- hr = HrOpenECSession(&~lpUserSession, "spooler/dagent:delivery",
- PROJECT_SVN_REV_STR, lpRecip->wstrUsername.c_str(), L"",
- lpArgs->strPath.c_str(), EC_PROFILE_FLAGS_NO_NOTIFICATIONS,
- g_lpConfig->GetSetting("sslkey_file", "", NULL),
- g_lpConfig->GetSetting("sslkey_pass", "", NULL));
- if (hr != hrSuccess)
- return hr;
- if(FNeedsAutoAccept(lpStore, *lppMessage)) {
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Starting MR autoaccepter");
- hr = HrAutoAccept(lpRecip, lpStore, *lppMessage);
- if(hr == hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Autoaccept processing completed successfully. Skipping further processing.");
- // The MR autoaccepter has processed the message. Skip any further work on this message: dont
- // run rules and dont send new mail notifications (The message should be deleted now)
- return MAPI_E_CANCEL;
- }
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Autoaccept processing failed, proceeding with rules processing: %s (%x).",
- GetMAPIErrorMessage(hr), hr);
- // The MR autoaccepter did not run properly. This could be correct behaviour; for example the
- // autoaccepter may want to defer accepting to a human controller. This means we have to continue
- // processing as if the autoaccepter was not used
- hr = hrSuccess;
- }
- else if (FNeedsAutoProcessing(*lppMessage)) {
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Starting MR auto processing");
- hr = HrAutoProcess(lpRecip, lpStore, *lppMessage);
- if (hr == hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Automatic MR processing successful.");
- else
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Automatic MR processing failed: %s (%x).",
- GetMAPIErrorMessage(hr), hr);
- }
- if (lpFolder == lpInbox) {
- // process rules for the inbox
- hr = HrProcessRules(convert_to<std::string>(lpRecip->wstrUsername), lppyMapiPlugin, lpUserSession, lpAdrBook, lpStore, lpInbox, lppMessage, sc);
- if (hr == MAPI_E_CANCEL)
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Message canceled by rule");
- else if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to process rules, error code: 0x%08X",hr);
- // continue, still send possible out-of-office message
- }
- // do not send vacation message for junk messages
- if (lpArgs->ulDeliveryMode != DM_JUNK &&
- // do not send vacation message on delegated messages
- (HrGetOneProp(*lppMessage, PR_DELEGATED_BY_RULE, &~ptrProp) != hrSuccess || ptrProp->Value.b == FALSE))
- SendOutOfOffice(lpAdrBook, lpStore, *lppMessage, lpRecip, lpArgs->strAutorespond);
- return hr;
- }
- /**
- * Find spam header if needed, and mark delivery as spam delivery if
- * header found.
- *
- * @param[in] strMail rfc2822 email being delivered
- * @param[in,out] lpArgs delivery options
- *
- * @return MAPI Error code
- */
- static HRESULT FindSpamMarker(const std::string &strMail,
- DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- const char *szHeader = g_lpConfig->GetSetting("spam_header_name", "", NULL);
- const char *szValue = g_lpConfig->GetSetting("spam_header_value", "", NULL);
- size_t end, pos;
- string match;
- string strHeaders;
- if (!szHeader || !szValue)
- goto exit;
- // find end of headers
- end = strMail.find("\r\n\r\n");
- if (end == string::npos)
- goto exit;
- end += 2;
- // copy headers in upper case, need to resize destination first
- strHeaders.resize(end);
- transform(strMail.begin(), strMail.begin() +end, strHeaders.begin(), ::toupper);
- match = strToUpper(std::string("\r\n") + szHeader);
- // find header
- pos = strHeaders.find(match.c_str());
- if (pos == string::npos)
- goto exit;
- // skip header and find end of line
- pos += match.length();
- end = strHeaders.find("\r\n", pos);
- match = strToUpper(szValue);
- // find value in header line (no header continuations supported here)
- pos = strHeaders.find(match.c_str(), pos);
- if (pos == string::npos || pos > end)
- goto exit;
- // found, override delivery to junkmail folder
- lpArgs->ulDeliveryMode = DM_JUNK;
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Spam marker found in e-mail, delivering to junk-mail folder");
- exit:
- sc -> countInc("DAgent", lpArgs->ulDeliveryMode == DM_JUNK ? "is_spam" : "is_ham");
- return hr;
- }
- /**
- * Deliver an email (source is either rfc2822 or previous delivered
- * mapi message) to a specific recipient.
- *
- * @param[in] lpSession MAPI session (user session when not in LMTP mode, else admin session)
- * @param[in] lpStore default store for lpSession (user store when not in LMTP mode, else admin store)
- * @param[in] bIsAdmin indicates that lpSession and lpStore are an admin session and store (true in LMTP mode)
- * @param[in] lpAdrBook Global Addressbook
- * @param[in] lpOrigMessage a previously delivered message, if any
- * @param[in] bFallbackDelivery previously delivered message was a fallback message
- * @param[in] strMail original received rfc2822 email
- * @param[in] lpRecip recipient to deliver message to
- * @param[in] lpArgs delivery options
- * @param[out] lppMessage the newly delivered message
- * @param[out] lpbFallbackDelivery newly delivered message is a fallback message
- *
- * @return MAPI Error code
- */
- static HRESULT ProcessDeliveryToRecipient(pym_plugin_intf *lppyMapiPlugin,
- IMAPISession *lpSession, IMsgStore *lpStore, bool bIsAdmin,
- LPADRBOOK lpAdrBook, IMessage *lpOrigMessage, bool bFallbackDelivery,
- const std::string &strMail, ECRecipient *lpRecip, DeliveryArgs *lpArgs,
- IMessage **lppMessage, bool *lpbFallbackDelivery)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMsgStore> lpTargetStore;
- object_ptr<IMAPIFolder> lpTargetFolder, lpFolder, lpInbox;
- object_ptr<IMessage> lpDeliveryMessage, lpMessageTmp;
- object_ptr<IABContainer> lpAddrDir;
- ULONG ulResult = 0;
- ULONG ulNewMailNotify = 0;
- // single user deliver did not lookup the user
- if (lpRecip->strSMTP.empty()) {
- hr = OpenResolveAddrFolder(lpAdrBook, &~lpAddrDir);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): OpenResolveAddrFolder failed %x", hr);
- return hr;
- }
- hr = ResolveUser(lpAddrDir, lpRecip);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): ResolveUser failed %x", hr);
- return hr;
- }
- }
- hr = HrGetDeliveryStoreAndFolder(lpSession, lpStore, lpRecip, lpArgs, &~lpTargetStore, &~lpInbox, &~lpTargetFolder);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrGetDeliveryStoreAndFolder failed %x", hr);
- return hr;
- }
- if (!lpOrigMessage) {
- /* No message was provided, we have to construct it personally */
- bool bExpired = false;
- hr = HrCreateMessage(lpTargetFolder, lpInbox, &~lpFolder, &~lpMessageTmp);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrCreateMessage failed %x", hr);
- return hr;
- }
- hr = HrStringToMAPIMessage(strMail, lpSession, lpTargetStore, lpAdrBook, lpFolder, lpMessageTmp, lpRecip, lpArgs, &~lpDeliveryMessage, &bFallbackDelivery);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrStringToMAPIMessage failed %x", hr);
- return hr;
- }
- /*
- * Check if the message has expired.
- */
- hr = HrMessageExpired(lpDeliveryMessage, &bExpired);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrMessageExpired failed %x", hr);
- return hr;
- }
- if (bExpired)
- /* Set special error code for callers */
- return MAPI_W_CANCEL_MESSAGE;
- hr = lppyMapiPlugin->MessageProcessing("PostConverting", lpSession, lpAdrBook, NULL, NULL, lpDeliveryMessage, &ulResult);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): MessageProcessing failed %x", hr);
- return hr;
- }
- // TODO do something with ulResult
- } else {
- /* Copy message to prepare for new delivery */
- hr = HrCopyMessageForDelivery(lpOrigMessage, lpTargetFolder, lpRecip, lpInbox, bFallbackDelivery, &~lpFolder, &~lpDeliveryMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrCopyMessageForDelivery failed %x", hr);
- return hr;
- }
- }
- hr = HrOverrideRecipProps(lpDeliveryMessage, lpRecip);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrOverrideRecipProps failed %x", hr);
- return hr;
- }
- if (bFallbackDelivery) {
- hr = HrOverrideFallbackProps(lpDeliveryMessage, lpRecip);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrOverrideFallbackProps failed %x", hr);
- return hr;
- }
- } else {
- hr = HrOverrideReceivedByProps(lpDeliveryMessage, lpRecip);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): HrOverrideReceivedByProps failed %x", hr);
- return hr;
- }
- }
- hr = lppyMapiPlugin->MessageProcessing("PreDelivery", lpSession, lpAdrBook, lpTargetStore, lpTargetFolder, lpDeliveryMessage, &ulResult);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): MessageProcessing(2) failed %x", hr);
- return hr;
- }
- // TODO do something with ulResult
- if (ulResult == MP_STOP_SUCCESS) {
- if (lppMessage)
- lpDeliveryMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- if (lpbFallbackDelivery)
- *lpbFallbackDelivery = bFallbackDelivery;
- return hr;
- }
- // Do rules & out-of-office
- hr = HrPostDeliveryProcessing(lppyMapiPlugin, lpAdrBook, lpTargetStore, lpInbox, lpTargetFolder, &+lpDeliveryMessage, lpRecip, lpArgs);
- if (hr != MAPI_E_CANCEL) {
- // ignore other errors for rules, still want to save the delivered message
- // Save message changes, message becomes visible for the user
- hr = lpDeliveryMessage->SaveChanges(KEEP_OPEN_READWRITE);
- if (hr != hrSuccess) {
- if (hr == MAPI_E_STORE_FULL)
- // make sure the error is printed on stderr, so this will be bounced as error by the MTA.
- // use cerr to avoid quiet mode.
- fprintf(stderr, "Store of user %ls is over quota limit.\n", lpRecip->wstrUsername.c_str());
- else
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to commit message: 0x%08X", hr);
- return hr;
- }
- hr = lppyMapiPlugin->MessageProcessing("PostDelivery", lpSession, lpAdrBook, lpTargetStore, lpTargetFolder, lpDeliveryMessage, &ulResult);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToRecipient(): MessageProcessing(3) failed %x", hr);
- return hr;
- }
- // TODO do something with ulResult
- if (parseBool(g_lpConfig->GetSetting("archive_on_delivery"))) {
- MAPISessionPtr ptrAdminSession;
- ArchivePtr ptrArchive;
- if (bIsAdmin)
- hr = lpSession->QueryInterface(ptrAdminSession.iid(), &~ptrAdminSession);
- else {
- const char *server = g_lpConfig->GetSetting("server_socket");
- server = GetServerUnixSocket(server); // let environment override if present
- hr = HrOpenECAdminSession(&~ptrAdminSession, "spooler/dagent:system", PROJECT_SVN_REV_STR, server, EC_PROFILE_FLAGS_NO_NOTIFICATIONS, 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 for archive access: 0x%08X", hr);
- return hr;
- }
- hr = Archive::Create(ptrAdminSession, &ptrArchive);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to instantiate archive object: 0x%08X", hr);
- return hr;
- }
- hr = ptrArchive->HrArchiveMessageForDelivery(lpDeliveryMessage);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to archive message: 0x%08X", hr);
- Util::HrDeleteMessage(lpSession, lpDeliveryMessage);
- return hr;
- }
- }
- if (lpArgs->bNewmailNotify) {
- ulNewMailNotify = TRUE;
- hr = lppyMapiPlugin->RequestCallExecution("SendNewMailNotify", lpSession, lpAdrBook, lpTargetStore, lpTargetFolder, lpDeliveryMessage, &ulNewMailNotify, &ulResult);
- if (hr != hrSuccess) {
- // Plugin failed so fallback on the original state
- ulNewMailNotify = lpArgs->bNewmailNotify;
- hr = hrSuccess;
- }
- if (ulNewMailNotify == true) {
- hr = HrNewMailNotification(lpTargetStore, lpDeliveryMessage);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to send 'New Mail' notification, error code: 0x%08X", hr);
- else
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Send 'New Mail' notification");
- hr = hrSuccess;
- }
- }
- }
- if (lppMessage)
- lpDeliveryMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- if (lpbFallbackDelivery)
- *lpbFallbackDelivery = bFallbackDelivery;
- return hr;
- }
- /**
- * Log that the message was expired, and send that response for every given LMTP received recipient
- *
- * @param[in] start Start of recipient list
- * @param[in] end End of recipient list
- */
- static void RespondMessageExpired(recipients_t::const_iterator iter,
- recipients_t::const_iterator end)
- {
- convert_context converter;
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Message was expired, not delivering");
- for (; iter != end; ++iter)
- (*iter)->wstrDeliveryStatus = L"250 2.4.7 %ls Delivery time expired";
- }
- /**
- * For a specific storage server, deliver the same message to a list of
- * recipients. This makes sure this message is correctly single
- * instanced on this server.
- *
- * In this function, it is mandatory to have processed all recipients
- * in the list.
- *
- * @param[in] lpUserSession optional session of one user the message is being delivered to (cmdline dagent, NULL on LMTP mode)
- * @param[in] lpMessage an already delivered message
- * @param[in] bFallbackDelivery already delivered message is a fallback message
- * @param[in] strMail the rfc2822 received email
- * @param[in] strServer uri of the storage server to connect to
- * @param[in] listRecipients list of recipients present on the server connecting to
- * @param[in] lpAdrBook Global addressbook
- * @param[in] lpArgs delivery options
- * @param[out] lppMessage The newly delivered message
- * @param[out] lpbFallbackDelivery newly delivered message is a fallback message
- *
- * @return MAPI Error code
- */
- static HRESULT ProcessDeliveryToServer(pym_plugin_intf *lppyMapiPlugin,
- IMAPISession *lpUserSession, IMessage *lpMessage, bool bFallbackDelivery,
- const std::string &strMail, const std::string &strServer,
- const recipients_t &listRecipients, LPADRBOOK lpAdrBook,
- DeliveryArgs *lpArgs, IMessage **lppMessage, bool *lpbFallbackDelivery)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPISession> lpSession;
- object_ptr<IMsgStore> lpStore;
- object_ptr<IMessage> lpOrigMessage;
- bool bFallbackDeliveryTmp = false;
- convert_context converter;
- sc -> countInc("DAgent", "to_server");
- // if we already had a message, we can create a copy.
- if (lpMessage)
- lpMessage->QueryInterface(IID_IMessage, &~lpOrigMessage);
- if (lpUserSession)
- hr = lpUserSession->QueryInterface(IID_IMAPISession, &~lpSession);
- else
- hr = HrOpenECAdminSession(&~lpSession, "spooler/dagent/delivery:system",
- PROJECT_SVN_REV_STR, strServer.c_str(),
- EC_PROFILE_FLAGS_NO_NOTIFICATIONS,
- g_lpConfig->GetSetting("sslkey_file", "", NULL),
- g_lpConfig->GetSetting("sslkey_pass", "", NULL));
- if (hr != hrSuccess || (hr = HrOpenDefaultStore(lpSession, &~lpStore)) != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open default store for system account, error code: 0x%08X", hr);
- // notify LMTP client soft error to try again later
- for (const auto &recip : listRecipients)
- // error will be shown in postqueue status in postfix, probably too in other serves and mail syslog service
- recip->wstrDeliveryStatus = L"450 4.5.0 %ls network or permissions error to storage server: " + wstringify(hr, true);
- return hr;
- }
- for (auto iter = listRecipients.cbegin(); iter != listRecipients.end(); ++iter) {
- const auto &recip = *iter;
- object_ptr<IMessage> lpMessageTmp;
- /*
- * Normal error codes must be ignored, since we want to attempt to deliver the email to all users,
- * however when the error code MAPI_W_CANCEL_MESSAGE was provided, the message has expired and it is
- * pointles to continue delivering the mail. However we must continue looping through all recipients
- * to inform the MTA we did handle the email properly.
- */
- hr = ProcessDeliveryToRecipient(lppyMapiPlugin, lpSession,
- lpStore, lpUserSession == NULL, lpAdrBook, lpOrigMessage,
- bFallbackDelivery, strMail, recip, lpArgs, &~lpMessageTmp,
- &bFallbackDeliveryTmp);
- if (hr == hrSuccess || hr == MAPI_E_CANCEL) {
- if (hr == hrSuccess) {
- memory_ptr<SPropValue> lpMessageId, lpSubject;
- wstring wMessageId;
- if (HrGetOneProp(lpMessageTmp, PR_INTERNET_MESSAGE_ID_W, &~lpMessageId) == hrSuccess)
- wMessageId = lpMessageId->Value.lpszW;
- HrGetOneProp(lpMessageTmp, PR_SUBJECT_W, &~lpSubject);
- g_lpLogger->Log(EC_LOGLEVEL_INFO,
- "Delivered message to '%ls', Subject: \"%ls\", Message-Id: %ls, size %lu",
- recip->wstrUsername.c_str(),
- (lpSubject != NULL) ? lpSubject->Value.lpszW : L"<none>",
- wMessageId.c_str(), static_cast<unsigned long>(strMail.size()));
- }
- // cancel already logged.
- hr = hrSuccess;
- recip->wstrDeliveryStatus = L"250 2.1.5 %ls Ok";
- } else if (hr == MAPI_W_CANCEL_MESSAGE) {
- /* Loop through all remaining recipients and start responding the status to LMTP */
- RespondMessageExpired(iter, listRecipients.cend());
- return MAPI_W_CANCEL_MESSAGE;
- } else {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to deliver message to '%ls', error code: 0x%08X", recip->wstrUsername.c_str(), hr);
-
- /* LMTP requires different notification when Quota for user was exceeded */
- if (hr == MAPI_E_STORE_FULL)
- recip->wstrDeliveryStatus = L"552 5.2.2 %ls Quota exceeded";
- else
- recip->wstrDeliveryStatus = L"450 4.2.0 %ls Mailbox temporarily unavailable";
- }
- if (lpMessageTmp) {
- if (lpOrigMessage == NULL)
- // If we delivered the message for the first time,
- // we keep the intermediate message to make copies of.
- lpMessageTmp->QueryInterface(IID_IMessage, &~lpOrigMessage);
- bFallbackDelivery = bFallbackDeliveryTmp;
- }
- }
- if (lppMessage != nullptr && lpOrigMessage)
- lpOrigMessage->QueryInterface(IID_IMessage, (void**)lppMessage);
- if (lpbFallbackDelivery)
- *lpbFallbackDelivery = bFallbackDelivery;
- return hr;
- }
- /**
- * Commandline dagent delivery entrypoint.
- * Deliver an email to one recipient.
- *
- * Although this function is passed a recipient list, it's only
- * because the rest of the functions it calls requires this and the
- * caller of this function already has a list.
- *
- * @param[in] lpSession User MAPI session
- * @param[in] lpAdrBook Global addressbook
- * @param[in] fp input file which contains the email to deliver
- * @param[in] lstSingleRecip list of recipients to deliver email to (one user)
- * @param[in] lpArgs delivery options
- *
- * @return MAPI Error code
- */
- static HRESULT ProcessDeliveryToSingleRecipient(pym_plugin_intf *lppyMapiPlugin,
- IMAPISession *lpSession, LPADRBOOK lpAdrBook, FILE *fp,
- recipients_t &lstSingleRecip, DeliveryArgs *lpArgs)
- {
- std::string strMail;
- sc -> countInc("DAgent", "to_single_recipient");
- /* Always start at the beginning of the file */
- rewind(fp);
- /* Read file into string */
- HRESULT hr = HrMapFileToString(fp, &strMail);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to map input to memory: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- FindSpamMarker(strMail, lpArgs);
-
- hr = ProcessDeliveryToServer(lppyMapiPlugin, lpSession, NULL, false, strMail, lpArgs->strPath, lstSingleRecip, lpAdrBook, lpArgs, NULL, NULL);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToSingleRecipient: ProcessDeliveryToServer failed %x", hr);
- return hr;
- }
- /**
- * Deliver email from file to a list of recipients which are grouped
- * by server.
- *
- * @param[in] lpSession Admin MAPI Session
- * @param[in] lpAdrBook Addressbook
- * @param[in] fp file containing the received email
- * @param[in] lpServerNameRecips recipients grouped by server
- * @param[in] lpArgs delivery options
- *
- * @return MAPI Error code
- */
- static HRESULT ProcessDeliveryToCompany(pym_plugin_intf *lppyMapiPlugin,
- IMAPISession *lpSession, LPADRBOOK lpAdrBook, FILE *fp,
- const serverrecipients_t *lpServerNameRecips, DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMessage> lpMasterMessage;
- std::string strMail;
- serverrecipients_t listServerPathRecips;
- bool bFallbackDelivery = false;
- bool bExpired = false;
- sc -> countInc("DAgent", "to_company");
- if (lpServerNameRecips == nullptr)
- return MAPI_E_INVALID_PARAMETER;
- /* Always start at the beginning of the file */
- rewind(fp);
- /* Read file into string */
- hr = HrMapFileToString(fp, &strMail);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to map input to memory: %s (%x)",
- GetMAPIErrorMessage(hr), hr);
- return hr;
- }
- FindSpamMarker(strMail, lpArgs);
- hr = ResolveServerToPath(lpSession, lpServerNameRecips, lpArgs->strPath, &listServerPathRecips);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToCompany(): ResolveServerToPath failed %x", hr);
- return hr;
- }
- for (const auto &iter : listServerPathRecips) {
- object_ptr<IMessage> lpMessageTmp;
- bool bFallbackDeliveryTmp = false;
- if (bExpired) {
- /* Simply loop through all recipients to respond to LMTP */
- RespondMessageExpired(iter.second.cbegin(), iter.second.cend());
- continue;
- }
- hr = ProcessDeliveryToServer(lppyMapiPlugin, NULL,
- lpMasterMessage, bFallbackDelivery, strMail,
- convert_to<std::string>(iter.first), iter.second,
- lpAdrBook, lpArgs, &~lpMessageTmp, &bFallbackDeliveryTmp);
- if (hr == MAPI_W_CANCEL_MESSAGE)
- bExpired = true;
- /* Don't report the error further (ignore it) */
- else if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to deliver all messages for server '%ls'",
- iter.first.c_str());
- /* lpMessage is our base message which we will copy to each server/recipient */
- if (lpMessageTmp == nullptr)
- continue;
- if (lpMasterMessage == NULL)
- // keep message to make copies of on the same server
- lpMessageTmp->QueryInterface(IID_IMessage, &~lpMasterMessage);
- bFallbackDelivery = bFallbackDeliveryTmp;
- }
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Finished processing message");
- return hrSuccess;
- }
- /**
- * Within a company space, find the recipient with the lowest
- * administrator rights. This user can be used to open the Global
- * Addressbook.
- *
- * @param[in] lpServerRecips all recipients to deliver for within a company
- * @param[out] lppRecipient a recipient with the rights lower than server admin
- *
- * @return MAPI Error code
- */
- static HRESULT
- FindLowestAdminLevelSession(const serverrecipients_t *lpServerRecips,
- DeliveryArgs *lpArgs, IMAPISession **lppUserSession)
- {
- HRESULT hr = hrSuccess;
- ECRecipient *lpRecip = NULL;
- bool bFound = false;
- for (const auto &server : *lpServerRecips) {
- for (const auto &recip : server.second) {
- if (recip->ulDisplayType == DT_REMOTE_MAILUSER)
- continue;
- else if (!lpRecip)
- lpRecip = recip;
- else if (recip->ulAdminLevel <= lpRecip->ulAdminLevel)
- lpRecip = recip;
- if (lpRecip->ulAdminLevel < 2) {
- // if this recipient cannot make the session for the addressbook, it will also not be able to open the store for delivery later on
- hr = HrGetSession(lpArgs, lpRecip->wstrUsername.c_str(), lppUserSession);
- if (hr == hrSuccess) {
- bFound = true;
- goto found;
- }
- // remove found entry, so higher admin levels can be found too if a lower cannot login
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Login on user %ls for addressbook resolves failed: 0x%08X", lpRecip->wstrUsername.c_str(), hr);
- lpRecip = NULL;
- }
- }
- }
- if (lpRecip && !bFound) {
- // we picked only an admin from the list, try this logon
- hr = HrGetSession(lpArgs, lpRecip->wstrUsername.c_str(), lppUserSession);
- bFound = (hr == hrSuccess);
- }
- found:
- if (!bFound)
- hr = MAPI_E_NOT_FOUND; /* This only happens if there are no recipients or everybody is a contact */
- return hr;
- }
- /**
- * LMTP delivery entry point
- * Deliver email to a list of recipients, grouped by company, grouped by server.
- *
- * @param[in] lpSession Admin MAPI Session
- * @param[in] fp file containing email to deliver
- * @param[in] lpCompanyRecips list of all recipients to deliver to
- * @param[in] lpArgs delivery options
- *
- * @return MAPI Error code
- */
- static HRESULT ProcessDeliveryToList(pym_plugin_intf *lppyMapiPlugin,
- IMAPISession *lpSession, FILE *fp, companyrecipients_t *lpCompanyRecips,
- DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- sc -> countInc("DAgent", "to_list");
- /*
- * Find user with lowest adminlevel, we will use the addressbook for this
- * user to make sure the recipient resolving for all recipients for the company
- * resolving will occur with the minimum set of view-levels to other
- * companies.
- */
- for (const auto &comp : *lpCompanyRecips) {
- object_ptr<IMAPISession> lpUserSession;
- object_ptr<IAddrBook> lpAdrBook;
- hr = FindLowestAdminLevelSession(&comp.second, lpArgs, &~lpUserSession);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToList(): FindLowestAdminLevelSession failed %x", hr);
- return hr;
- }
- hr = OpenResolveAddrFolder(lpUserSession, &~lpAdrBook, nullptr);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToList(): OpenResolveAddrFolder failed %x", hr);
- return hr;
- }
- hr = ProcessDeliveryToCompany(lppyMapiPlugin, lpSession, lpAdrBook, fp, &comp.second, lpArgs);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "ProcessDeliveryToList(): ProcessDeliveryToCompany failed %x", hr);
- return hr;
- }
- }
- return hrSuccess;
- }
- static void add_misc_headers(FILE *tmp, const std::string &helo,
- const std::string &from, const DeliveryArgs *args)
- {
- /*
- * 1. Return-Path
- * Add return-path header string, as specified by RFC 5321 (ZCP-12424)
- * https://tools.ietf.org/html/rfc5322
- * it should look like:
- * Return-Path: <noreply+dev=kopano.io@other.com>
- */
- fprintf(tmp, "Return-Path: <%s>\r\n", from.c_str());
- /*
- * 2. Received
- *
- * Received: from lists.digium.com (digium-69-16-138-164.phx1.puregig.net [69.16.138.164])
- * by blah.com (Postfix) with ESMTP id 78BEB1CA369
- * for <target@blah.com>; Mon, 12 Dec 2005 11:35:12 +0100 (CET)
- */
- std::string server_name;
- const char *dummy = g_lpConfig->GetSetting("server_name");
- if (dummy != nullptr) {
- server_name = dummy;
- } else {
- char buffer[4096] = {0};
- if (gethostname(buffer, sizeof buffer) == -1)
- strcpy(buffer, "???");
- server_name = buffer;
- }
- time_t t = time(nullptr);
- struct tm *tm = localtime(&t);
- char time_str[4096];
- strftime(time_str, sizeof(time_str), "%a, %d %b %Y %T %z (%Z)", tm);
- fprintf(tmp, "Received: from %s (%s)\r\n", helo.c_str(), args->lpChannel->peer_addr());
- fprintf(tmp, "\tby %s (kopano-dagent) with LMTP;\r\n", server_name.c_str());
- fprintf(tmp, "\t%s\r\n", time_str);
- }
- /**
- * Handle an incoming LMTP connection
- *
- * @param[in] lpArg delivery options
- *
- * @return NULL
- */
- static void *HandlerLMTP(void *lpArg)
- {
- auto lpArgs = static_cast<DeliveryArgs *>(lpArg);
- std::string strMailAddress;
- companyrecipients_t mapRCPT;
- std::list<std::string> lOrderedRecipients;
- std::map<std::string, std::string> mapRecipientResults;
- std::string inBuffer;
- HRESULT hr = hrSuccess;
- bool bLMTPQuit = false;
- int timeouts = 0;
- PyMapiPluginFactory pyMapiPluginFactory;
- convert_context converter;
- std::string curFrom = "???", heloName = "???";
- LMTP lmtp(lpArgs->lpChannel, lpArgs->strPath.c_str(), g_lpConfig);
- /* For resolving addresses from Address Book */
- object_ptr<IMAPISession> lpSession;
- object_ptr<IAddrBook> lpAdrBook;
- object_ptr<IABContainer> lpAddrDir;
- sc -> countInc("DAgent::LMTP", "sessions");
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Starting worker for LMTP request pid %d", getpid());
- char *lpEnvGDB = getenv("GDB");
- if (lpEnvGDB && parseBool(lpEnvGDB)) {
- lmtp.HrResponse("220-DEBUG MODE, please wait");
- Sleep(10000); //wait 10 seconds so you can attach gdb
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Starting worker for LMTP request");
- }
- hr = HrGetSession(lpArgs, KOPANO_SYSTEM_USER_W, &~lpSession);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HandlerLMTP(): HrGetSession failed %x", hr);
- lmtp.HrResponse("421 internal error: GetSession failed");
- goto exit;
- }
- hr = OpenResolveAddrFolder(lpSession, &~lpAdrBook, &~lpAddrDir);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "HandlerLMTP(): OpenResolveAddrFolder failed %x", hr);
- lmtp.HrResponse("421 internal error: OpenResolveAddrFolder failed");
- goto exit;
- }
- // Send hello message
- lmtp.HrResponse("220 2.1.5 LMTP server is ready");
- while (!bLMTPQuit && !g_bQuit) {
- LMTP_Command eCommand;
- hr = lpArgs->lpChannel->HrSelect(60);
- if (hr == MAPI_E_CANCEL)
- /* signalled - reevaluate quit status */
- continue;
- if(hr == MAPI_E_TIMEOUT) {
- if(timeouts < 10) {
- ++timeouts;
- continue;
- }
- lmtp.HrResponse("221 5.0.0 Connection closed due to timeout");
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Connection closed due to timeout");
- bLMTPQuit = true;
-
- break;
- } else if (hr == MAPI_E_NETWORK_ERROR) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Socket error: %s", strerror(errno));
- bLMTPQuit = true;
-
- break;
- }
- timeouts = 0;
- inBuffer.clear();
- errno = 0; // clear errno, might be from double logoff to server
- hr = lpArgs->lpChannel->HrReadLine(&inBuffer);
- if (hr != hrSuccess){
- if (errno)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to read line: %s", strerror(errno));
- else
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Client disconnected");
-
- bLMTPQuit = true;
- break;
- }
-
- if (g_bQuit) {
- lmtp.HrResponse("221 2.0.0 Server is shutting down");
- bLMTPQuit = true;
- hr = MAPI_E_CALL_FAILED;
- break;
- }
- if (g_lpLogger->Log(EC_LOGLEVEL_DEBUG))
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "> " + inBuffer);
- hr = lmtp.HrGetCommand(inBuffer, eCommand);
- if (hr != hrSuccess) {
- lmtp.HrResponse("555 5.5.4 Command not recognized");
- sc -> countInc("DAgent::LMTP", "unknown_command");
- continue;
- }
- switch (eCommand) {
- case LMTP_Command_LHLO:
- if (lmtp.HrCommandLHLO(inBuffer, heloName) == hrSuccess) {
- lmtp.HrResponse("250-SERVER ready");
- lmtp.HrResponse("250-PIPELINING");
- lmtp.HrResponse("250-ENHANCEDSTATUSCODE");
- lmtp.HrResponse("250 RSET");
- } else {
- lmtp.HrResponse("501 5.5.4 Syntax: LHLO hostname");
- sc -> countInc("DAgent::LMTP", "LHLO_fail");
- }
- break;
- case LMTP_Command_MAIL_FROM:
- // @todo, if this command is received a second time, repond: 503 5.5.1 Error: nested MAIL command
- if (lmtp.HrCommandMAILFROM(inBuffer, &curFrom) != hrSuccess) {
- lmtp.HrResponse("503 5.1.7 Bad sender's mailbox address syntax");
- sc -> countInc("DAgent::LMTP", "bad_sender_address");
- }
- else {
- lmtp.HrResponse("250 2.1.0 Ok");
- }
- break;
- case LMTP_Command_RCPT_TO: {
- if (lmtp.HrCommandRCPTTO(inBuffer, &strMailAddress) != hrSuccess) {
- lmtp.HrResponse("503 5.1.3 Bad destination mailbox address syntax");
- sc -> countInc("DAgent::LMTP", "bad_recipient_address");
- break;
- }
- auto lpRecipient = new ECRecipient(converter.convert_to<std::wstring>(strMailAddress));
-
- // Resolve the mail address, so to have a user name instead of a mail address
- hr = ResolveUser(lpAddrDir, lpRecipient);
- if (hr == hrSuccess) {
- // This is the status until it is delivered or some other error occurs
- lpRecipient->wstrDeliveryStatus = L"450 4.2.0 %ls Mailbox temporarily unavailable";
- hr = AddServerRecipient(&mapRCPT, &lpRecipient);
- if (hr != hrSuccess)
- lmtp.HrResponse("503 5.1.1 Failed to add user to recipients");
- else {
- // Save original order for final response when mail is delivered in DATA command
- lOrderedRecipients.push_back(strMailAddress);
- lmtp.HrResponse("250 2.1.5 Ok");
- }
- } else if (hr == MAPI_E_NOT_FOUND) {
- if (lpRecipient->ulResolveFlags == MAPI_AMBIGUOUS) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Requested e-mail address '%s' resolves to multiple users.", strMailAddress.c_str());
- lmtp.HrResponse("503 5.1.4 Destination mailbox address ambiguous");
- } else {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Requested e-mail address '%s' does not resolve to a user.", strMailAddress.c_str());
- lmtp.HrResponse("503 5.1.1 User does not exist");
- }
- } else {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to lookup email address, error: 0x%08X", hr);
- lmtp.HrResponse("503 5.1.1 Connection error: "+stringify(hr,1));
- }
- /*
- * If recipient resolving failed, we need to free the recipient structure,
- * only when the structure was added to the mapRCPT will it be freed automatically
- * later during email delivery.
- */
- delete lpRecipient;
- break;
- }
- case LMTP_Command_DATA: {
- if (mapRCPT.empty()) {
- lmtp.HrResponse("503 5.1.1 No recipients");
- sc->countInc("DAgent::LMTP", "no_recipients");
- break;
- }
- FILE *tmp = tmpfile();
- if (!tmp) {
- lmtp.HrResponse("503 5.1.1 Internal error during delivery");
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to create temp file for email delivery. Please check write-access in /tmp directory. Error: %s", strerror(errno));
- sc->countInc("DAgent::LMTP", "tmp_file_fail");
- break;
- }
- add_misc_headers(tmp, heloName, curFrom, lpArgs);
- hr = lmtp.HrCommandDATA(tmp);
- if (hr == hrSuccess) {
- std::unique_ptr<pym_plugin_intf> ptrPyMapiPlugin;
- hr = pyMapiPluginFactory.create_plugin(g_lpConfig, g_lpLogger, "DAgentPluginManager", &unique_tie(ptrPyMapiPlugin));
- if (hr != hrSuccess) {
- ec_log_crit("K-1731: Unable to initialize the dagent plugin manager: %s (%x).",
- GetMAPIErrorMessage(hr), hr);
- lmtp.HrResponse("503 5.1.1 Internal error during delivery");
- sc->countInc("DAgent::LMTP", "internal_error");
- fclose(tmp);
- hr = hrSuccess;
- break;
- }
- // During delivery lpArgs->ulDeliveryMode can be set to DM_JUNK. However it won't reset it
- // if required. So make sure to reset it here so we can safely reuse the LMTP connection
- delivery_mode ulDeliveryMode = lpArgs->ulDeliveryMode;
- ProcessDeliveryToList(ptrPyMapiPlugin.get(), lpSession, tmp, &mapRCPT, lpArgs);
- lpArgs->ulDeliveryMode = ulDeliveryMode;
- }
- // We're not that interested in the error value here; if an error occurs then this will be reflected in the
- // wstrDeliveryStatus of each recipient.
- hr = hrSuccess;
- /* Responses need to be sent in the same sequence that we received the recipients in.
- * Build all responses and find the sequence through the ordered list
- */
- auto rawmsg = g_lpConfig->GetSetting("log_raw_message");
- auto save_all = parseBool(rawmsg) && (strcasecmp(rawmsg, "all") == 0 || strcasecmp(rawmsg, "yes") == 0);
- if (save_all)
- SaveRawMessage(tmp, "LMTP");
- for (const auto &company : mapRCPT)
- for (const auto &server : company.second)
- for (const auto &recip : server.second) {
- WCHAR wbuffer[4096];
- for (const auto i : recip->vwstrRecipients) {
- swprintf(wbuffer, ARRAY_SIZE(wbuffer), recip->wstrDeliveryStatus.c_str(), i.c_str());
- mapRecipientResults.insert(make_pair<std::string, std::string>(converter.convert_to<std::string>(i),
- // rawsize([N]) returns N, not contents len, so cast to fix
- converter.convert_to<std::string>(CHARSET_CHAR, wbuffer, rawsize(reinterpret_cast<WCHAR *>(wbuffer)), CHARSET_WCHAR)));
- if (save_all)
- continue;
- auto save_username = converter.convert_to<std::string>(recip->wstrUsername);
- SaveRawMessage(tmp, save_username.c_str());
- }
- }
- fclose(tmp);
- // Reply each recipient in the received order
- for (const auto &i : lOrderedRecipients) {
- std::map<std::string, std::string>::const_iterator r = mapRecipientResults.find(i);
- if (r == mapRecipientResults.cend()) {
- // FIXME if a following item from lORderedRecipients does succeed, then this error status
- // is forgotten. is that ok? (FvH)
- hr = lmtp.HrResponse("503 5.1.1 Internal error while searching recipient delivery status");
- sc -> countInc("DAgent::LMTP", "internal_error");
- }
- else {
- hr = lmtp.HrResponse(r->second);
- }
- if (hr != hrSuccess)
- break;
- }
- sc->countInc("DAgent::LMTP", "received");
- // Reset RCPT TO list now
- FreeServerRecipients(&mapRCPT);
- lOrderedRecipients.clear();
- mapRecipientResults.clear();
- break;
- }
- case LMTP_Command_RSET:
- // Reset RCPT TO list
- FreeServerRecipients(&mapRCPT);
- lOrderedRecipients.clear();
- mapRecipientResults.clear();
- curFrom.clear();
- lmtp.HrResponse("250 2.1.0 Ok");
- break;
- case LMTP_Command_QUIT:
- lmtp.HrResponse("221 2.0.0 Bye");
- bLMTPQuit = 1;
- break;
- }
- }
- exit:
- FreeServerRecipients(&mapRCPT);
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "LMTP thread exiting");
- delete lpArgs;
- return NULL;
- }
- /**
- * Runs the LMTP service daemon. Listens on the LMTP port for incoming
- * connections and starts a new thread or child process to handle the
- * connection. Only accepts the incoming connection when the maximum
- * number of processes hasn't been reached.
- *
- * @param[in] servicename Name of the service, used to create a Unix pidfile.
- * @param[in] bDaemonize Starts a forked process in this loop to run in the background if true.
- * @param[in] lpArgs Struct containing delivery parameters
- * @retval MAPI error code
- */
- static HRESULT running_service(const char *servicename, bool bDaemonize,
- DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- int ulListenLMTP = 0;
- int err = 0;
- unsigned int nMaxThreads;
- int nCloseFDs = 0, pCloseFDs[1] = {0};
- struct pollfd pollfd;
- stack_t st;
- struct sigaction act;
- memset(&st, 0, sizeof(st));
- memset(&act, 0, sizeof(act));
- nMaxThreads = atoui(g_lpConfig->GetSetting("lmtp_max_threads"));
- if (nMaxThreads == 0 || nMaxThreads == INT_MAX)
- nMaxThreads = 20;
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Maximum LMTP threads set to %d", nMaxThreads);
- // Setup sockets
- hr = HrListen(g_lpConfig->GetSetting("server_bind"),
- atoi(g_lpConfig->GetSetting("lmtp_port")), &ulListenLMTP);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "running_service(): HrListen failed %x", hr);
- goto exit;
- }
-
- err = zcp_bindtodevice(ulListenLMTP,
- g_lpConfig->GetSetting("server_bind_intf"));
- if (err < 0) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "SO_BINDTODEVICE: %s",
- strerror(-err));
- goto exit;
- }
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "Listening on port %s for LMTP", g_lpConfig->GetSetting("lmtp_port"));
- pCloseFDs[nCloseFDs++] = ulListenLMTP;
- // Setup signals
- signal(SIGTERM, sigterm);
- signal(SIGINT, sigterm);
- signal(SIGHUP, sighup); // logrotate
- signal(SIGCHLD, sigchld);
- signal(SIGPIPE, SIG_IGN);
- // SIGSEGV backtrace support
- 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);
- struct rlimit file_limit;
- file_limit.rlim_cur = KC_DESIRED_FILEDES;
- file_limit.rlim_max = KC_DESIRED_FILEDES;
- if (setrlimit(RLIMIT_NOFILE, &file_limit) < 0)
- ec_log_err("WARNING: setrlimit(RLIMIT_NOFILE, %d) failed, you will only be able to connect up to %d sockets. Either start the process as root, or increase user limits for open file descriptors (%s)", KC_DESIRED_FILEDES, getdtablesize(), strerror(errno));
- 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))
- goto exit;
- if (bDaemonize && unix_daemonize(g_lpConfig))
- goto exit;
-
- if (!bDaemonize)
- setsid();
- unix_create_pidfile(servicename, g_lpConfig);
- g_lpLogger = StartLoggerProcess(g_lpConfig, g_lpLogger); // maybe replace logger
- ec_log_set(g_lpLogger);
- 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"));
- g_lpLogger->Log(EC_LOGLEVEL_ALWAYS, "Starting kopano-dagent LMTP mode version " PROJECT_VERSION_DAGENT_STR " (" PROJECT_SVN_REV_STR "), pid %d", getpid());
- pollfd.fd = ulListenLMTP;
- pollfd.events = POLLIN | POLLRDHUP;
- // Mainloop
- while (!g_bQuit) {
- err = poll(&pollfd, 1, 10 * 1000);
- if (err < 0) {
- if (errno != EINTR) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Socket error: %s", strerror(errno));
- g_bQuit = true;
- hr = MAPI_E_NETWORK_ERROR;
- }
- continue;
- } else if (err == 0) {
- continue;
- }
- // don't start more "threads" that lmtp_max_threads config option
- if (g_nLMTPThreads == nMaxThreads) {
- sc -> countInc("DAgent", "max_thread_count");
- Sleep(100);
- continue;
- }
- ++g_nLMTPThreads;
- // One socket has signalled a new incoming connection
- auto lpDeliveryArgs = new DeliveryArgs(*lpArgs);
- if (pollfd.revents & (POLLIN | POLLRDHUP)) {
- hr = HrAccept(ulListenLMTP, &lpDeliveryArgs->lpChannel);
-
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "running_service(): HrAccept failed %x", hr);
- // just keep running
- delete lpDeliveryArgs;
- hr = hrSuccess;
- continue;
- }
- sc -> countInc("DAgent", "incoming_session");
- if (unix_fork_function(HandlerLMTP, lpDeliveryArgs, nCloseFDs, pCloseFDs) < 0)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Can't create LMTP process.");
- // just keep running
- // main handler always closes information it doesn't need
- delete lpDeliveryArgs;
- hr = hrSuccess;
-
- continue;
- }
- // should not be able to get here because of continues
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Incoming traffic was not for me?!");
- }
- g_lpLogger->Log(EC_LOGLEVEL_ALWAYS, "LMTP service will now exit");
- // in forked mode, send all children the exit signal
- signal(SIGTERM, SIG_IGN);
- kill(0, SIGTERM);
- // wait max 30 seconds
- for (int i = 30; g_nLMTPThreads && i; --i) {
- if (i % 5 == 0)
- g_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Waiting for %d processes to terminate", g_nLMTPThreads);
- sleep(1);
- }
- if (g_nLMTPThreads)
- g_lpLogger->Log(EC_LOGLEVEL_NOTICE, "Forced shutdown with %d processes left", g_nLMTPThreads);
- else
- g_lpLogger->Log(EC_LOGLEVEL_INFO, "LMTP service shutdown complete");
- MAPIUninitialize();
- exit:
- ECChannel::HrFreeCtx();
- free(st.ss_sp);
- return hr;
- }
- /**
- * Commandline delivery for one given user.
- *
- * Deliver the main received from <stdin> to the user named in the
- * recipient parameter. Valid recipient input is a username in current
- * locale, or the emailadress of the user.
- *
- * @param[in] recipient Username or emailaddress of a user, in current locale.
- * @param[in] bStringEmail true if we should strip everything from the @ to get the name to deliver to.
- * @param[in] file Filepointer to start of email.
- * @param[in] lpArgs Delivery arguments, according to given options on the commandline.
- * @return MAPI Error code.
- */
- static HRESULT deliver_recipient(pym_plugin_intf *lppyMapiPlugin,
- const char *recipient, bool bStringEmail, FILE *file,
- DeliveryArgs *lpArgs)
- {
- HRESULT hr = hrSuccess;
- object_ptr<IMAPISession> lpSession;
- object_ptr<IAddrBook> lpAdrBook;
- object_ptr<IABContainer> lpAddrDir;
- recipients_t lRCPT;
- std::string strUsername;
- std::wstring strwLoginname;
- FILE *fpMail = NULL;
- sc -> countInc("DAgent::STDIN", "received");
- /* Make sure file uses CRLF */
- HRESULT hr2 = HrFileLFtoCRLF(file, &fpMail);
- if (hr2 != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to convert input to CRLF format: %s (%x)",
- GetMAPIErrorMessage(hr2), hr2);
- fpMail = file;
- }
- strUsername = recipient;
- if (bStringEmail)
- // we have to strip off the @domainname.tld to get the username
- strUsername = strUsername.substr(0, strUsername.find_first_of("@"));
- ECRecipient single_recip(convert_to<std::wstring>(strUsername));
-
- // Always try to resolve the user unless we just stripped an email address.
- if (!bStringEmail) {
- // only suppress error when it has no meaning (eg. delivery of Unix user to itself)
- hr = HrGetSession(lpArgs, KOPANO_SYSTEM_USER_W, &~lpSession, !lpArgs->bResolveAddress);
- if (hr == hrSuccess) {
- hr = OpenResolveAddrFolder(lpSession, &~lpAdrBook, &~lpAddrDir);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "deliver_recipient(): OpenResolveAddrFolder failed %x", hr);
- goto exit;
- }
- hr = ResolveUser(lpAddrDir, &single_recip);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "deliver_recipient(): ResolveUser failed %x", hr);
- if (hr == MAPI_E_NOT_FOUND)
- g_bTempfail = false;
- goto exit;
- }
- }
-
- else if (lpArgs->bResolveAddress) {
- // Failure to open the admin session will only result in error if resolve was requested.
- // Non fatal, so when config is changes the message can be delivered.
- goto exit;
- }
- else {
- // set commandline user in resolved name to deliver without resolve function
- single_recip.wstrUsername = single_recip.wstrRCPT;
- }
- }
- else {
- // set commandline user in resolved name to deliver without resolve function
- single_recip.wstrUsername = single_recip.wstrRCPT;
- }
-
- hr = HrGetSession(lpArgs, single_recip.wstrUsername.c_str(), &~lpSession);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "deliver_recipient(): HrGetSession failed %x", hr);
- if (hr == MAPI_E_LOGON_FAILED)
- // This is a hard failure, two things could have happened
- // * strUsername does not exist
- // * user does exist, but dagent is not running with the correct SYSTEM privileges, or user doesn't have a store
- // Since we cannot detect the difference, we're handling both of these situations
- // as hard errors
- g_bTempfail = false;
- goto exit;
- }
- hr = OpenResolveAddrFolder(lpSession, &~lpAdrBook, &~lpAddrDir);
- if (hr != hrSuccess) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "deliver_recipient(): OpenResolveAddrFolder failed %x", hr);
- goto exit;
- }
-
- lRCPT.insert(&single_recip);
- hr = ProcessDeliveryToSingleRecipient(lppyMapiPlugin, lpSession, lpAdrBook, fpMail, lRCPT, lpArgs);
- // Over quota is a hard error
- if (hr == MAPI_E_STORE_FULL)
- g_bTempfail = false;
- // Save copy of the raw message
- SaveRawMessage(fpMail, recipient);
- exit:
- if (fpMail && fpMail != file)
- fclose(fpMail);
- return hr;
- }
- static void print_help(const char *name)
- {
- cout << "Usage:\n" << endl;
- cout << name << " <recipient>" << endl;
- cout << " [-h|--host <serverpath>] [-c|--config <configfile>] [-f|--file <email-file>]" << endl;
- cout << " [-j|--junk] [-F|--folder <foldername>] [-P|--public <foldername>] [-p <separator>] [-C|--create]" << endl;
- cout<< " [-d|--deamonize] [-l|--listen] [-r|--read] [-s] [-v] [-q] [-e] [-n] [-R]" << endl;
- cout << endl;
- cout << " <recipient> Username or e-mail address of recipient" << endl;
- cout << " -f file\t read e-mail from file" << endl;
- cout << " -h path\t path to connect to (e.g. file:///var/run/socket)" << endl;
- cout << " -c filename\t Use configuration file (e.g. /etc/kopano/dagent.cfg)\n\t\t Default: no config file used." << endl;
- cout << " -j\t\t deliver in Junkmail" << endl;
- cout << " -F foldername\t deliver in a subfolder of the store. Eg. 'Inbox\\sales'" << endl;
- cout << " -P foldername\t deliver in a subfolder of the public store. Eg. 'sales\\incoming'" << endl;
- cout << " -p separator\t Override default path separator (\\). Eg. '-p % -F 'Inbox%dealers\\resellers'" << endl;
- cout << " -C\t\t Create the subfolder if it does not exist. Default behaviour is to revert to the normal Inbox folder" << endl;
- cout << endl;
- cout << " -s\t\t Make DAgent silent. No errors will be printed, except when the calling parameters are wrong." << endl;
- cout << " -v\t\t Make DAgent verbose. More information on processing email rules can be printed." << endl;
- cout << " -q\t\t Return qmail style errors." << endl;
- cout << " -e\t\t Strip email domain from storename, eg username@domain.com will deliver to 'username'." << endl;
- cout << " -R\t\t Attempt to resolve the passed name. Issue an error if the resolve fails. Only one of -e and -R may be specified." << endl;
- cout << " -n\t\t Use 'now' as delivery time. Otherwise, time from delivery at the mailserver will be used." << endl;
- cout << " -N\t\t Do not send a new mail notification to clients looking at this inbox. (Fixes Outlook 2000 running rules too)." << endl;
- cout << " -r\t\t Mark mail as read on delivery. Default: mark mail as new unread mail." << endl;
- cout << " -l\t\t Run DAgent as LMTP listener" << endl;
- cout << " -d\t\t Run DAgent as LMTP daemon, implicates -l. DAgent will run in the background." << endl;
- cout << endl;
- cout << " -a responder\t path to autoresponder (e.g. /usr/local/bin/autoresponder)" << endl;
- cout << "\t\t The autoresponder is called with </path/to/autoresponder> <from> <to> <subject> <kopano-username> <messagefile>" << endl;
- cout << "\t\t when the autoresponder is enabled for this user, and -j is not specified" << endl;
- cout << endl;
- cout << "<storename> is the name of the user where to deliver this mail." << endl;
- cout << "If no file is specified with -f, it will be read from standard in." << endl;
- cout << endl;
- }
- int main(int argc, char *argv[]) {
- FILE *fp = stdin;
- HRESULT hr = hrSuccess;
- bool bDefaultConfigWarning = false; // Provide warning when default configuration is used
- bool bExplicitConfig = false; // User added config option to commandline
- bool bDaemonize = false; // The dagent is not daemonized by default
- bool bListenLMTP = false; // Do not listen for LMTP by default
- bool qmail = false;
- int loglevel = EC_LOGLEVEL_WARNING; // normally, log warnings and up
- bool strip_email = false;
- bool bIgnoreUnknownConfigOptions = false;
- DeliveryArgs sDeliveryArgs;
- sDeliveryArgs.strPath = "";
- sDeliveryArgs.strAutorespond = "/usr/sbin/kopano-autorespond";
- sDeliveryArgs.bCreateFolder = false;
- sDeliveryArgs.strDeliveryFolder.clear();
- sDeliveryArgs.szPathSeperator = '\\';
- sDeliveryArgs.bResolveAddress = false;
- sDeliveryArgs.bNewmailNotify = true;
- sDeliveryArgs.ulDeliveryMode = DM_STORE;
- imopt_default_delivery_options(&sDeliveryArgs.sDeliveryOpts);
- const char *szConfig = ECConfig::GetDefaultPath("dagent.cfg");
- enum {
- OPT_HELP = UCHAR_MAX + 1,
- OPT_CONFIG,
- OPT_JUNK,
- OPT_FILE,
- OPT_HOST,
- OPT_DAEMONIZE,
- OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS,
- OPT_LISTEN,
- OPT_FOLDER,
- OPT_PUBLIC,
- OPT_CREATE,
- OPT_MARKREAD,
- OPT_NEWMAIL
- };
- static const struct option long_options[] = {
- { "help", 0, NULL, OPT_HELP }, // help text
- { "config", 1, NULL, OPT_CONFIG }, // config file
- { "junk", 0, NULL, OPT_JUNK }, // junk folder
- { "file", 1, NULL, OPT_FILE }, // file as input
- { "host", 1, NULL, OPT_HOST }, // kopano host parameter
- { "daemonize",0 ,NULL,OPT_DAEMONIZE}, // daemonize and listen for LMTP
- { "listen", 0, NULL, OPT_LISTEN}, // listen for LMTP
- { "folder", 1, NULL, OPT_FOLDER }, // subfolder of store to deliver in
- { "public", 1, NULL, OPT_PUBLIC }, // subfolder of public to deliver in
- { "create", 0, NULL, OPT_CREATE }, // create subfolder if not exist
- { "read", 0, NULL, OPT_MARKREAD }, // mark mail as read on delivery
- { "do-not-notify", 0, NULL, OPT_NEWMAIL }, // do not send new mail notification
- { "ignore-unknown-config-options", 0, NULL, OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS }, // ignore unknown settings
- { NULL, 0, NULL, 0 }
- };
- // Default settings
- static const configsetting_t lpDefaults[] = {
- { "server_bind", "" },
- { "server_bind_intf", "" },
- { "run_as_user", "kopano" },
- { "run_as_group", "kopano" },
- { "pid_file", "/var/run/kopano/dagent.pid" },
- { "coredump_enabled", "no" },
- { "lmtp_port", "2003" },
- { "lmtp_max_threads", "20" },
- { "process_model", "", CONFIGSETTING_UNUSED },
- { "log_method", "file" },
- { "log_file", "-" },
- { "log_level", "3", CONFIGSETTING_RELOADABLE },
- { "log_timestamp", "0" },
- { "log_buffer_size", "0" },
- { "server_socket", "default:" },
- { "sslkey_file", "" },
- { "sslkey_pass", "", CONFIGSETTING_EXACT },
- { "spam_header_name", "X-Spam-Status" },
- { "spam_header_value", "Yes," },
- { "log_raw_message", "no", CONFIGSETTING_RELOADABLE },
- { "log_raw_message_path", "/tmp", CONFIGSETTING_RELOADABLE },
- { "archive_on_delivery", "no", CONFIGSETTING_RELOADABLE },
- { "mr_autoaccepter", "/usr/sbin/kopano-mr-accept", CONFIGSETTING_RELOADABLE },
- { "mr_autoprocessor", "/usr/sbin/kopano-mr-process", CONFIGSETTING_RELOADABLE },
- { "plugin_enabled", "yes" },
- { "plugin_path", "/var/lib/kopano/dagent/plugins" },
- { "plugin_manager_path", "/usr/share/kopano-dagent/python" },
- { "default_charset", "us-ascii"},
- { "set_rule_headers", "yes", CONFIGSETTING_RELOADABLE },
- { "no_double_forward", "no", CONFIGSETTING_RELOADABLE },
- { "z_statsd_stats", "/var/run/kopano/statsd.sock" },
- { "tmp_path", "/tmp" },
- {"forward_whitelist_domains", "*"},
- {"html_safety_filter", "no"},
- { NULL, NULL },
- };
- // @todo: check if we need to setlocale(LC_MESSAGE, "");
- setlocale(LC_CTYPE, "");
- if (argc < 2) {
- print_help(argv[0]);
- return EX_USAGE;
- }
- int c;
- while (1) {
- c = my_getopt_long_permissive(argc, argv, "c:jf:dh:a:F:P:p:qsvenCVrRlN", long_options, NULL);
- if (c == -1)
- break;
- switch (c) {
- case OPT_CONFIG:
- case 'c':
- szConfig = optarg;
- bExplicitConfig = true;
- break;
- case OPT_JUNK:
- case 'j': // junkmail
- sDeliveryArgs.ulDeliveryMode = DM_JUNK;
- break;
- case OPT_FILE:
- case 'f': // use file as input
- fp = fopen(optarg, "rb");
- if(!fp) {
- cerr << "Unable to open file '" << optarg << "' for reading" << endl;
- return EX_USAGE;
- }
- break;
- case OPT_LISTEN:
- case 'l':
- bListenLMTP = true;
- bExplicitConfig = true;
- break;
- case OPT_DAEMONIZE:
- case 'd':
- //-d the Dagent is daemonized; service LMTP over socket starts listening on port 2003
- bDaemonize = true;
- bListenLMTP = true;
- bExplicitConfig = true;
- break;
-
- case OPT_HOST:
- case 'h': // 'host' (file:///var/run/kopano/server.sock)
- sDeliveryArgs.strPath = optarg;
- break;
- case 'a': // external autoresponder program
- sDeliveryArgs.strAutorespond = optarg;
- break;
- case 'q': // use qmail errors
- qmail = true;
- break;
- case 's': // silent, no logging
- loglevel = EC_LOGLEVEL_NONE;
- break;
- case 'v': // verbose logging
- if (loglevel == EC_LOGLEVEL_INFO)
- loglevel = EC_LOGLEVEL_DEBUG;
- else
- loglevel = EC_LOGLEVEL_INFO;
- break;
- case 'e': // strip @bla.com from username
- strip_email = true;
- break;
- case 'n':
- sDeliveryArgs.sDeliveryOpts.use_received_date = false; // conversion will use now()
- break;
- case OPT_FOLDER:
- case 'F':
- sDeliveryArgs.strDeliveryFolder = convert_to<wstring>(optarg);
- break;
- case OPT_PUBLIC:
- case 'P':
- sDeliveryArgs.ulDeliveryMode = DM_PUBLIC;
- sDeliveryArgs.strDeliveryFolder = convert_to<wstring>(optarg);
- break;
- case 'p':
- sDeliveryArgs.szPathSeperator = optarg[0];
- break;
- case OPT_CREATE:
- case 'C':
- sDeliveryArgs.bCreateFolder = true;
- break;
- case OPT_MARKREAD:
- case 'r':
- sDeliveryArgs.sDeliveryOpts.mark_as_read = true;
- break;
- case OPT_NEWMAIL:
- case 'N':
- sDeliveryArgs.bNewmailNotify = false;
- break;
- case OPT_IGNORE_UNKNOWN_CONFIG_OPTIONS:
- bIgnoreUnknownConfigOptions = true;
- break;
- case 'V':
- cout << "Product version:\t" << PROJECT_VERSION_DAGENT_STR << endl
- << "File version:\t\t" << PROJECT_SVN_REV_STR << endl;
- return EX_USAGE;
- case 'R':
- sDeliveryArgs.bResolveAddress = true;
- break;
- case OPT_HELP:
- default:
- print_help(argv[0]);
- return EX_USAGE;
- };
- }
- g_lpConfig = ECConfig::Create(lpDefaults);
- if (szConfig) {
- /* When LoadSettings fails, provide warning to user (but wait until we actually have the Logger) */
- if (!g_lpConfig->LoadSettings(szConfig))
- bDefaultConfigWarning = true;
- else {
- auto argidx = g_lpConfig->ParseParams(argc - optind, &argv[optind]);
- if (argidx < 0)
- goto exit;
- if (argidx > 0)
- // If one overrides the config, it is assumed that the
- // config is explicit. This causes errors from
- // ECConfig::ParseParams to be logged. Besides that
- // it doesn't make sense to override your config if
- // you don't know whats in it.
- bExplicitConfig = true;
- // 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;
- }
- }
- if (!bListenLMTP && optind == argc) {
- cerr << "Not enough options given, need at least the username" << endl;
- return EX_USAGE;
- }
- if (strip_email && sDeliveryArgs.bResolveAddress) {
- cerr << "You must specify either -e or -R, not both" << endl;
- return EX_USAGE;
- }
- if (!loglevel)
- g_lpLogger = new ECLogger_Null();
- else
- g_lpLogger = CreateLogger(g_lpConfig, argv[0], "KopanoDAgent");
- ec_log_set(g_lpLogger);
- if (!bExplicitConfig && loglevel)
- g_lpLogger->SetLoglevel(loglevel);
- /* Warn users that we are using the default configuration */
- if (bDefaultConfigWarning && bExplicitConfig) {
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open configuration file %s", szConfig);
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "Continuing with defaults");
- }
- 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!");
- /* If something went wrong, create special Logger, log message and bail out */
- if (g_lpConfig->HasErrors() && bExplicitConfig) {
- LogConfigErrors(g_lpConfig);
- hr = E_FAIL;
- goto exit;
- }
- /* When path wasn't provided through commandline, resolve it from config file */
- if (sDeliveryArgs.strPath.empty())
- sDeliveryArgs.strPath = g_lpConfig->GetSetting("server_socket");
- sDeliveryArgs.strPath = GetServerUnixSocket((char*)sDeliveryArgs.strPath.c_str()); // let environment override if present
- sDeliveryArgs.sDeliveryOpts.ascii_upgrade = g_lpConfig->GetSetting("default_charset");
- sDeliveryArgs.sDeliveryOpts.html_safety_filter = strcasecmp(g_lpConfig->GetSetting("html_safety_filter"), "yes") == 0;
- if (bListenLMTP) {
- /* MAPIInitialize done inside running_service */
- hr = running_service(argv[0], bDaemonize, &sDeliveryArgs);
- if (hr != hrSuccess)
- goto exit;
- }
- else {
- PyMapiPluginFactory pyMapiPluginFactory;
- std::unique_ptr<pym_plugin_intf> ptrPyMapiPlugin;
- // log process id prefix to distinguinsh events, file logger only affected
- 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"));
- hr = pyMapiPluginFactory.create_plugin(g_lpConfig, g_lpLogger, "DAgentPluginmanager", &unique_tie(ptrPyMapiPlugin));
- if (hr != hrSuccess) {
- ec_log_crit("K-1732: Unable to initialize the dagent plugin manager: %s (%x).",
- GetMAPIErrorMessage(hr), hr);
- hr = MAPI_E_CALL_FAILED;
- goto nonlmtpexit;
- }
- hr = deliver_recipient(ptrPyMapiPlugin.get(), argv[optind], strip_email, fp, &sDeliveryArgs);
- if (hr != hrSuccess)
- g_lpLogger->Log(EC_LOGLEVEL_ERROR, "main(): deliver_recipient failed %x", hr);
- fclose(fp);
- nonlmtpexit:
- MAPIUninitialize();
- }
- exit:
- delete sc;
- DeleteLogger(g_lpLogger);
- delete g_lpConfig;
- if (hr == hrSuccess || bListenLMTP)
- return EX_OK; // 0
- if (g_bTempfail)
- return qmail ? 111 : EX_TEMPFAIL; // please retry again later.
- return qmail ? 100 : EX_SOFTWARE; // fatal error, mail was undelivered (or Fallback delivery, but still return an error)
- }
|