IMAP.cpp 202 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <memory>
  19. #include <utility>
  20. #include <cstdio>
  21. #include <cstdlib>
  22. #include <sstream>
  23. #include <iostream>
  24. #include <algorithm>
  25. #include <kopano/hl.hpp>
  26. #include <kopano/memory.hpp>
  27. #include <kopano/hl.hpp>
  28. #include <mapi.h>
  29. #include <mapix.h>
  30. #include <mapicode.h>
  31. #include <mapidefs.h>
  32. #include <mapiutil.h>
  33. #include <mapiguid.h>
  34. #include <kopano/ECDefs.h>
  35. #include <kopano/ECRestriction.h>
  36. #include <kopano/CommonUtil.h>
  37. #include <kopano/ECTags.h>
  38. #include <kopano/ECIConv.h>
  39. #include <kopano/Util.h>
  40. #include <kopano/lockhelper.hpp>
  41. #include <inetmapi/inetmapi.h>
  42. #include <kopano/mapiext.h>
  43. #include <vector>
  44. #include <list>
  45. #include <set>
  46. #include <unordered_set>
  47. #include <map>
  48. #include <algorithm>
  49. #include <inetmapi/options.h>
  50. #include <edkmdb.h>
  51. #include <kopano/stringutil.h>
  52. #include <kopano/codepage.h>
  53. #include <kopano/charset/convert.h>
  54. #include <kopano/ecversion.h>
  55. #include <kopano/ECGuid.h>
  56. #include <kopano/namedprops.h>
  57. #include "ECFeatures.h"
  58. #include <kopano/mapi_ptr.h>
  59. #include "IMAP.h"
  60. using namespace std;
  61. using namespace KCHL;
  62. /**
  63. * @ingroup gateway_imap
  64. * @{
  65. */
  66. static const string strMonth[] = {
  67. "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  68. };
  69. /**
  70. * Returns TRUE if the given string starts with strPrefix
  71. *
  72. * @param[in] strInput string to find prefix in
  73. * @param[in] strPrefix test if input starts with this string
  74. */
  75. static bool Prefix(const std::string &strInput, const std::string &strPrefix)
  76. {
  77. return (strInput.compare(0, strPrefix.size(), strPrefix) == 0);
  78. }
  79. IMAP::IMAP(const char *szServerPath, ECChannel *lpChannel, ECLogger *lpLogger,
  80. ECConfig *lpConfig) :
  81. ClientProto(szServerPath, lpChannel, lpLogger, lpConfig)
  82. {
  83. imopt_default_delivery_options(&dopt);
  84. dopt.add_imap_data = parseBool(lpConfig->GetSetting("imap_store_rfc822"));
  85. dopt.html_safety_filter = strcasecmp(lpConfig->GetSetting("html_safety_filter"), "yes") == 0;
  86. bOnlyMailFolders = parseBool(lpConfig->GetSetting("imap_only_mailfolders"));
  87. bShowPublicFolder = parseBool(lpConfig->GetSetting("imap_public_folders"));
  88. cache_folders_time_limit = atoui(lpConfig->GetSetting("imap_cache_folders_time_limit"));
  89. }
  90. IMAP::~IMAP() {
  91. if (m_lpTable)
  92. m_lpTable->Release();
  93. CleanupObject();
  94. }
  95. void IMAP::CleanupObject()
  96. {
  97. // Free/release all new and allocated memory
  98. if (lpPublicStore)
  99. lpPublicStore->Release();
  100. lpPublicStore = NULL;
  101. if (lpStore)
  102. lpStore->Release();
  103. lpStore = NULL;
  104. if (lpAddrBook)
  105. lpAddrBook->Release();
  106. lpAddrBook = NULL;
  107. m_lpsIMAPTags.reset();
  108. if (lpSession)
  109. lpSession->Release();
  110. lpSession = NULL;
  111. // idle cleanup
  112. if (m_lpIdleAdviseSink)
  113. m_lpIdleAdviseSink->Release();
  114. m_lpIdleAdviseSink = NULL;
  115. if (m_lpIdleTable)
  116. m_lpIdleTable->Release();
  117. m_lpIdleTable = NULL;
  118. }
  119. void IMAP::ReleaseContentsCache()
  120. {
  121. if (m_lpTable)
  122. m_lpTable->Release();
  123. m_lpTable = NULL;
  124. m_vTableDataColumns.clear();
  125. }
  126. /**
  127. * Returns number of minutes to keep connection alive
  128. *
  129. * @return user logged in (true) or not (false)
  130. */
  131. int IMAP::getTimeoutMinutes() {
  132. if (lpStore != NULL)
  133. return 30; // 30 minutes when logged in
  134. else
  135. return 1; // 1 minute when not logged in
  136. }
  137. /**
  138. * Case insensitive std::string compare
  139. *
  140. * @param[in] strA compare with strB
  141. * @param[in] strB compare with strA
  142. * @return true if equal, not considering case differences
  143. */
  144. bool IMAP::CaseCompare(const string& strA, const string& strB)
  145. {
  146. return strcasecmp(strA.c_str(), strB.c_str()) == 0;
  147. }
  148. /**
  149. * Split a string with IMAP_HIERARCHY_DELIMITER into a vector
  150. *
  151. * @param[in] strInput Folder string with the IMAP_HIERARCHY_DELIMITER for a hierarchy
  152. * @param[out] vPaths Vector to all folders found in strInput
  153. * @retval hrSuccess
  154. */
  155. HRESULT IMAP::HrSplitPath(const wstring &strInputParam, vector<wstring> &vPaths) {
  156. std::wstring strInput = strInputParam;
  157. // Strip leading and trailing /
  158. if(strInput[0] == IMAP_HIERARCHY_DELIMITER)
  159. strInput.erase(0,1);
  160. if(strInput[strInput.size()-1] == IMAP_HIERARCHY_DELIMITER)
  161. strInput.erase(strInput.size()-1, 1);
  162. // split into parts
  163. vPaths = tokenize(strInput, IMAP_HIERARCHY_DELIMITER);
  164. return hrSuccess;
  165. }
  166. /**
  167. * Concatenates a vector to a string with IMAP_HIERARCHY_DELIMITER
  168. *
  169. * @param[in] vPaths Vector to all folders names
  170. * @param[out] strOutput Folder string with the IMAP_HIERARCHY_DELIMITER for a hierarchy
  171. * @retval hrSuccess
  172. */
  173. HRESULT IMAP::HrUnsplitPath(const vector<wstring> &vPaths, wstring &strOutput) {
  174. strOutput.clear();
  175. for (size_t i = 0; i < vPaths.size(); ++i) {
  176. strOutput += vPaths[i];
  177. if(i != vPaths.size()-1)
  178. strOutput += IMAP_HIERARCHY_DELIMITER;
  179. }
  180. return hrSuccess;
  181. }
  182. /**
  183. * Splits IMAP list input into a vector and appends result in return variable.
  184. *
  185. * @param[in] strInput Single IMAP command argument that should be split in words
  186. * @param[out] vWords vector of words found in the strInput
  187. * @retval hrSuccess
  188. */
  189. HRESULT IMAP::HrSplitInput(const string &strInput, vector<string> &vWords) {
  190. unsigned int uSpecialCount = 0;
  191. string::size_type beginPos = 0;
  192. string::size_type currentPos = 0;
  193. string::size_type findPos = strInput.find_first_of("\"()[] ", currentPos);
  194. string::size_type specialPos = string::npos;
  195. while (findPos != string::npos) {
  196. if (uSpecialCount == 0 && strInput[findPos] == '"') {
  197. // find corresponding " and add the string
  198. specialPos = findPos;
  199. do {
  200. specialPos = strInput.find_first_of("\"", specialPos + 1);
  201. } while (specialPos != string::npos && strInput[specialPos-1] == '\\');
  202. if (specialPos != string::npos) {
  203. vWords.push_back(strInput.substr(findPos + 1, specialPos - findPos - 1));
  204. findPos = specialPos;
  205. beginPos = findPos + 1;
  206. }
  207. } else if (strInput[findPos] == '(' || strInput[findPos] == '[') {
  208. ++uSpecialCount;
  209. } else if (strInput[findPos] == ')' || strInput[findPos] == ']') {
  210. if (uSpecialCount > 0)
  211. --uSpecialCount;
  212. } else if (uSpecialCount == 0) {
  213. if (findPos > beginPos)
  214. vWords.push_back(strInput.substr(beginPos, findPos - beginPos));
  215. beginPos = findPos + 1;
  216. }
  217. currentPos = findPos + 1;
  218. findPos = strInput.find_first_of("\"()[] ", currentPos);
  219. }
  220. if (beginPos < strInput.size())
  221. vWords.push_back(strInput.substr(beginPos));
  222. return hrSuccess;
  223. }
  224. /**
  225. * Send login greeting to client
  226. *
  227. * @param[in] strHostString optional hostname string
  228. *
  229. * @return
  230. */
  231. HRESULT IMAP::HrSendGreeting(const std::string &strHostString)
  232. {
  233. if (parseBool(lpConfig->GetSetting("server_hostname_greeting")))
  234. HrResponse(RESP_UNTAGGED, "OK [" + GetCapabilityString(false) + "] IMAP gateway ready" + strHostString);
  235. else
  236. HrResponse(RESP_UNTAGGED, "OK [" + GetCapabilityString(false) + "] IMAP gateway ready");
  237. return hrSuccess;
  238. }
  239. /**
  240. * Send client an error message that the socket will be closed by the server
  241. *
  242. * @param[in] strQuitMsg quit message for client
  243. * @return MAPI error code
  244. */
  245. HRESULT IMAP::HrCloseConnection(const std::string &strQuitMsg)
  246. {
  247. HrResponse(RESP_UNTAGGED, strQuitMsg);
  248. return hrSuccess;
  249. }
  250. /**
  251. * Execute the IMAP command from the client
  252. *
  253. * @param strInput received input from client
  254. *
  255. * @return MAPI error code
  256. */
  257. HRESULT IMAP::HrProcessCommand(const std::string &strInput)
  258. {
  259. HRESULT hr = hrSuccess;
  260. vector<string> strvResult;
  261. std::string strTag;
  262. std::string strCommand;
  263. ULONG ulMaxMessageSize = atoui(lpConfig->GetSetting("imap_max_messagesize"));
  264. static constexpr const struct {
  265. const char *command;
  266. int params;
  267. HRESULT (IMAP::*func)(const string &, const std::vector<std::string> &);
  268. } cmds[] = {
  269. {"SELECT", 1, &IMAP::HrCmdSelect<false>},
  270. {"EXAMINE", 1, &IMAP::HrCmdSelect<true>},
  271. {"LIST", 2, &IMAP::HrCmdList<false>},
  272. {"LSUB", 2, &IMAP::HrCmdList<true>},
  273. {"LOGIN", 2, &IMAP::HrCmdLogin},
  274. {"CREATE", 1, &IMAP::HrCmdCreate},
  275. {"DELETE", 1, &IMAP::HrCmdDelete},
  276. {"SUBSCRIBE", 1, &IMAP::HrCmdSubscribe<true>},
  277. {"UNSUBSCRIBE", 1, &IMAP::HrCmdSubscribe<false>},
  278. {"GETQUOTAROOT", 1, &IMAP::HrCmdGetQuotaRoot},
  279. {"GETQUOTA", 1, &IMAP::HrCmdGetQuota},
  280. {"SETQUOTA", 2, &IMAP::HrCmdSetQuota},
  281. {"RENAME", 2, &IMAP::HrCmdRename},
  282. {"STATUS", 2, &IMAP::HrCmdStatus}
  283. };
  284. static constexpr const struct {
  285. const char *command;
  286. HRESULT (IMAP::*func)(const std::string &);
  287. } cmds_zero_args[] = {
  288. {"CAPABILITY", &IMAP::HrCmdCapability},
  289. {"NOOP", &IMAP::HrCmdNoop<false>},
  290. {"LOGOUT", &IMAP::HrCmdLogout},
  291. {"STARTTLS", &IMAP::HrCmdStarttls},
  292. {"CHECK", &IMAP::HrCmdNoop<true>},
  293. {"CLOSE", &IMAP::HrCmdClose},
  294. {"IDLE", &IMAP::HrCmdIdle},
  295. {"NAMESPACE", &IMAP::HrCmdNamespace},
  296. {"STARTTLS", &IMAP::HrCmdStarttls}
  297. };
  298. if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
  299. lpLogger->Log(EC_LOGLEVEL_DEBUG, "< %s", strInput.c_str());
  300. HrSplitInput(strInput, strvResult);
  301. if (strvResult.empty())
  302. return MAPI_E_CALL_FAILED;
  303. if (strvResult.size() == 1) {
  304. // must be idle, and command must be done
  305. // DONE is not really a command, but the end of the IDLE command by the client marker
  306. strvResult[0] = strToUpper(strvResult[0]);
  307. if (strvResult[0].compare("DONE") == 0)
  308. return HrDone(true);
  309. HrResponse(RESP_UNTAGGED, "BAD Command not recognized");
  310. return hrSuccess;
  311. }
  312. while (hr == hrSuccess && !strvResult.empty() && strvResult.back().size() > 2 && strvResult.back()[0] == '{')
  313. {
  314. bool bPlus = false;
  315. char *lpcres = NULL;
  316. string inBuffer;
  317. string strByteTag;
  318. ULONG ulByteCount;
  319. strByteTag = strvResult.back().substr(1, strvResult.back().length() -1);
  320. ulByteCount = strtoul(strByteTag.c_str(), &lpcres, 10);
  321. if (lpcres == strByteTag.c_str() || (*lpcres != '}' && *lpcres != '+')) {
  322. // invalid tag received
  323. lpLogger->Log(EC_LOGLEVEL_ERROR, "Invalid size tag received: %s", strByteTag.c_str());
  324. return hr;
  325. }
  326. bPlus = (*lpcres == '+');
  327. // no need to output the
  328. if (!bPlus) {
  329. try {
  330. HrResponse(RESP_CONTINUE, "Ready for literal data");
  331. } catch (const KMAPIError &e) {
  332. lpLogger->Log(EC_LOGLEVEL_ERROR, "Error sending during continuation");
  333. return e.code();
  334. }
  335. }
  336. if (ulByteCount > ulMaxMessageSize) {
  337. lpLogger->Log(EC_LOGLEVEL_WARNING, "Discarding %d bytes of data", ulByteCount);
  338. hr = lpChannel->HrReadAndDiscardBytes(ulByteCount);
  339. if (hr != hrSuccess)
  340. break;
  341. } else {
  342. // @todo select for timeout
  343. hr = lpChannel->HrReadBytes(&inBuffer, ulByteCount);
  344. if (hr != hrSuccess)
  345. break;
  346. // replace size request with literal
  347. strvResult.back() = inBuffer;
  348. if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
  349. lpLogger->Log(EC_LOGLEVEL_DEBUG, "< <%d bytes data> %s", ulByteCount, inBuffer.c_str());
  350. }
  351. hr = lpChannel->HrReadLine(&inBuffer);
  352. if (hr != hrSuccess) {
  353. if (errno)
  354. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to read line: %s", strerror(errno));
  355. else
  356. lpLogger->Log(EC_LOGLEVEL_ERROR, "Client disconnected");
  357. break;
  358. }
  359. HrSplitInput(inBuffer, strvResult);
  360. if (ulByteCount > ulMaxMessageSize) {
  361. lpLogger->Log(EC_LOGLEVEL_ERROR, "Maximum message size reached (%u), message size is %u bytes", ulMaxMessageSize, ulByteCount);
  362. HrResponse(RESP_TAGGED_NO, strTag, "[ALERT] Maximum message size reached");
  363. hr = MAPI_E_NOT_ENOUGH_MEMORY;
  364. break;
  365. }
  366. }
  367. if (hr != hrSuccess)
  368. return hr;
  369. strTag = strvResult.front();
  370. strvResult.erase(strvResult.begin());
  371. strCommand = strvResult.front();
  372. strvResult.erase(strvResult.begin());
  373. strCommand = strToUpper(strCommand);
  374. if (isIdle()) {
  375. HrResponse(RESP_UNTAGGED, "BAD still in idle state");
  376. HrDone(false); // false for no output
  377. return hrSuccess;
  378. }
  379. // process {} and end of line
  380. for (const auto &cmd : cmds_zero_args) {
  381. if (strCommand.compare(cmd.command) != 0)
  382. continue;
  383. if (strvResult.size() == 0)
  384. return (this->*cmd.func)(strTag);
  385. HrResponse(RESP_TAGGED_BAD, strTag, std::string(cmd.command) +
  386. " must have 0 arguments");
  387. return hrSuccess;
  388. }
  389. for (const auto &cmd : cmds) {
  390. if (strCommand.compare(cmd.command) != 0)
  391. continue;
  392. if (strvResult.size() == cmd.params)
  393. return (this->*cmd.func)(strTag, strvResult);
  394. HrResponse(RESP_TAGGED_BAD, strTag, std::string(cmd.command) +
  395. " must have " + stringify(cmd.params) + " arguments");
  396. return hrSuccess;
  397. }
  398. if (strCommand.compare("AUTHENTICATE") == 0) {
  399. if (strvResult.size() == 1)
  400. return HrCmdAuthenticate(strTag, strvResult[0], string());
  401. else if (strvResult.size() == 2)
  402. return HrCmdAuthenticate(strTag, strvResult[0], strvResult[1]);
  403. HrResponse(RESP_TAGGED_BAD, strTag, "AUTHENTICATE must have 1 or 2 arguments");
  404. } else if (strCommand.compare("APPEND") == 0) {
  405. if (strvResult.size() == 2) {
  406. return HrCmdAppend(strTag, strvResult[0], strvResult[1]);
  407. } else if (strvResult.size() == 3) {
  408. if (strvResult[1][0] == '(')
  409. return HrCmdAppend(strTag, strvResult[0], strvResult[2], strvResult[1]);
  410. return HrCmdAppend(strTag, strvResult[0], strvResult[2], string(), strvResult[1]);
  411. } else if (strvResult.size() == 4) {
  412. // if both flags and time are given, it must be in that order
  413. return HrCmdAppend(strTag, strvResult[0], strvResult[3], strvResult[1], strvResult[2]);
  414. }
  415. HrResponse(RESP_TAGGED_BAD, strTag, "APPEND must have 2, 3 or 4 arguments");
  416. return hrSuccess;
  417. } else if (strCommand.compare("EXPUNGE") == 0) {
  418. if (!strvResult.empty()) {
  419. HrResponse(RESP_TAGGED_BAD, strTag, "EXPUNGE must have 0 arguments");
  420. return hrSuccess;
  421. }
  422. return HrCmdExpunge(strTag, string());
  423. } else if (strCommand.compare("SEARCH") == 0) {
  424. if (strvResult.empty()) {
  425. HrResponse(RESP_TAGGED_BAD, strTag, "SEARCH must have 1 or more arguments");
  426. return hrSuccess;
  427. }
  428. return HrCmdSearch(strTag, strvResult, false);
  429. } else if (strCommand.compare("FETCH") == 0) {
  430. if (strvResult.size() != 2) {
  431. HrResponse(RESP_TAGGED_BAD, strTag, "FETCH must have 2 arguments");
  432. return hrSuccess;
  433. }
  434. return HrCmdFetch(strTag, strvResult[0], strvResult[1], false);
  435. } else if (strCommand.compare("STORE") == 0) {
  436. if (strvResult.size() != 3) {
  437. HrResponse(RESP_TAGGED_BAD, strTag, "STORE must have 3 arguments");
  438. return hrSuccess;
  439. }
  440. return HrCmdStore(strTag, strvResult[0], strvResult[1], strvResult[2], false);
  441. } else if (strCommand.compare("COPY") == 0) {
  442. if (strvResult.size() != 2) {
  443. HrResponse(RESP_TAGGED_BAD, strTag, "COPY must have 2 arguments");
  444. return hrSuccess;
  445. }
  446. return HrCmdCopy(strTag, strvResult[0], strvResult[1], false);
  447. } else if (strCommand.compare("UID") == 0) {
  448. if (strvResult.empty()) {
  449. HrResponse(RESP_TAGGED_BAD, strTag, "UID must have a command");
  450. return hrSuccess;
  451. }
  452. strCommand = strvResult.front();
  453. strvResult.erase(strvResult.begin());
  454. strCommand = strToUpper(strCommand);
  455. if (strCommand.compare("SEARCH") == 0) {
  456. if (strvResult.empty()) {
  457. HrResponse(RESP_TAGGED_BAD, strTag, "UID SEARCH must have 1 or more arguments");
  458. return hrSuccess;
  459. }
  460. return HrCmdSearch(strTag, strvResult, true);
  461. } else if (strCommand.compare("FETCH") == 0) {
  462. if (strvResult.size() != 2) {
  463. HrResponse(RESP_TAGGED_BAD, strTag, "UID FETCH must have 2 arguments");
  464. return hrSuccess;
  465. }
  466. return HrCmdFetch(strTag, strvResult[0], strvResult[1], true);
  467. } else if (strCommand.compare("STORE") == 0) {
  468. if (strvResult.size() != 3) {
  469. HrResponse(RESP_TAGGED_BAD, strTag, "UID STORE must have 3 arguments");
  470. return hrSuccess;
  471. }
  472. return HrCmdStore(strTag, strvResult[0], strvResult[1], strvResult[2], true);
  473. } else if (strCommand.compare("COPY") == 0) {
  474. if (strvResult.size() != 2) {
  475. HrResponse(RESP_TAGGED_BAD, strTag, "UID COPY must have 2 arguments");
  476. return hrSuccess;
  477. }
  478. return HrCmdCopy(strTag, strvResult[0], strvResult[1], true);
  479. } else if (strCommand.compare("XAOL-MOVE") == 0) {
  480. if (strvResult.size() != 2) {
  481. HrResponse(RESP_TAGGED_BAD, strTag, "UID XAOL-MOVE must have 2 arguments");
  482. return hrSuccess;
  483. }
  484. return HrCmdUidXaolMove(strTag, strvResult[0], strvResult[1]);
  485. } else if (strCommand.compare("EXPUNGE") == 0) {
  486. if (strvResult.size() != 1) {
  487. HrResponse(RESP_TAGGED_BAD, strTag, "UID EXPUNGE must have 1 argument");
  488. return hrSuccess;
  489. }
  490. return HrCmdExpunge(strTag, strvResult[0]);
  491. } else {
  492. HrResponse(RESP_TAGGED_BAD, strTag, "UID Command not supported");
  493. }
  494. } else {
  495. HrResponse(RESP_TAGGED_BAD, strTag, "Command not supported");
  496. }
  497. return hrSuccess;
  498. }
  499. /**
  500. * Continue command, only supported for AUTHENTICATE command
  501. *
  502. * @param[in] strInput more input from client
  503. *
  504. * @return MAPI Error code
  505. */
  506. HRESULT IMAP::HrProcessContinue(const string &strInput)
  507. {
  508. return HrCmdAuthenticate(m_strContinueTag, "PLAIN", strInput);
  509. }
  510. /**
  511. * Returns the CAPABILITY string. In some stages, items can be listed
  512. * or not. This depends on the command received from the client, and
  513. * the logged on status of the user. Last state is autodetected in the
  514. * class.
  515. *
  516. * @param[in] bAllFlags Whether all capabilities should be placed in the string, and not only the pre-logon capabilities.
  517. *
  518. * @return The capabilities string
  519. */
  520. std::string IMAP::GetCapabilityString(bool bAllFlags)
  521. {
  522. string strCapabilities;
  523. const char *idle = lpConfig->GetSetting("imap_capability_idle");
  524. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  525. // capabilities we always have
  526. strCapabilities = "CAPABILITY IMAP4rev1 LITERAL+";
  527. if (lpSession == NULL) {
  528. // authentication capabilities
  529. if (!lpChannel->UsingSsl() && lpChannel->sslctx())
  530. strCapabilities += " STARTTLS";
  531. if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0)
  532. strCapabilities += " LOGINDISABLED";
  533. else
  534. strCapabilities += " AUTH=PLAIN";
  535. }
  536. if (lpSession || bAllFlags) {
  537. // capabilities after authentication
  538. strCapabilities += " CHILDREN XAOL-OPTION NAMESPACE QUOTA";
  539. if (idle && strcmp(idle, "yes") == 0)
  540. strCapabilities += " IDLE";
  541. }
  542. return strCapabilities;
  543. }
  544. /**
  545. * @brief Handles the CAPABILITY command
  546. *
  547. * Sends all the gateway capabilities to the client, depending on the
  548. * state we're in. Authentication capabilities are skipped when a user
  549. * was already logged in.
  550. *
  551. * @param[in] strTag the IMAP tag for this command
  552. *
  553. * @return hrSuccess
  554. */
  555. HRESULT IMAP::HrCmdCapability(const string &strTag) {
  556. HrResponse(RESP_UNTAGGED, GetCapabilityString(true));
  557. HrResponse(RESP_TAGGED_OK, strTag, "CAPABILITY Completed");
  558. return hrSuccess;
  559. }
  560. /**
  561. * @brief Handles the NOOP command
  562. *
  563. * Checks the current selected folder for changes, and possebly sends
  564. * the EXISTS and/or RECENT values to the client.
  565. *
  566. * @param[in] strTag the IMAP tag for this command
  567. *
  568. * @return hrSuccess
  569. */
  570. HRESULT IMAP::HrCmdNoop(const std::string &strTag, bool check)
  571. {
  572. HRESULT hr = hrSuccess;
  573. if (!strCurrentFolder.empty() || check)
  574. hr = HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
  575. if (hr != hrSuccess) {
  576. HrResponse(RESP_TAGGED_BAD, strTag, (check ? std::string("CHECK") : std::string("NOOP")) + " completed");
  577. return hr;
  578. }
  579. HrResponse(RESP_TAGGED_OK, strTag, (check ? std::string("CHECK") : std::string("NOOP")) + " completed");
  580. return hrSuccess;
  581. }
  582. template<bool check> HRESULT IMAP::HrCmdNoop(const std::string &strTag)
  583. {
  584. return HrCmdNoop(strTag, check);
  585. }
  586. /**
  587. * @brief Handles the LOGOUT command
  588. *
  589. * Sends the BYE response to the client. HandleIMAP() will close the
  590. * connection.
  591. *
  592. * @param[in] strTag the IMAP tag for this command
  593. *
  594. * @return hrSuccess
  595. */
  596. HRESULT IMAP::HrCmdLogout(const string &strTag) {
  597. HrResponse(RESP_UNTAGGED, "BYE server logging out");
  598. HrResponse(RESP_TAGGED_OK, strTag, "LOGOUT completed");
  599. /* Let the gateway quit from the socket read loop. */
  600. return MAPI_E_END_OF_SESSION;
  601. }
  602. /**
  603. * @brief Handles the STARTTLS command
  604. *
  605. * Tries to set the current connection to use SSL encryption.
  606. *
  607. * @param[in] strTag the IMAP tag for this command
  608. *
  609. * @return hrSuccess
  610. */
  611. HRESULT IMAP::HrCmdStarttls(const string &strTag) {
  612. if (!lpChannel->sslctx())
  613. HrResponse(RESP_TAGGED_NO, strTag, "STARTTLS error in ssl context");
  614. if (lpChannel->UsingSsl())
  615. HrResponse(RESP_TAGGED_NO, strTag, "STARTTLS already using SSL/TLS");
  616. HrResponse(RESP_TAGGED_OK, strTag, "Begin TLS negotiation now");
  617. auto hr = lpChannel->HrEnableTLS();
  618. if (hr != hrSuccess) {
  619. HrResponse(RESP_TAGGED_BAD, strTag, "[ALERT] Error switching to secure SSL/TLS connection");
  620. lpLogger->Log(EC_LOGLEVEL_ERROR, "Error switching to SSL in STARTTLS");
  621. /* Let the gateway quit from the socket read loop. */
  622. return MAPI_E_END_OF_SESSION;
  623. }
  624. if (lpChannel->UsingSsl())
  625. lpLogger->Log(EC_LOGLEVEL_INFO, "Using SSL now");
  626. return hrSuccess;
  627. }
  628. /**
  629. * @brief Handles the AUTENTICATE command
  630. *
  631. * The authenticate command only implements the PLAIN authentication
  632. * method, since we require the actual password, and not just a hash
  633. * value.
  634. *
  635. * According to the RFC, the data in strAuthData is UTF-8, but thunderbird still sends iso-8859-X.
  636. *
  637. * @param[in] strTag The IMAP tag for this command
  638. * @param[in] strAuthMethod Must be set to PLAIN
  639. * @param[in] strAuthData (optional) if empty we use a continuation request, otherwise we can authenticate, base64 encoded string
  640. *
  641. * @return MAPI error code
  642. */
  643. HRESULT IMAP::HrCmdAuthenticate(const string &strTag, string strAuthMethod, const string &strAuthData)
  644. {
  645. vector<string> vAuth;
  646. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  647. // If plaintext authentication was disabled any authentication attempt must be refused very soon
  648. if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
  649. HrResponse(RESP_TAGGED_NO, strTag, "[PRIVACYREQUIRED] Plaintext authentication disallowed on non-secure "
  650. "(SSL/TLS) connections.");
  651. lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s without username (tried to use disallowed plaintext auth)",
  652. lpChannel->peer_addr());
  653. return hrSuccess;
  654. }
  655. strAuthMethod = strToUpper(strAuthMethod);
  656. if (strAuthMethod.compare("PLAIN") != 0) {
  657. HrResponse(RESP_TAGGED_NO, strTag, "AUTHENTICATE " + strAuthMethod + " method not supported");
  658. return MAPI_E_NO_SUPPORT;
  659. }
  660. if (strAuthData.empty() && !m_bContinue) {
  661. // request the rest of the authentication data by sending one space in a continuation line
  662. HrResponse(RESP_CONTINUE, string());
  663. m_strContinueTag = strTag;
  664. m_bContinue = true;
  665. return MAPI_W_PARTIAL_COMPLETION;
  666. }
  667. m_bContinue = false;
  668. vAuth = tokenize(base64_decode(strAuthData), '\0');
  669. // vAuth[0] is the authorization name (ignored for now, but maybe we can use this for opening other stores?)
  670. // vAuth[1] is the authentication name
  671. // vAuth[2] is the password for vAuth[1]
  672. if (vAuth.size() != 3) {
  673. lpLogger->Log(EC_LOGLEVEL_INFO, "Invalid authentication data received, expected 3 items, have %zu items.", vAuth.size());
  674. HrResponse(RESP_TAGGED_NO, strTag, "AUTHENTICATE " + strAuthMethod + " incomplete data received");
  675. return MAPI_E_LOGON_FAILED;
  676. }
  677. return HrCmdLogin(strTag, {vAuth[1], vAuth[2]});
  678. }
  679. /**
  680. * @brief Handles the LOGIN command
  681. *
  682. * Opens a MAPI session, addressbook and possebly open the public if
  683. * the username and password are correctly entered.
  684. *
  685. * @param[in] strTag IMAP tag
  686. * @param[in] strUser Username, currently tried as windows-1252 charsets
  687. * @param[in] strPass Password, currently tried as windows-1252 charsets
  688. *
  689. * @return MAPI error code
  690. */
  691. HRESULT IMAP::HrCmdLogin(const std::string &strTag,
  692. const std::vector<std::string> &args)
  693. {
  694. HRESULT hr = hrSuccess;
  695. string strUsername;
  696. size_t i;
  697. wstring strwUsername;
  698. wstring strwPassword;
  699. unsigned int flags;
  700. const char *plain = lpConfig->GetSetting("disable_plaintext_auth");
  701. const std::string &strUser = args[0], &strPass = args[1];
  702. // strUser isn't sent in imap style utf-7, but \ is escaped, so strip those
  703. for (i = 0; i < strUser.length(); ++i) {
  704. if (strUser[i] == '\\' && i+1 < strUser.length() && (strUser[i+1] == '"' || strUser[i+1] == '\\'))
  705. ++i;
  706. strUsername += strUser[i];
  707. }
  708. // If plaintext authentication was disabled any login attempt must be refused very soon
  709. if (!lpChannel->UsingSsl() && lpChannel->sslctx() && plain && strcmp(plain, "yes") == 0 && lpChannel->peer_is_local() <= 0) {
  710. HrResponse(RESP_UNTAGGED, "BAD [ALERT] Plaintext authentication not allowed without SSL/TLS, but your client "
  711. "did it anyway. If anyone was listening, the password was exposed.");
  712. HrResponse(RESP_TAGGED_NO, strTag, "[PRIVACYREQUIRED] Plaintext authentication disallowed on non-secure "
  713. "(SSL/TLS) connections.");
  714. lpLogger->Log(EC_LOGLEVEL_ERROR, "Aborted login from %s with username \"%s\" (tried to use disallowed plaintext auth)",
  715. lpChannel->peer_addr(), strUsername.c_str());
  716. goto exitpm;
  717. }
  718. if (lpSession != NULL) {
  719. lpLogger->Log(EC_LOGLEVEL_INFO, "Ignoring to login TWICE for username \"%s\"", strUsername.c_str());
  720. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN Can't login twice");
  721. // hr = MAPI_E_CALL_FAILED;
  722. goto exitpm;
  723. }
  724. hr = TryConvert(strUsername, rawsize(strUsername), "windows-1252", strwUsername);
  725. if (hr != hrSuccess) {
  726. lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in username");
  727. goto exitpm;
  728. }
  729. hr = TryConvert(strPass, rawsize(strPass), "windows-1252", strwPassword);
  730. if (hr != hrSuccess) {
  731. lpLogger->Log(EC_LOGLEVEL_ERROR, "Illegal byte sequence in password");
  732. goto exitpm;
  733. }
  734. flags = EC_PROFILE_FLAGS_NO_COMPRESSION;
  735. if (!parseBool(lpConfig->GetSetting("bypass_auth")))
  736. flags |= EC_PROFILE_FLAGS_NO_UID_AUTH;
  737. // do not disable notifications for imap connections, may be idle and sessions on the storage server will disappear.
  738. hr = HrOpenECSession(&lpSession, "gateway/imap", PROJECT_SVN_REV_STR,
  739. strwUsername.c_str(), strwPassword.c_str(), m_strPath.c_str(),
  740. flags, NULL, NULL);
  741. if (hr != hrSuccess) {
  742. lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to login from %s with invalid username \"%s\" or wrong password. Error: 0x%08X",
  743. lpChannel->peer_addr(), strUsername.c_str(), hr);
  744. if (hr == MAPI_E_LOGON_FAILED)
  745. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN wrong username or password");
  746. else
  747. HrResponse(RESP_TAGGED_BAD, strTag, "Internal error: OpenECSession failed");
  748. ++m_ulFailedLogins;
  749. if (m_ulFailedLogins >= LOGIN_RETRIES)
  750. // disconnect client
  751. hr = MAPI_E_END_OF_SESSION;
  752. goto exitpm;
  753. }
  754. hr = HrOpenDefaultStore(lpSession, &lpStore);
  755. if (hr != hrSuccess) {
  756. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open default store");
  757. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't open default store");
  758. goto exitpm;
  759. }
  760. hr = lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &lpAddrBook);
  761. if (hr != hrSuccess) {
  762. lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open addressbook");
  763. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't open addressbook");
  764. goto exitpm;
  765. }
  766. // check if imap access is disabled
  767. if (isFeatureDisabled("imap", lpAddrBook, lpStore)) {
  768. lpLogger->Log(EC_LOGLEVEL_ERROR, "IMAP not enabled for user '%s'", strUsername.c_str());
  769. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN imap feature disabled");
  770. hr = MAPI_E_LOGON_FAILED;
  771. goto exitpm;
  772. }
  773. m_strwUsername = strwUsername;
  774. {
  775. PROPMAP_START(1)
  776. PROPMAP_NAMED_ID(ENVELOPE, PT_STRING8, PS_EC_IMAP, dispidIMAPEnvelope);
  777. PROPMAP_INIT(lpStore);
  778. hr = MAPIAllocateBuffer(CbNewSPropTagArray(4), &~m_lpsIMAPTags);
  779. if (hr != hrSuccess)
  780. goto exitpm;
  781. m_lpsIMAPTags->aulPropTag[0] = PROP_ENVELOPE;
  782. }
  783. hr = HrGetSubscribedList();
  784. // ignore error, empty list of subscribed folder
  785. if(bShowPublicFolder){
  786. hr = HrOpenECPublicStore(lpSession, &lpPublicStore);
  787. if (hr != hrSuccess) {
  788. lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to open public store");
  789. lpPublicStore = NULL;
  790. }
  791. }
  792. hr = HrMakeSpecialsList();
  793. if (hr != hrSuccess) {
  794. lpLogger->Log(EC_LOGLEVEL_WARNING, "Failed to find special folder properties");
  795. HrResponse(RESP_TAGGED_NO, strTag, "LOGIN can't find special folder properties");
  796. goto exitpm;
  797. }
  798. lpLogger->Log(EC_LOGLEVEL_NOTICE, "IMAP Login from %s for user %s", lpChannel->peer_addr(), strUsername.c_str());
  799. HrResponse(RESP_TAGGED_OK, strTag, "[" + GetCapabilityString(false) + "] LOGIN completed");
  800. exitpm:
  801. if (hr != hrSuccess)
  802. CleanupObject();
  803. return hr;
  804. }
  805. /**
  806. * @brief Handles the SELECT and EXAMINE commands
  807. *
  808. * Make the strFolder the current working folder.
  809. *
  810. * @param[in] strTag IMAP command tag
  811. * @param[in] strFolder IMAP folder name in UTF-7 something charset
  812. * @param[in] bReadOnly The EXAMINE command was given instead of the SELECT command
  813. */
  814. HRESULT IMAP::HrCmdSelect(const std::string &strTag,
  815. const std::vector<std::string> &args, bool bReadOnly)
  816. {
  817. HRESULT hr = hrSuccess;
  818. char szResponse[IMAP_RESP_MAX + 1];
  819. unsigned int ulUnseen = 0;
  820. string command = "SELECT";
  821. ULONG ulUIDValidity = 1;
  822. const std::string &strFolder = args[0];
  823. if (bReadOnly)
  824. command = "EXAMINE";
  825. if (!lpSession) {
  826. HrResponse(RESP_TAGGED_NO, strTag, command + " error no session");
  827. return MAPI_E_CALL_FAILED;
  828. }
  829. // close old contents table if cached version was open
  830. ReleaseContentsCache();
  831. // Apple mail client does this request, so we need to block it.
  832. if (strFolder.empty()) {
  833. HrResponse(RESP_TAGGED_NO, strTag, command + " invalid folder name");
  834. return MAPI_E_CALL_FAILED;
  835. }
  836. hr = IMAP2MAPICharset(strFolder, strCurrentFolder);
  837. if (hr != hrSuccess) {
  838. HrResponse(RESP_TAGGED_NO, strTag, command + " invalid folder name");
  839. return hr;
  840. }
  841. bCurrentFolderReadOnly = bReadOnly;
  842. hr = HrRefreshFolderMails(true, !bCurrentFolderReadOnly, &ulUnseen, &ulUIDValidity);
  843. if (hr != hrSuccess) {
  844. HrResponse(RESP_TAGGED_NO, strTag, command + " error getting mails in folder");
  845. return hr;
  846. }
  847. // \Seen = PR_MESSAGE_FLAGS MSGFLAG_READ
  848. // \Answered = PR_MSG_STATUS MSGSTATUS_ANSWERED //PR_LAST_VERB_EXECUTED EXCHIVERB_REPLYTOSENDER EXCHIVERB_REPLYTOALL
  849. // \Draft = PR_MSG_STATUS MSGSTATUS_DRAFT //PR_MESSAGE_FLAGS MSGFLAG_UNSENT
  850. // \Flagged = PR_FLAG_STATUS
  851. // \Deleted = PR_MSG_STATUS MSGSTATUS_DELMARKED
  852. // \Recent = ??? (arrived after last command/login)
  853. // $Forwarded = PR_LAST_VERB_EXECUTED: NOTEIVERB_FORWARD
  854. HrResponse(RESP_UNTAGGED, "FLAGS (\\Seen \\Draft \\Deleted \\Flagged \\Answered $Forwarded)");
  855. HrResponse(RESP_UNTAGGED, "OK [PERMANENTFLAGS (\\Seen \\Draft \\Deleted \\Flagged \\Answered $Forwarded)] Permanent flags");
  856. snprintf(szResponse, IMAP_RESP_MAX, "OK [UIDNEXT %u] Predicted next UID", m_ulLastUid + 1);
  857. HrResponse(RESP_UNTAGGED, szResponse);
  858. if(ulUnseen) {
  859. snprintf(szResponse, IMAP_RESP_MAX, "OK [UNSEEN %u] First unseen message", ulUnseen);
  860. HrResponse(RESP_UNTAGGED, szResponse);
  861. }
  862. snprintf(szResponse, IMAP_RESP_MAX, "OK [UIDVALIDITY %u] UIDVALIDITY value", ulUIDValidity);
  863. HrResponse(RESP_UNTAGGED, szResponse);
  864. if (bReadOnly)
  865. HrResponse(RESP_TAGGED_OK, strTag, "[READ-ONLY] EXAMINE completed");
  866. else
  867. HrResponse(RESP_TAGGED_OK, strTag, "[READ-WRITE] SELECT completed");
  868. return hrSuccess;
  869. }
  870. template<bool read_only>
  871. HRESULT IMAP::HrCmdSelect(const std::string &strTag,
  872. const std::vector<std::string> &args)
  873. {
  874. return HrCmdSelect(strTag, args, read_only);
  875. }
  876. /**
  877. * @brief Handles the CREATE command
  878. *
  879. * Recursively create a new folders, starting from the root folder.
  880. *
  881. * @param[in] strTag the IMAP tag for this command
  882. * @param[in] strFolderParam The foldername, encoded in IMAP UTF-7 charset
  883. *
  884. * @return MAPI Error code
  885. */
  886. HRESULT IMAP::HrCmdCreate(const std::string &strTag,
  887. const std::vector<std::string> &args)
  888. {
  889. HRESULT hr = hrSuccess;
  890. object_ptr<IMAPIFolder> lpFolder, lpSubFolder;
  891. vector<wstring> strPaths;
  892. wstring strFolder;
  893. wstring strPath;
  894. SPropValue sFolderClass;
  895. const std::string &strFolderParam = args[0];
  896. if (!lpSession) {
  897. HrResponse(RESP_TAGGED_NO, strTag, "CREATE error no session");
  898. hr = MAPI_E_CALL_FAILED;
  899. goto exit;
  900. }
  901. if (strFolderParam.empty()) {
  902. HrResponse(RESP_TAGGED_NO, strTag, "CREATE error no folder");
  903. hr = MAPI_E_CALL_FAILED;
  904. goto exit;
  905. }
  906. hr = IMAP2MAPICharset(strFolderParam, strFolder);
  907. if (hr != hrSuccess) {
  908. HrResponse(RESP_TAGGED_NO, strTag, "CREATE invalid folder name");
  909. goto exit;
  910. }
  911. if (strFolder[0] == IMAP_HIERARCHY_DELIMITER) {
  912. // courier and dovecot also block this
  913. HrResponse(RESP_TAGGED_NO, strTag, "CREATE invalid folder name");
  914. goto exit;
  915. }
  916. hr = HrFindFolderPartial(strFolder, &~lpFolder, &strPath);
  917. if (hr != hrSuccess) {
  918. HrResponse(RESP_TAGGED_NO, strTag, "CREATE error opening destination folder");
  919. goto exit;
  920. }
  921. if (strPath.empty()) {
  922. HrResponse(RESP_TAGGED_NO, strTag, "CREATE folder already exists");
  923. goto exit;
  924. }
  925. strPaths = tokenize(strPath, IMAP_HIERARCHY_DELIMITER);
  926. for (const auto &path : strPaths) {
  927. hr = lpFolder->CreateFolder(FOLDER_GENERIC, const_cast<TCHAR *>(path.c_str()), nullptr, nullptr, MAPI_UNICODE, &~lpSubFolder);
  928. if (hr != hrSuccess) {
  929. if (hr == MAPI_E_COLLISION)
  930. HrResponse(RESP_TAGGED_NO, strTag, "CREATE folder already exists");
  931. else
  932. HrResponse(RESP_TAGGED_NO, strTag, "CREATE can't create folder");
  933. goto exit;
  934. }
  935. sFolderClass.ulPropTag = PR_CONTAINER_CLASS_A;
  936. sFolderClass.Value.lpszA = const_cast<char *>("IPF.Note");
  937. hr = HrSetOneProp(lpSubFolder, &sFolderClass);
  938. if (hr != hrSuccess)
  939. goto exit;
  940. lpFolder = std::move(lpSubFolder);
  941. }
  942. HrResponse(RESP_TAGGED_OK, strTag, "CREATE completed");
  943. exit:
  944. cached_folders.clear();
  945. return hr;
  946. }
  947. /**
  948. * @brief Handles the DELETE command
  949. *
  950. * Deletes folders, starting from the root folder. Special MAPI
  951. * folders may not be deleted. The folder will be removed from the
  952. * subscribed list, if it was subscribed.
  953. *
  954. * @test write test which checks if delete removes folder from subscribed list
  955. *
  956. * @param[in] strTag the IMAP tag for this command
  957. * @param[in] strFolderParam The foldername, encoded in IMAP UTF-7 charset
  958. *
  959. * @return MAPI Error code
  960. */
  961. HRESULT IMAP::HrCmdDelete(const std::string &strTag,
  962. const std::vector<std::string> &args)
  963. {
  964. HRESULT hr = hrSuccess;
  965. object_ptr<IMAPIFolder> lpParentFolder;
  966. ULONG cbEntryID;
  967. memory_ptr<ENTRYID> lpEntryID;
  968. wstring strFolder;
  969. const std::string &strFolderParam = args[0];
  970. if (!lpSession) {
  971. HrResponse(RESP_TAGGED_NO, strTag, "DELETE error no session");
  972. hr = MAPI_E_CALL_FAILED;
  973. goto exit;
  974. }
  975. hr = IMAP2MAPICharset(strFolderParam, strFolder);
  976. if (hr != hrSuccess) {
  977. HrResponse(RESP_TAGGED_NO, strTag, "DELETE invalid folder name");
  978. goto exit;
  979. }
  980. strFolder = strToUpper(strFolder);
  981. if (strFolder.compare(L"INBOX") == 0) {
  982. HrResponse(RESP_TAGGED_NO, strTag, "DELETE error deleting INBOX is not allowed");
  983. hr = MAPI_E_CALL_FAILED;
  984. goto exit;
  985. }
  986. hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
  987. if (hr != hrSuccess) {
  988. HrResponse(RESP_TAGGED_NO, strTag, "DELETE error folder not found");
  989. goto exit;
  990. }
  991. if (IsSpecialFolder(cbEntryID, lpEntryID)) {
  992. HrResponse(RESP_TAGGED_NO, strTag, "DELETE special folder may not be deleted");
  993. goto exit;
  994. }
  995. hr = HrOpenParentFolder(cbEntryID, lpEntryID, &~lpParentFolder);
  996. if (hr != hrSuccess) {
  997. HrResponse(RESP_TAGGED_NO, strTag, "DELETE error opening parent folder");
  998. goto exit;
  999. }
  1000. hr = lpParentFolder->DeleteFolder(cbEntryID, lpEntryID, 0, NULL, DEL_FOLDERS | DEL_MESSAGES);
  1001. if (hr != hrSuccess) {
  1002. HrResponse(RESP_TAGGED_NO, strTag, "DELETE error deleting folder");
  1003. goto exit;
  1004. }
  1005. // remove from subscribed list
  1006. hr = ChangeSubscribeList(false, cbEntryID, lpEntryID);
  1007. if (hr != hrSuccess) {
  1008. lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to update subscribed list for deleted folder '%ls'", strFolder.c_str());
  1009. hr = hrSuccess;
  1010. }
  1011. // close folder if it was selected
  1012. if (strCurrentFolder == strFolder) {
  1013. strCurrentFolder.clear();
  1014. // close old contents table if cached version was open
  1015. ReleaseContentsCache();
  1016. }
  1017. HrResponse(RESP_TAGGED_OK, strTag, "DELETE completed");
  1018. exit:
  1019. cached_folders.clear();
  1020. return hr;
  1021. }
  1022. /**
  1023. * @brief Handles the RENAME command
  1024. *
  1025. * Renames or moves a folder. A folder cannot be moved under itself. A
  1026. * folder cannot be renamed to another existing folder.
  1027. *
  1028. * @param[in] strTag the IMAP tag for this command
  1029. * @param[in] strExistingFolderParam the folder to rename or move, in IMAP UTF-7 charset
  1030. * @param[in] strNewFolderParam the new folder name, in IMAP UTF-7 charset
  1031. *
  1032. * @return MAPI Error code
  1033. */
  1034. HRESULT IMAP::HrCmdRename(const std::string &strTag,
  1035. const std::vector<std::string> &args)
  1036. {
  1037. HRESULT hr = hrSuccess;
  1038. memory_ptr<SPropValue> lppvFromEntryID, lppvDestEntryID;
  1039. object_ptr<IMAPIFolder> lpParentFolder, lpMakeFolder, lpSubFolder;
  1040. ULONG ulObjType = 0;
  1041. string::size_type deliPos;
  1042. ULONG cbMovFolder = 0;
  1043. memory_ptr<ENTRYID> lpMovFolder;
  1044. wstring strExistingFolder;
  1045. wstring strNewFolder;
  1046. wstring strPath;
  1047. wstring strFolder;
  1048. SPropValue sFolderClass;
  1049. const std::string &strExistingFolderParam = args[0];
  1050. const std::string &strNewFolderParam = args[1];
  1051. if (!lpSession) {
  1052. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error no session");
  1053. hr = MAPI_E_CALL_FAILED;
  1054. goto exit;
  1055. }
  1056. hr = IMAP2MAPICharset(strExistingFolderParam, strExistingFolder);
  1057. if (hr != hrSuccess) {
  1058. HrResponse(RESP_TAGGED_NO, strTag, "RENAME invalid folder name");
  1059. goto exit;
  1060. }
  1061. hr = IMAP2MAPICharset(strNewFolderParam, strNewFolder);
  1062. if (hr != hrSuccess) {
  1063. HrResponse(RESP_TAGGED_NO, strTag, "RENAME invalid folder name");
  1064. goto exit;
  1065. }
  1066. strExistingFolder = strToUpper(strExistingFolder);
  1067. if (strExistingFolder.compare(L"INBOX") == 0) {
  1068. // FIXME, rfc text:
  1069. //
  1070. // Renaming INBOX is permitted, and has special behavior. It moves
  1071. // all messages in INBOX to a new mailbox with the given name,
  1072. // leaving INBOX empty. If the server implementation supports
  1073. // inferior hierarchical names of INBOX, these are unaffected by a
  1074. // rename of INBOX.
  1075. hr = MAPI_E_CALL_FAILED;
  1076. goto exit;
  1077. }
  1078. hr = HrFindFolderEntryID(strExistingFolder, &cbMovFolder, &~lpMovFolder);
  1079. if (hr != hrSuccess) {
  1080. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error source folder not found");
  1081. goto exit;
  1082. }
  1083. if (IsSpecialFolder(cbMovFolder, lpMovFolder)) {
  1084. HrResponse(RESP_TAGGED_NO, strTag, "RENAME special folder may not be moved or renamed");
  1085. goto exit;
  1086. }
  1087. hr = HrOpenParentFolder(cbMovFolder, lpMovFolder, &~lpParentFolder);
  1088. if (hr != hrSuccess) {
  1089. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening parent folder");
  1090. goto exit;
  1091. }
  1092. // Find the folder as far as we can
  1093. hr = HrFindFolderPartial(strNewFolder, &~lpMakeFolder, &strPath);
  1094. if(hr != hrSuccess) {
  1095. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening destination folder");
  1096. goto exit;
  1097. }
  1098. if (strPath.empty()) {
  1099. HrResponse(RESP_TAGGED_NO, strTag, "RENAME destination already exists");
  1100. goto exit;
  1101. }
  1102. // strPath now contains subfolder we want to create (eg sub/new). So now we have to
  1103. // mkdir -p all the folder leading up to the last (if any)
  1104. do {
  1105. deliPos = strPath.find(IMAP_HIERARCHY_DELIMITER);
  1106. if (deliPos == string::npos) {
  1107. strFolder = strPath;
  1108. continue;
  1109. }
  1110. strFolder = strPath.substr(0, deliPos);
  1111. strPath = strPath.substr(deliPos + 1);
  1112. if (!strFolder.empty())
  1113. hr = lpMakeFolder->CreateFolder(FOLDER_GENERIC, (TCHAR *)strFolder.c_str(), nullptr, nullptr, MAPI_UNICODE | OPEN_IF_EXISTS, &~lpSubFolder);
  1114. if (hr != hrSuccess || lpSubFolder == NULL) {
  1115. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error creating folder");
  1116. goto exit;
  1117. }
  1118. sFolderClass.ulPropTag = PR_CONTAINER_CLASS_A;
  1119. sFolderClass.Value.lpszA = const_cast<char *>("IPF.Note");
  1120. hr = HrSetOneProp(lpSubFolder, &sFolderClass);
  1121. if (hr != hrSuccess)
  1122. goto exit;
  1123. lpMakeFolder = std::move(lpSubFolder);
  1124. } while (deliPos != string::npos);
  1125. if (HrGetOneProp(lpParentFolder, PR_ENTRYID, &~lppvFromEntryID) != hrSuccess ||
  1126. HrGetOneProp(lpMakeFolder, PR_ENTRYID, &~lppvDestEntryID) != hrSuccess) {
  1127. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening source or destination");
  1128. goto exit;
  1129. }
  1130. // When moving in the same folder, just rename
  1131. if (lppvFromEntryID->Value.bin.cb != lppvDestEntryID->Value.bin.cb || memcmp(lppvFromEntryID->Value.bin.lpb, lppvDestEntryID->Value.bin.lpb, lppvDestEntryID->Value.bin.cb) != 0) {
  1132. // Do the real move
  1133. hr = lpParentFolder->CopyFolder(cbMovFolder, lpMovFolder, &IID_IMAPIFolder, lpMakeFolder,
  1134. (TCHAR *) strFolder.c_str(), 0, NULL, MAPI_UNICODE | FOLDER_MOVE);
  1135. } else {
  1136. // from is same as dest folder, use SetProps(PR_DISPLAY_NAME)
  1137. SPropValue propName;
  1138. propName.ulPropTag = PR_DISPLAY_NAME_W;
  1139. propName.Value.lpszW = (WCHAR*)strFolder.c_str();
  1140. hr = lpSession->OpenEntry(cbMovFolder, lpMovFolder, &IID_IMAPIFolder, MAPI_MODIFY,
  1141. &ulObjType, &~lpSubFolder);
  1142. if (hr != hrSuccess) {
  1143. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error opening folder");
  1144. goto exit;
  1145. }
  1146. hr = lpSubFolder->SetProps(1, &propName, NULL);
  1147. }
  1148. if (hr != hrSuccess) {
  1149. HrResponse(RESP_TAGGED_NO, strTag, "RENAME error moving folder");
  1150. goto exit;
  1151. }
  1152. HrResponse(RESP_TAGGED_OK, strTag, "RENAME completed");
  1153. exit:
  1154. cached_folders.clear();
  1155. return hr;
  1156. }
  1157. /**
  1158. * @brief Handles the SUBSCRIBE and UNSUBSCRIBE commands
  1159. *
  1160. * Subscribe or unsubscribe the given folder. Special IMAP folders
  1161. * cannot be unsubscribed. Subscribed folders are listed with the LSUB
  1162. * command.
  1163. *
  1164. * @note we differ here from other IMAP server, who do allow
  1165. * unsubscribing the INBOX (which is normally the only special IMAP
  1166. * folder)
  1167. *
  1168. * @param[in] strTag the IMAP tag for this command
  1169. * @param[in] strFolderParam The folder to (un)subscribe, in IMAP UTF-7 charset
  1170. * @param[in] bSubscribe subscribe (true) or unsubscribe (false)
  1171. *
  1172. * @return MAPI Error code
  1173. */
  1174. HRESULT IMAP::HrCmdSubscribe(const std::string &strTag,
  1175. const std::vector<std::string> &args, bool bSubscribe)
  1176. {
  1177. HRESULT hr = hrSuccess;
  1178. string strAction;
  1179. ULONG cbEntryID = 0;
  1180. memory_ptr<ENTRYID> lpEntryID;
  1181. wstring strFolder;
  1182. const std::string &strFolderParam = args[0];
  1183. if (bSubscribe)
  1184. strAction = "SUBSCRIBE";
  1185. else
  1186. strAction = "UNSUBSCRIBE";
  1187. if (!lpSession) {
  1188. HrResponse(RESP_TAGGED_NO, strTag, strAction + " error no session");
  1189. return MAPI_E_CALL_FAILED;
  1190. }
  1191. hr = IMAP2MAPICharset(strFolderParam, strFolder);
  1192. if (hr != hrSuccess) {
  1193. HrResponse(RESP_TAGGED_NO, strTag, strAction + " invalid folder name");
  1194. return hr;
  1195. }
  1196. hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
  1197. if (hr != hrSuccess) {
  1198. // folder not found, but not error, so thunderbird updates view correctly.
  1199. HrResponse(RESP_TAGGED_OK, strTag, strAction + " folder not found");
  1200. return hr;
  1201. }
  1202. if (IsSpecialFolder(cbEntryID, lpEntryID)) {
  1203. if (!bSubscribe)
  1204. HrResponse(RESP_TAGGED_NO, strTag, strAction + " cannot unsubscribe this special folder");
  1205. else
  1206. HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
  1207. return hrSuccess;
  1208. }
  1209. hr = ChangeSubscribeList(bSubscribe, cbEntryID, lpEntryID);
  1210. if (hr != hrSuccess) {
  1211. HrResponse(RESP_TAGGED_NO, strTag, strAction + " writing subscriptions to server failed");
  1212. return hr;
  1213. }
  1214. HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
  1215. return hrSuccess;
  1216. }
  1217. template<bool subscribe>
  1218. HRESULT IMAP::HrCmdSubscribe(const std::string &tag,
  1219. const std::vector<std::string> &args)
  1220. {
  1221. return HrCmdSubscribe(tag, args, subscribe);
  1222. }
  1223. /**
  1224. * @brief Handles the LIST and LSUB commands
  1225. *
  1226. * Lists all or subscribed folders, with wildcard filtering. Supported
  1227. * folder flags are \Noselect and \Has(No)Children.
  1228. * @todo I don't think we check the \Noselect in the SELECT/EXAMINE command
  1229. * @test with strFindFolder in UTF-7 with wildcards
  1230. *
  1231. * @param[in] strTag the IMAP tag for this command
  1232. * @param[in] strReferenceFolder list folders below this folder, in IMAP UTF-7 charset
  1233. * @param[in] strFindFolder list folders with names matching this name with/or wildcards (* and %) , in IMAP UTF-7 charset
  1234. * @param[in] bSubscribedOnly Behave for the LIST command (false) or LSUB command (true)
  1235. *
  1236. * @return MAPI Error code
  1237. */
  1238. HRESULT IMAP::HrCmdList(const std::string &strTag,
  1239. const std::vector<std::string> &args, bool bSubscribedOnly)
  1240. {
  1241. HRESULT hr = hrSuccess;
  1242. string strAction;
  1243. string strResponse;
  1244. wstring strPattern;
  1245. string strListProps;
  1246. string strCompare;
  1247. wstring strFolderPath;
  1248. std::string strReferenceFolder = args[0], strFindFolder = args[1];
  1249. if (bSubscribedOnly)
  1250. strAction = "LSUB";
  1251. else
  1252. strAction = "LIST";
  1253. if (!lpSession) {
  1254. HrResponse(RESP_TAGGED_NO, strTag, strAction + " error no session");
  1255. return MAPI_E_CALL_FAILED;
  1256. }
  1257. if (strFindFolder.empty()) {
  1258. strResponse = strAction + " (\\Noselect) \"";
  1259. strResponse += IMAP_HIERARCHY_DELIMITER;
  1260. strResponse += "\" \"\"";
  1261. HrResponse(RESP_UNTAGGED, strResponse);
  1262. HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
  1263. return hrSuccess;
  1264. }
  1265. HrGetSubscribedList();
  1266. // ignore error
  1267. // add trailing delimiter to strReferenceFolder
  1268. if (!strReferenceFolder.empty() &&
  1269. strReferenceFolder[strReferenceFolder.length()-1] != IMAP_HIERARCHY_DELIMITER)
  1270. strReferenceFolder += IMAP_HIERARCHY_DELIMITER;
  1271. strReferenceFolder += strFindFolder;
  1272. hr = IMAP2MAPICharset(strReferenceFolder, strPattern);
  1273. if (hr != hrSuccess) {
  1274. HrResponse(RESP_TAGGED_NO, strTag, strAction + " invalid folder name");
  1275. return hr;
  1276. }
  1277. strPattern = strToUpper(strPattern);
  1278. list<SFolder> *folders = &cached_folders;
  1279. list<SFolder> tmp_folders;
  1280. if (cache_folders_time_limit > 0) {
  1281. hr = HrGetFolderList(cached_folders);
  1282. cache_folders_last_used = std::time(nullptr);
  1283. }
  1284. else {
  1285. hr = HrGetFolderList(tmp_folders);
  1286. folders = &tmp_folders;
  1287. }
  1288. // Get all folders
  1289. if(hr != hrSuccess) {
  1290. HrResponse(RESP_TAGGED_NO, strTag, strAction + " unable to list folders");
  1291. return hr;
  1292. }
  1293. // Loop through all folders to see if they match
  1294. for (auto iFld = folders->cbegin(); iFld != folders->cend(); ++iFld) {
  1295. if (bSubscribedOnly && !iFld->bActive && !iFld->bSpecialFolder)
  1296. // Folder is not subscribed to
  1297. continue;
  1298. // Get full path name
  1299. strFolderPath.clear();
  1300. // if path is empty, we're probably dealing the IPM_SUBTREE entry
  1301. if(HrGetFolderPath(iFld, *folders, strFolderPath) != hrSuccess || strFolderPath.empty())
  1302. continue;
  1303. if (!strFolderPath.empty())
  1304. strFolderPath.erase(0,1); // strip / from start of string
  1305. if (MatchFolderPath(strFolderPath, strPattern)) {
  1306. hr = MAPI2IMAPCharset(strFolderPath, strResponse);
  1307. if (hr != hrSuccess) {
  1308. lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to represent foldername '%ls' in UTF-7", strFolderPath.c_str());
  1309. continue;
  1310. }
  1311. strResponse = (string)"\"" + IMAP_HIERARCHY_DELIMITER + "\" \"" + strResponse + "\""; // prepend folder delimiter
  1312. strListProps = strAction + " (";
  1313. if (!iFld->bMailFolder)
  1314. strListProps += "\\Noselect ";
  1315. if (!bSubscribedOnly) {
  1316. // don't list flag on LSUB command
  1317. if (iFld->bHasSubfolders)
  1318. strListProps += "\\HasChildren";
  1319. else
  1320. strListProps += "\\HasNoChildren";
  1321. }
  1322. strListProps += ") ";
  1323. strResponse = strListProps + strResponse;
  1324. HrResponse(RESP_UNTAGGED, strResponse);
  1325. }
  1326. }
  1327. HrResponse(RESP_TAGGED_OK, strTag, strAction + " completed");
  1328. return hrSuccess;
  1329. }
  1330. template<bool sub_only> HRESULT
  1331. IMAP::HrCmdList(const std::string &tag, const std::vector<std::string> &args)
  1332. {
  1333. return HrCmdList(tag, args, sub_only);
  1334. }
  1335. HRESULT IMAP::get_uid_next(KFolder &&folder, const std::string &tag, ULONG &uid_next)
  1336. {
  1337. HRESULT hr = hrSuccess;
  1338. try {
  1339. auto table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
  1340. table.columns({PR_EC_IMAP_ID});
  1341. table.sort({{PR_EC_IMAP_ID, KTable::DESCEND}});
  1342. auto rows = table.rows(1, 0);
  1343. uid_next = rows.count() > 0 ? (rows[0][0].ul() + 1) : 1;
  1344. }
  1345. catch (const KMAPIError &e) {
  1346. hr = e.code();
  1347. HrResponse(RESP_TAGGED_NO, tag, "STATUS error getting contents");
  1348. }
  1349. return hr;
  1350. }
  1351. HRESULT IMAP::get_recent(KFolder &&folder, const std::string &tag, ULONG &recent, const ULONG &messages) {
  1352. HRESULT hr = hrSuccess;
  1353. try {
  1354. KProp max_id = nullptr;
  1355. try {
  1356. max_id = folder.get_prop(PR_EC_IMAP_MAX_ID);
  1357. }
  1358. catch (const KMAPIError &e) {
  1359. if (e.code() != MAPI_E_NOT_FOUND)
  1360. throw;
  1361. }
  1362. if (max_id != nullptr) {
  1363. auto table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
  1364. auto restr = ECPropertyRestriction(RELOP_GT, PR_EC_IMAP_ID, max_id, ECRestriction::Cheap);
  1365. hr = restr.RestrictTable(table, TBL_BATCH);
  1366. recent = table.count();
  1367. }
  1368. else
  1369. recent = messages;
  1370. }
  1371. catch (const KMAPIError &e) {
  1372. hr = e.code();
  1373. HrResponse(RESP_TAGGED_NO, tag, "STATUS error getting contents");
  1374. }
  1375. return hr;
  1376. }
  1377. /**
  1378. * @brief Handles STATUS command
  1379. *
  1380. * Returns values for status items. Status items are:
  1381. * @li \b MESSAGES The number of messages in the mailbox.
  1382. * @li \b RECENT The number of messages with the \Recent flag set.
  1383. * @li \b UIDNEXT The next unique identifier value of the mailbox.
  1384. * @li \b UIDVALIDITY The unique identifier validity value of the mailbox.
  1385. * @li \b UNSEEN The number of messages which do not have the \Seen flag set.
  1386. *
  1387. * @note dovecot seems to be the only one enforcing strStatusData to be a list, and with actual items
  1388. * @note courier doesn't return NIL for unknown status items
  1389. *
  1390. * @param[in] strTag the IMAP tag for this command
  1391. * @param[in] strFolder The folder to query the status of, in IMAP UTF-7 charset
  1392. * @param[in] strStatusData A list of status items to query
  1393. *
  1394. * @return MAPI error code
  1395. */
  1396. HRESULT IMAP::HrCmdStatus(const std::string &strTag,
  1397. const std::vector<std::string> &args)
  1398. {
  1399. HRESULT hr = hrSuccess;
  1400. KFolder lpStatusFolder;
  1401. vector<string> lstStatusData;
  1402. string strData;
  1403. string strResponse;
  1404. char szBuffer[11];
  1405. ULONG ulCounter;
  1406. ULONG ulMessages = 0;
  1407. ULONG ulUnseen = 0;
  1408. ULONG ulUIDValidity = 0;
  1409. ULONG ulUIDNext = 0;
  1410. ULONG ulRecent = 0;
  1411. ULONG cStatusData = 0;
  1412. ULONG cValues;
  1413. static constexpr const SizedSPropTagArray(3, sPropsFolderCounters) =
  1414. {3, {PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_EC_HIERARCHYID}};
  1415. memory_ptr<SPropValue> lpPropCounters, lpPropMaxID;
  1416. wstring strIMAPFolder;
  1417. std::string strFolder = args[0], strStatusData = args[1];
  1418. if (!lpSession) {
  1419. HrResponse(RESP_TAGGED_NO, strTag, "STATUS error no session");
  1420. return MAPI_E_CALL_FAILED;
  1421. }
  1422. hr = IMAP2MAPICharset(strFolder, strIMAPFolder);
  1423. if (hr != hrSuccess) {
  1424. HrResponse(RESP_TAGGED_NO, strTag, "STATUS invalid folder name");
  1425. return hr;
  1426. }
  1427. strStatusData = strToUpper(strStatusData);
  1428. strIMAPFolder = strToUpper(strIMAPFolder);
  1429. object_ptr<IMAPIFolder> tmp_folder;
  1430. hr = HrFindFolder(strIMAPFolder, false, &~tmp_folder);
  1431. if(hr != hrSuccess) {
  1432. HrResponse(RESP_TAGGED_NO, strTag, "STATUS error finding folder");
  1433. return MAPI_E_CALL_FAILED;
  1434. }
  1435. lpStatusFolder = tmp_folder.release();
  1436. if (!IsMailFolder(lpStatusFolder)) {
  1437. HrResponse(RESP_TAGGED_NO, strTag, "STATUS error no mail folder");
  1438. return MAPI_E_CALL_FAILED;
  1439. }
  1440. hr = lpStatusFolder->GetProps(sPropsFolderCounters, 0, &cValues, &~lpPropCounters);
  1441. if (FAILED(hr)) {
  1442. HrResponse(RESP_TAGGED_NO, strTag, "STATUS error fetching folder content counters");
  1443. return hr;
  1444. }
  1445. hr = hrSuccess;
  1446. if (lpPropCounters[0].ulPropTag == PR_CONTENT_COUNT)
  1447. ulMessages = lpPropCounters[0].Value.ul;
  1448. if (lpPropCounters[1].ulPropTag == PR_CONTENT_UNREAD)
  1449. ulUnseen = lpPropCounters[1].Value.ul;
  1450. if (lpPropCounters[2].ulPropTag == PR_EC_HIERARCHYID)
  1451. ulUIDValidity = lpPropCounters[2].Value.ul;
  1452. // split statusdata
  1453. if (strStatusData.size() > 1 && strStatusData[0] == '(') {
  1454. strStatusData.erase(0,1);
  1455. strStatusData.resize(strStatusData.size()-1);
  1456. }
  1457. HrSplitInput(strStatusData, lstStatusData);
  1458. // loop statusdata
  1459. cStatusData = lstStatusData.size();
  1460. strResponse = "STATUS \"";
  1461. strResponse += strFolder; // strFolder is in upper case, works with all clients?
  1462. strResponse += "\" (";
  1463. for (ulCounter = 0; ulCounter < cStatusData; ++ulCounter) {
  1464. strData = lstStatusData[ulCounter];
  1465. strResponse += strData;
  1466. strResponse += " ";
  1467. if (strData.compare("MESSAGES") == 0) {
  1468. snprintf(szBuffer, 10, "%u", ulMessages);
  1469. strResponse += szBuffer;
  1470. } else if (strData.compare("RECENT") == 0) {
  1471. get_recent(std::move(lpStatusFolder), strTag, ulRecent, ulMessages);
  1472. snprintf(szBuffer, 10, "%u", ulRecent);
  1473. strResponse += szBuffer;
  1474. } else if (strData.compare("UIDNEXT") == 0) {
  1475. get_uid_next(std::move(lpStatusFolder), strTag, ulUIDNext);
  1476. snprintf(szBuffer, 10, "%u", ulUIDNext);
  1477. strResponse += szBuffer;
  1478. } else if (strData.compare("UIDVALIDITY") == 0) {
  1479. snprintf(szBuffer, 10, "%u", ulUIDValidity);
  1480. strResponse += szBuffer;
  1481. } else if (strData.compare("UNSEEN") == 0) {
  1482. snprintf(szBuffer, 10, "%u", ulUnseen);
  1483. strResponse += szBuffer;
  1484. } else {
  1485. strResponse += "NIL";
  1486. }
  1487. if (ulCounter+1 < cStatusData)
  1488. strResponse += " ";
  1489. }
  1490. strResponse += ")";
  1491. HrResponse(RESP_UNTAGGED, strResponse);
  1492. HrResponse(RESP_TAGGED_OK, strTag, "STATUS completed");
  1493. return hrSuccess;
  1494. }
  1495. /**
  1496. * @brief Handles the APPEND command
  1497. *
  1498. * Create a new mail message in the given folder, and possebly set
  1499. * flags and received time on the new message.
  1500. *
  1501. * @param[in] strTag the IMAP tag for this command
  1502. * @param[in] strFolderParam the folder to create the message in, in IMAP UTF-7 charset
  1503. * @param[in] strData the RFC 2822 formatted email to save
  1504. * @param[in] strFlags optional, contains a list of extra flags to save on the message (eg. \Seen)
  1505. * @param[in] strTime optional, a timestamp for the message: internal date is received date
  1506. *
  1507. * @return MAPI Error code
  1508. */
  1509. HRESULT IMAP::HrCmdAppend(const string &strTag, const string &strFolderParam, const string &strData, string strFlags, const string &strTime) {
  1510. HRESULT hr = hrSuccess;
  1511. object_ptr<IMAPIFolder> lpAppendFolder;
  1512. object_ptr<IMessage> lpMessage;
  1513. vector<string> lstFlags;
  1514. ULONG ulCounter;
  1515. string strFlag;
  1516. memory_ptr<SPropValue> lpPropVal;
  1517. wstring strFolder;
  1518. string strAppendUid;
  1519. ULONG ulFolderUid = 0;
  1520. ULONG ulMsgUid = 0;
  1521. SizedSPropTagArray(10, delFrom) = { 10, {
  1522. PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_NAME_W,
  1523. PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY,
  1524. PR_SENDER_ADDRTYPE_W, PR_SENDER_NAME_W,
  1525. PR_SENDER_EMAIL_ADDRESS_W, PR_SENDER_ENTRYID, PR_SENDER_SEARCH_KEY
  1526. } };
  1527. if (!lpSession) {
  1528. HrResponse(RESP_TAGGED_NO, strTag, "APPEND error no session");
  1529. return MAPI_E_CALL_FAILED;
  1530. }
  1531. hr = IMAP2MAPICharset(strFolderParam, strFolder);
  1532. if (hr != hrSuccess) {
  1533. HrResponse(RESP_TAGGED_NO, strTag, "APPEND invalid folder name");
  1534. return hr;
  1535. }
  1536. hr = HrFindFolder(strFolder, false, &~lpAppendFolder);
  1537. if(hr != hrSuccess) {
  1538. HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] APPEND error finding folder");
  1539. return MAPI_E_CALL_FAILED;
  1540. }
  1541. if (!IsMailFolder(lpAppendFolder)) {
  1542. HrResponse(RESP_TAGGED_NO, strTag, "APPEND error not a mail folder");
  1543. return MAPI_E_CALL_FAILED;
  1544. }
  1545. hr = HrGetOneProp(lpAppendFolder, PR_EC_HIERARCHYID, &~lpPropVal);
  1546. if (hr == hrSuccess)
  1547. ulFolderUid = lpPropVal->Value.ul;
  1548. hr = lpAppendFolder->CreateMessage(nullptr, 0, &~lpMessage);
  1549. if (hr != hrSuccess) {
  1550. HrResponse(RESP_TAGGED_NO, strTag, "APPEND error creating message");
  1551. return hr;
  1552. }
  1553. hr = IMToMAPI(lpSession, lpStore, lpAddrBook, lpMessage, strData, dopt);
  1554. if (hr != hrSuccess) {
  1555. HrResponse(RESP_TAGGED_NO, strTag, "APPEND error converting message");
  1556. return hr;
  1557. }
  1558. if (IsSentItemFolder(lpAppendFolder) &&
  1559. HrGetOneProp(lpAppendFolder, PR_ENTRYID, &~lpPropVal) == hrSuccess) {
  1560. // needed for blackberry
  1561. lpPropVal->ulPropTag = PR_SENTMAIL_ENTRYID;
  1562. HrSetOneProp(lpMessage, lpPropVal);
  1563. }
  1564. if (strFlags.size() > 2 && strFlags[0] == '(') {
  1565. // remove () around flags
  1566. strFlags.erase(0, 1);
  1567. strFlags.erase(strFlags.size()-1, 1);
  1568. }
  1569. strFlags = strToUpper(strFlags);
  1570. HrSplitInput(strFlags, lstFlags);
  1571. for (ulCounter = 0; ulCounter < lstFlags.size(); ++ulCounter) {
  1572. strFlag = lstFlags[ulCounter];
  1573. if (strFlag.compare("\\SEEN") == 0) {
  1574. if (HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpPropVal) != hrSuccess) {
  1575. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1576. if (hr != hrSuccess)
  1577. return hr;
  1578. lpPropVal->ulPropTag = PR_MESSAGE_FLAGS;
  1579. lpPropVal->Value.ul = 0;
  1580. }
  1581. lpPropVal->Value.ul |= MSGFLAG_READ;
  1582. HrSetOneProp(lpMessage, lpPropVal);
  1583. } else if (strFlag.compare("\\DRAFT") == 0) {
  1584. if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
  1585. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1586. if (hr != hrSuccess)
  1587. return hr;
  1588. lpPropVal->ulPropTag = PR_MSG_STATUS;
  1589. lpPropVal->Value.ul = 0;
  1590. }
  1591. lpPropVal->Value.ul |= MSGSTATUS_DRAFT;
  1592. HrSetOneProp(lpMessage, lpPropVal);
  1593. // When saving a draft, also mark it as UNSENT, so webaccess opens the editor, not the viewer
  1594. if (HrGetOneProp(lpMessage, PR_MESSAGE_FLAGS, &~lpPropVal) != hrSuccess) {
  1595. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1596. if (hr != hrSuccess)
  1597. return hr;
  1598. lpPropVal->ulPropTag = PR_MESSAGE_FLAGS;
  1599. lpPropVal->Value.ul = 0;
  1600. }
  1601. lpPropVal->Value.ul |= MSGFLAG_UNSENT;
  1602. HrSetOneProp(lpMessage, lpPropVal);
  1603. // remove "from" properties, and ignore error
  1604. lpMessage->DeleteProps(delFrom, NULL);
  1605. } else if (strFlag.compare("\\FLAGGED") == 0) {
  1606. if (lpPropVal == NULL) {
  1607. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1608. if (hr != hrSuccess)
  1609. return hr;
  1610. }
  1611. lpPropVal->ulPropTag = PR_FLAG_STATUS;
  1612. lpPropVal->Value.ul = 2;
  1613. HrSetOneProp(lpMessage, lpPropVal);
  1614. } else if (strFlag.compare("\\ANSWERED") == 0 || strFlag.compare("$FORWARDED") == 0) {
  1615. if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
  1616. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1617. if (hr != hrSuccess)
  1618. return hr;
  1619. lpPropVal->ulPropTag = PR_MSG_STATUS;
  1620. lpPropVal->Value.ul = 0;
  1621. }
  1622. if (strFlag[0] == '\\') {
  1623. lpPropVal->Value.ul |= MSGSTATUS_ANSWERED;
  1624. HrSetOneProp(lpMessage, lpPropVal);
  1625. }
  1626. hr = MAPIAllocateBuffer(sizeof(SPropValue) * 3, &~lpPropVal);
  1627. if (hr != hrSuccess)
  1628. return hr;
  1629. lpPropVal[0].ulPropTag = PR_LAST_VERB_EXECUTED;
  1630. if (strFlag[0] == '\\')
  1631. lpPropVal[0].Value.ul = NOTEIVERB_REPLYTOSENDER;
  1632. else
  1633. lpPropVal[0].Value.ul = NOTEIVERB_FORWARD;
  1634. lpPropVal[1].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  1635. GetSystemTimeAsFileTime(&lpPropVal[1].Value.ft);
  1636. lpPropVal[2].ulPropTag = PR_ICON_INDEX;
  1637. if (strFlag[0] == '\\')
  1638. lpPropVal[2].Value.ul = ICON_MAIL_REPLIED;
  1639. else
  1640. lpPropVal[2].Value.ul = ICON_MAIL_FORWARDED;
  1641. hr = lpMessage->SetProps(3, lpPropVal, NULL);
  1642. if (hr != hrSuccess)
  1643. return hr;
  1644. } else if (strFlag.compare("\\DELETED") == 0) {
  1645. if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
  1646. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1647. if (hr != hrSuccess)
  1648. return hr;
  1649. lpPropVal->ulPropTag = PR_MSG_STATUS;
  1650. lpPropVal->Value.ul = 0;
  1651. }
  1652. lpPropVal->Value.ul |= MSGSTATUS_DELMARKED;
  1653. // what, new deleted mail? moved deleted mail? uh?
  1654. // @todo imap_expunge_on_delete
  1655. HrSetOneProp(lpMessage, lpPropVal);
  1656. }
  1657. }
  1658. // set time
  1659. if (lpPropVal == NULL) {
  1660. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  1661. if (hr != hrSuccess)
  1662. return hr;
  1663. }
  1664. if (!strTime.empty()) {
  1665. lpPropVal->Value.ft = StringToFileTime(strTime);
  1666. lpPropVal->ulPropTag = PR_MESSAGE_DELIVERY_TIME;
  1667. HrSetOneProp(lpMessage, lpPropVal);
  1668. lpPropVal->Value.ft = StringToFileTime(strTime, true);
  1669. lpPropVal->ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
  1670. HrSetOneProp(lpMessage, lpPropVal);
  1671. }
  1672. hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
  1673. if (hr != hrSuccess) {
  1674. HrResponse(RESP_TAGGED_NO, strTag, "APPEND error saving message");
  1675. return hr;
  1676. }
  1677. hr = HrGetOneProp(lpMessage, PR_EC_IMAP_ID, &~lpPropVal);
  1678. if (hr == hrSuccess)
  1679. ulMsgUid = lpPropVal->Value.ul;
  1680. if (ulMsgUid && ulFolderUid)
  1681. strAppendUid = string("[APPENDUID ") + stringify(ulFolderUid) + " " + stringify(ulMsgUid) + "] ";
  1682. if (strCurrentFolder == strFolder)
  1683. // Fixme, add the appended message instead of HrRefreshFolderMails; the message is now seen as Recent
  1684. HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
  1685. HrResponse(RESP_TAGGED_OK, strTag, strAppendUid + "APPEND completed");
  1686. return hr;
  1687. }
  1688. /**
  1689. * @brief Handles the CLOSE command
  1690. *
  1691. * Closes the current selected mailbox, expunging any \Delete marked
  1692. * messages in this mailbox.
  1693. *
  1694. * @param[in] strTag the IMAP tag for this command
  1695. *
  1696. * @return MAPI Error code
  1697. */
  1698. HRESULT IMAP::HrCmdClose(const string &strTag) {
  1699. HRESULT hr = hrSuccess;
  1700. if (strCurrentFolder.empty() || !lpSession) {
  1701. hr = MAPI_E_CALL_FAILED;
  1702. HrResponse(RESP_TAGGED_NO, strTag, "CLOSE error no folder");
  1703. goto exit;
  1704. }
  1705. // close old contents table if cached version was open
  1706. ReleaseContentsCache();
  1707. if (bCurrentFolderReadOnly) {
  1708. // cannot expunge messages on a readonly folder
  1709. HrResponse(RESP_TAGGED_OK, strTag, "CLOSE completed");
  1710. goto exit;
  1711. }
  1712. hr = HrExpungeDeleted(strTag, "CLOSE", std::unique_ptr<ECRestriction>());
  1713. if (hr != hrSuccess)
  1714. goto exit;
  1715. HrResponse(RESP_TAGGED_OK, strTag, "CLOSE completed");
  1716. exit:
  1717. strCurrentFolder.clear(); // always "close" the SELECT command
  1718. return hr;
  1719. }
  1720. /**
  1721. * @brief Handles the EXPUNGE command
  1722. *
  1723. * All \Deleted marked emails will actually be removed (softdeleted in
  1724. * Kopano). Optional is the sequence set (UIDPLUS extension), which
  1725. * messages only must be expunged if \Deleted flag was marked AND are
  1726. * present in this sequence.
  1727. *
  1728. * @param[in] strTag the IMAP tag for this command
  1729. * @param[in] strStatusData optional sequence set
  1730. *
  1731. * @return MAPI Error code
  1732. */
  1733. HRESULT IMAP::HrCmdExpunge(const string &strTag, const string &strSeqSet) {
  1734. HRESULT hr = hrSuccess;
  1735. list<ULONG> lstMails;
  1736. string strCommand;
  1737. std::unique_ptr<ECRestriction> rst;
  1738. static_assert(std::is_polymorphic<ECRestriction>::value, "ECRestriction needs to be polymorphic for unique_ptr to work");
  1739. if (strSeqSet.empty())
  1740. strCommand = "EXPUNGE";
  1741. else
  1742. strCommand = "UID EXPUNGE";
  1743. if (strCurrentFolder.empty() || !lpSession) {
  1744. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error no folder");
  1745. return MAPI_E_CALL_FAILED;
  1746. }
  1747. if (bCurrentFolderReadOnly) {
  1748. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error folder read only");
  1749. return MAPI_E_CALL_FAILED;
  1750. }
  1751. // UID EXPUNGE is always passed UIDs
  1752. hr = HrSeqUidSetToRestriction(strSeqSet, rst);
  1753. if (hr != hrSuccess)
  1754. return hr;
  1755. hr = HrExpungeDeleted(strTag, strCommand, std::move(rst));
  1756. if (hr != hrSuccess)
  1757. return hr;
  1758. // Let HrRefreshFolderMails output the actual EXPUNGEs
  1759. HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
  1760. HrResponse(RESP_TAGGED_OK, strTag, strCommand + " completed");
  1761. return hrSuccess;
  1762. }
  1763. /**
  1764. * @brief Handles the SEARCH command
  1765. *
  1766. * Searches in the current selected folder using the given search
  1767. * criteria.
  1768. *
  1769. * @param[in] strTag the IMAP tag for this command
  1770. * @param[in] lstSearchCriteria The clients search options.
  1771. * @param[in] bUidMode UID SEARCH (true) or flat ID SEARCH (false)
  1772. *
  1773. * @return MAPI Error code
  1774. */
  1775. HRESULT IMAP::HrCmdSearch(const string &strTag, vector<string> &lstSearchCriteria, bool bUidMode) {
  1776. HRESULT hr = hrSuccess;
  1777. list<ULONG> lstMailnr;
  1778. ULONG ulCriterianr = 0;
  1779. string strResponse;
  1780. char szBuffer[33];
  1781. std::unique_ptr<ECIConv> iconv;
  1782. string strMode;
  1783. if (bUidMode)
  1784. strMode = "UID ";
  1785. if (strCurrentFolder.empty() || !lpSession) {
  1786. HrResponse(RESP_TAGGED_NO, strTag, strMode + "SEARCH error no folder");
  1787. return MAPI_E_CALL_FAILED;
  1788. }
  1789. // don't support other charsets
  1790. // @todo unicode searches
  1791. if (lstSearchCriteria[0].compare("CHARSET") == 0) {
  1792. if (lstSearchCriteria[1] != "WINDOWS-1252") {
  1793. iconv.reset(new ECIConv("windows-1252", lstSearchCriteria[1]));
  1794. if (!iconv->canConvert()) {
  1795. HrResponse(RESP_TAGGED_NO, strTag, "[BADCHARSET (WINDOWS-1252)] " + strMode + "SEARCH charset not supported");
  1796. return MAPI_E_CALL_FAILED;
  1797. }
  1798. }
  1799. ulCriterianr += 2;
  1800. }
  1801. hr = HrSearch(std::move(lstSearchCriteria), ulCriterianr, lstMailnr);
  1802. if (hr != hrSuccess) {
  1803. HrResponse(RESP_TAGGED_NO, strTag, strMode + "SEARCH error");
  1804. return hr;
  1805. }
  1806. strResponse = "SEARCH";
  1807. for (auto nr : lstMailnr) {
  1808. snprintf(szBuffer, 32, " %u", bUidMode ? lstFolderMailEIDs[nr].ulUid : nr + 1);
  1809. strResponse += szBuffer;
  1810. }
  1811. HrResponse(RESP_UNTAGGED, strResponse);
  1812. HrResponse(RESP_TAGGED_OK, strTag, strMode + "SEARCH completed");
  1813. return hr;
  1814. }
  1815. /**
  1816. * @brief Handles the FETCH command
  1817. *
  1818. * Fetch specified parts of a list of emails.
  1819. *
  1820. * @param[in] strTag the IMAP tag for this command
  1821. * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
  1822. * @param[in] strMsgDataItemNames The parts of the emails to fetch
  1823. * @param[in] bUidMode use UID (true) or ID (false) numbers
  1824. *
  1825. * @return MAPI Error code
  1826. */
  1827. HRESULT IMAP::HrCmdFetch(const string &strTag, const string &strSeqSet, const string &strMsgDataItemNames, bool bUidMode) {
  1828. HRESULT hr = hrSuccess;
  1829. vector<string> lstDataItems;
  1830. list<ULONG> lstMails;
  1831. ULONG ulCurrent = 0;
  1832. bool bFound = false;
  1833. string strMode;
  1834. if (bUidMode)
  1835. strMode = "UID ";
  1836. if (strCurrentFolder.empty() || !lpSession) {
  1837. HrResponse(RESP_TAGGED_BAD, strTag, strMode + "FETCH error no folder");
  1838. return MAPI_E_CALL_FAILED;
  1839. }
  1840. HrGetDataItems(strMsgDataItemNames, lstDataItems);
  1841. if (bUidMode) {
  1842. for (ulCurrent = 0; !bFound && ulCurrent < lstDataItems.size(); ++ulCurrent)
  1843. if (lstDataItems[ulCurrent].compare("UID") == 0)
  1844. bFound = true;
  1845. if (!bFound)
  1846. lstDataItems.push_back("UID");
  1847. }
  1848. if (bUidMode)
  1849. hr = HrParseSeqUidSet(strSeqSet, lstMails);
  1850. else
  1851. hr = HrParseSeqSet(strSeqSet, lstMails);
  1852. if (hr != hrSuccess) {
  1853. HrResponse(RESP_TAGGED_NO, strTag, strMode + "FETCH sequence parse error in: " + strSeqSet);
  1854. return hr;
  1855. }
  1856. hr = HrPropertyFetch(lstMails, lstDataItems);
  1857. if (hr != hrSuccess)
  1858. HrResponse(RESP_TAGGED_NO, strTag, strMode + "FETCH failed");
  1859. else
  1860. HrResponse(RESP_TAGGED_OK, strTag, strMode + "FETCH completed");
  1861. return hr;
  1862. }
  1863. /**
  1864. * @brief Handles the STORE command
  1865. *
  1866. * Sets, appends or removes flags from a list of emails.
  1867. *
  1868. * @param[in] strTag the IMAP tag for this command
  1869. * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
  1870. * @param[in] strMsgDataItemName contains the FLAGS modifier command
  1871. * @param[in] strMsgDataItemValue contains a list of flags to modify the emails with
  1872. * @param[in] bUidMode use UID (true) or ID (false) numbers
  1873. *
  1874. * @return MAPI error code
  1875. */
  1876. HRESULT IMAP::HrCmdStore(const string &strTag, const string &strSeqSet, const string &strMsgDataItemName, const string &strMsgDataItemValue, bool bUidMode) {
  1877. HRESULT hr = hrSuccess;
  1878. list<ULONG> lstMails;
  1879. vector<string> lstDataItems;
  1880. string strMode;
  1881. bool bDelete = false;
  1882. if (bUidMode)
  1883. strMode = "UID";
  1884. strMode += " STORE";
  1885. if (strCurrentFolder.empty() || !lpSession) {
  1886. HrResponse(RESP_TAGGED_NO, strTag, strMode + " error no folder");
  1887. return MAPI_E_CALL_FAILED;
  1888. }
  1889. if (bCurrentFolderReadOnly) {
  1890. HrResponse(RESP_TAGGED_NO, strTag, strMode + " error folder read only");
  1891. return MAPI_E_CALL_FAILED;
  1892. }
  1893. lstDataItems.push_back("FLAGS");
  1894. if (bUidMode)
  1895. lstDataItems.push_back("UID");
  1896. if (bUidMode)
  1897. hr = HrParseSeqUidSet(strSeqSet, lstMails);
  1898. else
  1899. hr = HrParseSeqSet(strSeqSet, lstMails);
  1900. if (hr != hrSuccess) {
  1901. HrResponse(RESP_TAGGED_NO, strTag, strMode + " sequence parse error in: " + strSeqSet);
  1902. return hr;
  1903. }
  1904. hr = HrStore(lstMails, strMsgDataItemName, strMsgDataItemValue, &bDelete);
  1905. if (hr != MAPI_E_NOT_ME) {
  1906. HrPropertyFetch(lstMails, lstDataItems);
  1907. hr = hrSuccess;
  1908. }
  1909. if (bDelete && parseBool(lpConfig->GetSetting("imap_expunge_on_delete"))) {
  1910. std::unique_ptr<ECRestriction> rst;
  1911. static_assert(std::is_polymorphic<ECRestriction>::value, "ECRestriction needs to be polymorphic for unique_ptr to work");
  1912. if (bUidMode) {
  1913. hr = HrSeqUidSetToRestriction(strSeqSet, rst);
  1914. if (hr != hrSuccess)
  1915. return hr;
  1916. }
  1917. if (HrExpungeDeleted(strTag, strMode, std::move(rst)) != hrSuccess)
  1918. // HrExpungeDeleted sent client NO result.
  1919. return hrSuccess;
  1920. // Let HrRefreshFolderMails output the actual EXPUNGEs
  1921. HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
  1922. }
  1923. HrResponse(RESP_TAGGED_OK, strTag, strMode + " completed");
  1924. return hr;
  1925. }
  1926. /**
  1927. * @brief Handles the COPY command
  1928. *
  1929. * Copy a list of emails from the current selected folder to the given folder.
  1930. *
  1931. * @param[in] strTag the IMAP tag for this command
  1932. * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
  1933. * @param[in] strFolder the folder to copy the message to, in IMAP UTF-7 charset
  1934. * @param[in] bUidMode use UID (true) or ID (false) numbers
  1935. *
  1936. * @return MAPI Error code
  1937. */
  1938. HRESULT IMAP::HrCmdCopy(const string &strTag, const string &strSeqSet, const string &strFolder, bool bUidMode) {
  1939. HRESULT hr = hrSuccess;
  1940. list<ULONG> lstMails;
  1941. string strMode;
  1942. if (bUidMode)
  1943. strMode = "UID ";
  1944. if (strCurrentFolder.empty() || !lpSession) {
  1945. HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY error no folder");
  1946. return MAPI_E_CALL_FAILED;
  1947. }
  1948. if (bUidMode)
  1949. hr = HrParseSeqUidSet(strSeqSet, lstMails);
  1950. else
  1951. hr = HrParseSeqSet(strSeqSet, lstMails);
  1952. if (hr != hrSuccess) {
  1953. HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY sequence parse error in: " + strSeqSet);
  1954. return hr;
  1955. }
  1956. hr = HrCopy(lstMails, strFolder, false);
  1957. if (hr == MAPI_E_NOT_FOUND) {
  1958. HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] " + strMode + "COPY folder not found");
  1959. return hr;
  1960. } else if (hr != hrSuccess) {
  1961. HrResponse(RESP_TAGGED_NO, strTag, strMode + "COPY error");
  1962. return hr;
  1963. }
  1964. HrResponse(RESP_TAGGED_OK, strTag, strMode + "COPY completed");
  1965. return hr;
  1966. }
  1967. /**
  1968. * @brief Handles the UID XAOL-MOVE command (non-RFC command)
  1969. *
  1970. * This extension, currently only used by thunderbird, moves a list of
  1971. * emails into the given folder. Normally IMAP can only move mails
  1972. * with COPY/STORE(\Deleted) commands.
  1973. *
  1974. * @param[in] strTag the IMAP tag for this command
  1975. * @param[in] strSeqSet an IMAP sequence of IDs or UIDs depending on bUidMode
  1976. * @param[in] strFolder the folder to copy the message to, in IMAP UTF-7 charset
  1977. *
  1978. * @return MAPI Error code
  1979. */
  1980. HRESULT IMAP::HrCmdUidXaolMove(const string &strTag, const string &strSeqSet, const string &strFolder) {
  1981. HRESULT hr = hrSuccess;
  1982. list<ULONG> lstMails;
  1983. if (strCurrentFolder.empty() || !lpSession) {
  1984. HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE error no folder");
  1985. return MAPI_E_CALL_FAILED;
  1986. }
  1987. hr = HrParseSeqUidSet(strSeqSet, lstMails);
  1988. if (hr != hrSuccess) {
  1989. HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE sequence parse error in: " + strSeqSet);
  1990. return hr;
  1991. }
  1992. hr = HrCopy(lstMails, strFolder, true);
  1993. if (hr == MAPI_E_NOT_FOUND) {
  1994. HrResponse(RESP_TAGGED_NO, strTag, "[TRYCREATE] UID XAOL-MOVE folder not found");
  1995. return hr;
  1996. } else if (hr != hrSuccess) {
  1997. HrResponse(RESP_TAGGED_NO, strTag, "UID XAOL-MOVE error");
  1998. return hr;
  1999. }
  2000. // Let HrRefreshFolderMails output the actual EXPUNGEs
  2001. HrRefreshFolderMails(false, !bCurrentFolderReadOnly, NULL);
  2002. HrResponse(RESP_TAGGED_OK, strTag, "UID XAOL-MOVE completed");
  2003. return hr;
  2004. }
  2005. /**
  2006. * Convert a MAPI array of properties (from a table) to an IMAP FLAGS list.
  2007. *
  2008. * @todo, flags always are in a list, so this function should add the ()
  2009. *
  2010. * @param[in] lpProps Array of MAPI properties
  2011. * @param[in] cValues Number of properties in lpProps
  2012. * @param[in] bRecent Add the recent flag to the list
  2013. *
  2014. * @return string with IMAP Flags
  2015. */
  2016. std::string IMAP::PropsToFlags(LPSPropValue lpProps, unsigned int cValues, bool bRecent, bool bRead) {
  2017. string strFlags;
  2018. auto lpMessageFlags = PCpropFindProp(lpProps, cValues, PR_MESSAGE_FLAGS);
  2019. auto lpFlagStatus = PCpropFindProp(lpProps, cValues, PR_FLAG_STATUS);
  2020. auto lpMsgStatus = PCpropFindProp(lpProps, cValues, PR_MSG_STATUS);
  2021. auto lpLastVerb = PCpropFindProp(lpProps, cValues, PR_LAST_VERB_EXECUTED);
  2022. if ((lpMessageFlags != NULL &&
  2023. lpMessageFlags->Value.ul & MSGFLAG_READ) || bRead)
  2024. strFlags += "\\Seen ";
  2025. if (lpFlagStatus != NULL && lpFlagStatus->Value.ul != 0)
  2026. strFlags += "\\Flagged ";
  2027. if (lpLastVerb) {
  2028. if (lpLastVerb->Value.ul == NOTEIVERB_REPLYTOSENDER ||
  2029. lpLastVerb->Value.ul == NOTEIVERB_REPLYTOALL)
  2030. strFlags += "\\Answered ";
  2031. // there is no flag in imap for forwards. thunderbird uses the custom flag $Forwarded,
  2032. // and this is the only custom flag we support.
  2033. if (lpLastVerb->Value.ul == NOTEIVERB_FORWARD)
  2034. strFlags += "$Forwarded ";
  2035. }
  2036. if (lpMsgStatus) {
  2037. if (lpMsgStatus->Value.ul & MSGSTATUS_DRAFT)
  2038. strFlags += "\\Draft ";
  2039. if (lpLastVerb == NULL &&
  2040. lpMsgStatus->Value.ul & MSGSTATUS_ANSWERED)
  2041. strFlags += "\\Answered ";
  2042. if (lpMsgStatus->Value.ul & MSGSTATUS_DELMARKED)
  2043. strFlags += "\\Deleted ";
  2044. }
  2045. if (bRecent)
  2046. strFlags += "\\Recent ";
  2047. // strip final space
  2048. if (!strFlags.empty())
  2049. strFlags.resize(strFlags.size() - 1);
  2050. return strFlags;
  2051. }
  2052. /**
  2053. * The notify callback function. Sends changes to the client on the
  2054. * current selected folder during the IDLE command.
  2055. *
  2056. * @param[in] lpContext callback data, containing "this" IMAP class
  2057. * @param[in] cNotif Number of notification messages in lpNotif
  2058. * @param[in] lpNotif Array of changes on the folder
  2059. *
  2060. * @return MAPI Error code
  2061. */
  2062. LONG __stdcall IMAP::IdleAdviseCallback(void *lpContext, ULONG cNotif,
  2063. LPNOTIFICATION lpNotif)
  2064. {
  2065. auto lpIMAP = static_cast<IMAP *>(lpContext);
  2066. string strFlags;
  2067. ULONG ulMailNr = 0;
  2068. ULONG ulRecent = 0;
  2069. bool bReload = false;
  2070. vector<IMAP::SMail> oldMails;
  2071. enum { EID, IKEY, IMAPID, MESSAGE_FLAGS, FLAG_STATUS, MSG_STATUS, LAST_VERB, NUM_COLS };
  2072. IMAP::SMail sMail;
  2073. {
  2074. // modify:
  2075. // * [mailnr] FETCH (FLAGS (\Seen \Recent \Flagged \Deleted))
  2076. // remove:
  2077. // * [mailnr] EXPUNGE
  2078. // new mail/add:
  2079. // * [total] exists (optional?)
  2080. // * 1 recent
  2081. }
  2082. if (!lpIMAP)
  2083. return MAPI_E_CALL_FAILED;
  2084. scoped_lock l_idle(lpIMAP->m_mIdleLock);
  2085. if (!lpIMAP->m_bIdleMode)
  2086. return MAPI_E_CALL_FAILED;
  2087. for (ULONG i = 0; i < cNotif && !bReload; ++i) {
  2088. if (lpNotif[i].ulEventType != fnevTableModified)
  2089. continue;
  2090. switch (lpNotif[i].info.tab.ulTableEvent) {
  2091. case TABLE_ROW_ADDED:
  2092. sMail.sEntryID = BinaryArray(lpNotif[i].info.tab.row.lpProps[EID].Value.bin);
  2093. sMail.sInstanceKey = BinaryArray(lpNotif[i].info.tab.propIndex.Value.bin);
  2094. sMail.bRecent = true;
  2095. if (lpNotif[i].info.tab.row.lpProps[IMAPID].ulPropTag == PR_EC_IMAP_ID)
  2096. sMail.ulUid = lpNotif[i].info.tab.row.lpProps[IMAPID].Value.ul;
  2097. sMail.strFlags = lpIMAP->PropsToFlags(lpNotif[i].info.tab.row.lpProps, lpNotif[i].info.tab.row.cValues, true, false);
  2098. lpIMAP->lstFolderMailEIDs.push_back(sMail);
  2099. lpIMAP->m_ulLastUid = max(lpIMAP->m_ulLastUid, sMail.ulUid);
  2100. ++ulRecent;
  2101. break;
  2102. case TABLE_ROW_DELETED:
  2103. // find number and print N EXPUNGE
  2104. if (lpNotif[i].info.tab.propIndex.ulPropTag == PR_INSTANCE_KEY) {
  2105. auto iterMail = lpIMAP->lstFolderMailEIDs.begin();
  2106. for (; iterMail != lpIMAP->lstFolderMailEIDs.cend(); ++iterMail)
  2107. if (iterMail->sInstanceKey == BinaryArray(lpNotif[i].info.tab.propIndex.Value.bin))
  2108. break;
  2109. if (iterMail != lpIMAP->lstFolderMailEIDs.cend()) {
  2110. ulMailNr = iterMail - lpIMAP->lstFolderMailEIDs.cbegin();
  2111. // remove mail from list
  2112. lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulMailNr+1) + " EXPUNGE");
  2113. lpIMAP->lstFolderMailEIDs.erase(iterMail);
  2114. }
  2115. }
  2116. break;
  2117. case TABLE_ROW_MODIFIED:
  2118. // find number and print N FETCH (FLAGS (flags...))
  2119. strFlags.clear();
  2120. if (lpNotif[i].info.tab.row.lpProps[IMAPID].ulPropTag == PR_EC_IMAP_ID) {
  2121. auto iterMail = find(lpIMAP->lstFolderMailEIDs.cbegin(), lpIMAP->lstFolderMailEIDs.cend(), lpNotif[i].info.tab.row.lpProps[IMAPID].Value.ul);
  2122. // not found probably means the client needs to sync
  2123. if (iterMail != lpIMAP->lstFolderMailEIDs.cend()) {
  2124. ulMailNr = iterMail - lpIMAP->lstFolderMailEIDs.cbegin();
  2125. strFlags = lpIMAP->PropsToFlags(lpNotif[i].info.tab.row.lpProps, lpNotif[i].info.tab.row.cValues, iterMail->bRecent, false);
  2126. lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulMailNr+1) + " FETCH (FLAGS (" + strFlags + "))");
  2127. }
  2128. }
  2129. break;
  2130. case TABLE_RELOAD:
  2131. // TABLE_RELOAD is unused in Kopano
  2132. case TABLE_CHANGED:
  2133. lpIMAP->HrRefreshFolderMails(false, !lpIMAP->bCurrentFolderReadOnly, NULL);
  2134. break;
  2135. };
  2136. }
  2137. if (ulRecent) {
  2138. lpIMAP->HrResponse(RESP_UNTAGGED, stringify(ulRecent) + " RECENT");
  2139. lpIMAP->HrResponse(RESP_UNTAGGED, stringify(lpIMAP->lstFolderMailEIDs.size()) + " EXISTS");
  2140. }
  2141. return S_OK;
  2142. }
  2143. /**
  2144. * @brief Handles the IDLE command
  2145. *
  2146. * If we have a current selected folder, a notification advise is
  2147. * created in another thread, which will print updates to the
  2148. * client. The IMAP client can at anytime close the thread by exiting,
  2149. * or sending the DONE command.
  2150. *
  2151. * @param[in] strTag the IMAP tag for this command
  2152. *
  2153. * @return MAPI Error code
  2154. */
  2155. HRESULT IMAP::HrCmdIdle(const string &strTag) {
  2156. HRESULT hr = hrSuccess;
  2157. object_ptr<IMAPIFolder> lpFolder;
  2158. enum { EID, IKEY, IMAPID, MESSAGE_FLAGS, FLAG_STATUS, MSG_STATUS, LAST_VERB, NUM_COLS };
  2159. static constexpr const SizedSPropTagArray(NUM_COLS, spt) =
  2160. {NUM_COLS, {PR_ENTRYID, PR_INSTANCE_KEY, PR_EC_IMAP_ID,
  2161. PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
  2162. PR_LAST_VERB_EXECUTED}};
  2163. ulock_normal l_idle(m_mIdleLock, std::defer_lock_t());
  2164. // Outlook (express) IDLEs without selecting a folder.
  2165. // When sending an error from this command, Outlook loops on the IDLE command forever :(
  2166. // Therefore, we can never return a HrResultBad() or ...No() here, so we always "succeed"
  2167. m_strIdleTag = strTag;
  2168. m_bIdleMode = true;
  2169. if (strCurrentFolder.empty() || !lpSession) {
  2170. HrResponse(RESP_CONTINUE, "empty idle, nothing is going to happen");
  2171. goto exit;
  2172. }
  2173. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  2174. if (hr != hrSuccess) {
  2175. HrResponse(RESP_CONTINUE, "Can't open selected folder to idle in");
  2176. goto exit;
  2177. }
  2178. hr = lpFolder->GetContentsTable(0, &m_lpIdleTable);
  2179. if (hr != hrSuccess) {
  2180. HrResponse(RESP_CONTINUE, "Can't open selected contents table to idle in");
  2181. goto exit;
  2182. }
  2183. hr = m_lpIdleTable->SetColumns(spt, 0);
  2184. if (hr != hrSuccess) {
  2185. HrResponse(RESP_CONTINUE, "Cannot select columns on selected contents table for idle information");
  2186. goto exit;
  2187. }
  2188. hr = HrAllocAdviseSink(&IMAP::IdleAdviseCallback, (void*)this, &m_lpIdleAdviseSink);
  2189. if (hr != hrSuccess) {
  2190. HrResponse(RESP_CONTINUE, "Can't allocate memory to idle");
  2191. goto exit;
  2192. }
  2193. l_idle.lock();
  2194. hr = m_lpIdleTable->Advise(fnevTableModified, m_lpIdleAdviseSink, &m_ulIdleAdviseConnection);
  2195. if (hr != hrSuccess) {
  2196. HrResponse(RESP_CONTINUE, "Can't advise on current selected folder");
  2197. l_idle.unlock();
  2198. goto exit;
  2199. }
  2200. // \o/ we really succeeded this time
  2201. HrResponse(RESP_CONTINUE, "waiting for notifications");
  2202. l_idle.unlock();
  2203. exit:
  2204. if (hr != hrSuccess) {
  2205. if (m_ulIdleAdviseConnection && m_lpIdleTable) {
  2206. m_lpIdleTable->Unadvise(m_ulIdleAdviseConnection);
  2207. m_ulIdleAdviseConnection = 0;
  2208. }
  2209. if (m_lpIdleAdviseSink) {
  2210. m_lpIdleAdviseSink->Release();
  2211. m_lpIdleAdviseSink = NULL;
  2212. }
  2213. if (m_lpIdleTable) {
  2214. m_lpIdleTable->Release();
  2215. m_lpIdleTable = NULL;
  2216. }
  2217. }
  2218. return hr;
  2219. }
  2220. /**
  2221. * @brief Handles the DONE command
  2222. *
  2223. * The DONE command closes the previous IDLE command, and has no IMAP
  2224. * tag. This function can be also called implicitly, so a response to
  2225. * the client is not always required.
  2226. *
  2227. * @param[in] bSendResponse Send a response to the client or not
  2228. *
  2229. * @return MAPI Error code
  2230. */
  2231. HRESULT IMAP::HrDone(bool bSendResponse) {
  2232. HRESULT hr = hrSuccess;
  2233. // TODO: maybe add sleep here, so thunderbird gets all notifications?
  2234. scoped_lock l_idle(m_mIdleLock);
  2235. if (bSendResponse) {
  2236. if (m_bIdleMode)
  2237. HrResponse(RESP_TAGGED_OK, m_strIdleTag, "IDLE complete");
  2238. else
  2239. HrResponse(RESP_TAGGED_BAD, m_strIdleTag, "was not idling");
  2240. }
  2241. if (m_ulIdleAdviseConnection && m_lpIdleTable) {
  2242. m_lpIdleTable->Unadvise(m_ulIdleAdviseConnection);
  2243. m_ulIdleAdviseConnection = 0;
  2244. }
  2245. if (m_lpIdleAdviseSink)
  2246. m_lpIdleAdviseSink->Release();
  2247. m_lpIdleAdviseSink = NULL;
  2248. m_ulIdleAdviseConnection = 0;
  2249. m_bIdleMode = false;
  2250. m_strIdleTag.clear();
  2251. if (m_lpIdleTable)
  2252. m_lpIdleTable->Release();
  2253. m_lpIdleTable = NULL;
  2254. return hr;
  2255. }
  2256. /**
  2257. * @brief Handles the NAMESPACE command
  2258. *
  2259. * We currently only support the user namespace.
  2260. *
  2261. * @param[in] strTag the IMAP tag for this command
  2262. *
  2263. * @return MAPI Error code
  2264. */
  2265. HRESULT IMAP::HrCmdNamespace(const string &strTag) {
  2266. HrResponse(RESP_UNTAGGED, string("NAMESPACE ((\"\" \"") +
  2267. IMAP_HIERARCHY_DELIMITER + "\")) NIL NIL");
  2268. HrResponse(RESP_TAGGED_OK, strTag, "NAMESPACE Completed");
  2269. return hrSuccess;
  2270. }
  2271. /**
  2272. * Sends a response to the client with specific quota information. We
  2273. * only export the hard quota level to the IMAP client.
  2274. *
  2275. * @param[in] strTag Tag of the IMAP command
  2276. * @param[in] strQuotaRoot Which quota root is requested
  2277. *
  2278. * @return MAPI Error code
  2279. */
  2280. HRESULT IMAP::HrPrintQuotaRoot(const string& strTag)
  2281. {
  2282. HRESULT hr = hrSuccess;
  2283. static constexpr const SizedSPropTagArray(2, sStoreProps) =
  2284. {2, {PR_MESSAGE_SIZE_EXTENDED, PR_QUOTA_RECEIVE_THRESHOLD}};
  2285. memory_ptr<SPropValue> lpProps;
  2286. ULONG cValues = 0;
  2287. hr = lpStore->GetProps(sStoreProps, 0, &cValues, &~lpProps);
  2288. if (hr != hrSuccess) {
  2289. HrResponse(RESP_TAGGED_NO, strTag, "GetQuota MAPI Error");
  2290. return hr;
  2291. }
  2292. // only print quota if we have a level
  2293. if (lpProps[1].Value.ul)
  2294. HrResponse(RESP_UNTAGGED, "QUOTA \"\" (STORAGE "+stringify(lpProps[0].Value.li.QuadPart / 1024) + " " + stringify(lpProps[1].Value.ul) + ")");
  2295. return hrSuccess;
  2296. }
  2297. /**
  2298. * @brief Handles the GETQUOTAROOT command
  2299. *
  2300. * Sends which quota roots are available (only hard quota).
  2301. *
  2302. * @param[in] strTag Tag of the IMAP command
  2303. * @param[in] strFolder The folder to request quota information on
  2304. *
  2305. * @todo check if folder is in public and show no quota?
  2306. *
  2307. * @return
  2308. */
  2309. HRESULT IMAP::HrCmdGetQuotaRoot(const std::string &strTag,
  2310. const std::vector<std::string> &args)
  2311. {
  2312. HRESULT hr = hrSuccess;
  2313. const std::string &strFolder = args[0];
  2314. if (!lpStore) {
  2315. HrResponse(RESP_TAGGED_BAD, strTag, "Login first");
  2316. return MAPI_E_CALL_FAILED;
  2317. }
  2318. // @todo check if folder exists
  2319. HrResponse(RESP_UNTAGGED, "QUOTAROOT \"" + strFolder + "\" \"\"");
  2320. hr = HrPrintQuotaRoot(strTag);
  2321. if (hr != hrSuccess)
  2322. return hr; /* handle error? */
  2323. HrResponse(RESP_TAGGED_OK, strTag, "GetQuotaRoot complete");
  2324. return hr;
  2325. }
  2326. /**
  2327. * Get the quota value for a given quota root. We only have the "" quota root.
  2328. *
  2329. * @param[in] strTag Tag of the IMAP command
  2330. * @param[in] strQuotaRoot given quota root
  2331. *
  2332. * @return
  2333. */
  2334. HRESULT IMAP::HrCmdGetQuota(const std::string &strTag,
  2335. const std::vector<std::string> &args)
  2336. {
  2337. const std::string &strQuotaRoot = args[0];
  2338. if (!lpStore) {
  2339. HrResponse(RESP_TAGGED_BAD, strTag, "Login first");
  2340. return MAPI_E_CALL_FAILED;
  2341. }
  2342. if (strQuotaRoot.empty()) {
  2343. HRESULT hr = HrPrintQuotaRoot(strTag);
  2344. if (hr != hrSuccess)
  2345. return hr;
  2346. HrResponse(RESP_TAGGED_OK, strTag, "GetQuota complete");
  2347. return hrSuccess;
  2348. }
  2349. HrResponse(RESP_TAGGED_NO, strTag, "Quota root does not exist");
  2350. return hrSuccess;
  2351. }
  2352. HRESULT IMAP::HrCmdSetQuota(const std::string &strTag,
  2353. const std::vector<std::string> &args)
  2354. {
  2355. HrResponse(RESP_TAGGED_NO, strTag, "SetQuota Permission denied");
  2356. return hrSuccess;
  2357. }
  2358. /**
  2359. * Send an untagged response.
  2360. *
  2361. * @param[in] strUntag Either RESP_UNTAGGED or RESP_CONTINUE
  2362. * @param[in] strResponse Status information to send to the client
  2363. *
  2364. * @return MAPI Error code
  2365. */
  2366. void IMAP::HrResponse(const string &strUntag, const string &strResponse)
  2367. {
  2368. // Early cutoff of debug messages. This means the current process's config
  2369. // determines if we log debug info (so HUP will only affect new processes if
  2370. // you want debug output)
  2371. if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
  2372. lpLogger->Log(EC_LOGLEVEL_DEBUG, "> %s%s", strUntag.c_str(), strResponse.c_str());
  2373. HRESULT hr = lpChannel->HrWriteLine(strUntag + strResponse);
  2374. if (hr != hrSuccess)
  2375. throw KMAPIError(hr);
  2376. }
  2377. /**
  2378. * Tagged response to the client. You may only send one tagged
  2379. * response to the client per received command.
  2380. *
  2381. * @note be careful when sending NO or BAD results: some clients may try the same command over and over.
  2382. *
  2383. * @param[in] strTag The tag received from the client for a command
  2384. * @param[in] strResult The result of the command, either RESP_TAGGED_OK, RESP_TAGGED_NO or RESP_TAGGED_BAD
  2385. * @param[in] strResponse The result of the command to send to the client
  2386. *
  2387. * @return MAPI Error code
  2388. */
  2389. void IMAP::HrResponse(const string &strResult, const string &strTag, const string &strResponse)
  2390. {
  2391. unsigned int max_err;
  2392. max_err = strtoul(lpConfig->GetSetting("imap_max_fail_commands"), NULL, 0);
  2393. // Some clients keep looping, so if we keep sending errors, just disconnect the client.
  2394. if (strResult.compare(RESP_TAGGED_OK) == 0)
  2395. m_ulErrors = 0;
  2396. else
  2397. ++m_ulErrors;
  2398. if (m_ulErrors >= max_err) {
  2399. lpLogger->Log(EC_LOGLEVEL_ERROR, "Disconnecting client of user %ls because too many (%u) erroneous commands received, last reply:", m_strwUsername.c_str(), max_err);
  2400. lpLogger->Log(EC_LOGLEVEL_ERROR, "%s%s%s", strTag.c_str(), strResult.c_str(), strResponse.c_str());
  2401. throw KMAPIError(MAPI_E_END_OF_SESSION);
  2402. }
  2403. if (lpLogger->Log(EC_LOGLEVEL_DEBUG))
  2404. lpLogger->Log(EC_LOGLEVEL_DEBUG, "> %s%s%s", strTag.c_str(), strResult.c_str(), strResponse.c_str());
  2405. HRESULT hr = lpChannel->HrWriteLine(strTag + strResult + strResponse);
  2406. if (hr != hrSuccess)
  2407. throw KMAPIError(hr);
  2408. }
  2409. /**
  2410. * Remove \Deleted marked message from current selected folder. Only
  2411. * response in case of an error.
  2412. *
  2413. * @param[in] strTag IMAP tag given for command
  2414. * @param[in] strCommand IMAP command which triggered the expunge
  2415. * @param[in] lpUIDRestriction optional restriction to limit messages actually deleted (see HrCmdExpunge)
  2416. *
  2417. * @return MAPI Error code
  2418. */
  2419. HRESULT IMAP::HrExpungeDeleted(const std::string &strTag,
  2420. const std::string &strCommand, std::unique_ptr<ECRestriction> &&uid_rst)
  2421. {
  2422. HRESULT hr = hrSuccess;
  2423. object_ptr<IMAPIFolder> lpFolder;
  2424. ENTRYLIST sEntryList;
  2425. memory_ptr<SRestriction> lpRootRestrict;
  2426. object_ptr<IMAPITable> lpTable;
  2427. rowset_ptr lpRows;
  2428. enum { EID, NUM_COLS };
  2429. static constexpr const SizedSPropTagArray(NUM_COLS, spt) = {NUM_COLS, {PR_ENTRYID}};
  2430. ECAndRestriction rst;
  2431. sEntryList.lpbin = NULL;
  2432. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  2433. if (hr != hrSuccess) {
  2434. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error opening folder");
  2435. goto exit;
  2436. }
  2437. hr = lpFolder->GetContentsTable(MAPI_DEFERRED_ERRORS , &~lpTable);
  2438. if (hr != hrSuccess) {
  2439. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error opening folder contents");
  2440. goto exit;
  2441. }
  2442. if (uid_rst != nullptr)
  2443. rst += std::move(*uid_rst.get());
  2444. rst += ECExistRestriction(PR_MSG_STATUS);
  2445. rst += ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED);
  2446. hr = rst.CreateMAPIRestriction(&~lpRootRestrict, ECRestriction::Cheap);
  2447. if (hr != hrSuccess)
  2448. goto exit;
  2449. hr = HrQueryAllRows(lpTable, spt, lpRootRestrict, nullptr, 0, &~lpRows);
  2450. if (hr != hrSuccess) {
  2451. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error queryring rows");
  2452. goto exit;
  2453. }
  2454. if(lpRows->cRows) {
  2455. sEntryList.cValues = 0;
  2456. if ((hr = MAPIAllocateBuffer(sizeof(SBinary) * lpRows->cRows, (LPVOID *) &sEntryList.lpbin)) != hrSuccess)
  2457. goto exit;
  2458. for (ULONG ulMailnr = 0; ulMailnr < lpRows->cRows; ++ulMailnr) {
  2459. hr = lpFolder->SetMessageStatus(lpRows->aRow[ulMailnr].lpProps[EID].Value.bin.cb, (LPENTRYID)lpRows->aRow[ulMailnr].lpProps[EID].Value.bin.lpb,
  2460. 0, ~MSGSTATUS_DELMARKED, NULL);
  2461. if (hr != hrSuccess)
  2462. lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to update message status flag during " + strCommand);
  2463. sEntryList.lpbin[sEntryList.cValues++] = lpRows->aRow[ulMailnr].lpProps[EID].Value.bin;
  2464. }
  2465. hr = lpFolder->DeleteMessages(&sEntryList, 0, NULL, 0);
  2466. if (hr != hrSuccess) {
  2467. HrResponse(RESP_TAGGED_NO, strTag, strCommand + " error deleting messages");
  2468. goto exit;
  2469. }
  2470. }
  2471. exit:
  2472. MAPIFreeBuffer(sEntryList.lpbin);
  2473. return hr;
  2474. }
  2475. /**
  2476. * Create a flat list of all folders in the users tree, and possibly
  2477. * add the public folders to this list too.
  2478. *
  2479. * @param[out] lstFolders Sets the folder list
  2480. *
  2481. * @return MAPI Error code
  2482. */
  2483. HRESULT IMAP::HrGetFolderList(list<SFolder> &lstFolders) {
  2484. HRESULT hr = hrSuccess;
  2485. memory_ptr<SPropValue> lpPropVal;
  2486. ULONG cbEntryID;
  2487. memory_ptr<ENTRYID> lpEntryID;
  2488. lstFolders.clear();
  2489. hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpPropVal);
  2490. if (hr != hrSuccess)
  2491. return hr;
  2492. // make folders list from IPM_SUBTREE
  2493. hr = HrGetSubTree(lstFolders, lpPropVal->Value.bin, wstring(), lstFolders.end());
  2494. if (hr != hrSuccess)
  2495. return hr;
  2496. hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  2497. if (hr != hrSuccess)
  2498. return hr;
  2499. // find the inbox, and name it INBOX
  2500. for (auto &folder : lstFolders)
  2501. if (cbEntryID == folder.sEntryID.cb && memcmp(folder.sEntryID.lpb, lpEntryID, cbEntryID) == 0) {
  2502. folder.strFolderName = L"INBOX";
  2503. break;
  2504. }
  2505. if(!lpPublicStore)
  2506. return hr;
  2507. hr = HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpPropVal);
  2508. if (hr != hrSuccess) {
  2509. lpLogger->Log(EC_LOGLEVEL_WARNING, "Public store is enabled in configuration, but Public Folders inside public store could not be found.");
  2510. return hrSuccess;
  2511. }
  2512. // make public folder folders list
  2513. hr = HrGetSubTree(lstFolders, lpPropVal->Value.bin, PUBLIC_FOLDERS_NAME, --lstFolders.end());
  2514. if (hr != hrSuccess)
  2515. lpLogger->Log(EC_LOGLEVEL_WARNING, "Public store is enabled in configuration, but Public Folders inside public store could not be found.");
  2516. return hrSuccess;
  2517. }
  2518. /**
  2519. * Loads a binary blob from PR_EC_IMAP_SUBSCRIBED property on the
  2520. * Inbox where entryids of folders are store in, which describes the
  2521. * folders the user subscribed to with an IMAP client.
  2522. *
  2523. * @return MAPI Error code
  2524. * @retval hrSuccess Subscribed folder list loaded in m_vSubscriptions
  2525. */
  2526. HRESULT IMAP::HrGetSubscribedList() {
  2527. HRESULT hr = hrSuccess;
  2528. object_ptr<IStream> lpStream;
  2529. object_ptr<IMAPIFolder> lpInbox;
  2530. ULONG cbEntryID = 0;
  2531. memory_ptr<ENTRYID> lpEntryID;
  2532. ULONG ulObjType = 0;
  2533. ULONG size, i;
  2534. ULONG read;
  2535. ULONG cb = 0;
  2536. m_vSubscriptions.clear();
  2537. hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  2538. if (hr != hrSuccess)
  2539. return hr;
  2540. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, 0, &ulObjType, &~lpInbox);
  2541. if (hr != hrSuccess)
  2542. return hr;
  2543. hr = lpInbox->OpenProperty(PR_EC_IMAP_SUBSCRIBED, &IID_IStream, 0, 0, &~lpStream);
  2544. if (hr != hrSuccess)
  2545. return hr;
  2546. hr = lpStream->Read(&size, sizeof(ULONG), &read);
  2547. if (hr != hrSuccess || read != sizeof(ULONG))
  2548. return hr;
  2549. if (size == 0)
  2550. return hr;
  2551. for (i = 0; i < size; ++i) {
  2552. std::unique_ptr<BYTE[]> lpb;
  2553. hr = lpStream->Read(&cb, sizeof(ULONG), &read);
  2554. if (hr != hrSuccess || read != sizeof(ULONG))
  2555. return hr;
  2556. lpb.reset(new BYTE[cb]);
  2557. hr = lpStream->Read(lpb.get(), cb, &read);
  2558. if (hr != hrSuccess || read != cb)
  2559. return MAPI_E_NOT_FOUND;
  2560. m_vSubscriptions.push_back(BinaryArray(lpb.get(), cb));
  2561. }
  2562. return hr;
  2563. }
  2564. /**
  2565. * Saves the m_vSubscriptions list of subscribed folders to
  2566. * PR_EC_IMAP_SUBSCRIBED property in the inbox of the user.
  2567. *
  2568. * @return MAPI Error code
  2569. * @retval hrSuccess Subscribed folder list saved in Inbox property
  2570. */
  2571. HRESULT IMAP::HrSetSubscribedList() {
  2572. HRESULT hr = hrSuccess;
  2573. object_ptr<IStream> lpStream;
  2574. object_ptr<IMAPIFolder> lpInbox;
  2575. ULONG cbEntryID = 0;
  2576. memory_ptr<ENTRYID> lpEntryID;
  2577. ULONG ulObjType = 0;
  2578. ULONG written;
  2579. ULONG size;
  2580. ULARGE_INTEGER liZero = {{0, 0}};
  2581. hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  2582. if (hr != hrSuccess)
  2583. return hr;
  2584. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, MAPI_BEST_ACCESS, &ulObjType, &~lpInbox);
  2585. if (hr != hrSuccess)
  2586. return hr;
  2587. hr = lpInbox->OpenProperty(PR_EC_IMAP_SUBSCRIBED, &IID_IStream, STGM_TRANSACTED, MAPI_CREATE | MAPI_MODIFY, &~lpStream);
  2588. if (hr != hrSuccess)
  2589. return hr;
  2590. lpStream->SetSize(liZero);
  2591. size = m_vSubscriptions.size();
  2592. hr = lpStream->Write(&size, sizeof(ULONG), &written);
  2593. if (hr != hrSuccess)
  2594. return hr;
  2595. for (const auto &folder : m_vSubscriptions) {
  2596. hr = lpStream->Write(&folder.cb, sizeof(ULONG), &written);
  2597. if (hr != hrSuccess)
  2598. return hr;
  2599. hr = lpStream->Write(folder.lpb, folder.cb, &written);
  2600. if (hr != hrSuccess)
  2601. return hr;
  2602. }
  2603. return lpStream->Commit(0);
  2604. }
  2605. /**
  2606. * Add or remove a folder EntryID from the subscribed list, and save
  2607. * it to the server when changed.
  2608. *
  2609. * @param[in] bSubscribe Add (true) to the list, or remove (false)
  2610. * @param[in] cbEntryID number of bytes in lpEntryID
  2611. * @param[in] lpEntryID EntryID to find in the subscribed list
  2612. *
  2613. * @return MAPI Error code
  2614. */
  2615. HRESULT IMAP::ChangeSubscribeList(bool bSubscribe, ULONG cbEntryID, LPENTRYID lpEntryID)
  2616. {
  2617. bool bChanged = false;
  2618. auto iFolder = find(m_vSubscriptions.begin(), m_vSubscriptions.end(),
  2619. BinaryArray(reinterpret_cast<BYTE *>(lpEntryID),
  2620. cbEntryID, true));
  2621. if (iFolder == m_vSubscriptions.cend()) {
  2622. if (bSubscribe) {
  2623. m_vSubscriptions.push_back(BinaryArray((BYTE*)lpEntryID, cbEntryID));
  2624. bChanged = true;
  2625. }
  2626. } else if (!bSubscribe) {
  2627. m_vSubscriptions.erase(iFolder);
  2628. bChanged = true;
  2629. }
  2630. if (bChanged) {
  2631. HRESULT hr = HrSetSubscribedList();
  2632. if (hr != hrSuccess)
  2633. return hr;
  2634. }
  2635. return hrSuccess;
  2636. }
  2637. /**
  2638. * Create a list of special IMAP folders, which may not be deleted,
  2639. * renamed or unsubscribed from.
  2640. *
  2641. * @return MAPI Error code
  2642. * @retval hrSuccess the special folderlist is saved in lstSpecialEntryIDs
  2643. */
  2644. HRESULT IMAP::HrMakeSpecialsList() {
  2645. HRESULT hr = hrSuccess;
  2646. ULONG cbEntryID = 0;
  2647. memory_ptr<ENTRYID> lpEntryID;
  2648. object_ptr<IMAPIFolder> lpInbox;
  2649. ULONG ulObjType = 0;
  2650. ULONG cValues = 0;
  2651. memory_ptr<SPropValue> lpPropArrayStore, lpPropArrayInbox, lpPropVal;
  2652. static constexpr const SizedSPropTagArray(3, sPropsStore) =
  2653. {3, {PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID,
  2654. PR_IPM_WASTEBASKET_ENTRYID}};
  2655. static constexpr const SizedSPropTagArray(6, sPropsInbox) =
  2656. {6, {PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID,
  2657. PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID,
  2658. PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID}};
  2659. hr = lpStore->GetProps(sPropsStore, 0, &cValues, &~lpPropArrayStore);
  2660. if (hr != hrSuccess)
  2661. return hr;
  2662. for (ULONG i = 0; i < cValues; ++i)
  2663. if (PROP_TYPE(lpPropArrayStore[i].ulPropTag) == PT_BINARY)
  2664. lstSpecialEntryIDs.insert(BinaryArray(lpPropArrayStore[i].Value.bin.lpb, lpPropArrayStore[i].Value.bin.cb));
  2665. hr = lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, &cbEntryID, &~lpEntryID, NULL);
  2666. if (hr != hrSuccess)
  2667. return hr;
  2668. // inbox is special too
  2669. lstSpecialEntryIDs.insert(BinaryArray(reinterpret_cast<unsigned char *>(lpEntryID.get()), cbEntryID));
  2670. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, &IID_IMAPIFolder, 0, &ulObjType, &~lpInbox);
  2671. if (hr != hrSuccess)
  2672. return hr;
  2673. hr = lpInbox->GetProps(sPropsInbox, 0, &cValues, &~lpPropArrayInbox);
  2674. if (hr != hrSuccess)
  2675. return hr;
  2676. for (ULONG i = 0; i < cValues; ++i)
  2677. if (PROP_TYPE(lpPropArrayInbox[i].ulPropTag) == PT_BINARY)
  2678. lstSpecialEntryIDs.insert(BinaryArray(lpPropArrayInbox[i].Value.bin.lpb, lpPropArrayInbox[i].Value.bin.cb));
  2679. if (HrGetOneProp(lpInbox, PR_ADDITIONAL_REN_ENTRYIDS, &~lpPropVal) == hrSuccess &&
  2680. lpPropVal->Value.MVbin.cValues >= 5 && lpPropVal->Value.MVbin.lpbin[4].cb != 0)
  2681. lstSpecialEntryIDs.insert(BinaryArray(lpPropVal->Value.MVbin.lpbin[4].lpb, lpPropVal->Value.MVbin.lpbin[4].cb));
  2682. if(!lpPublicStore)
  2683. return hrSuccess;
  2684. if (HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpPropVal) == hrSuccess)
  2685. lstSpecialEntryIDs.insert(BinaryArray(lpPropVal->Value.bin.lpb, lpPropVal->Value.bin.cb));
  2686. return hrSuccess;
  2687. }
  2688. /**
  2689. * Check if the folder with the given EntryID is a special folder.
  2690. *
  2691. * @param[in] cbEntryID number of bytes in lpEntryID
  2692. * @param[in] lpEntryID bytes of the entryid
  2693. *
  2694. * @return is a special folder (true) or a custom user folder (false)
  2695. */
  2696. bool IMAP::IsSpecialFolder(ULONG cbEntryID, LPENTRYID lpEntryID) {
  2697. return lstSpecialEntryIDs.find(BinaryArray(reinterpret_cast<BYTE *>(lpEntryID), cbEntryID, true)) !=
  2698. lstSpecialEntryIDs.end();
  2699. }
  2700. /**
  2701. * Make a list of all mails in the current selected folder.
  2702. *
  2703. * @param[in] bInitialLoad Create a new clean list of mails (false to append only)
  2704. * @param[in] bResetRecent Update the value of PR_EC_IMAP_MAX_ID for this folder
  2705. * @param[out] lpulUnseen The number of unread emails in this folder
  2706. * @param[out] lpulUIDValidity The UIDVALIDITY value for this folder (optional)
  2707. *
  2708. * @return MAPI Error code
  2709. */
  2710. HRESULT IMAP::HrRefreshFolderMails(bool bInitialLoad, bool bResetRecent, unsigned int *lpulUnseen, ULONG *lpulUIDValidity) {
  2711. HRESULT hr = hrSuccess;
  2712. object_ptr<IMAPIFolder> lpFolder;
  2713. ULONG ulMailnr = 0;
  2714. ULONG ulMaxUID = 0;
  2715. ULONG ulRecent = 0;
  2716. int n = 0;
  2717. SMail sMail;
  2718. bool bNewMail = false;
  2719. enum { EID, IKEY, IMAPID, FLAGS, FLAGSTATUS, MSGSTATUS, LAST_VERB, NUM_COLS };
  2720. vector<SMail>::const_iterator iterMail;
  2721. map<unsigned int, unsigned int> mapUIDs; // Map UID -> ID
  2722. SPropValue sPropMax;
  2723. unsigned int ulUnseen = 0;
  2724. static constexpr const SizedSPropTagArray(2, sPropsFolderIDs) =
  2725. {2, {PR_EC_IMAP_MAX_ID, PR_EC_HIERARCHYID}};
  2726. memory_ptr<SPropValue> lpFolderIDs;
  2727. ULONG cValues;
  2728. if (strCurrentFolder.empty() || lpSession == nullptr)
  2729. return MAPI_E_CALL_FAILED;
  2730. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  2731. if (hr != hrSuccess)
  2732. return hr;
  2733. hr = lpFolder->GetProps(sPropsFolderIDs, 0, &cValues, &~lpFolderIDs);
  2734. if (FAILED(hr))
  2735. return hr;
  2736. if (lpFolderIDs[0].ulPropTag == PR_EC_IMAP_MAX_ID)
  2737. ulMaxUID = lpFolderIDs[0].Value.ul;
  2738. else
  2739. ulMaxUID = 0;
  2740. if (lpulUIDValidity && lpFolderIDs[1].ulPropTag == PR_EC_HIERARCHYID)
  2741. *lpulUIDValidity = lpFolderIDs[1].Value.ul;
  2742. auto folder = KFolder(lpFolder.release());
  2743. auto table = KTable(nullptr);
  2744. try {
  2745. table = folder.get_contents_table(MAPI_DEFERRED_ERRORS);
  2746. table.columns({PR_ENTRYID, PR_INSTANCE_KEY, PR_EC_IMAP_ID,
  2747. PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
  2748. PR_LAST_VERB_EXECUTED}, TBL_BATCH);
  2749. table.sort({{PR_EC_IMAP_ID, KTable::ASCEND}}, TBL_BATCH);
  2750. }
  2751. catch (const KMAPIError &e) {
  2752. return e.code();
  2753. }
  2754. // Remember UIDs if needed
  2755. if(!bInitialLoad)
  2756. for (const auto &mail : lstFolderMailEIDs)
  2757. mapUIDs[mail.ulUid] = n++;
  2758. if(bInitialLoad) {
  2759. lstFolderMailEIDs.clear();
  2760. m_ulLastUid = 0;
  2761. }
  2762. iterMail = lstFolderMailEIDs.cbegin();
  2763. // Scan MAPI for new and existing messages
  2764. while(1) {
  2765. rowset_ptr lpRows;
  2766. hr = table->QueryRows(ROWS_PER_REQUEST, 0, &~lpRows);
  2767. if (hr != hrSuccess)
  2768. return hr;
  2769. if(lpRows->cRows == 0)
  2770. break;
  2771. for (ulMailnr = 0; ulMailnr < lpRows->cRows; ++ulMailnr) {
  2772. if (lpRows->aRow[ulMailnr].lpProps[EID].ulPropTag != PR_ENTRYID ||
  2773. lpRows->aRow[ulMailnr].lpProps[IKEY].ulPropTag != PR_INSTANCE_KEY ||
  2774. lpRows->aRow[ulMailnr].lpProps[IMAPID].ulPropTag != PR_EC_IMAP_ID)
  2775. continue;
  2776. auto iterUID = mapUIDs.find(lpRows->aRow[ulMailnr].lpProps[IMAPID].Value.ul);
  2777. if(iterUID == mapUIDs.end()) {
  2778. // There is a new message
  2779. sMail.sEntryID = BinaryArray(lpRows->aRow[ulMailnr].lpProps[EID].Value.bin);
  2780. sMail.sInstanceKey = BinaryArray(lpRows->aRow[ulMailnr].lpProps[IKEY].Value.bin);
  2781. sMail.ulUid = lpRows->aRow[ulMailnr].lpProps[IMAPID].Value.ul;
  2782. // Mark as recent if the message has a UID higher than the last highest read UID
  2783. // in this folder. This means that this session is the only one to see the message
  2784. // as recent.
  2785. sMail.bRecent = sMail.ulUid > ulMaxUID;
  2786. // Remember flags
  2787. sMail.strFlags = PropsToFlags(lpRows->aRow[ulMailnr].lpProps, lpRows->aRow[ulMailnr].cValues, sMail.bRecent, false);
  2788. // Put message on the end of our message
  2789. lstFolderMailEIDs.push_back(sMail);
  2790. m_ulLastUid = max(sMail.ulUid, m_ulLastUid);
  2791. bNewMail = true;
  2792. // Remember the first unseen message
  2793. if (ulUnseen == 0 &&
  2794. lpRows->aRow[ulMailnr].lpProps[FLAGS].ulPropTag == PR_MESSAGE_FLAGS && (lpRows->aRow[ulMailnr].lpProps[FLAGS].Value.ul & MSGFLAG_READ) == 0)
  2795. ulUnseen = lstFolderMailEIDs.size()-1+1; // size()-1 = last offset, mail ID = position + 1
  2796. continue;
  2797. }
  2798. // Check flags
  2799. std::string strFlags = PropsToFlags(lpRows->aRow[ulMailnr].lpProps, lpRows->aRow[ulMailnr].cValues, lstFolderMailEIDs[iterUID->second].bRecent, false);
  2800. if (lstFolderMailEIDs[iterUID->second].strFlags != strFlags) {
  2801. // Flags have changed, notify it
  2802. HrResponse(RESP_UNTAGGED, stringify(iterUID->second+1) + " FETCH (FLAGS (" + strFlags + "))");
  2803. lstFolderMailEIDs[iterUID->second].strFlags = strFlags;
  2804. }
  2805. // We already had this message, remove it from setUIDs
  2806. mapUIDs.erase(iterUID);
  2807. }
  2808. }
  2809. // All messages left in mapUIDs have been deleted, so loop through the current list so we can
  2810. // send the correct EXPUNGE calls; At the same time, count RECENT messages.
  2811. ulMailnr = 0;
  2812. while(ulMailnr < lstFolderMailEIDs.size()) {
  2813. if (mapUIDs.find(lstFolderMailEIDs[ulMailnr].ulUid) != mapUIDs.cend()) {
  2814. HrResponse(RESP_UNTAGGED, stringify(ulMailnr+1) + " EXPUNGE");
  2815. lstFolderMailEIDs.erase(lstFolderMailEIDs.begin() + ulMailnr);
  2816. continue;
  2817. }
  2818. if (lstFolderMailEIDs[ulMailnr].bRecent)
  2819. ++ulRecent;
  2820. ++iterMail;
  2821. ++ulMailnr;
  2822. }
  2823. if (bNewMail || bInitialLoad) {
  2824. HrResponse(RESP_UNTAGGED, stringify(lstFolderMailEIDs.size()) + " EXISTS");
  2825. HrResponse(RESP_UNTAGGED, stringify(ulRecent) + " RECENT");
  2826. }
  2827. sort(lstFolderMailEIDs.begin(), lstFolderMailEIDs.end());
  2828. // Save the max UID so that other session will not see the items as \Recent
  2829. if(bResetRecent && ulRecent) {
  2830. sPropMax.ulPropTag = PR_EC_IMAP_MAX_ID;
  2831. sPropMax.Value.ul = m_ulLastUid;
  2832. HrSetOneProp(folder, &sPropMax);
  2833. }
  2834. if (lpulUnseen)
  2835. *lpulUnseen = ulUnseen;
  2836. return hrSuccess;
  2837. }
  2838. /**
  2839. * Return the IMAP Path for a given folder. Recursively recreates the
  2840. * path using the parent iterator in the SFolder struct.
  2841. *
  2842. * @param[in] lpFolder Return the path name for this folder
  2843. * @param[in] lstFolders The list of folders for this user, where lpFolder is a valid iterator in
  2844. * @param[out] strPath The full imap path with separators in wide characters
  2845. *
  2846. * @return MAPI Error code
  2847. * @retval MAPI_E_NOT_FOUND lpFolder is not a valid iterator in lstFolders
  2848. */
  2849. HRESULT IMAP::HrGetFolderPath(list<SFolder>::const_iterator lpFolder, const list<SFolder> &lstFolders, wstring &strPath) {
  2850. if (lpFolder == lstFolders.cend())
  2851. return MAPI_E_NOT_FOUND;
  2852. if (lpFolder->lpParentFolder != lstFolders.cend()) {
  2853. HRESULT hr = HrGetFolderPath(lpFolder->lpParentFolder, lstFolders,
  2854. strPath);
  2855. if (hr != hrSuccess)
  2856. return hr;
  2857. strPath += IMAP_HIERARCHY_DELIMITER;
  2858. strPath += lpFolder->strFolderName;
  2859. }
  2860. return hrSuccess;
  2861. }
  2862. /**
  2863. * Appends the given folder list with the given folder from the
  2864. * EntryID with name. If bSubfolders is true, the given folder should
  2865. * have subfolders, and these will recursively be processed.
  2866. *
  2867. * @param[out] lstFolders Add the given folder data to this list
  2868. * @param[in] sEntryID Folder EntryID to add to list, and to process for subfolders
  2869. * @param[in] strFolderName The name of the folder to add to the list
  2870. * @param[in] lpParentFolder iterator in lstFolders to set as the parent folder
  2871. *
  2872. * @return MAPI Error code
  2873. */
  2874. HRESULT IMAP::HrGetSubTree(list<SFolder> &folders, const SBinary &in_entry_id, const wstring &in_folder_name, list<SFolder>::const_iterator parent_folder)
  2875. {
  2876. if (lpSession == nullptr)
  2877. return MAPI_E_CALL_FAILED;
  2878. SFolder sfolder;
  2879. sfolder.bActive = true;
  2880. sfolder.bSpecialFolder = IsSpecialFolder(in_entry_id.cb, reinterpret_cast<ENTRYID *>(in_entry_id.lpb));
  2881. sfolder.bMailFolder = false;
  2882. sfolder.lpParentFolder = parent_folder;
  2883. sfolder.strFolderName = in_folder_name;
  2884. sfolder.bHasSubfolders = true;
  2885. folders.push_front(sfolder);
  2886. parent_folder = folders.cbegin();
  2887. ULONG obj_type;
  2888. object_ptr<IMAPIFolder> mapi_folder;
  2889. HRESULT hr = lpSession->OpenEntry(in_entry_id.cb, reinterpret_cast<ENTRYID *>(in_entry_id.lpb), &IID_IMAPIFolder, 0, &obj_type, &~mapi_folder);
  2890. if (hr != hrSuccess)
  2891. return hr;
  2892. enum { EID, PEID, NAME, IMAPID, SUBFOLDERS, CONTAINERCLASS, NUM_COLS };
  2893. try {
  2894. KFolder folder = mapi_folder.release();
  2895. KTable table = folder.get_hierarchy_table(CONVENIENT_DEPTH);
  2896. table.columns({PR_ENTRYID, PR_PARENT_ENTRYID, PR_DISPLAY_NAME_W, PR_EC_IMAP_ID, PR_SUBFOLDERS, PR_CONTAINER_CLASS_A});
  2897. table.sort({{PR_DEPTH, KTable::ASCEND}});
  2898. KRowSet rows = table.rows(-1, 0);
  2899. for (unsigned int i = 0; i < rows.count(); ++i) {
  2900. if (rows[i][IMAPID].prop_type() != PT_LONG) {
  2901. lpLogger->Log(EC_LOGLEVEL_FATAL, "Server does not support PR_EC_IMAP_ID. Please update the storage server.");
  2902. break;
  2903. }
  2904. try {
  2905. string container_class = "";
  2906. bool mailfolder = true;
  2907. wstring foldername = rows[i][NAME].wstr();
  2908. bool subfolders = rows[i][SUBFOLDERS].b();
  2909. try {
  2910. container_class = rows[i][CONTAINERCLASS].str();
  2911. }
  2912. catch (const KMAPIError &e) {
  2913. if(e.code() != MAPI_E_NOT_FOUND && e.code() != MAPI_E_INVALID_TYPE)
  2914. throw;
  2915. }
  2916. while (foldername.find(IMAP_HIERARCHY_DELIMITER) != string::npos)
  2917. foldername.erase(foldername.find(IMAP_HIERARCHY_DELIMITER), 1);
  2918. container_class = strToUpper(container_class);
  2919. if (!container_class.empty() &&
  2920. container_class.compare(0, 3, "IPM") != 0 &&
  2921. container_class.compare("IPF.NOTE") != 0) {
  2922. if (bOnlyMailFolders)
  2923. continue;
  2924. mailfolder = false;
  2925. }
  2926. auto entry_id = rows[i][EID].entry_id();
  2927. auto parent_entry_id = rows[i][PEID].entry_id();
  2928. list<SFolder>::const_iterator tmp_parent_folder = parent_folder;
  2929. for (auto iter = folders.cbegin(); iter != folders.cend(); iter++) {
  2930. if (iter->sEntryID == parent_entry_id) {
  2931. tmp_parent_folder = iter;
  2932. break;
  2933. }
  2934. }
  2935. auto subscribed_iter = find(m_vSubscriptions.cbegin(), m_vSubscriptions.cend(), BinaryArray(entry_id));
  2936. sfolder.bActive = subscribed_iter != m_vSubscriptions.cend();
  2937. sfolder.bSpecialFolder = IsSpecialFolder(entry_id.cb(), entry_id.lpb());
  2938. sfolder.bMailFolder = mailfolder;
  2939. sfolder.lpParentFolder = tmp_parent_folder;
  2940. sfolder.strFolderName = foldername;
  2941. sfolder.sEntryID = entry_id;
  2942. sfolder.bHasSubfolders = subfolders;
  2943. folders.push_front(sfolder);
  2944. }
  2945. catch (const KMAPIError &e) {
  2946. /* just continue */
  2947. }
  2948. }
  2949. }
  2950. catch (const KMAPIError &e) {
  2951. return e.code();
  2952. }
  2953. return hrSuccess;
  2954. }
  2955. /**
  2956. * Extends IMAP shortcuts into real full IMAP proptags, and returns an
  2957. * vector of all separate and capitalized items.
  2958. *
  2959. * @param[in] strMsgDataItemNames String of wanted data items from a FETCH command.
  2960. * @param[out] lstDataItems Vector of all separate items in the string in uppercase.
  2961. *
  2962. * @return hrSuccess
  2963. */
  2964. HRESULT IMAP::HrGetDataItems(string strMsgDataItemNames, vector<string> &lstDataItems) {
  2965. // translate macro's
  2966. strMsgDataItemNames = strToUpper(strMsgDataItemNames);
  2967. if (strMsgDataItemNames.compare("ALL") == 0)
  2968. strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE";
  2969. else if (strMsgDataItemNames.compare("FAST") == 0)
  2970. strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE";
  2971. else if (strMsgDataItemNames.compare("FULL") == 0)
  2972. strMsgDataItemNames = "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY";
  2973. // split data items
  2974. if (strMsgDataItemNames.size() > 1 && strMsgDataItemNames[0] == '(') {
  2975. strMsgDataItemNames.erase(0,1);
  2976. strMsgDataItemNames.erase(strMsgDataItemNames.size()-1, 1);
  2977. }
  2978. return HrSplitInput(strMsgDataItemNames, lstDataItems);
  2979. }
  2980. /**
  2981. * Replaces all ; characters in the input to , characters.
  2982. *
  2983. * @param[in] strData The string to modify
  2984. *
  2985. * @return hrSuccess
  2986. */
  2987. HRESULT IMAP::HrSemicolonToComma(string &strData) {
  2988. string::size_type ulPos = strData.find(";");
  2989. while (ulPos != string::npos) {
  2990. strData.replace(ulPos, 1, ",");
  2991. ulPos = strData.find(";", ulPos + 1);
  2992. }
  2993. return hrSuccess;
  2994. }
  2995. /**
  2996. * Do a FETCH based on table data for a specific list of
  2997. * messages. Replies directly to the IMAP client with the result for
  2998. * each mail given.
  2999. *
  3000. * @param[in] lstMails a full sorted list of emails to process
  3001. * @param[in] lstDataItems vector of IMAP data items to send to the client
  3002. *
  3003. * @return MAPI Error code
  3004. */
  3005. HRESULT IMAP::HrPropertyFetch(list<ULONG> &lstMails, vector<string> &lstDataItems) {
  3006. HRESULT hr = hrSuccess;
  3007. object_ptr<IMAPIFolder> lpFolder;
  3008. rowset_ptr lpRows;
  3009. LPSRow lpRow = NULL;
  3010. LONG nRow = -1;
  3011. ULONG ulDataItemNr;
  3012. string strDataItem;
  3013. SPropValue sPropVal;
  3014. string strResponse;
  3015. memory_ptr<SPropTagArray> lpPropTags;
  3016. set<ULONG> setProps;
  3017. int n;
  3018. LPSPropValue lpProps;
  3019. ULONG cValues;
  3020. unsigned int ulReadAhead = 0;
  3021. static constexpr const SizedSSortOrderSet(1, sSortUID) =
  3022. {1, 0, 0, {{PR_EC_IMAP_ID, TABLE_SORT_ASCEND}}};
  3023. bool bMarkAsRead = false;
  3024. memory_ptr<ENTRYLIST> lpEntryList;
  3025. if (strCurrentFolder.empty() || lpSession == nullptr)
  3026. return MAPI_E_CALL_FAILED;
  3027. // Setup the readahead length
  3028. ulReadAhead = lstMails.size() > ROWS_PER_REQUEST ? ROWS_PER_REQUEST : lstMails.size();
  3029. // Find out which properties we will be needing from the table. This should be kept in-sync
  3030. // with the properties that are used in HrPropertyFetchRow()
  3031. // Also check if we need to mark the message as read.
  3032. for (ulDataItemNr = 0; ulDataItemNr < lstDataItems.size(); ++ulDataItemNr) {
  3033. strDataItem = lstDataItems[ulDataItemNr];
  3034. if (strDataItem.compare("FLAGS") == 0) {
  3035. setProps.insert(PR_MESSAGE_FLAGS);
  3036. setProps.insert(PR_FLAG_STATUS);
  3037. setProps.insert(PR_MSG_STATUS);
  3038. setProps.insert(PR_LAST_VERB_EXECUTED);
  3039. } else if (strDataItem.compare("XAOL.SIZE") == 0) {
  3040. // estimated size
  3041. setProps.insert(PR_MESSAGE_SIZE);
  3042. } else if (strDataItem.compare("INTERNALDATE") == 0) {
  3043. setProps.insert(PR_MESSAGE_DELIVERY_TIME);
  3044. setProps.insert(PR_CLIENT_SUBMIT_TIME);
  3045. } else if (strDataItem.compare("BODY") == 0) {
  3046. setProps.insert(PR_EC_IMAP_BODY);
  3047. } else if (strDataItem.compare("BODYSTRUCTURE") == 0) {
  3048. setProps.insert(PR_EC_IMAP_BODYSTRUCTURE);
  3049. } else if (strDataItem.compare("ENVELOPE") == 0) {
  3050. setProps.insert(m_lpsIMAPTags->aulPropTag[0]);
  3051. } else if (strDataItem.compare("RFC822.SIZE") == 0) {
  3052. // real size
  3053. setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
  3054. } else if (strstr(strDataItem.c_str(), "HEADER") != NULL) {
  3055. // RFC822.HEADER, BODY[HEADER or BODY.PEEK[HEADER
  3056. setProps.insert(PR_TRANSPORT_MESSAGE_HEADERS_A);
  3057. // if we have the full body, we can skip some hacks to make headers match with the otherwise regenerated version.
  3058. setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
  3059. // this is where RFC822.HEADER seems to differ from BODY[HEADER] requests
  3060. // (according to dovecot and courier)
  3061. if (Prefix(strDataItem, "BODY["))
  3062. bMarkAsRead = true;
  3063. } else if (Prefix(strDataItem, "BODY") || Prefix(strDataItem, "RFC822")) {
  3064. // we don't want PR_EC_IMAP_EMAIL in the table (size problem),
  3065. // and it must be in sync with PR_EC_IMAP_EMAIL_SIZE anyway, so detect presence from size
  3066. setProps.insert(PR_EC_IMAP_EMAIL_SIZE);
  3067. if (strstr(strDataItem.c_str(), "PEEK") == NULL)
  3068. bMarkAsRead = true;
  3069. }
  3070. }
  3071. if (bMarkAsRead) {
  3072. setProps.insert(PR_MESSAGE_FLAGS);
  3073. setProps.insert(PR_FLAG_STATUS);
  3074. setProps.insert(PR_MSG_STATUS);
  3075. setProps.insert(PR_LAST_VERB_EXECUTED);
  3076. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~lpEntryList);
  3077. if (hr != hrSuccess)
  3078. return hr;
  3079. hr = MAPIAllocateMore(lstMails.size()*sizeof(SBinary), lpEntryList, (void**)&lpEntryList->lpbin);
  3080. if (hr != hrSuccess)
  3081. return hr;
  3082. lpEntryList->cValues = 0;
  3083. }
  3084. if(!setProps.empty() && m_vTableDataColumns != lstDataItems) {
  3085. ReleaseContentsCache();
  3086. // Build an LPSPropTagArray
  3087. hr = MAPIAllocateBuffer(CbNewSPropTagArray(setProps.size() + 1), &~lpPropTags);
  3088. if(hr != hrSuccess)
  3089. return hr;
  3090. // Always get UID
  3091. lpPropTags->aulPropTag[0] = PR_INSTANCE_KEY;
  3092. n = 1;
  3093. for (auto prop : setProps)
  3094. lpPropTags->aulPropTag[n++] = prop;
  3095. lpPropTags->cValues = setProps.size()+1;
  3096. // Open the folder in question
  3097. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  3098. if (hr != hrSuccess)
  3099. return hr;
  3100. // Don't let the server cap the contents to 255 bytes, so our PR_TRANSPORT_MESSAGE_HEADERS is complete in the table
  3101. hr = lpFolder->GetContentsTable(EC_TABLE_NOCAP | MAPI_DEFERRED_ERRORS, &m_lpTable);
  3102. if (hr != hrSuccess)
  3103. return hr;
  3104. // Request our columns
  3105. hr = m_lpTable->SetColumns(lpPropTags, TBL_BATCH);
  3106. if (hr != hrSuccess)
  3107. return hr;
  3108. // Messages are usually requested in UID order, so sort the table in UID order too. This improves
  3109. // the row prefetch hit ratio.
  3110. hr = m_lpTable->SortTable(sSortUID, TBL_BATCH);
  3111. if(hr != hrSuccess)
  3112. return hr;
  3113. m_vTableDataColumns = lstDataItems;
  3114. } else if (bMarkAsRead) {
  3115. // we need the folder to mark mails as read
  3116. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  3117. if (hr != hrSuccess)
  3118. return hr;
  3119. }
  3120. if (m_lpTable) {
  3121. hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
  3122. if(hr != hrSuccess)
  3123. return hr;
  3124. }
  3125. // Setup a find restriction that we modify for each row
  3126. sPropVal.ulPropTag = PR_INSTANCE_KEY;
  3127. ECPropertyRestriction sRestriction(RELOP_EQ, PR_INSTANCE_KEY, &sPropVal, ECRestriction::Cheap);
  3128. // Loop through all requested rows, and get the data for each (FIXME: slow for large requests)
  3129. for (auto mail_idx : lstMails) {
  3130. const SPropValue *lpProp = NULL; // non-free // by default: no need to mark-as-read
  3131. sPropVal.Value.bin.cb = lstFolderMailEIDs[mail_idx].sInstanceKey.cb;
  3132. sPropVal.Value.bin.lpb = lstFolderMailEIDs[mail_idx].sInstanceKey.lpb;
  3133. // We use a read-ahead mechanism here, reading 50 rows at a time.
  3134. if (m_lpTable) {
  3135. // First, see if the next row is somewhere in our already-read data
  3136. lpRow = NULL;
  3137. if (lpRows != nullptr)
  3138. // use nRow to start checking where we left off
  3139. for (unsigned int i = nRow + 1; i < lpRows->cRows; ++i)
  3140. if(lpRows->aRow[i].lpProps[0].ulPropTag == PR_INSTANCE_KEY && BinaryArray(lpRows->aRow[i].lpProps[0].Value.bin) == BinaryArray(sPropVal.Value.bin)) {
  3141. lpRow = &lpRows->aRow[i];
  3142. nRow = i;
  3143. break;
  3144. }
  3145. if(lpRow == NULL) {
  3146. lpRows.reset();
  3147. // Row was not found in our current data, request new data
  3148. if (sRestriction.FindRowIn(m_lpTable, BOOKMARK_CURRENT, 0) == hrSuccess &&
  3149. m_lpTable->QueryRows(ulReadAhead, 0, &~lpRows) == hrSuccess &&
  3150. lpRows->cRows != 0) {
  3151. // The row we want is the first returned row
  3152. lpRow = &lpRows->aRow[0];
  3153. nRow = 0;
  3154. }
  3155. }
  3156. // Pass the row data for conversion
  3157. if(lpRow) {
  3158. // possebly add message to mark-as-read
  3159. if (bMarkAsRead) {
  3160. lpProp = PCpropFindProp(lpRow->lpProps, lpRow->cValues, PR_MESSAGE_FLAGS);
  3161. if (!lpProp || (lpProp->Value.ul & MSGFLAG_READ) == 0) {
  3162. lpEntryList->lpbin[lpEntryList->cValues].cb = lstFolderMailEIDs[mail_idx].sEntryID.cb;
  3163. lpEntryList->lpbin[lpEntryList->cValues].lpb = lstFolderMailEIDs[mail_idx].sEntryID.lpb;
  3164. ++lpEntryList->cValues;
  3165. }
  3166. }
  3167. cValues = lpRow->cValues;
  3168. lpProps = lpRow->lpProps;
  3169. } else {
  3170. cValues = 0;
  3171. lpProps = NULL;
  3172. }
  3173. } else {
  3174. // If the row is unavailable, or the table is not needed, do not pass any properties
  3175. cValues = 0;
  3176. lpProps = NULL;
  3177. }
  3178. // Fetch the row data
  3179. if (HrPropertyFetchRow(lpProps, cValues, strResponse, mail_idx, (lpProp != NULL), lstDataItems) != hrSuccess) {
  3180. lpLogger->Log(EC_LOGLEVEL_WARNING, "{?} Error fetching mail");
  3181. } else {
  3182. HrResponse(RESP_UNTAGGED, strResponse);
  3183. }
  3184. }
  3185. if (lpEntryList && lpEntryList->cValues) {
  3186. // mark unread messages as read
  3187. hr = lpFolder->SetReadFlags(lpEntryList, 0, NULL, SUPPRESS_RECEIPT);
  3188. if (FAILED(hr))
  3189. return hr;
  3190. }
  3191. return hr;
  3192. }
  3193. /**
  3194. * Does a FETCH based on row-data from a MAPI table. If the table data
  3195. * is not sufficient, the PR_EC_IMAP_EMAIL property may be fetched
  3196. * when present, or a full re-generation of the email will be
  3197. * triggered to get the data.
  3198. *
  3199. * @param[in] lpProps Array of MAPI properties of a message
  3200. * @param[in] cValues Number of properties in lpProps
  3201. * @param[out] strResponse The string to send to the client
  3202. * @param[in] ulMailnr Number of current email which we're creating a response for
  3203. * @param[in] lstDataItems IMAP data items to add to the result string
  3204. *
  3205. * @return MAPI Error code
  3206. */
  3207. HRESULT IMAP::HrPropertyFetchRow(LPSPropValue lpProps, ULONG cValues, string &strResponse, ULONG ulMailnr, bool bForceFlags, const vector<string> &lstDataItems)
  3208. {
  3209. HRESULT hr = hrSuccess;
  3210. string strItem;
  3211. string strParts;
  3212. string::size_type ulPos;
  3213. char szBuffer[IMAP_RESP_MAX + 1];
  3214. IMessage *lpMessage = NULL;
  3215. ULONG ulObjType = 0;
  3216. sending_options sopt;
  3217. imopt_default_sending_options(&sopt);
  3218. sopt.no_recipients_workaround = true; // do not stop processing mail on empty recipient table
  3219. sopt.alternate_boundary = const_cast<char *>("=_ZG_static");
  3220. sopt.force_utf8 = parseBool(lpConfig->GetSetting("imap_generate_utf8"));
  3221. sopt.ignore_missing_attachments = true;
  3222. string strMessage;
  3223. string strMessagePart;
  3224. unsigned int ulCount = 0;
  3225. ostringstream oss;
  3226. string strFlags;
  3227. bool bSkipOpen = true;
  3228. vector<string> vProps;
  3229. // Response always starts with "<id> FETCH ("
  3230. snprintf(szBuffer, IMAP_RESP_MAX, "%u FETCH (", ulMailnr + 1);
  3231. strResponse = szBuffer;
  3232. // rules to open the message:
  3233. // 1. BODY requested and not present in table (generate)
  3234. // 2. BODYSTRUCTURE requested and not present in table (generate)
  3235. // 3. ENVELOPE requested and not present in table
  3236. // 4. BODY* or body part requested
  3237. // 5. RFC822* requested
  3238. // and ! cached
  3239. for (auto iFetch = lstDataItems.cbegin();
  3240. bSkipOpen && iFetch != lstDataItems.cend(); ++iFetch)
  3241. {
  3242. if (iFetch->compare("BODY") == 0)
  3243. bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY) != NULL;
  3244. else if (iFetch->compare("BODYSTRUCTURE") == 0)
  3245. bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE) != NULL;
  3246. else if (iFetch->compare("ENVELOPE") == 0)
  3247. bSkipOpen = PCpropFindProp(lpProps, cValues, m_lpsIMAPTags->aulPropTag[0]) != NULL;
  3248. else if (iFetch->compare("RFC822.SIZE") == 0)
  3249. bSkipOpen = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE) != NULL;
  3250. else if (strstr(iFetch->c_str(), "HEADER") != NULL)
  3251. // we can only use PR_TRANSPORT_MESSAGE_HEADERS when we have the full email.
  3252. bSkipOpen = (PCpropFindProp(lpProps, cValues, PR_TRANSPORT_MESSAGE_HEADERS_A) != NULL &&
  3253. PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE) != NULL);
  3254. // full/partial body fetches, or size
  3255. else if (Prefix(*iFetch, "BODY") || Prefix(*iFetch, "RFC822"))
  3256. bSkipOpen = false;
  3257. }
  3258. if (!bSkipOpen && m_ulCacheUID != lstFolderMailEIDs[ulMailnr].ulUid)
  3259. // ignore error, we can't print an error halfway to the imap client
  3260. lpSession->OpenEntry(lstFolderMailEIDs[ulMailnr].sEntryID.cb, (LPENTRYID) lstFolderMailEIDs[ulMailnr].sEntryID.lpb,
  3261. &IID_IMessage, MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN *) &lpMessage);
  3262. // Handle requested properties
  3263. for (const auto &item : lstDataItems) {
  3264. if (item.compare("FLAGS") == 0) {
  3265. // if flags were already set from message, skip this version.
  3266. if (strFlags.empty()) {
  3267. strFlags = "FLAGS (";
  3268. strFlags += PropsToFlags(lpProps, cValues, lstFolderMailEIDs[ulMailnr].bRecent, bForceFlags);
  3269. strFlags += ")";
  3270. }
  3271. } else if (item.compare("XAOL.SIZE") == 0) {
  3272. auto lpProp = PCpropFindProp(lpProps, cValues, PR_MESSAGE_SIZE);
  3273. vProps.push_back(item);
  3274. vProps.push_back(lpProp ? stringify(lpProp->Value.ul) : "NIL");
  3275. } else if (item.compare("INTERNALDATE") == 0) {
  3276. vProps.push_back(item);
  3277. auto lpProp = PCpropFindProp(lpProps, cValues, PR_MESSAGE_DELIVERY_TIME);
  3278. if (!lpProp)
  3279. lpProp = PCpropFindProp(lpProps, cValues, PR_CLIENT_SUBMIT_TIME);
  3280. if (!lpProp)
  3281. lpProp = PCpropFindProp(lpProps, cValues, PR_CREATION_TIME);
  3282. if (lpProp != NULL)
  3283. vProps.push_back("\"" + FileTimeToString(lpProp->Value.ft) + "\"");
  3284. else
  3285. vProps.push_back("NIL");
  3286. } else if (item.compare("UID") == 0) {
  3287. vProps.push_back(item);
  3288. vProps.push_back(stringify(lstFolderMailEIDs[ulMailnr].ulUid));
  3289. } else if (item.compare("ENVELOPE") == 0) {
  3290. auto lpProp = PCpropFindProp(lpProps, cValues, m_lpsIMAPTags->aulPropTag[0]);
  3291. if (lpProp) {
  3292. vProps.push_back(item);
  3293. vProps.push_back(string("(") + lpProp->Value.lpszA + ")");
  3294. } else if (lpMessage) {
  3295. string strEnvelope;
  3296. HrGetMessageEnvelope(strEnvelope, lpMessage);
  3297. vProps.push_back(strEnvelope); // @note contains ENVELOPE (...)
  3298. } else {
  3299. vProps.push_back(item);
  3300. vProps.push_back("NIL");
  3301. }
  3302. } else if (bSkipOpen && item.compare("BODY") == 0) {
  3303. // table version
  3304. auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY);
  3305. vProps.push_back(item);
  3306. vProps.push_back(lpProp ? lpProp->Value.lpszA : "NIL");
  3307. } else if (bSkipOpen && item.compare("BODYSTRUCTURE") == 0) {
  3308. // table version
  3309. auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE);
  3310. vProps.push_back(item);
  3311. vProps.push_back(lpProp ? lpProp->Value.lpszA : "NIL");
  3312. } else if (Prefix(item, "BODY") || Prefix(item, "RFC822")) {
  3313. // the only exceptions when we don't need to generate anything yet.
  3314. if (item.compare("RFC822.SIZE") == 0) {
  3315. auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE);
  3316. if (lpProp) {
  3317. vProps.push_back(item);
  3318. vProps.push_back(stringify(lpProp->Value.ul));
  3319. continue;
  3320. }
  3321. }
  3322. // mapping with RFC822 to BODY[* requests
  3323. strItem = item;
  3324. if (strItem.compare("RFC822") == 0)
  3325. strItem = "BODY[]";
  3326. else if (strItem.compare("RFC822.TEXT") == 0)
  3327. strItem = "BODY[TEXT]";
  3328. else if (strItem.compare("RFC822.HEADER") == 0)
  3329. strItem = "BODY[HEADER]";
  3330. // structure only, take shortcut if we have it
  3331. if (strItem.find('[') == string::npos) {
  3332. /* RFC 3501 6.4.5:
  3333. * BODY
  3334. * Non-extensible form of BODYSTRUCTURE.
  3335. * BODYSTRUCTURE
  3336. * The [MIME-IMB] body structure of the message.
  3337. */
  3338. const SPropValue *lpProp;
  3339. if (item.length() > 4)
  3340. lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODYSTRUCTURE);
  3341. else
  3342. lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_BODY);
  3343. if (lpProp) {
  3344. vProps.push_back(item);
  3345. vProps.push_back(lpProp->Value.lpszA);
  3346. continue;
  3347. }
  3348. // data not available in table, need to regenerate.
  3349. }
  3350. strMessage.clear();
  3351. sopt.headers_only = strstr(strItem.c_str(), "HEADER") != NULL;
  3352. if (m_ulCacheUID == lstFolderMailEIDs[ulMailnr].ulUid) {
  3353. // Get message from cache
  3354. strMessage = m_strCache;
  3355. } else {
  3356. // We need to send headers or a body(part) to the client.
  3357. // For some clients, we need to make sure that headers match the bodies,
  3358. // So if we don't have the full email in the database, we must fix the headers to match
  3359. // vmime regenerated messages.
  3360. if (sopt.headers_only && bSkipOpen) {
  3361. auto lpProp = PCpropFindProp(lpProps, cValues, PR_TRANSPORT_MESSAGE_HEADERS_A);
  3362. if (lpProp != NULL)
  3363. strMessage = lpProp->Value.lpszA;
  3364. else
  3365. // still need to convert message
  3366. hr = MAPI_E_NOT_FOUND;
  3367. } else {
  3368. // If we have the full body, download that property
  3369. auto lpProp = PCpropFindProp(lpProps, cValues, PR_EC_IMAP_EMAIL_SIZE);
  3370. if (lpProp) {
  3371. // we have PR_EC_IMAP_EMAIL_SIZE, so we also have PR_EC_IMAP_EMAIL
  3372. object_ptr<IStream> lpStream;
  3373. hr = lpMessage->OpenProperty(PR_EC_IMAP_EMAIL, &IID_IStream, 0, 0, &~lpStream);
  3374. if (hr == hrSuccess)
  3375. hr = Util::HrStreamToString(lpStream, strMessage);
  3376. } else {
  3377. hr = MAPI_E_NOT_FOUND;
  3378. }
  3379. }
  3380. // no full imap email in database available, so regenerate all
  3381. if (hr != hrSuccess) {
  3382. assert(lpMessage);
  3383. if (oss.tellp() == ostringstream::pos_type(0) && // already converted in previous loop?
  3384. (lpMessage == NULL || IMToINet(lpSession, lpAddrBook, lpMessage, oss, sopt) != hrSuccess)) {
  3385. vProps.push_back(item);
  3386. vProps.push_back("NIL");
  3387. lpLogger->Log(EC_LOGLEVEL_WARNING, "Error in generating message %d for user %ls in folder %ls", ulMailnr+1, m_strwUsername.c_str(), strCurrentFolder.c_str());
  3388. continue;
  3389. }
  3390. strMessage = oss.str();
  3391. hr = hrSuccess;
  3392. // @todo save message and all generated crap and when not headers only
  3393. }
  3394. // Cache the generated message
  3395. if(!sopt.headers_only) {
  3396. m_ulCacheUID = lstFolderMailEIDs[ulMailnr].ulUid;
  3397. m_strCache = strMessage;
  3398. }
  3399. }
  3400. if (item.compare("RFC822.SIZE") == 0) {
  3401. // We must return the real size, since clients use this when using chunked mode to download the full message
  3402. vProps.push_back(item);
  3403. vProps.push_back(stringify(strMessage.size()));
  3404. continue;
  3405. }
  3406. if (item.compare("BODY") == 0 || item.compare("BODYSTRUCTURE") == 0) {
  3407. string strData;
  3408. HrGetBodyStructure(item.length() > 4, strData, strMessage);
  3409. vProps.push_back(item);
  3410. vProps.push_back(strData);
  3411. continue;
  3412. }
  3413. /* RFC 3501 6.4.5:
  3414. * BODY[<section>]<<partial>>
  3415. * The text of a particular body section, without boundaries.
  3416. * BODY.PEEK[<section>]<<partial>>
  3417. * An alternate form of BODY[<section>] that does not implicitly
  3418. * set the \Seen flag.
  3419. */
  3420. if (strstr(strItem.c_str(), "[]") != NULL) {
  3421. // Nasty: eventhough the client requests .PEEK, it may not be present in the reply.
  3422. string strReply = item;
  3423. ulPos = strReply.find(".PEEK");
  3424. if (ulPos != string::npos)
  3425. strReply.erase(ulPos, strlen(".PEEK"));
  3426. // Nasty: eventhough the client requests <12345.12345>, it may not be present in the reply.
  3427. ulPos = strReply.rfind('<');
  3428. if (ulPos != string::npos)
  3429. strReply.erase(ulPos, string::npos);
  3430. vProps.push_back(strReply);
  3431. // Handle BODY[] and RFC822 (entire message)
  3432. strMessagePart = strMessage;
  3433. } else {
  3434. // Handle BODY[subparts]
  3435. // BODY[subpart], strParts = <subpart> (so "1.2.3" or "3.HEADER" or "TEXT" etc)
  3436. ulPos = strItem.find("[");
  3437. if (ulPos != string::npos)
  3438. strParts = strItem.substr(ulPos + 1);
  3439. ulPos = strParts.find("]");
  3440. if (ulPos != string::npos)
  3441. strParts.erase(ulPos);
  3442. if (Prefix(item, "BODY"))
  3443. vProps.push_back("BODY[" + strParts + "]");
  3444. else
  3445. vProps.push_back("RFC822." + strParts);
  3446. // Get the correct message part (1.2.3, TEXT, HEADER, 1.2.3.TEXT, 1.2.3.HEADER)
  3447. HrGetMessagePart(strMessagePart, strMessage, strParts);
  3448. }
  3449. // Process byte-part request ( <12345.12345> ) for BODY
  3450. ulPos = strItem.rfind('<');
  3451. if (ulPos != string::npos) {
  3452. strParts = strItem.substr(ulPos + 1, strItem.size() - ulPos - 2);
  3453. ulPos = strParts.find('.');
  3454. if (ulPos != string::npos) {
  3455. ulCount = strtoul(strParts.substr(0, ulPos).c_str(), NULL, 0);
  3456. ulPos = strtoul(strParts.substr(ulPos + 1).c_str(), NULL, 0);
  3457. } else {
  3458. ulCount = strtoul(strParts.c_str(), NULL, 0);
  3459. ulPos = strMessagePart.size();
  3460. }
  3461. if (ulCount > strMessagePart.size()) {
  3462. strMessagePart.clear();
  3463. } else if (ulCount + ulPos > strMessagePart.size()) {
  3464. strMessagePart.erase(0, ulCount);
  3465. } else {
  3466. strMessagePart.erase(0, ulCount);
  3467. strMessagePart.erase(ulPos);
  3468. }
  3469. snprintf(szBuffer, IMAP_RESP_MAX, "<%u>", ulCount);
  3470. vProps.back() += szBuffer;
  3471. }
  3472. if (strMessagePart.empty()) {
  3473. vProps.push_back("NIL");
  3474. } else {
  3475. // Output actual data
  3476. snprintf(szBuffer, IMAP_RESP_MAX, "{%u}\r\n", (ULONG)strMessagePart.size());
  3477. vProps.push_back(szBuffer);
  3478. vProps.back() += strMessagePart;
  3479. }
  3480. } else {
  3481. // unknown item
  3482. vProps.push_back(item);
  3483. vProps.push_back("NIL");
  3484. }
  3485. }
  3486. if (bForceFlags && strFlags.empty()) {
  3487. strFlags = "FLAGS (";
  3488. strFlags += PropsToFlags(lpProps, cValues, lstFolderMailEIDs[ulMailnr].bRecent, bForceFlags);
  3489. strFlags += ")";
  3490. }
  3491. // Output flags if modified
  3492. if (!strFlags.empty())
  3493. vProps.push_back(std::move(strFlags));
  3494. strResponse += kc_join(vProps, " ");
  3495. strResponse += ")";
  3496. if(lpMessage)
  3497. lpMessage->Release();
  3498. return hr;
  3499. }
  3500. /**
  3501. * Returns a recipient block for the envelope request. Format:
  3502. * (("fullname" NIL "email name" "email domain")(...))
  3503. *
  3504. * @param[in] lpRows recipient table rows
  3505. * @param[in] ulType recipient type to print
  3506. * @param[in] strCharset charset for the fullname
  3507. * @return string containing the To/Cc/Bcc recipient data
  3508. */
  3509. std::string IMAP::HrEnvelopeRecipients(LPSRowSet lpRows, ULONG ulType, std::string& strCharset, bool bIgnore)
  3510. {
  3511. ULONG ulCount;
  3512. std::string strResponse;
  3513. std::string::size_type ulPos;
  3514. enum { EMAIL_ADDRESS, DISPLAY_NAME, RECIPIENT_TYPE, ADDRTYPE, ENTRYID, NUM_COLS };
  3515. strResponse = "(";
  3516. for (ulCount = 0; ulCount < lpRows->cRows; ++ulCount) {
  3517. SPropValue *pr = lpRows->aRow[ulCount].lpProps;
  3518. if (pr[RECIPIENT_TYPE].Value.ul != ulType)
  3519. continue;
  3520. /*
  3521. * """The fields of an address structure are in the following
  3522. * order: personal name, SMTP at-domain-list (source route),
  3523. * mailbox name, and host name.""" RFC 3501 §2.3.5 p.76.
  3524. */
  3525. strResponse += "(";
  3526. if (pr[DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME_W)
  3527. strResponse += EscapeString(pr[DISPLAY_NAME].Value.lpszW, strCharset, bIgnore);
  3528. else
  3529. strResponse += "NIL";
  3530. strResponse += " NIL ";
  3531. bool has_email = pr[EMAIL_ADDRESS].ulPropTag == PR_EMAIL_ADDRESS_A;
  3532. bool za_addr = pr[ADDRTYPE].ulPropTag == PR_ADDRTYPE_W &&
  3533. wcscmp(pr[ADDRTYPE].Value.lpszW, L"ZARAFA") == 0;
  3534. std::string strPart;
  3535. if (has_email && za_addr) {
  3536. std::wstring name, type, email;
  3537. HRESULT ret;
  3538. ret = HrGetAddress(lpAddrBook, pr, NUM_COLS,
  3539. PR_ENTRYID, PR_DISPLAY_NAME_W, PR_ADDRTYPE_W,
  3540. PR_EMAIL_ADDRESS_A, name, type, email);
  3541. if (ret == hrSuccess)
  3542. strPart = convert_to<std::string>(email);
  3543. } else if (has_email) {
  3544. /* treat all non-ZARAFA cases as "SMTP" */
  3545. strPart = pr[EMAIL_ADDRESS].Value.lpszA;
  3546. }
  3547. if (strPart.length() > 0) {
  3548. ulPos = strPart.find("@");
  3549. if (ulPos != string::npos) {
  3550. strResponse += EscapeStringQT(strPart.substr(0, ulPos));
  3551. strResponse += " ";
  3552. strResponse += EscapeStringQT(strPart.substr(ulPos + 1));
  3553. } else {
  3554. strResponse += EscapeStringQT(strPart);
  3555. strResponse += " NIL";
  3556. }
  3557. } else {
  3558. strResponse += "NIL NIL";
  3559. }
  3560. strResponse += ") ";
  3561. }
  3562. if (strResponse.compare(strResponse.size() - 1, 1, " ") == 0) {
  3563. strResponse.resize(strResponse.size() - 1);
  3564. strResponse += ") ";
  3565. } else {
  3566. // no recipients at all
  3567. strResponse.resize(strResponse.size() - 1);
  3568. strResponse += "NIL ";
  3569. }
  3570. return strResponse;
  3571. }
  3572. /**
  3573. * Returns a sender block for the envelope request. Format:
  3574. * (("fullname" NIL "email name" "email domain"))
  3575. *
  3576. * @param[in] lpMessage GetProps object
  3577. * @param[in] ulTagName Proptag to get for the fullname
  3578. * @param[in] ulTagEmail Proptag to get for the email address
  3579. * @param[in] strCharset Charset for the fullname
  3580. * @return string containing the From/Sender/Reply-To envelope data
  3581. */
  3582. std::string IMAP::HrEnvelopeSender(LPMESSAGE lpMessage, ULONG ulTagName, ULONG ulTagEmail, std::string& strCharset, bool bIgnore)
  3583. {
  3584. HRESULT hr = hrSuccess;
  3585. std::string strResponse;
  3586. std::string strPart;
  3587. std::string::size_type ulPos;
  3588. memory_ptr<SPropValue> lpPropValues;
  3589. ULONG ulProps;
  3590. SizedSPropTagArray(2, sPropTags) = { 2, {ulTagName, ulTagEmail} };
  3591. hr = lpMessage->GetProps(sPropTags, 0, &ulProps, &~lpPropValues);
  3592. strResponse = "((";
  3593. if (!FAILED(hr) && PROP_TYPE(lpPropValues[0].ulPropTag) != PT_ERROR)
  3594. strResponse += EscapeString(lpPropValues[0].Value.lpszW, strCharset, bIgnore);
  3595. else
  3596. strResponse += "NIL";
  3597. strResponse += " NIL ";
  3598. if (!FAILED(hr) && PROP_TYPE(lpPropValues[1].ulPropTag) != PT_ERROR) {
  3599. strPart = lpPropValues[1].Value.lpszA;
  3600. ulPos = strPart.find("@", 0);
  3601. if (ulPos != string::npos) {
  3602. strResponse += EscapeStringQT(strPart.substr(0, ulPos));
  3603. strResponse += " ";
  3604. strResponse += EscapeStringQT(strPart.substr(ulPos + 1));
  3605. } else {
  3606. strResponse += EscapeStringQT(strPart);
  3607. strResponse += " NIL";
  3608. }
  3609. } else {
  3610. strResponse += "NIL";
  3611. }
  3612. strResponse += ")) ";
  3613. return strResponse;
  3614. }
  3615. /**
  3616. * Returns the IMAP ENVELOPE string of a specific email. Since this
  3617. * doesn't come from vmime, the values in this response may differ
  3618. * from other requests.
  3619. *
  3620. * @param[out] strResponse The ENVELOPE answer will be concatenated to this string
  3621. * @param[in] lpMessage The MAPI message object
  3622. * @return MAPI Error code
  3623. */
  3624. HRESULT IMAP::HrGetMessageEnvelope(string &strResponse, LPMESSAGE lpMessage) {
  3625. HRESULT hr = hrSuccess;
  3626. memory_ptr<SPropValue> lpPropVal, lpInternetCPID;
  3627. const char *lpszCharset = NULL;
  3628. string strCharset;
  3629. bool bIgnoreCharsetErrors = false;
  3630. object_ptr<IMAPITable> lpTable;
  3631. rowset_ptr lpRows;
  3632. static constexpr const SizedSPropTagArray(5, spt) =
  3633. {5, {PR_EMAIL_ADDRESS_A, PR_DISPLAY_NAME_W, PR_RECIPIENT_TYPE,
  3634. PR_ADDRTYPE_W, PR_ENTRYID}};
  3635. if (lpMessage == nullptr)
  3636. return MAPI_E_CALL_FAILED;
  3637. // Get the outgoing charset we want to be using
  3638. // @todo, add gateway force_utf8 option
  3639. if (!parseBool(lpConfig->GetSetting("imap_generate_utf8")) &&
  3640. HrGetOneProp(lpMessage, PR_INTERNET_CPID, &~lpInternetCPID) == hrSuccess &&
  3641. HrGetCharsetByCP(lpInternetCPID->Value.ul, &lpszCharset) == hrSuccess)
  3642. {
  3643. strCharset = lpszCharset;
  3644. bIgnoreCharsetErrors = true;
  3645. } else {
  3646. // default to UTF-8 if not set
  3647. strCharset = "UTF-8";
  3648. }
  3649. strResponse += "ENVELOPE (";
  3650. // date string
  3651. if (HrGetOneProp(lpMessage, PR_CLIENT_SUBMIT_TIME, &~lpPropVal) == hrSuccess ||
  3652. HrGetOneProp(lpMessage, PR_MESSAGE_DELIVERY_TIME, &~lpPropVal) == hrSuccess) {
  3653. strResponse += "\"";
  3654. strResponse += FileTimeToString(lpPropVal->Value.ft);
  3655. strResponse += "\" ";
  3656. } else {
  3657. strResponse += "NIL ";
  3658. }
  3659. // subject
  3660. if (HrGetOneProp(lpMessage, PR_SUBJECT_W, &~lpPropVal) == hrSuccess)
  3661. strResponse += EscapeString(lpPropVal->Value.lpszW, strCharset, bIgnoreCharsetErrors);
  3662. strResponse += " ";
  3663. // from
  3664. strResponse += HrEnvelopeSender(lpMessage, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
  3665. // sender
  3666. strResponse += HrEnvelopeSender(lpMessage, PR_SENDER_NAME_W, PR_SENDER_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
  3667. // reply-to, @fixme use real reply-to info from PR_REPLY_RECIPIENT_ENTRIES
  3668. strResponse += HrEnvelopeSender(lpMessage, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_A, strCharset, bIgnoreCharsetErrors);
  3669. // recipients
  3670. hr = lpMessage->GetRecipientTable(0, &~lpTable);
  3671. if (hr != hrSuccess)
  3672. goto recipientsdone;
  3673. hr = lpTable->SetColumns(spt, 0);
  3674. if (hr != hrSuccess)
  3675. goto recipientsdone;
  3676. hr = lpTable->QueryRows(-1, 0, &~lpRows);
  3677. if (hr != hrSuccess)
  3678. goto recipientsdone;
  3679. strResponse += HrEnvelopeRecipients(lpRows, MAPI_TO, strCharset, bIgnoreCharsetErrors);
  3680. strResponse += HrEnvelopeRecipients(lpRows, MAPI_CC, strCharset, bIgnoreCharsetErrors);
  3681. strResponse += HrEnvelopeRecipients(lpRows, MAPI_BCC, strCharset, bIgnoreCharsetErrors);
  3682. recipientsdone:
  3683. if (hr != hrSuccess) {
  3684. strResponse += "NIL NIL NIL ";
  3685. hr = hrSuccess;
  3686. }
  3687. // in reply to
  3688. if (HrGetOneProp(lpMessage, PR_IN_REPLY_TO_ID_A, &~lpPropVal) == hrSuccess)
  3689. strResponse += EscapeStringQT(lpPropVal->Value.lpszA);
  3690. else
  3691. strResponse += "NIL";
  3692. strResponse += " ";
  3693. // internet message id
  3694. if (HrGetOneProp(lpMessage, PR_INTERNET_MESSAGE_ID_A, &~lpPropVal) == hrSuccess)
  3695. strResponse += EscapeStringQT(lpPropVal->Value.lpszA);
  3696. else
  3697. strResponse += "NIL";
  3698. strResponse += ")";
  3699. return hrSuccess;
  3700. }
  3701. /**
  3702. * Returns IMAP flags for a given message
  3703. *
  3704. * @param[out] strResponse the FLAGS reply for the given message
  3705. * @param[in] lpMessage the MAPI message to get the IMAP flags for
  3706. * @param[in] bRecent mark this message as recent
  3707. *
  3708. * @return MAPI Error code
  3709. */
  3710. HRESULT IMAP::HrGetMessageFlags(string &strResponse, LPMESSAGE lpMessage, bool bRecent) {
  3711. memory_ptr<SPropValue> lpProps;
  3712. ULONG cValues;
  3713. static constexpr const SizedSPropTagArray(4, sptaFlagProps) =
  3714. {4, {PR_MESSAGE_FLAGS, PR_FLAG_STATUS, PR_MSG_STATUS,
  3715. PR_LAST_VERB_EXECUTED}};
  3716. if (lpMessage == nullptr)
  3717. return MAPI_E_CALL_FAILED;
  3718. HRESULT hr = lpMessage->GetProps(sptaFlagProps, 0, &cValues, &~lpProps);
  3719. if (FAILED(hr))
  3720. return hr;
  3721. strResponse += "FLAGS (" + PropsToFlags(lpProps, cValues, bRecent, false) + ")";
  3722. return hrSuccess;
  3723. }
  3724. /*
  3725. * RFC 3501, section 6.4.5:
  3726. *
  3727. * BODY[<section>]<<partial>>
  3728. *
  3729. * The text of a particular body section. The section
  3730. * specification is a set of zero or more part specifiers
  3731. * delimited by periods. A part specifier is either a part number
  3732. * or one of the following: HEADER, HEADER.FIELDS,
  3733. * HEADER.FIELDS.NOT, MIME, and TEXT. An empty section
  3734. * specification refers to the entire message, including the
  3735. * header.
  3736. *
  3737. * --- end of RFC text
  3738. *
  3739. * Please note that there is a difference between getting MIME and HEADER:
  3740. * - HEADER and TEXT are only used in the top-level object OR an embedded message/rfc822 message
  3741. * - MIME are the Content-* headers for the MIME part, NEVER the header for a message/rfc822 message
  3742. * - The 'whole' part is HEADER + TEXT for toplevel and message/rfc822 parts
  3743. * - The 'whole' part does NOT include the MIME-IMB headers part
  3744. *
  3745. */
  3746. /**
  3747. * Returns a body part of an RFC 2822 message
  3748. *
  3749. * @param[out] strMessagePart The requested message part
  3750. * @param[in] strMessage The full mail to scan for the part
  3751. * @param[in] strPartName IMAP request identifying a part in strMessage
  3752. *
  3753. * @return MAPI Error code
  3754. */
  3755. HRESULT IMAP::HrGetMessagePart(string &strMessagePart, string &strMessage, string strPartName) {
  3756. string::size_type ulPos;
  3757. unsigned long int ulPartnr;
  3758. string strHeaders;
  3759. string strBoundary;
  3760. size_t ulHeaderBegin;
  3761. size_t ulHeaderEnd;
  3762. size_t ulCounter;
  3763. const char *ptr, *end;
  3764. if (strPartName.find_first_of("123456789") == 0) {
  3765. // @todo rewrite without copying strings
  3766. std::string strNextPart;
  3767. // BODY[1] or BODY[1.2] etc
  3768. // Find subsection
  3769. ulPos = strPartName.find(".");
  3770. if (ulPos == string::npos) // first section
  3771. ulPartnr = strtoul(strPartName.c_str(), NULL, 0);
  3772. else // sub section
  3773. ulPartnr = strtoul(strPartName.substr(0, ulPos).c_str(), NULL, 0);
  3774. // Find the correct part
  3775. end = str_ifind((char*)strMessage.c_str(), "\r\n\r\n");
  3776. ptr = str_ifind((char*)strMessage.c_str(), "boundary=");
  3777. if (ptr && end && ptr < end) {
  3778. ulHeaderBegin = std::distance(strMessage.c_str(), ptr) + strlen("boundary=");
  3779. if (strMessage[ulHeaderBegin] == '"') {
  3780. ++ulHeaderBegin;
  3781. // space in boundary is a possebility.
  3782. ulHeaderEnd = strMessage.find_first_of("\"", ulHeaderBegin);
  3783. } else {
  3784. ulHeaderEnd = strMessage.find_first_of(" ;\t\r\n", ulHeaderBegin);
  3785. }
  3786. if (ulHeaderEnd != string::npos) {
  3787. // strBoundary is the boundary we are looking for
  3788. strBoundary = strMessage.substr(ulHeaderBegin, ulHeaderEnd - ulHeaderBegin);
  3789. // strHeaders is what we are looking for
  3790. strHeaders = (string) "\r\n--" + strBoundary + "\r\n"; //Skip always the end header
  3791. ulHeaderBegin = strMessage.find(strHeaders, ulHeaderBegin);
  3792. // Find the section/part by looking for the Nth boundary string
  3793. for (ulCounter = 0; ulCounter < ulPartnr && ulHeaderBegin != string::npos; ++ulCounter) {
  3794. ulHeaderBegin += strHeaders.size();
  3795. ulHeaderEnd = ulHeaderBegin;
  3796. ulHeaderBegin = strMessage.find(strHeaders, ulHeaderBegin);
  3797. }
  3798. if (ulHeaderBegin != string::npos) {
  3799. // Found it, discard data after and before the part we want
  3800. strMessage.erase(ulHeaderBegin);
  3801. strMessage.erase(0, ulHeaderEnd);
  3802. } else {
  3803. // Didn't find it, see if we can find the trailing boundary
  3804. ulHeaderBegin = strMessage.find((string) "\r\n--" + strBoundary + "--\r\n", ulHeaderEnd);
  3805. if(ulHeaderBegin != string::npos) {
  3806. // If found, output everything up to the trailing boundary
  3807. strMessage.erase(ulHeaderBegin);
  3808. strMessage.erase(0, ulHeaderEnd);
  3809. } else {
  3810. // Otherwise, treat the rest of the message as data
  3811. strMessage.erase(0, ulHeaderEnd);
  3812. }
  3813. }
  3814. }
  3815. }
  3816. // We now have the entire MIME part in strMessage, decide what to do with it
  3817. if (ulPos != string::npos) {
  3818. // There are sub sections, see what we want to do
  3819. strNextPart = strPartName.substr(ulPos+1);
  3820. if(strNextPart.compare("MIME") == 0) {
  3821. // Handle MIME request
  3822. ulPos = strMessage.find("\r\n\r\n");
  3823. if (ulPos != string::npos)
  3824. strMessagePart = strMessage.substr(0, ulPos+4); // include trailing \r\n\r\n (+4)
  3825. else
  3826. // Only headers in the message
  3827. strMessagePart = strMessage + "\r\n\r\n";
  3828. // All done
  3829. return hrSuccess;
  3830. } else if(strNextPart.find_first_of("123456789") == 0) {
  3831. // Handle Subpart
  3832. HrGetMessagePart(strMessagePart, strMessage, strNextPart);
  3833. // All done
  3834. return hrSuccess;
  3835. }
  3836. }
  3837. // Handle any other request (HEADER, TEXT or 'empty'). This means we first skip the MIME-IMB headers
  3838. // and process the rest from there.
  3839. ulPos = strMessage.find("\r\n\r\n");
  3840. if (ulPos != string::npos)
  3841. strMessage.erase(0, ulPos + 4);
  3842. else
  3843. // The message only has headers ?
  3844. strMessage.clear();
  3845. // Handle HEADER and TEXT if requested
  3846. if (!strNextPart.empty())
  3847. HrGetMessagePart(strMessagePart, strMessage, strNextPart);
  3848. else
  3849. // Swap to conserve memory: Original: strMessagePart = strMessage
  3850. swap(strMessagePart, strMessage);
  3851. } else if (strPartName.compare("TEXT") == 0) {
  3852. // Everything except for the headers
  3853. ulPos = strMessage.find("\r\n\r\n");
  3854. if (ulPos != string::npos) {
  3855. // Swap for less memory usage. Original: strMessagePart = strMessage.substr(ulPos+4)
  3856. strMessage.erase(0,ulPos+4);
  3857. swap(strMessage, strMessagePart);
  3858. } else {
  3859. // The message only has headers ?
  3860. strMessagePart.clear();
  3861. }
  3862. } else if (strPartName.compare("HEADER") == 0) {
  3863. // Only the headers
  3864. ulPos = strMessage.find("\r\n\r\n");
  3865. if (ulPos != string::npos) {
  3866. // Swap for less memory usage. Original: strMessagePart = strMessage.substr(0, ulPos+4);
  3867. strMessage.erase(ulPos+4, strMessage.size() - (ulPos+4));
  3868. swap(strMessagePart, strMessage);
  3869. } else {
  3870. // Only headers in the message
  3871. strMessagePart = strMessage + "\r\n\r\n";
  3872. }
  3873. } else if (Prefix(strPartName, "HEADER.FIELDS")) {
  3874. /* RFC 3501, section 6.4.5
  3875. *
  3876. * HEADER.FIELDS and HEADER.FIELDS.NOT are followed by a list of
  3877. * field-name (as defined in [RFC-2822]) names, and return a
  3878. * subset of the header.
  3879. *
  3880. * eg. HEADER.FIELDS (SUBJECT TO)
  3881. * eg. HEADER.FIELDS.NOT (SUBJECT)
  3882. */
  3883. bool bNot = Prefix(strPartName, "HEADER.FIELDS.NOT");
  3884. list<pair<string, string> > lstFields;
  3885. string strFields;
  3886. // Parse headers in message
  3887. HrParseHeaders(strMessage, lstFields);
  3888. // Get (<fields>)
  3889. HrGetSubString(strFields, strPartName, "(", ")");
  3890. strMessagePart.clear();
  3891. if(bNot) {
  3892. set<string> setFields;
  3893. // Get fields as set
  3894. HrTokenize(setFields, strFields);
  3895. // Output all headers except those specified
  3896. for (const auto &field : lstFields) {
  3897. std::string strFieldUpper = field.first;
  3898. strFieldUpper = strToUpper(strFieldUpper);
  3899. if (setFields.find(strFieldUpper) != setFields.cend())
  3900. continue;
  3901. strMessagePart += field.first + ": " + field.second + "\r\n";
  3902. }
  3903. } else {
  3904. vector<string> lstReqFields;
  3905. std::unordered_set<std::string> seen;
  3906. // Get fields as vector
  3907. lstReqFields = tokenize(strFields, " ");
  3908. // Output headers specified, in order of field set
  3909. for (const auto &reqfield : lstReqFields) {
  3910. if (!seen.insert(reqfield).second)
  3911. continue;
  3912. for (const auto &field : lstFields) {
  3913. if (!CaseCompare(reqfield, field.first))
  3914. continue;
  3915. strMessagePart += field.first + ": " + field.second + "\r\n";
  3916. break;
  3917. }
  3918. }
  3919. }
  3920. // mark end-of-headers
  3921. strMessagePart += "\r\n";
  3922. } else {
  3923. strMessagePart = "NIL";
  3924. }
  3925. return hrSuccess;
  3926. }
  3927. /**
  3928. * Convert a sequence number to its actual number. It will either
  3929. * return a number or a UID, depending on the input.
  3930. * A special treatment for
  3931. *
  3932. * @param[in] szNr a number of the sequence input
  3933. * @param[in] bUID sequence input are UID numbers or not
  3934. *
  3935. * @return the number corresponding to the input.
  3936. */
  3937. ULONG IMAP::LastOrNumber(const char *szNr, bool bUID)
  3938. {
  3939. if (*szNr != '*')
  3940. return atoui(szNr);
  3941. if (!bUID)
  3942. return lstFolderMailEIDs.size();
  3943. if (lstFolderMailEIDs.empty())
  3944. return 0; // special case: return an "invalid" number
  3945. else
  3946. return lstFolderMailEIDs.back().ulUid;
  3947. }
  3948. /**
  3949. * Convert a UID sequence set into a MAPI restriction.
  3950. *
  3951. * @param[in] strSeqSet The sequence set with uids given by the client
  3952. * @param[out] lppRestriction The MAPI restriction to get all messages matching the sequence
  3953. *
  3954. * @return MAPI Error code
  3955. */
  3956. HRESULT IMAP::HrSeqUidSetToRestriction(const string &strSeqSet,
  3957. std::unique_ptr<ECRestriction> &ret)
  3958. {
  3959. vector<string> vSequences;
  3960. string::size_type ulPos = 0;
  3961. SPropValue sProp;
  3962. SPropValue sPropEnd;
  3963. if (strSeqSet.empty()) {
  3964. // no restriction
  3965. ret.reset();
  3966. return hrSuccess;
  3967. }
  3968. sProp.ulPropTag = PR_EC_IMAP_ID;
  3969. sPropEnd.ulPropTag = PR_EC_IMAP_ID;
  3970. vSequences = tokenize(strSeqSet, ',');
  3971. auto rst = new ECOrRestriction();
  3972. for (ULONG i = 0; i < vSequences.size(); ++i) {
  3973. ulPos = vSequences[i].find(':');
  3974. if (ulPos == string::npos) {
  3975. // single number
  3976. sProp.Value.ul = LastOrNumber(vSequences[i].c_str(), true);
  3977. *rst += ECPropertyRestriction(RELOP_EQ, PR_EC_IMAP_ID, &sProp, ECRestriction::Full);
  3978. } else {
  3979. sProp.Value.ul = LastOrNumber(vSequences[i].c_str(), true);
  3980. sPropEnd.Value.ul = LastOrNumber(vSequences[i].c_str() + ulPos + 1, true);
  3981. if (sProp.Value.ul > sPropEnd.Value.ul)
  3982. swap(sProp.Value.ul, sPropEnd.Value.ul);
  3983. *rst += ECAndRestriction(
  3984. ECPropertyRestriction(RELOP_GE, PR_EC_IMAP_ID, &sProp, ECRestriction::Full) +
  3985. ECPropertyRestriction(RELOP_LE, PR_EC_IMAP_ID, &sPropEnd, ECRestriction::Full));
  3986. }
  3987. }
  3988. ret.reset(rst);
  3989. return hrSuccess;
  3990. }
  3991. /**
  3992. * Convert an IMAP sequence set to a flat list of email numbers. See
  3993. * RFC-3501 paragraph 9 for the syntax. This function will return the
  3994. * closest range of requested items, and thus may return an empty
  3995. * list.
  3996. *
  3997. * @param[in] strSeqSet IMAP sequence set of UID numbers
  3998. * @param[out] lstMails flat list of email numbers
  3999. *
  4000. * @return MAPI Error code
  4001. */
  4002. HRESULT IMAP::HrParseSeqUidSet(const string &strSeqSet, list<ULONG> &lstMails) {
  4003. HRESULT hr = hrSuccess;
  4004. vector<string> vSequences;
  4005. string::size_type ulPos = 0;
  4006. ULONG ulMailnr;
  4007. ULONG ulBeginMailnr;
  4008. // split different sequence parts into a vector
  4009. vSequences = tokenize(strSeqSet, ',');
  4010. for (ULONG i = 0; i < vSequences.size(); ++i) {
  4011. ulPos = vSequences[i].find(':');
  4012. if (ulPos == string::npos) {
  4013. // single number
  4014. ulMailnr = LastOrNumber(vSequences[i].c_str(), true);
  4015. auto i = find(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulMailnr);
  4016. if (i != lstFolderMailEIDs.cend())
  4017. lstMails.push_back(std::distance(lstFolderMailEIDs.cbegin(), i));
  4018. } else {
  4019. // range
  4020. ulBeginMailnr = LastOrNumber(vSequences[i].c_str(), true);
  4021. ulMailnr = LastOrNumber(vSequences[i].c_str() + ulPos + 1, true);
  4022. if (ulBeginMailnr > ulMailnr && ulBeginMailnr <= lstFolderMailEIDs.size())
  4023. swap(ulBeginMailnr, ulMailnr);
  4024. auto b = std::lower_bound(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulBeginMailnr);
  4025. auto e = std::upper_bound(lstFolderMailEIDs.cbegin(), lstFolderMailEIDs.cend(), ulMailnr);
  4026. for (auto i = b; i != e; ++i)
  4027. lstMails.push_back(std::distance(lstFolderMailEIDs.cbegin(), i));
  4028. }
  4029. }
  4030. lstMails.sort();
  4031. lstMails.unique();
  4032. return hr;
  4033. }
  4034. /**
  4035. * Convert an IMAP sequence set to a flat list of email numbers. See
  4036. * RFC-3501 paragraph 9 for the syntax. These exact numbers requested
  4037. * must be present in the folder, otherwise an error will be returned.
  4038. *
  4039. * @param[in] strSeqSet IMAP sequence set of direct numbers
  4040. * @param[out] lstMails flat list of email numbers
  4041. *
  4042. * @return MAPI Error code
  4043. */
  4044. HRESULT IMAP::HrParseSeqSet(const string &strSeqSet, list<ULONG> &lstMails) {
  4045. vector<string> vSequences;
  4046. string::size_type ulPos = 0;
  4047. ULONG ulMailnr;
  4048. ULONG ulBeginMailnr;
  4049. if (lstFolderMailEIDs.empty())
  4050. return MAPI_E_NOT_FOUND;
  4051. // split different sequence parts into a vector
  4052. vSequences = tokenize(strSeqSet, ',');
  4053. for (ULONG i = 0; i < vSequences.size(); ++i) {
  4054. ulPos = vSequences[i].find(':');
  4055. if (ulPos == string::npos) {
  4056. // single number
  4057. ulMailnr = LastOrNumber(vSequences[i].c_str(), false) - 1;
  4058. if (ulMailnr >= lstFolderMailEIDs.size())
  4059. return MAPI_E_CALL_FAILED;
  4060. lstMails.push_back(ulMailnr);
  4061. } else {
  4062. // range
  4063. ulBeginMailnr = LastOrNumber(vSequences[i].c_str(), false) - 1;
  4064. ulMailnr = LastOrNumber(vSequences[i].c_str() + ulPos + 1, false) - 1;
  4065. if (ulBeginMailnr > ulMailnr)
  4066. swap(ulBeginMailnr, ulMailnr);
  4067. if (ulBeginMailnr >= lstFolderMailEIDs.size() ||
  4068. ulMailnr >= lstFolderMailEIDs.size())
  4069. return MAPI_E_CALL_FAILED;
  4070. for (ULONG j = ulBeginMailnr; j <= ulMailnr; ++j)
  4071. lstMails.push_back(j);
  4072. }
  4073. }
  4074. lstMails.sort();
  4075. lstMails.unique();
  4076. return hrSuccess;
  4077. }
  4078. /**
  4079. * Implementation of the STORE command
  4080. *
  4081. * @param[in] lstMails list of emails to process
  4082. * @param[in] strMsgDataItemName how to modify the message (set, append, remove)
  4083. * @param[in] strMsgDataItemValue new flag values from IMAP client
  4084. *
  4085. * @return MAPI Error code
  4086. */
  4087. // @todo c store 2 (+FLAGS) (\Deleted) shouldn't but does work
  4088. // @todo c store 2 +FLAGS (\Deleted) should and does work
  4089. HRESULT IMAP::HrStore(const list<ULONG> &lstMails, string strMsgDataItemName, string strMsgDataItemValue, bool *lpbDoDelete)
  4090. {
  4091. HRESULT hr = hrSuccess;
  4092. vector<string> lstFlags;
  4093. ULONG ulCurrent;
  4094. memory_ptr<SPropValue> lpPropVal;
  4095. ULONG cValues;
  4096. ULONG ulObjType;
  4097. string strNewFlags;
  4098. bool bDelete = false;
  4099. static constexpr const SizedSPropTagArray(4, proptags4) =
  4100. {4, {PR_MSG_STATUS, PR_ICON_INDEX, PR_LAST_VERB_EXECUTED, PR_LAST_VERB_EXECUTION_TIME}};
  4101. static constexpr const SizedSPropTagArray(5, proptags5) =
  4102. {5, {PR_MSG_STATUS, PR_FLAG_STATUS, PR_ICON_INDEX,
  4103. PR_LAST_VERB_EXECUTED, PR_LAST_VERB_EXECUTION_TIME}};
  4104. if (strCurrentFolder.empty() || lpSession == nullptr)
  4105. return MAPI_E_CALL_FAILED;
  4106. strMsgDataItemName = strToUpper(strMsgDataItemName);
  4107. strMsgDataItemValue = strToUpper(strMsgDataItemValue);
  4108. if (strMsgDataItemValue.size() > 1 && strMsgDataItemValue[0] == '(') {
  4109. strMsgDataItemValue.erase(0, 1);
  4110. strMsgDataItemValue.erase(strMsgDataItemValue.size() - 1, 1);
  4111. }
  4112. HrSplitInput(strMsgDataItemValue, lstFlags);
  4113. for (auto mail_idx : lstMails) {
  4114. object_ptr<IMessage> lpMessage;
  4115. hr = lpSession->OpenEntry(lstFolderMailEIDs[mail_idx].sEntryID.cb, reinterpret_cast<ENTRYID *>(lstFolderMailEIDs[mail_idx].sEntryID.lpb),
  4116. &IID_IMessage, MAPI_MODIFY, &ulObjType, &~lpMessage);
  4117. if (hr != hrSuccess)
  4118. return hr;
  4119. // FLAGS, FLAGS.SILENT, +FLAGS, +FLAGS.SILENT, -FLAGS, -FLAGS.SILENT
  4120. if (strMsgDataItemName.compare(0, 5, "FLAGS") == 0) {
  4121. if (strMsgDataItemValue.find("\\SEEN") == string::npos)
  4122. hr = lpMessage->SetReadFlag(CLEAR_READ_FLAG);
  4123. else
  4124. hr = lpMessage->SetReadFlag(SUPPRESS_RECEIPT);
  4125. if (hr != hrSuccess)
  4126. return hr;
  4127. hr = lpMessage->GetProps(proptags5, 0, &cValues, &~lpPropVal);
  4128. if (FAILED(hr))
  4129. return hr;
  4130. cValues = 5;
  4131. lpPropVal[1].ulPropTag = PR_FLAG_STATUS;
  4132. if (strMsgDataItemValue.find("\\FLAGGED") == string::npos)
  4133. lpPropVal[1].Value.ul = 0;
  4134. else
  4135. lpPropVal[1].Value.ul = 2;
  4136. if (lpPropVal[2].ulPropTag != PR_ICON_INDEX) {
  4137. lpPropVal[2].ulPropTag = PR_ICON_INDEX;
  4138. lpPropVal[2].Value.l = ICON_FOLDER_DEFAULT;
  4139. }
  4140. if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
  4141. lpPropVal[0].ulPropTag = PR_MSG_STATUS;
  4142. lpPropVal[0].Value.ul = 0;
  4143. }
  4144. if (strMsgDataItemValue.find("\\ANSWERED") == string::npos) {
  4145. lpPropVal[0].Value.ul &= ~MSGSTATUS_ANSWERED;
  4146. cValues -= 2; // leave PR_LAST_VERB_EXECUTED properties
  4147. } else {
  4148. lpPropVal[0].Value.ul |= MSGSTATUS_ANSWERED;
  4149. lpPropVal[2].Value.ul = ICON_MAIL_REPLIED;
  4150. lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTED;
  4151. lpPropVal[3].Value.ul = NOTEIVERB_REPLYTOSENDER;
  4152. lpPropVal[4].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  4153. GetSystemTimeAsFileTime(&lpPropVal[4].Value.ft);
  4154. }
  4155. if (strMsgDataItemValue.find("$FORWARDED") == string::npos) {
  4156. if (cValues == 5)
  4157. cValues -= 2; // leave PR_LAST_VERB_EXECUTED properties if still present
  4158. } else {
  4159. lpPropVal[2].Value.ul = ICON_MAIL_FORWARDED;
  4160. lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTED;
  4161. lpPropVal[3].Value.ul = NOTEIVERB_FORWARD;
  4162. lpPropVal[4].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  4163. GetSystemTimeAsFileTime(&lpPropVal[4].Value.ft);
  4164. }
  4165. if (strMsgDataItemValue.find("\\DELETED") == string::npos) {
  4166. lpPropVal[0].Value.ul &= ~MSGSTATUS_DELMARKED;
  4167. } else {
  4168. lpPropVal[0].Value.ul |= MSGSTATUS_DELMARKED;
  4169. bDelete = true;
  4170. }
  4171. // remove all "flag" properties
  4172. hr = lpMessage->DeleteProps(proptags5, NULL);
  4173. if (hr != hrSuccess)
  4174. return hr;
  4175. // set new values (can be partial, see answered and forwarded)
  4176. hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
  4177. if (hr != hrSuccess)
  4178. return hr;
  4179. hr = lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
  4180. if (hr != hrSuccess)
  4181. return hr;
  4182. } else if (strMsgDataItemName.compare(0, 6, "+FLAGS") == 0) {
  4183. for (ulCurrent = 0; ulCurrent < lstFlags.size(); ++ulCurrent) {
  4184. if (lstFlags[ulCurrent].compare("\\SEEN") == 0) {
  4185. hr = lpMessage->SetReadFlag(SUPPRESS_RECEIPT);
  4186. if (hr != hrSuccess)
  4187. return hr;
  4188. } else if (lstFlags[ulCurrent].compare("\\DRAFT") == 0) {
  4189. // not allowed
  4190. } else if (lstFlags[ulCurrent].compare("\\FLAGGED") == 0) {
  4191. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  4192. if (hr != hrSuccess)
  4193. return hr;
  4194. lpPropVal->ulPropTag = PR_FLAG_STATUS;
  4195. lpPropVal->Value.ul = 2; // 0: none, 1: green ok mark, 2: red flag
  4196. HrSetOneProp(lpMessage, lpPropVal);
  4197. // TODO: set PR_FLAG_ICON here too?
  4198. } else if (lstFlags[ulCurrent].compare("\\ANSWERED") == 0 || lstFlags[ulCurrent].compare("$FORWARDED") == 0) {
  4199. hr = lpMessage->GetProps(proptags4, 0, &cValues, &~lpPropVal);
  4200. if (FAILED(hr))
  4201. return hr;
  4202. cValues = 4;
  4203. if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
  4204. lpPropVal[0].ulPropTag = PR_MSG_STATUS;
  4205. lpPropVal[0].Value.ul = 0;
  4206. }
  4207. // answered
  4208. if (lstFlags[ulCurrent][0] == '\\')
  4209. lpPropVal->Value.ul |= MSGSTATUS_ANSWERED;
  4210. lpPropVal[1].ulPropTag = PR_ICON_INDEX;
  4211. if (lstFlags[ulCurrent][0] == '\\')
  4212. lpPropVal[1].Value.l = ICON_MAIL_REPLIED;
  4213. else
  4214. lpPropVal[1].Value.l = ICON_MAIL_FORWARDED;
  4215. lpPropVal[2].ulPropTag = PR_LAST_VERB_EXECUTED;
  4216. if (lstFlags[ulCurrent][0] == '\\')
  4217. lpPropVal[2].Value.ul = NOTEIVERB_REPLYTOSENDER;
  4218. else
  4219. lpPropVal[2].Value.l = NOTEIVERB_FORWARD;
  4220. lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  4221. GetSystemTimeAsFileTime(&lpPropVal[3].Value.ft);
  4222. hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
  4223. if (hr != hrSuccess)
  4224. return hr;
  4225. } else if (lstFlags[ulCurrent].compare("\\DELETED") == 0) {
  4226. if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
  4227. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  4228. if (hr != hrSuccess)
  4229. return hr;
  4230. lpPropVal->ulPropTag = PR_MSG_STATUS;
  4231. lpPropVal->Value.ul = 0;
  4232. }
  4233. lpPropVal->Value.ul |= MSGSTATUS_DELMARKED;
  4234. HrSetOneProp(lpMessage, lpPropVal);
  4235. bDelete = true;
  4236. }
  4237. lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
  4238. }
  4239. } else if (strMsgDataItemName.compare(0, 6, "-FLAGS") == 0) {
  4240. for (ulCurrent = 0; ulCurrent < lstFlags.size(); ++ulCurrent) {
  4241. if (lstFlags[ulCurrent].compare("\\SEEN") == 0) {
  4242. hr = lpMessage->SetReadFlag(CLEAR_READ_FLAG);
  4243. if (hr != hrSuccess)
  4244. return hr;
  4245. } else if (lstFlags[ulCurrent].compare("\\DRAFT") == 0) {
  4246. // not allowed
  4247. } else if (lstFlags[ulCurrent].compare("\\FLAGGED") == 0) {
  4248. if (lpPropVal == NULL) {
  4249. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  4250. if (hr != hrSuccess)
  4251. return hr;
  4252. }
  4253. lpPropVal->ulPropTag = PR_FLAG_STATUS;
  4254. lpPropVal->Value.ul = 0;
  4255. HrSetOneProp(lpMessage, lpPropVal);
  4256. } else if (lstFlags[ulCurrent].compare("\\ANSWERED") == 0 || lstFlags[ulCurrent].compare("$FORWARDED") == 0) {
  4257. hr = lpMessage->GetProps(proptags4, 0, &cValues, &~lpPropVal);
  4258. if (FAILED(hr))
  4259. return hr;
  4260. cValues = 4;
  4261. if (lpPropVal[0].ulPropTag != PR_MSG_STATUS) {
  4262. lpPropVal[0].ulPropTag = PR_MSG_STATUS;
  4263. lpPropVal[0].Value.ul = 0;
  4264. }
  4265. lpPropVal->Value.ul &= ~MSGSTATUS_ANSWERED;
  4266. lpPropVal[1].ulPropTag = PR_ICON_INDEX;
  4267. lpPropVal[1].Value.l = ICON_FOLDER_DEFAULT;
  4268. lpPropVal[2].ulPropTag = PR_LAST_VERB_EXECUTED;
  4269. lpPropVal[2].Value.ul = NOTEIVERB_OPEN;
  4270. lpPropVal[3].ulPropTag = PR_LAST_VERB_EXECUTION_TIME;
  4271. GetSystemTimeAsFileTime(&lpPropVal[3].Value.ft);
  4272. hr = lpMessage->SetProps(cValues, lpPropVal, NULL);
  4273. if (hr != hrSuccess)
  4274. return hr;
  4275. } else if (lstFlags[ulCurrent].compare("\\DELETED") == 0) {
  4276. if (HrGetOneProp(lpMessage, PR_MSG_STATUS, &~lpPropVal) != hrSuccess) {
  4277. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpPropVal);
  4278. if (hr != hrSuccess)
  4279. return hr;
  4280. lpPropVal->ulPropTag = PR_MSG_STATUS;
  4281. lpPropVal->Value.ul = 0;
  4282. }
  4283. lpPropVal->Value.ul &= ~MSGSTATUS_DELMARKED;
  4284. HrSetOneProp(lpMessage, lpPropVal);
  4285. }
  4286. lpMessage->SaveChanges(KEEP_OPEN_READWRITE | FORCE_SAVE);
  4287. }
  4288. }
  4289. /* Get the newly updated flags */
  4290. hr = HrGetMessageFlags(strNewFlags, lpMessage, lstFolderMailEIDs[mail_idx].bRecent);
  4291. if (hr != hrSuccess)
  4292. return hr;
  4293. /* Update our internal flag status */
  4294. lstFolderMailEIDs[mail_idx].strFlags = strNewFlags;
  4295. } // loop on mails
  4296. if (strMsgDataItemName.size() > 7 &&
  4297. strMsgDataItemName.compare(strMsgDataItemName.size() - 7, 7, ".SILENT") == 0)
  4298. hr = MAPI_E_NOT_ME; // abuse error code that will not be used elsewhere from this function
  4299. if (lpbDoDelete)
  4300. *lpbDoDelete = bDelete;
  4301. return hr;
  4302. }
  4303. /**
  4304. * Implementation of the COPY and XAOL-MOVE commands
  4305. *
  4306. * @param[in] lstMails list of email to copy or move
  4307. * @param[in] strFolderParam folder to copy/move mails to, in IMAP UTF-7 charset
  4308. * @param[in] bMove copy (false) or move (true)
  4309. *
  4310. * @return MAPI Error code
  4311. */
  4312. HRESULT IMAP::HrCopy(const list<ULONG> &lstMails, const string &strFolderParam, bool bMove) {
  4313. HRESULT hr = hrSuccess;
  4314. object_ptr<IMAPIFolder> lpFromFolder, lpDestFolder;
  4315. ULONG ulCount;
  4316. ENTRYLIST sEntryList;
  4317. wstring strFolder;
  4318. sEntryList.lpbin = NULL;
  4319. if (strCurrentFolder.empty() || !lpSession) {
  4320. hr = MAPI_E_CALL_FAILED;
  4321. goto exit;
  4322. }
  4323. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFromFolder);
  4324. if (hr != hrSuccess)
  4325. goto exit;
  4326. // get dest folder
  4327. hr = IMAP2MAPICharset(strFolderParam, strFolder);
  4328. if (hr != hrSuccess)
  4329. goto exit;
  4330. hr = HrFindFolder(strFolder, false, &~lpDestFolder);
  4331. if (hr != hrSuccess)
  4332. goto exit;
  4333. sEntryList.cValues = lstMails.size();
  4334. if ((hr = MAPIAllocateBuffer(sizeof(SBinary) * lstMails.size(), (LPVOID *) &sEntryList.lpbin)) != hrSuccess)
  4335. goto exit;
  4336. ulCount = 0;
  4337. for (auto mail_idx : lstMails) {
  4338. sEntryList.lpbin[ulCount].cb = lstFolderMailEIDs[mail_idx].sEntryID.cb;
  4339. sEntryList.lpbin[ulCount].lpb = lstFolderMailEIDs[mail_idx].sEntryID.lpb;
  4340. ++ulCount;
  4341. }
  4342. hr = lpFromFolder->CopyMessages(&sEntryList, NULL, lpDestFolder, 0, NULL, bMove ? MESSAGE_MOVE : 0);
  4343. exit:
  4344. MAPIFreeBuffer(sEntryList.lpbin);
  4345. return hr;
  4346. }
  4347. /**
  4348. * Implements the SEARCH command
  4349. *
  4350. * @param[in] lstSearchCriteria search options from the (this value is modified, cannot be used again)
  4351. * @param[in] ulStartCriteria offset in the lstSearchCriteria to start parsing
  4352. * @param[out] lstMailnr number of email messages that match the search criteria
  4353. *
  4354. * @return MAPI Error code
  4355. */
  4356. HRESULT IMAP::HrSearch(std::vector<std::string> &&lstSearchCriteria,
  4357. ULONG ulStartCriteria, std::list<ULONG> &lstMailnr)
  4358. {
  4359. HRESULT hr = hrSuccess;
  4360. string strSearchCriterium;
  4361. vector<string> vSubSearch;
  4362. list<ULONG> lstMails;
  4363. object_ptr<IMAPIFolder> lpFolder;
  4364. object_ptr<IMAPITable> lpTable;
  4365. ULONG ulMailnr, ulRownr;
  4366. enum { EID, NUM_COLS };
  4367. static constexpr const SizedSPropTagArray(NUM_COLS, spt) = {NUM_COLS, {PR_EC_IMAP_ID}};
  4368. map<unsigned int, unsigned int> mapUIDs;
  4369. int n = 0;
  4370. if (strCurrentFolder.empty() || lpSession == nullptr)
  4371. return MAPI_E_CALL_FAILED;
  4372. // no need to search in empty folders, won't find anything
  4373. if (lstFolderMailEIDs.empty())
  4374. return hr;
  4375. // don't search if only search for uid, sequence set, all, recent, new or old
  4376. strSearchCriterium = lstSearchCriteria[ulStartCriteria];
  4377. strSearchCriterium = strToUpper(strSearchCriterium);
  4378. if (lstSearchCriteria.size() - ulStartCriteria == 2 &&
  4379. strSearchCriterium.compare("UID") == 0)
  4380. return HrParseSeqUidSet(lstSearchCriteria[ulStartCriteria + 1], lstMailnr);
  4381. if (lstSearchCriteria.size() - ulStartCriteria == 1) {
  4382. if (strSearchCriterium.find_first_of("123456789*") == 0) {
  4383. hr = HrParseSeqSet(lstSearchCriteria[ulStartCriteria], lstMailnr);
  4384. return hr;
  4385. } else if (strSearchCriterium.compare("ALL") == 0) {
  4386. for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
  4387. lstMailnr.push_back(ulMailnr);
  4388. return hr;
  4389. } else if (strSearchCriterium.compare("RECENT") == 0) {
  4390. for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
  4391. if(lstFolderMailEIDs[ulMailnr].bRecent)
  4392. lstMailnr.push_back(ulMailnr);
  4393. return hr;
  4394. } else if (strSearchCriterium.compare("NEW") == 0) {
  4395. for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
  4396. if(lstFolderMailEIDs[ulMailnr].bRecent && lstFolderMailEIDs[ulMailnr].strFlags.find("Seen") == std::string::npos)
  4397. lstMailnr.push_back(ulMailnr);
  4398. return hr;
  4399. } else if (strSearchCriterium.compare("OLD") == 0) {
  4400. for (ulMailnr = 0; ulMailnr < lstFolderMailEIDs.size(); ++ulMailnr)
  4401. if(!lstFolderMailEIDs[ulMailnr].bRecent)
  4402. lstMailnr.push_back(ulMailnr);
  4403. return hr;
  4404. }
  4405. }
  4406. // Make a map of UID->ID
  4407. for (const auto &e : lstFolderMailEIDs)
  4408. mapUIDs[e.ulUid] = n++;
  4409. hr = HrFindFolder(strCurrentFolder, bCurrentFolderReadOnly, &~lpFolder);
  4410. if (hr != hrSuccess)
  4411. return hr;
  4412. hr = lpFolder->GetContentsTable(MAPI_DEFERRED_ERRORS, &~lpTable);
  4413. if (hr != hrSuccess)
  4414. return hr;
  4415. ECAndRestriction root_rst;
  4416. std::vector<IRestrictionPush *> lstRestrictions;
  4417. lstRestrictions.push_back(&root_rst);
  4418. /*
  4419. * Add EXIST(PR_INSTANCE_KEY) to make sure that the query will not be
  4420. * passed to the indexer.
  4421. */
  4422. root_rst += ECExistRestriction(PR_INSTANCE_KEY);
  4423. // Thunderbird searches:
  4424. // or:
  4425. // 12 uid SEARCH UNDELETED (OR SUBJECT "Undelivered" TO "henk")
  4426. // 15 uid SEARCH UNDELETED (OR (OR SUBJECT "sender" SUBJECT "mail") SUBJECT "returned")
  4427. // 17 uid SEARCH UNDELETED (OR SUBJECT "Undelivered" NOT TO "henk")
  4428. // and:
  4429. // 14 uid SEARCH UNDELETED SUBJECT "Undelivered" TO "henk"
  4430. // 16 uid SEARCH UNDELETED SUBJECT "Undelivered" NOT TO "henk"
  4431. // both cases, ulStartCriteria == 3: UNDELETED
  4432. // this breaks to following search:
  4433. // (or subject henk subject kees) (or to henk from kees)
  4434. // since this will translate in all or's, not: and(or(subj:henk,subj:kees),or(to:henk,from:kees))
  4435. // however, thunderbird cannot build such a query, so we don't care currently.
  4436. while (ulStartCriteria < lstSearchCriteria.size())
  4437. {
  4438. if (lstSearchCriteria[ulStartCriteria][0] == '(') {
  4439. // remove all () and [], and resplit.
  4440. strSearchCriterium.clear();
  4441. for (auto c : lstSearchCriteria[ulStartCriteria]) {
  4442. if (c == '(' || c == ')' || c == '[' || c == ']')
  4443. continue;
  4444. strSearchCriterium += c;
  4445. }
  4446. vSubSearch.clear();
  4447. HrSplitInput(strSearchCriterium, vSubSearch);
  4448. // replace in list.
  4449. lstSearchCriteria.erase(lstSearchCriteria.begin() + ulStartCriteria);
  4450. lstSearchCriteria.insert(lstSearchCriteria.begin() + ulStartCriteria, vSubSearch.begin(), vSubSearch.end());
  4451. }
  4452. strSearchCriterium = lstSearchCriteria[ulStartCriteria];
  4453. strSearchCriterium = strToUpper(strSearchCriterium);
  4454. assert(lstRestrictions.size() >= 1);
  4455. IRestrictionPush &top_rst = *lstRestrictions[lstRestrictions.size()-1];
  4456. if (lstRestrictions.size() > 1)
  4457. lstRestrictions.pop_back();
  4458. SPropValue pv, pv2;
  4459. if (strSearchCriterium.find_first_of("123456789*") == 0) { // sequence set
  4460. lstMails.clear();
  4461. hr = HrParseSeqSet(strSearchCriterium, lstMails);
  4462. if (hr != hrSuccess)
  4463. return hr;
  4464. ECOrRestriction or_rst;
  4465. for (auto mail_idx : lstMails) {
  4466. pv.ulPropTag = PR_EC_IMAP_ID;
  4467. pv.Value.ul = lstFolderMailEIDs[mail_idx].ulUid;
  4468. or_rst += ECPropertyRestriction(RELOP_EQ, pv.ulPropTag, &pv, ECRestriction::Shallow);
  4469. }
  4470. top_rst += std::move(or_rst);
  4471. ++ulStartCriteria;
  4472. } else if (strSearchCriterium.compare("ALL") == 0 || strSearchCriterium.compare("NEW") == 0 || strSearchCriterium.compare("RECENT") == 0) {
  4473. // do nothing
  4474. ++ulStartCriteria;
  4475. } else if (strSearchCriterium.compare("ANSWERED") == 0) {
  4476. top_rst += ECAndRestriction(
  4477. ECExistRestriction(PR_MSG_STATUS) +
  4478. ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_ANSWERED));
  4479. ++ulStartCriteria;
  4480. // TODO: find also in PR_LAST_VERB_EXECUTED
  4481. } else if (strSearchCriterium.compare("BEFORE") == 0) {
  4482. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4483. return MAPI_E_CALL_FAILED;
  4484. pv.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
  4485. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4486. top_rst += ECAndRestriction(
  4487. ECExistRestriction(pv.ulPropTag) +
  4488. ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow));
  4489. ulStartCriteria += 2;
  4490. } else if (strSearchCriterium == "BODY") {
  4491. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4492. return MAPI_E_CALL_FAILED;
  4493. unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
  4494. pv.ulPropTag = PR_BODY_A;
  4495. pv.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
  4496. top_rst += ECAndRestriction(
  4497. ECExistRestriction(PR_BODY) +
  4498. ECContentRestriction(flags, PR_BODY, &pv, ECRestriction::Shallow));
  4499. ulStartCriteria += 2;
  4500. } else if (strSearchCriterium.compare("DELETED") == 0) {
  4501. top_rst += ECAndRestriction(
  4502. ECExistRestriction(PR_MSG_STATUS) +
  4503. ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED));
  4504. ++ulStartCriteria;
  4505. } else if (strSearchCriterium.compare("DRAFT") == 0) {
  4506. top_rst += ECAndRestriction(
  4507. ECExistRestriction(PR_MSG_STATUS) +
  4508. ECBitMaskRestriction(BMR_NEZ, PR_MSG_STATUS, MSGSTATUS_DRAFT));
  4509. ++ulStartCriteria;
  4510. // FIXME: add restriction to find PR_MESSAGE_FLAGS with MSGFLAG_UNSENT on
  4511. } else if (strSearchCriterium.compare("FLAGGED") == 0) {
  4512. top_rst += ECAndRestriction(
  4513. ECExistRestriction(PR_FLAG_STATUS) +
  4514. ECBitMaskRestriction(BMR_NEZ, PR_FLAG_STATUS, 0xFFFF));
  4515. ++ulStartCriteria;
  4516. } else if (strSearchCriterium.compare("FROM") == 0) {
  4517. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4518. return MAPI_E_CALL_FAILED;
  4519. unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
  4520. pv.ulPropTag = PR_SENT_REPRESENTING_NAME_A;
  4521. pv.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
  4522. top_rst += ECOrRestriction(
  4523. ECContentRestriction(flags, PR_SENT_REPRESENTING_NAME, &pv, ECRestriction::Shallow) +
  4524. ECContentRestriction(flags, PR_SENT_REPRESENTING_EMAIL_ADDRESS, &pv, ECRestriction::Shallow));
  4525. ulStartCriteria += 2;
  4526. } else if (strSearchCriterium.compare("KEYWORD") == 0) {
  4527. top_rst += ECBitMaskRestriction(BMR_NEZ, PR_ENTRYID, 0);
  4528. ulStartCriteria += 2;
  4529. } else if (strSearchCriterium.compare("LARGER") == 0) {
  4530. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4531. return MAPI_E_CALL_FAILED;
  4532. pv.ulPropTag = PR_EC_IMAP_EMAIL_SIZE;
  4533. pv2.ulPropTag = PR_MESSAGE_SIZE;
  4534. pv2.Value.ul = pv.Value.ul = strtoul(lstSearchCriteria[ulStartCriteria+1].c_str(), nullptr, 0);
  4535. top_rst += ECOrRestriction(
  4536. ECAndRestriction(
  4537. ECExistRestriction(pv.ulPropTag) +
  4538. ECPropertyRestriction(RELOP_GT, pv.ulPropTag, &pv, ECRestriction::Shallow)
  4539. ) +
  4540. ECAndRestriction(
  4541. ECNotRestriction(ECExistRestriction(pv.ulPropTag)) +
  4542. ECPropertyRestriction(RELOP_GT, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
  4543. ));
  4544. ulStartCriteria += 2;
  4545. // NEW done with ALL
  4546. } else if (strSearchCriterium.compare("NOT") == 0) {
  4547. ECRestriction *r = top_rst += ECNotRestriction(nullptr);
  4548. lstRestrictions.push_back(static_cast<ECNotRestriction *>(r));
  4549. ++ulStartCriteria;
  4550. } else if (strSearchCriterium.compare("OLD") == 0) { // none?
  4551. top_rst += ECBitMaskRestriction(BMR_NEZ, PR_ENTRYID, 0);
  4552. ++ulStartCriteria;
  4553. } else if (strSearchCriterium.compare("ON") == 0) {
  4554. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4555. return MAPI_E_CALL_FAILED;
  4556. pv.ulPropTag = pv2.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
  4557. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4558. pv2.Value.ft = AddDay(pv.Value.ft);
  4559. top_rst += ECAndRestriction(
  4560. ECExistRestriction(pv.ulPropTag) +
  4561. ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow) +
  4562. ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow));
  4563. ulStartCriteria += 2;
  4564. } else if (strSearchCriterium.compare("OR") == 0) {
  4565. ECRestriction *new_rst = top_rst += ECOrRestriction();
  4566. auto or_rst = static_cast<ECOrRestriction *>(new_rst);
  4567. lstRestrictions.push_back(or_rst);
  4568. lstRestrictions.push_back(or_rst);
  4569. ++ulStartCriteria;
  4570. // RECENT done with ALL
  4571. } else if (strSearchCriterium.compare("SEEN") == 0) {
  4572. top_rst += ECAndRestriction(
  4573. ECExistRestriction(PR_MESSAGE_FLAGS) +
  4574. ECBitMaskRestriction(BMR_NEZ, PR_MESSAGE_FLAGS, MSGFLAG_READ));
  4575. ++ulStartCriteria;
  4576. } else if (strSearchCriterium.compare("SENTBEFORE") == 0) {
  4577. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4578. return MAPI_E_CALL_FAILED;
  4579. pv.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
  4580. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4581. top_rst += ECAndRestriction(
  4582. ECExistRestriction(pv.ulPropTag) +
  4583. ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow));
  4584. ulStartCriteria += 2;
  4585. } else if (strSearchCriterium.compare("SENTON") == 0) {
  4586. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4587. return MAPI_E_CALL_FAILED;
  4588. pv.ulPropTag = pv2.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
  4589. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4590. pv2.Value.ft = AddDay(pv.Value.ft);
  4591. top_rst += ECAndRestriction(
  4592. ECExistRestriction(pv.ulPropTag) +
  4593. ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow) +
  4594. ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow));
  4595. ulStartCriteria += 2;
  4596. } else if (strSearchCriterium.compare("SENTSINCE") == 0) {
  4597. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4598. return MAPI_E_CALL_FAILED;
  4599. pv.ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
  4600. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4601. top_rst += ECAndRestriction(
  4602. ECExistRestriction(pv.ulPropTag) +
  4603. ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow));
  4604. ulStartCriteria += 2;
  4605. } else if (strSearchCriterium.compare("SINCE") == 0) {
  4606. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4607. return MAPI_E_CALL_FAILED;
  4608. pv.ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
  4609. pv.Value.ft = StringToFileTime(lstSearchCriteria[ulStartCriteria+1].c_str());
  4610. top_rst += ECAndRestriction(
  4611. ECExistRestriction(pv.ulPropTag) +
  4612. ECPropertyRestriction(RELOP_GE, pv.ulPropTag, &pv, ECRestriction::Shallow));
  4613. ulStartCriteria += 2;
  4614. } else if (strSearchCriterium.compare("SMALLER") == 0) {
  4615. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4616. return MAPI_E_CALL_FAILED;
  4617. pv.ulPropTag = PR_EC_IMAP_EMAIL_SIZE;
  4618. pv2.ulPropTag = PR_MESSAGE_SIZE;
  4619. pv.Value.ul = pv2.Value.ul = strtoul(lstSearchCriteria[ulStartCriteria+1].c_str(), nullptr, 0);
  4620. top_rst += ECOrRestriction(
  4621. ECAndRestriction(
  4622. ECExistRestriction(pv.ulPropTag) +
  4623. ECPropertyRestriction(RELOP_LT, pv.ulPropTag, &pv, ECRestriction::Shallow)
  4624. ) +
  4625. ECAndRestriction(
  4626. ECNotRestriction(ECExistRestriction(pv.ulPropTag)) +
  4627. ECPropertyRestriction(RELOP_LT, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
  4628. ));
  4629. ulStartCriteria += 2;
  4630. } else if (strSearchCriterium.compare("SUBJECT") == 0) {
  4631. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4632. return MAPI_E_CALL_FAILED;
  4633. // Handle SUBJECT <s>
  4634. const char *const szSearch = lstSearchCriteria[ulStartCriteria+1].c_str();
  4635. unsigned int flags = szSearch[0] ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
  4636. pv.ulPropTag = PR_SUBJECT_A;
  4637. pv.Value.lpszA = const_cast<char *>(szSearch);
  4638. top_rst += ECAndRestriction(
  4639. ECExistRestriction(PR_SUBJECT) +
  4640. ECContentRestriction(flags, PR_SUBJECT, &pv, ECRestriction::Shallow));
  4641. ulStartCriteria += 2;
  4642. } else if (strSearchCriterium.compare("TEXT") == 0) {
  4643. unsigned int flags = lstSearchCriteria[ulStartCriteria+1].size() > 0 ? (FL_SUBSTRING | FL_IGNORECASE) : FL_FULLSTRING;
  4644. pv.ulPropTag = PR_BODY_A;
  4645. pv2.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
  4646. pv.Value.lpszA = pv2.Value.lpszA = const_cast<char *>(lstSearchCriteria[ulStartCriteria+1].c_str());
  4647. top_rst += ECOrRestriction(
  4648. ECAndRestriction(
  4649. ECExistRestriction(PR_BODY) +
  4650. ECContentRestriction(flags, PR_BODY, &pv, ECRestriction::Shallow)
  4651. ) +
  4652. ECAndRestriction(
  4653. ECExistRestriction(pv2.ulPropTag) +
  4654. ECContentRestriction(flags, pv2.ulPropTag, &pv2, ECRestriction::Shallow)
  4655. ));
  4656. ulStartCriteria += 2;
  4657. }
  4658. else if (strSearchCriterium.compare("TO") == 0 || strSearchCriterium.compare("CC") == 0 || strSearchCriterium.compare("BCC") == 0) {
  4659. if (lstSearchCriteria.size() - ulStartCriteria <= 1)
  4660. return MAPI_E_CALL_FAILED;
  4661. // Search for "^HEADER:.*DATA" in PR_TRANSPORT_HEADERS
  4662. std::string strSearch = (string)"^" + strSearchCriterium + ":.*" + lstSearchCriteria[ulStartCriteria+1];
  4663. pv.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
  4664. pv.Value.lpszA = const_cast<char *>(strSearch.c_str());
  4665. top_rst += ECPropertyRestriction(RELOP_RE, pv.ulPropTag, &pv, ECRestriction::Shallow);
  4666. ulStartCriteria += 2;
  4667. } else if (strSearchCriterium.compare("UID") == 0) {
  4668. ECOrRestriction or_rst;
  4669. lstMails.clear();
  4670. hr = HrParseSeqUidSet(lstSearchCriteria[ulStartCriteria + 1], lstMails);
  4671. if (hr != hrSuccess)
  4672. return hr;
  4673. for (auto mail_idx : lstMails) {
  4674. pv.ulPropTag = PR_EC_IMAP_ID;
  4675. pv.Value.ul = lstFolderMailEIDs[mail_idx].ulUid;
  4676. or_rst += ECPropertyRestriction(RELOP_EQ, pv.ulPropTag, &pv, ECRestriction::Shallow);
  4677. }
  4678. top_rst += std::move(or_rst);
  4679. ulStartCriteria += 2;
  4680. } else if (strSearchCriterium.compare("UNANSWERED") == 0) {
  4681. top_rst += ECOrRestriction(
  4682. ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
  4683. ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_ANSWERED));
  4684. ++ulStartCriteria;
  4685. // TODO: also find in PR_LAST_VERB_EXECUTED
  4686. } else if (strSearchCriterium.compare("UNDELETED") == 0) {
  4687. top_rst += ECOrRestriction(
  4688. ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
  4689. ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_DELMARKED));
  4690. ++ulStartCriteria;
  4691. } else if (strSearchCriterium.compare("UNDRAFT") == 0) {
  4692. top_rst += ECOrRestriction(
  4693. ECNotRestriction(ECExistRestriction(PR_MSG_STATUS)) +
  4694. ECBitMaskRestriction(BMR_EQZ, PR_MSG_STATUS, MSGSTATUS_DRAFT));
  4695. ++ulStartCriteria;
  4696. // FIXME: add restrictin to find PR_MESSAGE_FLAGS with MSGFLAG_UNSENT off
  4697. } else if (strSearchCriterium.compare("UNFLAGGED") == 0) {
  4698. top_rst += ECOrRestriction(
  4699. ECNotRestriction(ECExistRestriction(PR_FLAG_STATUS)) +
  4700. ECBitMaskRestriction(BMR_EQZ, PR_FLAG_STATUS, 0xFFFF));
  4701. ++ulStartCriteria;
  4702. } else if (strSearchCriterium.compare("UNKEYWORD") == 0) {
  4703. top_rst += ECExistRestriction(PR_ENTRYID);
  4704. ulStartCriteria += 2;
  4705. } else if (strSearchCriterium.compare("UNSEEN") == 0) {
  4706. top_rst += ECOrRestriction(
  4707. ECNotRestriction(ECExistRestriction(PR_MESSAGE_FLAGS)) +
  4708. ECBitMaskRestriction(BMR_EQZ, PR_MESSAGE_FLAGS, MSGFLAG_READ));
  4709. ++ulStartCriteria;
  4710. } else if (strSearchCriterium.compare("HEADER") == 0) {
  4711. if (lstSearchCriteria.size() - ulStartCriteria <= 2)
  4712. return MAPI_E_CALL_FAILED;
  4713. // Search for "^HEADER:.*DATA" in PR_TRANSPORT_HEADERS
  4714. std::string strSearch = "^" + lstSearchCriteria[ulStartCriteria+1] + ":.*" + lstSearchCriteria[ulStartCriteria+2];
  4715. pv.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
  4716. pv.Value.lpszA = const_cast<char *>(strSearch.c_str());
  4717. top_rst += ECPropertyRestriction(RELOP_RE, pv.ulPropTag, &pv, ECRestriction::Shallow);
  4718. ulStartCriteria += 3;
  4719. } else {
  4720. return MAPI_E_CALL_FAILED;
  4721. }
  4722. }
  4723. root_rst += ECExistRestriction(PR_ENTRYID);
  4724. memory_ptr<SRestriction> classic_rst;
  4725. hr = root_rst.CreateMAPIRestriction(&~classic_rst, ECRestriction::Cheap);
  4726. if (hr != hrSuccess)
  4727. return hr;
  4728. rowset_ptr lpRows;
  4729. hr = HrQueryAllRows(lpTable, spt, classic_rst, nullptr, 0, &~lpRows);
  4730. if (hr != hrSuccess)
  4731. return hr;
  4732. if (lpRows->cRows == 0)
  4733. return hrSuccess;
  4734. for (ulRownr = 0; ulRownr < lpRows->cRows; ++ulRownr) {
  4735. auto iterUID = mapUIDs.find(lpRows->aRow[ulRownr].lpProps[0].Value.ul);
  4736. if (iterUID == mapUIDs.cend())
  4737. // Found a match for a message that is not in our message list .. skip it
  4738. continue;
  4739. lstMailnr.push_back(iterUID->second);
  4740. }
  4741. lstMailnr.sort();
  4742. return hrSuccess;
  4743. }
  4744. /**
  4745. * Lookup a header value in a full message
  4746. *
  4747. * @param[in] strMessage The message to find the header in
  4748. * @param[in] strHeader The header to find in the header part of the message
  4749. * @param[in] strDefault The default value of the header if it wasn't found
  4750. *
  4751. * @return The header value from the message enclosed in quotes, or the given default
  4752. */
  4753. string IMAP::GetHeaderValue(const string &strMessage, const string &strHeader, const string &strDefault) {
  4754. string::size_type posStart, posEnd;
  4755. posStart = strMessage.find(strHeader);
  4756. if (posStart == string::npos)
  4757. return strDefault;
  4758. posStart += strHeader.length();
  4759. posEnd = strMessage.find("\r\n", posStart);
  4760. if (posEnd == string::npos)
  4761. return strDefault;
  4762. // don't care about uppercase, it's all good.
  4763. return "\"" + strMessage.substr(posStart, posEnd - posStart) + "\"";
  4764. }
  4765. /**
  4766. * Create a bodystructure (RFC 3501). Since this is parsed from a
  4767. * VMIME generated message, this function has alot of assumptions. It
  4768. * will still fail to correctly generate a body structure in the case
  4769. * when an email contains another (quoted) RFC 2822 email.
  4770. *
  4771. * @param[in] bExtended generate BODYSTRUCTURE (true) or BODY (false) version
  4772. * @param[out] strBodyStructure The generated body structure
  4773. * @param[in] strMessage use this message to generate the output
  4774. *
  4775. * @return MAPI Error code
  4776. */
  4777. HRESULT IMAP::HrGetBodyStructure(bool bExtended, string &strBodyStructure, const string& strMessage) {
  4778. if (bExtended)
  4779. return createIMAPProperties(strMessage, nullptr, nullptr, &strBodyStructure);
  4780. return createIMAPProperties(strMessage, nullptr, &strBodyStructure, nullptr);
  4781. }
  4782. /**
  4783. * Convert a MAPI FILETIME structure to a IMAP string. This string is
  4784. * slightly differently formatted than an RFC 2822 string.
  4785. *
  4786. * Format: 01-Jan-2006 00:00:00 +0000
  4787. *
  4788. * @param[in] sFileTime time structure to convert
  4789. *
  4790. * @return date/time in string format
  4791. */
  4792. string IMAP::FileTimeToString(FILETIME sFileTime) {
  4793. string strTime;
  4794. char szBuffer[31];
  4795. time_t sTime;
  4796. struct tm ptr;
  4797. sTime = FileTimeToUnixTime(sFileTime.dwHighDateTime, sFileTime.dwLowDateTime);
  4798. gmtime_safe(&sTime, &ptr);
  4799. strftime(szBuffer, 30, "%d-", &ptr);
  4800. strTime += szBuffer;
  4801. strTime += strMonth[ptr.tm_mon];
  4802. strftime(szBuffer, 30, "-%Y %H:%M:%S +0000", &ptr);
  4803. strTime += szBuffer;
  4804. return strTime;
  4805. }
  4806. /**
  4807. * Parses an IMAP date/time string into a MAPI FILETIME struct.
  4808. *
  4809. * @todo, rewrite this function, but it's not widely used (append + search, both slow anyway)
  4810. *
  4811. * @param[in] strTime parse this string into a FILETIME structure
  4812. * @param[in] bDateOnly parse date part only (true) or add time (false)
  4813. *
  4814. * @return MAPI FILETIME structure
  4815. */
  4816. FILETIME IMAP::StringToFileTime(string strTime, bool bDateOnly) {
  4817. FILETIME sFileTime;
  4818. struct tm sTm;
  4819. ULONG ulMonth;
  4820. time_t sTime;
  4821. sTm.tm_mday = 1;
  4822. sTm.tm_mon = 0;
  4823. sTm.tm_year = 100; // years since 1900
  4824. sTm.tm_hour = 0;
  4825. sTm.tm_min = 0;
  4826. sTm.tm_sec = 0;
  4827. sTm.tm_isdst = -1; // daylight saving time off
  4828. // 01-Jan-2006 00:00:00 +0000
  4829. if (strTime.size() < 2)
  4830. goto done;
  4831. if (strTime.at(1) == '-')
  4832. strTime = " " + strTime;
  4833. // day of month
  4834. if (strTime.at(0) == ' ')
  4835. sTm.tm_mday = atoi(strTime.substr(1, 1).c_str());
  4836. else
  4837. sTm.tm_mday = atoi(strTime.substr(0, 2).c_str());
  4838. // month name 3 chars
  4839. if (strTime.size() < 6)
  4840. goto done;
  4841. sTm.tm_mon = 0;
  4842. for (ulMonth = 0; ulMonth < 12; ++ulMonth)
  4843. if (CaseCompare(strMonth[ulMonth],strTime.substr(3, 3)))
  4844. sTm.tm_mon = ulMonth;
  4845. if (strTime.size() < 11)
  4846. goto done;
  4847. sTm.tm_year = atoi(strTime.substr(7, 4).c_str()) - 1900; // year 4 chars
  4848. if (strTime.size() < 14)
  4849. goto done;
  4850. if (bDateOnly)
  4851. goto done;
  4852. sTm.tm_hour = atoi(strTime.substr(12, 2).c_str()); // hours
  4853. if (strTime.size() < 17)
  4854. goto done;
  4855. sTm.tm_min = atoi(strTime.substr(15, 2).c_str()); // minutes
  4856. if (strTime.size() < 20)
  4857. goto done;
  4858. sTm.tm_sec = atoi(strTime.substr(18, 2).c_str()); // seconds
  4859. if (strTime.size() < 26)
  4860. goto done;
  4861. if (strTime.substr(21, 1) == "+") {
  4862. sTm.tm_hour -= atoi(strTime.substr(22, 2).c_str());
  4863. sTm.tm_min -= atoi(strTime.substr(24, 2).c_str());
  4864. } else if (strTime.substr(21, 1) == "-") {
  4865. sTm.tm_hour += atoi(strTime.substr(22, 2).c_str());
  4866. sTm.tm_min += atoi(strTime.substr(24, 2).c_str());
  4867. }
  4868. done:
  4869. sTime = timegm(&sTm);
  4870. UnixTimeToFileTime(sTime, &sFileTime);
  4871. return sFileTime;
  4872. }
  4873. /**
  4874. * Add 24 hours to the given time struct
  4875. *
  4876. * @param[in] sFileTime Original time
  4877. *
  4878. * @return Input + 24 hours
  4879. */
  4880. FILETIME IMAP::AddDay(FILETIME sFileTime) {
  4881. FILETIME sFT;
  4882. // add 24 hour in seconds = 24*60*60 seconds
  4883. UnixTimeToFileTime(FileTimeToUnixTime(sFileTime.dwHighDateTime, sFileTime.dwLowDateTime) + 24 * 60 * 60, &sFT);
  4884. return sFT;
  4885. }
  4886. /**
  4887. * Converts a unicode string to an encoded representation in a
  4888. * specified charset. This function can return either quoted-printable
  4889. * or base64 encoded data.
  4890. *
  4891. * @param[in] input string to escape in quoted-printable or base64
  4892. * @param[in] charset charset for output string
  4893. * @param[in] bIgnore add the //TRANSLIT or //IGNORE flag to iconv
  4894. *
  4895. * @return
  4896. */
  4897. string IMAP::EscapeString(WCHAR *input, std::string& charset, bool bIgnore)
  4898. {
  4899. std::string tmp;
  4900. std::string iconvCharset = charset;
  4901. if (bIgnore)
  4902. setCharsetBestAttempt(iconvCharset);
  4903. try {
  4904. tmp = convert_to<std::string>(iconvCharset.c_str(), input, rawsize(input), CHARSET_WCHAR);
  4905. } catch (const convert_exception &ce) {
  4906. return "NIL";
  4907. }
  4908. // known charsets that are better represented in base64 than quoted-printable
  4909. if (CaseCompare(charset, "UTF-8") || CaseCompare(charset, "ISO-2022-JP"))
  4910. return "\"" + ToQuotedBase64Header(tmp, charset) + "\"";
  4911. else
  4912. return "\"" + ToQuotedPrintable(tmp, charset, true, true) + "\"";
  4913. }
  4914. /**
  4915. * Escapes input string with \ character for specified characters.
  4916. *
  4917. * @param[in] input string to escape
  4918. *
  4919. * @return escaped string
  4920. */
  4921. string IMAP::EscapeStringQT(const string &input) {
  4922. string s;
  4923. unsigned int i;
  4924. /*
  4925. * qtext = NO-WS-CTL / ; Non white space controls
  4926. * %d33 / ; The rest of the US-ASCII
  4927. * %d35-91 / ; characters not including "\"
  4928. * %d93-126 ; or the quote character
  4929. */
  4930. s.reserve(input.length() * 2); // worst-case, only short strings are passing in this function
  4931. s.append(1, '"');
  4932. // We quote NO-WS-CTL anyway, just to be sure
  4933. for (i = 0; i < input.length(); ++i) {
  4934. if (input[i] == 33 || (input[i] >= 35 && input[i] <= 91) || (input[i] >= 93 && input[i] <= 126))
  4935. s += input[i];
  4936. else if (input[i] == 34) {
  4937. // " found, should send literal and data
  4938. return "{" + stringify(input.length()) + "}\n" + input;
  4939. } else {
  4940. s.append(1, '\\');
  4941. s += input[i];
  4942. }
  4943. }
  4944. s.append(1, '"');
  4945. return s;
  4946. }
  4947. /**
  4948. * @brief Converts an unicode string to modified UTF-7
  4949. *
  4950. * IMAP folder encoding is a modified form of utf-7 (+ becomes &, so & is "escaped",
  4951. * utf-7 is a modifed form of base64, based from the utf16 character
  4952. * I'll use the iconv convertor for this, per character .. sigh
  4953. *
  4954. * @param[in] input unicode string to convert
  4955. * @param[out] output valid IMAP folder name to send to the client
  4956. * @return MAPI Error code
  4957. */
  4958. HRESULT IMAP::MAPI2IMAPCharset(const wstring &input, string &output) {
  4959. size_t i;
  4960. convert_context converter;
  4961. output.clear();
  4962. output.reserve(input.size() * 2);
  4963. for (i = 0; i < input.length(); ++i) {
  4964. if ( (input[i] >= 0x20 && input[i] <= 0x25) || (input[i] >= 0x27 && input[i] <= 0x7e) ) {
  4965. if (input[i] == '"' || input[i] == '\\')
  4966. output += '\\';
  4967. output += input[i];
  4968. } else if (input[i] == 0x26) {
  4969. output += "&-"; // & is encoded as &-
  4970. } else {
  4971. wstring conv;
  4972. string utf7;
  4973. conv = input[i];
  4974. while (i+1 < input.length() && (input[i+1] < 0x20 || input[i+1] >= 0x7f))
  4975. conv += input[++i];
  4976. try {
  4977. utf7 = converter.convert_to<string>("UTF-7", conv, rawsize(conv), CHARSET_WCHAR);
  4978. } catch(...) {
  4979. return MAPI_E_BAD_CHARWIDTH;
  4980. }
  4981. utf7[0] = '&'; // convert + to &
  4982. for (size_t j = 0; j < utf7.size(); ++j)
  4983. if (utf7[j] == '/')
  4984. utf7[j] = ','; // convert / from base64 to ,
  4985. output += utf7; // also contains the terminating -
  4986. }
  4987. }
  4988. return hrSuccess;
  4989. }
  4990. /**
  4991. * Converts an IMAP encoded folder string to an unicode string.
  4992. *
  4993. * @param[in] input IMAP folder name, in modified UTF-7
  4994. * @param[out] output widestring version of input
  4995. * @return MAPI Error code
  4996. */
  4997. HRESULT IMAP::IMAP2MAPICharset(const string& input, wstring& output) {
  4998. size_t i;
  4999. convert_context converter;
  5000. output.clear();
  5001. output.reserve(input.size());
  5002. for (i = 0; i < input.length(); ++i) {
  5003. if (input[i] < 0 || input[i] > 127)
  5004. return MAPI_E_BAD_CHARWIDTH;
  5005. if (input[i] != '&') {
  5006. if (input[i] == '\\' && i+1 < input.length() && (input[i+1] == '"' || input[i+1] == '\\'))
  5007. ++i;
  5008. output += input[i];
  5009. continue;
  5010. }
  5011. if (i+1 >= input.length()) {
  5012. // premature end of string
  5013. output += input[i];
  5014. break;
  5015. }
  5016. if (input[i+1] == '-') {
  5017. output += '&';
  5018. ++i; // skip '-'
  5019. continue;
  5020. }
  5021. string conv = "+";
  5022. ++i; // skip imap '&', is a '+' in utf-7
  5023. while (i < input.length() && input[i] != '-') {
  5024. if (input[i] == ',')
  5025. conv += '/'; // , -> / for utf-7
  5026. else
  5027. conv += input[i];
  5028. ++i;
  5029. }
  5030. try {
  5031. output += converter.convert_to<wstring>(CHARSET_WCHAR, conv, rawsize(conv), "UTF-7");
  5032. } catch(...) {
  5033. return MAPI_E_BAD_CHARWIDTH;
  5034. }
  5035. }
  5036. return hrSuccess;
  5037. }
  5038. /**
  5039. * Check for a MAPI LIST/LSUB pattern in a given foldername.
  5040. *
  5041. * @param[in] strFolder folderpath to match
  5042. * @param[in] strPattern Pattern to match with, in uppercase.
  5043. *
  5044. * @return whether folder matches
  5045. */
  5046. bool IMAP::MatchFolderPath(wstring strFolder, const wstring& strPattern)
  5047. {
  5048. bool bMatch = false;
  5049. int f = 0;
  5050. int p = 0;
  5051. strFolder = strToUpper(strFolder);
  5052. while(1) {
  5053. if (f == static_cast<int>(strFolder.size()) &&
  5054. p == static_cast<int>(strPattern.size()))
  5055. // Reached the end of the folder and the pattern strings, so match
  5056. return true;
  5057. if(strPattern[p] == '*') {
  5058. // Match 0-n chars, try longest match first
  5059. for (int i = strFolder.size(); i >= f; --i)
  5060. // Try matching the rest of the string from position i in the string
  5061. if (MatchFolderPath(strFolder.substr(i), strPattern.substr(p + 1)))
  5062. // Match OK, apply the 'skip i' chars
  5063. return true;
  5064. // No match found, failed
  5065. return false;
  5066. } else if(strPattern[p] == '%') {
  5067. // Match 0-n chars excluding '/', try longest match first
  5068. size_t slash = strFolder.find('/', f);
  5069. if(slash == std::string::npos)
  5070. slash = strFolder.size();
  5071. for (int i = slash; i >= f; --i)
  5072. // Try matching the rest of the string from position i in the string
  5073. if (MatchFolderPath(strFolder.substr(i), strPattern.substr(p + 1)))
  5074. // Match OK, apply the 'skip i' chars
  5075. return true;
  5076. // No match found, failed
  5077. return false;
  5078. } else {
  5079. // Match normal string
  5080. if(strFolder[f] != strPattern[p])
  5081. break;
  5082. ++f;
  5083. ++p;
  5084. }
  5085. }
  5086. return bMatch;
  5087. }
  5088. /**
  5089. * Parse RFC 2822 email headers in to a list of string <name, value> pairs.
  5090. *
  5091. * @param[in] strHeaders Email headers to parse (this data will be modified and should not be used after this function)
  5092. * @param[out] lstHeaders list of headers, in header / value pairs
  5093. */
  5094. void IMAP::HrParseHeaders(const string &strHeaders, list<pair<string, string> > &lstHeaders)
  5095. {
  5096. size_t pos = 0;
  5097. string strLine;
  5098. string strField;
  5099. string strData;
  5100. list<pair<string, string> >::iterator iterLast;
  5101. lstHeaders.clear();
  5102. iterLast = lstHeaders.end();
  5103. while(1) {
  5104. size_t end = strHeaders.find("\r\n", pos);
  5105. if (end == string::npos)
  5106. strLine = strHeaders.substr(pos);
  5107. else
  5108. strLine = strHeaders.substr(pos, end-pos);
  5109. if (strLine.empty())
  5110. break; // parsed all headers
  5111. if((strLine[0] == ' ' || strLine[0] == '\t') && iterLast != lstHeaders.end()) {
  5112. // Continuation of previous header
  5113. iterLast->second += "\r\n" + strLine;
  5114. } else {
  5115. size_t colon = strLine.find(":");
  5116. if(colon != string::npos) {
  5117. // Get field name
  5118. strField = strLine.substr(0, colon);
  5119. strData = strLine.substr(colon+1);
  5120. // Remove leading spaces
  5121. while (strData[0] == ' ')
  5122. strData.erase(0,1);
  5123. lstHeaders.push_back(pair<string, string>(strField, strData));
  5124. iterLast = --lstHeaders.end();
  5125. }
  5126. // else: Broken header ? (no :)
  5127. }
  5128. if(end == string::npos)
  5129. break;
  5130. pos = end + 2; // Go to next line (+2 = \r\n)
  5131. }
  5132. }
  5133. /**
  5134. * Find a substring in strInput that starts with strBegin, and
  5135. * possebly ends with strEnd. Only the data between the strBegin and
  5136. * strEnd is returned.
  5137. *
  5138. * @param[out] strOutput The found substring in strInput
  5139. * @param[in] strInput Input string
  5140. * @param[in] strBegin Substring should start with
  5141. * @param[in] strEnd Substring should end with
  5142. */
  5143. void IMAP::HrGetSubString(std::string &strOutput, const std::string &strInput,
  5144. const std::string &strBegin, const std::string &strEnd)
  5145. {
  5146. size_t begin;
  5147. size_t end;
  5148. strOutput.clear();
  5149. begin = strInput.find(strBegin);
  5150. if(begin == string::npos)
  5151. return;
  5152. end = strInput.find(strEnd, begin+1);
  5153. if(end == string::npos)
  5154. strOutput = strInput.substr(begin+1);
  5155. else
  5156. strOutput = strInput.substr(begin+1, end-begin-1);
  5157. }
  5158. /**
  5159. * Make a set of tokens from a string, separated by spaces.
  5160. *
  5161. * @param[out] setTokens A set of strings from input
  5162. * @param[in] strInput split by spaces into the set
  5163. */
  5164. void IMAP::HrTokenize(std::set<std::string> &setTokens,
  5165. const std::string &strInput)
  5166. {
  5167. vector<string> lstTokens = tokenize(strInput, " ");
  5168. setTokens.clear();
  5169. std::copy(lstTokens.begin(), lstTokens.end(), std::inserter(setTokens, setTokens.begin()));
  5170. }
  5171. /**
  5172. * Find the MAPI folder from a full folder path
  5173. *
  5174. * @param[in] strFolder The full folder path
  5175. * @param[in] bReadOnly open read-only or with write access (if possible)
  5176. * @param[out] lppFolder The MAPI folder corresponding to the given name
  5177. *
  5178. * @return MAPI Error code
  5179. */
  5180. HRESULT IMAP::HrFindFolder(const wstring& strFolder, bool bReadOnly, IMAPIFolder **lppFolder)
  5181. {
  5182. HRESULT hr = hrSuccess;
  5183. ULONG cbEntryID = 0;
  5184. memory_ptr<ENTRYID> lpEntryID;
  5185. ULONG ulObjType = 0;
  5186. object_ptr<IMAPIFolder> lpFolder;
  5187. ULONG ulFlags = 0;
  5188. if (!bReadOnly)
  5189. ulFlags |= MAPI_MODIFY;
  5190. hr = HrFindFolderEntryID(strFolder, &cbEntryID, &~lpEntryID);
  5191. if(hr != hrSuccess)
  5192. return hr;
  5193. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, ulFlags, &ulObjType, &~lpFolder);
  5194. if(hr != hrSuccess)
  5195. return hr;
  5196. if (ulObjType != MAPI_FOLDER)
  5197. return MAPI_E_INVALID_PARAMETER;
  5198. *lppFolder = lpFolder.release();
  5199. return hrSuccess;
  5200. }
  5201. /**
  5202. * Find an EntryID of a folder from a full folder path
  5203. *
  5204. * @param[in] strFolder Full path of a folder to find the MAPI EntryID for
  5205. * @param[out] lpcbEntryID number of bytes in lppEntryID
  5206. * @param[in] lppEntryID The EntryID of the given folder
  5207. *
  5208. * @return MAPI Error code
  5209. */
  5210. HRESULT IMAP::HrFindFolderEntryID(const wstring& strFolder, ULONG *lpcbEntryID, LPENTRYID *lppEntryID)
  5211. {
  5212. HRESULT hr = hrSuccess;
  5213. list<SFolder> tmp_folders;
  5214. list<SFolder> *folders = &cached_folders;
  5215. bool should_cache_folders = cache_folders_time_limit > 0;
  5216. time_t expire_time = cache_folders_last_used + cache_folders_time_limit;
  5217. if (should_cache_folders &&
  5218. (std::time(nullptr) > expire_time || !cached_folders.size())) {
  5219. HrGetFolderList(cached_folders);
  5220. cache_folders_last_used = std::time(nullptr);
  5221. }
  5222. else if (!should_cache_folders) {
  5223. HrGetFolderList(tmp_folders);
  5224. folders = &tmp_folders;
  5225. }
  5226. wstring find_folder = strFolder;
  5227. if (find_folder.length() == 0)
  5228. return MAPI_E_NOT_FOUND;
  5229. if (find_folder[0] != '/')
  5230. find_folder = wstring(L"/") + find_folder;
  5231. find_folder = strToUpper(find_folder);
  5232. auto iter = folders->cbegin();
  5233. for (; iter != folders->cend(); iter++) {
  5234. wstring folder_name;
  5235. hr = HrGetFolderPath(iter, *folders, folder_name);
  5236. if (hr != hrSuccess)
  5237. return hr;
  5238. folder_name = strToUpper(folder_name);
  5239. if (folder_name == find_folder)
  5240. break;
  5241. }
  5242. if (iter == folders->cend())
  5243. return MAPI_E_NOT_FOUND;
  5244. *lpcbEntryID = iter->sEntryID.cb;
  5245. hr = MAPIAllocateBuffer(*lpcbEntryID, (void **)lppEntryID);
  5246. if (hr != hrSuccess)
  5247. return hr;
  5248. memcpy(*lppEntryID, iter->sEntryID.lpb, *lpcbEntryID);
  5249. return hrSuccess;
  5250. }
  5251. /**
  5252. * Find the EntryID for a named subfolder in a given MAPI Folder
  5253. *
  5254. * @param[in] lpFolder The parent folder to find strFolder in, or NULL when no parent is present yet.
  5255. * When no parent is present, one will be found as either it's: INBOX, "public", or the user store.
  5256. * @param[in] strFolder The name of the subfolder to find
  5257. * @param[out] lpcbEntryID number of bytes in lppEntryID
  5258. * @param[out] lppEntryID The EntryID of the folder
  5259. *
  5260. * @return MAPI Error code
  5261. */
  5262. HRESULT IMAP::HrFindSubFolder(IMAPIFolder *lpFolder, const wstring& strFolder, ULONG *lpcbEntryID, LPENTRYID *lppEntryID)
  5263. {
  5264. HRESULT hr = hrSuccess;
  5265. object_ptr<IMAPITable> lpTable;
  5266. SPropValue sProp;
  5267. static constexpr const SizedSPropTagArray(2, sptaCols) =
  5268. {2, {PR_ENTRYID, PR_DISPLAY_NAME_W}};
  5269. LPENTRYID lpEntryID = NULL;
  5270. ULONG cbEntryID = 0;
  5271. memory_ptr<SPropValue> lpProp;
  5272. object_ptr<IMAPIFolder> lpSubTree;
  5273. ULONG ulObjType = 0;
  5274. sProp.ulPropTag = PR_DISPLAY_NAME_W;
  5275. sProp.Value.lpszW = (WCHAR *)strFolder.c_str();
  5276. // lpFolder is NULL when we're referring to the IMAP root. The IMAP root contains
  5277. // INBOX, the public folder container, and all folders under the users IPM_SUBTREE.
  5278. if(lpFolder == NULL) {
  5279. if(wcscasecmp(strFolder.c_str(), L"INBOX") == 0) {
  5280. // Inbox request, we know where that is.
  5281. return lpStore->GetReceiveFolder((LPTSTR)"IPM", 0, lpcbEntryID, lppEntryID, nullptr);
  5282. } else if(wcscasecmp(strFolder.c_str(), PUBLIC_FOLDERS_NAME) == 0) {
  5283. // Public folders requested, we know where that is too
  5284. if (lpPublicStore == nullptr)
  5285. return MAPI_E_NOT_FOUND;
  5286. hr = HrGetOneProp(lpPublicStore, PR_IPM_PUBLIC_FOLDERS_ENTRYID, &~lpProp);
  5287. if(hr != hrSuccess)
  5288. return hr;
  5289. cbEntryID = lpProp->Value.bin.cb;
  5290. hr = MAPIAllocateBuffer(cbEntryID, (void **)&lpEntryID);
  5291. if(hr != hrSuccess)
  5292. return hr;
  5293. memcpy(lpEntryID, lpProp->Value.bin.lpb, cbEntryID);
  5294. *lppEntryID = lpEntryID;
  5295. *lpcbEntryID = cbEntryID;
  5296. return hr;
  5297. } else {
  5298. // Other folder in the root requested, use normal search algorithm to find it
  5299. // under IPM_SUBTREE
  5300. hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpProp);
  5301. if(hr != hrSuccess)
  5302. return hr;
  5303. hr = lpStore->OpenEntry(lpProp->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpProp->Value.bin.lpb), nullptr, 0, &ulObjType, &~lpSubTree);
  5304. if(hr != hrSuccess)
  5305. return hr;
  5306. lpFolder = lpSubTree;
  5307. // Fall through to normal folder lookup code
  5308. }
  5309. }
  5310. // Use a restriction to find the folder in the hierarchy table
  5311. hr = lpFolder->GetHierarchyTable(MAPI_DEFERRED_ERRORS, &~lpTable);
  5312. if(hr != hrSuccess)
  5313. return hr;
  5314. hr = lpTable->SetColumns(sptaCols, 0);
  5315. if(hr != hrSuccess)
  5316. return hr;
  5317. hr = ECPropertyRestriction(RELOP_EQ, PR_DISPLAY_NAME_W, &sProp, ECRestriction::Cheap)
  5318. .RestrictTable(lpTable, TBL_BATCH);
  5319. if (hr != hrSuccess)
  5320. return hr;
  5321. rowset_ptr lpRowSet;
  5322. hr = lpTable->QueryRows(1, 0, &~lpRowSet);
  5323. if (hr != hrSuccess)
  5324. return hr;
  5325. if (lpRowSet->cRows == 0)
  5326. return MAPI_E_NOT_FOUND;
  5327. if (lpRowSet->aRow[0].lpProps[0].ulPropTag != PR_ENTRYID)
  5328. return MAPI_E_INVALID_PARAMETER;
  5329. cbEntryID = lpRowSet->aRow[0].lpProps[0].Value.bin.cb;
  5330. hr = MAPIAllocateBuffer(cbEntryID, (void **)&lpEntryID);
  5331. if(hr != hrSuccess)
  5332. return hr;
  5333. memcpy(lpEntryID, lpRowSet->aRow[0].lpProps[0].Value.bin.lpb, cbEntryID);
  5334. *lppEntryID = lpEntryID;
  5335. *lpcbEntryID = cbEntryID;
  5336. return hrSuccess;
  5337. }
  5338. /**
  5339. * Find the deepest folder in strFolder and return that IMAPIFolder
  5340. * and the remaining folders which were not found.
  5341. * If no folders are found from strFolder at all, return the IPM subtree.
  5342. *
  5343. * @param[in] strFolder Folder string complete path to find deepest IMAPIFolder for
  5344. * @param[out] lppFolder Last IMAPIFolder found in strFolder
  5345. * @param[out] strNotFound Folders not found in strFolder
  5346. */
  5347. HRESULT IMAP::HrFindFolderPartial(const wstring& strFolder, IMAPIFolder **lppFolder, wstring *strNotFound)
  5348. {
  5349. HRESULT hr = hrSuccess;
  5350. vector<wstring> vFolders;
  5351. ULONG cbEntryID = 0;
  5352. memory_ptr<ENTRYID> lpEntryID;
  5353. object_ptr<IMAPIFolder> lpFolder;
  5354. ULONG ulObjType = 0;
  5355. memory_ptr<SPropValue> lpTree;
  5356. unsigned int i = 0;
  5357. hr = HrSplitPath(strFolder, vFolders);
  5358. if(hr != hrSuccess)
  5359. return hr;
  5360. // Loop through all the path parts until we find a part that we can't find
  5361. for (i = 0; i < vFolders.size(); ++i) {
  5362. hr = HrFindSubFolder(lpFolder, vFolders[i], &cbEntryID, &~lpEntryID);
  5363. if(hr != hrSuccess) {
  5364. hr = hrSuccess; // Not an error
  5365. break;
  5366. }
  5367. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
  5368. if(hr != hrSuccess)
  5369. return hr;
  5370. }
  5371. // Remove parts that we already have processed
  5372. vFolders.erase(vFolders.begin(),vFolders.begin()+i);
  5373. // The remaining path parts are the relative path that we could not find
  5374. hr = HrUnsplitPath(vFolders, *strNotFound);
  5375. if(hr != hrSuccess)
  5376. return hr;
  5377. if(lpFolder == NULL) {
  5378. hr = HrGetOneProp(lpStore, PR_IPM_SUBTREE_ENTRYID, &~lpTree);
  5379. if(hr != hrSuccess)
  5380. return hr;
  5381. hr = lpSession->OpenEntry(lpTree->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpTree->Value.bin.lpb), nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
  5382. if(hr != hrSuccess)
  5383. return hr;
  5384. }
  5385. *lppFolder = lpFolder.release();
  5386. return hrSuccess;
  5387. }
  5388. /**
  5389. * Special MAPI Folders are blocked to delete, rename or unsubscribe from.
  5390. *
  5391. * @param[in] lpFolder MAPI Folder to check
  5392. *
  5393. * @return Special (true) or not (false)
  5394. */
  5395. bool IMAP::IsSpecialFolder(IMAPIFolder *lpFolder)
  5396. {
  5397. memory_ptr<SPropValue> lpProp;
  5398. if (HrGetOneProp(lpFolder, PR_ENTRYID, &~lpProp) != hrSuccess)
  5399. return false;
  5400. return IsSpecialFolder(lpProp->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpProp->Value.bin.lpb));
  5401. }
  5402. /**
  5403. * Check if this folder contains e-mail
  5404. *
  5405. * @param[in] lpFolder MAPI Folder to check
  5406. *
  5407. * @return may contain e-mail (true) or not (false)
  5408. */
  5409. bool IMAP::IsMailFolder(IMAPIFolder *lpFolder)
  5410. {
  5411. memory_ptr<SPropValue> lpProp;
  5412. if (HrGetOneProp(lpFolder, PR_CONTAINER_CLASS_A, &~lpProp) != hrSuccess)
  5413. // if the property is missing, treat it as an email folder
  5414. return true;
  5415. return strcasecmp(lpProp->Value.lpszA, "IPM") == 0 ||
  5416. strcasecmp(lpProp->Value.lpszA, "IPF.NOTE") == 0;
  5417. }
  5418. bool IMAP::IsSentItemFolder(IMAPIFolder *lpFolder)
  5419. {
  5420. ULONG ulResult = FALSE;
  5421. memory_ptr<SPropValue> lpProp, lpPropStore;
  5422. HRESULT hr = HrGetOneProp(lpFolder, PR_ENTRYID, &~lpProp);
  5423. if (hr != hrSuccess)
  5424. return false;
  5425. hr = HrGetOneProp(lpStore, PR_IPM_SENTMAIL_ENTRYID, &~lpPropStore);
  5426. if (hr != hrSuccess)
  5427. return false;
  5428. hr = lpStore->CompareEntryIDs(lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb, lpPropStore->Value.bin.cb, (LPENTRYID)lpPropStore->Value.bin.lpb , 0, &ulResult);
  5429. if (hr != hrSuccess)
  5430. return false;
  5431. return ulResult;
  5432. }
  5433. /**
  5434. * Return the parent folder for an EntryID
  5435. *
  5436. * @param[in] cbEntryID number of bytes in lpEntryID
  5437. * @param[in] lpEntryID EntryID of a folder
  5438. * @param[out] lppFolder Parent MAPI Folder of given EntryID
  5439. *
  5440. * @return MAPI Error code
  5441. */
  5442. HRESULT IMAP::HrOpenParentFolder(ULONG cbEntryID, LPENTRYID lpEntryID, IMAPIFolder **lppFolder)
  5443. {
  5444. HRESULT hr = hrSuccess;
  5445. object_ptr<IMAPIFolder> lpFolder;
  5446. ULONG ulObjType = 0;
  5447. hr = lpSession->OpenEntry(cbEntryID, lpEntryID, nullptr, MAPI_MODIFY, &ulObjType, &~lpFolder);
  5448. if (hr != hrSuccess)
  5449. return hr;
  5450. if (ulObjType != MAPI_FOLDER)
  5451. return MAPI_E_NOT_FOUND;
  5452. return HrOpenParentFolder(lpFolder, lppFolder);
  5453. }
  5454. /**
  5455. * Open parent MAPI folder for given MAPI folder
  5456. *
  5457. * @param[in] lpFolder MAPI Folder to open the parent folder for
  5458. * @param[out] lppFolder Parent folder
  5459. *
  5460. * @return MAPI Error code
  5461. */
  5462. HRESULT IMAP::HrOpenParentFolder(IMAPIFolder *lpFolder, IMAPIFolder **lppFolder)
  5463. {
  5464. memory_ptr<SPropValue> lpParent;
  5465. ULONG ulObjType = 0;
  5466. HRESULT hr = HrGetOneProp(lpFolder, PR_PARENT_ENTRYID, &~lpParent);
  5467. if(hr != hrSuccess)
  5468. return hr;
  5469. return lpSession->OpenEntry(lpParent->Value.bin.cb,
  5470. reinterpret_cast<ENTRYID *>(lpParent->Value.bin.lpb), nullptr,
  5471. MAPI_MODIFY, &ulObjType, reinterpret_cast<IUnknown **>(lppFolder));
  5472. }
  5473. /** @} */