IdIMAP4Server.pas 102 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Prior revision history
  18. Rev 1.31 2/9/2005 11:44:20 AM JPMugaas
  19. Fixed compiler problem and removed some warnings about virtual
  20. methods hiding stuff in the base class.
  21. Rev 1.30 2/8/05 6:20:16 PM RLebeau
  22. Added additional overriden methods.
  23. Rev 1.29 10/26/2004 11:08:06 PM JPMugaas
  24. Updated refs.
  25. Rev 1.28 10/21/2004 1:49:12 PM BGooijen
  26. Raid 214213
  27. Rev 1.27 09/06/2004 09:54:56 CCostelloe
  28. Kylix 3 patch
  29. Rev 1.26 2004.05.20 11:37:34 AM czhower
  30. IdStreamVCL
  31. Rev 1.25 4/8/2004 11:49:56 AM BGooijen
  32. Fix for D5
  33. Rev 1.24 03/03/2004 01:16:20 CCostelloe
  34. Yet another check-in as part of continuing development
  35. Rev 1.23 01/03/2004 23:32:24 CCostelloe
  36. Another check-in as part of continuing development
  37. Rev 1.22 3/1/2004 12:55:28 PM JPMugaas
  38. Updated for problem with new code.
  39. Rev 1.21 26/02/2004 02:01:14 CCostelloe
  40. Another intermediate check-in, approx half of functions are debugged
  41. Rev 1.20 24/02/2004 10:34:50 CCostelloe
  42. Storage-specific code moved to IdIMAP4ServerDemo
  43. Rev 1.19 2/22/2004 12:09:54 AM JPMugaas
  44. Fixes for IMAP4Server compile failure in DotNET. This also fixes
  45. a potential problem where file handles can be leaked in the server
  46. needlessly.
  47. Rev 1.18 12/02/2004 02:40:56 CCostelloe
  48. Minor bugfix
  49. Rev 1.17 12/02/2004 02:24:30 CCostelloe
  50. Completed revision, apart from parts support and BODYSTRUCTURE, not
  51. yet debugged.
  52. Rev 1.16 05/02/2004 00:25:32 CCostelloe
  53. This version actually works!
  54. Rev 1.15 2/4/2004 2:37:38 AM JPMugaas
  55. Moved more units down to the implementation clause in the units to
  56. make them easier to compile.
  57. Rev 1.14 2/3/2004 4:12:42 PM JPMugaas
  58. Fixed up units so they should compile.
  59. Rev 1.13 1/29/2004 9:07:54 PM JPMugaas
  60. Now uses TIdExplicitTLSServer so it can take advantage of that framework.
  61. Rev 1.12 1/21/2004 3:11:02 PM JPMugaas
  62. InitComponent
  63. Rev 1.11 27/12/2003 22:28:48 ANeillans
  64. Design fix, Login event only passed the username (first param)
  65. Rev 1.10 2003.10.21 9:13:08 PM czhower
  66. Now compiles.
  67. Rev 1.9 10/19/2003 6:00:24 PM DSiders
  68. Added localization coimments.
  69. Rev 1.8 9/19/2003 03:29:58 PM JPMugaas
  70. Now should compile again.
  71. Rev 1.7 07/09/2003 12:29:08 CCostelloe
  72. Warning that variable LIO is declared but never used in
  73. TIdIMAP4Server.DoCommandSTARTTLS fixed.
  74. Rev 1.6 7/20/2003 6:20:06 PM SPerry
  75. Switched to IdCmdTCPServer, also some modifications
  76. Rev 1.5 3/14/2003 10:44:36 PM BGooijen
  77. Removed warnings, changed StartSSL to PassThrough:=false;
  78. Rev 1.4 3/14/2003 10:04:10 PM BGooijen
  79. Removed TIdServerIOHandlerSSLBase.PeerPassthrough, the ssl is now
  80. enabled in the server-protocol-files
  81. Rev 1.3 3/13/2003 09:49:20 AM JPMugaas
  82. Now uses an abstract SSL base class instead of OpenSSL so
  83. 3rd-party vendors can plug-in their products.
  84. Rev 1.2 2/24/2003 09:03:14 PM JPMugaas
  85. Rev 1.1 2/6/2003 03:18:14 AM JPMugaas
  86. Updated components that compile with Indy 10.
  87. Rev 1.0 11/13/2002 07:55:02 AM JPMugaas
  88. 2002-Apr-21 - J. Berg
  89. use fetch()
  90. 2000-May-18 - J. Peter Mugaas
  91. Ported to Indy
  92. 2000-Jan-13 - MTL
  93. Moved to new Palette Scheme (Winshoes Servers)
  94. 1999-Aug-26 - Ray Malone
  95. Started unit
  96. }
  97. unit IdIMAP4Server;
  98. {
  99. TODO (ex RFC 3501):
  100. Dont allow & to be used as a mailbox separator.
  101. Certain server data (unsolicited responses) MUST be recorded,
  102. see Server Responses section.
  103. UIDs must be unique to a mailbox AND any subsequent mailbox with
  104. the same name - record in a text file.
  105. \Recent cannot be changed by STORE or APPEND.
  106. COPY should preserve the date of the original message.
  107. TODO (ccostelloe):
  108. Add a file recording the UIDVALIDITY in each mailbox.
  109. Emails should be ordered in date order.
  110. Optional date/time param to be implemented in APPEND.
  111. Consider integrating IdUserAccounts into login mechanism
  112. (or per-user passwords).
  113. Implement utf mailbox encoding.
  114. Implement * in message numbers.
  115. Implement multiple-option FETCH commands (will need breaking out some
  116. options which are abbreviations into their subsets).
  117. Need some method of preserving flags permanently.
  118. }
  119. {
  120. IMPLEMENTATION NOTES:
  121. Major rewrite started 2nd February 2004, Ciaran Costelloe, [email protected].
  122. Prior to this, it was a simple wrapper class with a few problems.
  123. Note that IMAP servers should return BAD for an unknown command or
  124. invalid arguments (synthax errors and unsupported commands) and BAD
  125. if the command is valid but there was some problem in executing
  126. (e.g. trying a change an email's flag if it is a read-only mailbox).
  127. FUseDefaultMechanismsForUnassignedCommands defaults to True: if you
  128. set it to False, you need to implement command handlers for all the
  129. commands you need to implement. If True, this class implements a
  130. default mechanism and provides default behaviour for all commands.
  131. It does not include any filesystem-specific functions, which you
  132. need to implement.
  133. The default behaviour uses a default password of 'admin' - change this
  134. if you have any consideration for security!
  135. FSaferMode defaults to False: you should probably leave it False for
  136. testing, because this generates diagnostically-useful error messages.
  137. However, setting it True generates minimal responses for the greeting
  138. and for login failures, making life more difficult for a hacker.
  139. WARNING: you should also implement one of the Indy-provided more-secure
  140. logins than the default plaintext password login!
  141. You may want to assign handlers to the OnBeforeCmd and OnBeforeSend
  142. events to easily log data in & out of the server.
  143. WARNING: TIdIMAP4PeerContext has a TIdMailBox which holds various
  144. status info, including UIDs in its message collection. Do NOT use the
  145. message collection for loading messages into, or you may thrash message
  146. UIDs or flags!
  147. }
  148. interface
  149. {$i IdCompilerDefines.inc}
  150. {$IFDEF DOTNET}
  151. {$I IdUnitPlatformOff.inc}
  152. {$I IdSymbolPlatformOff.inc}
  153. {$ENDIF}
  154. uses
  155. Classes,
  156. IdAssignedNumbers,
  157. IdCustomTCPServer, //for TIdServerContext
  158. IdCmdTCPServer,
  159. IdContext,
  160. IdCommandHandlers,
  161. IdException,
  162. IdExplicitTLSClientServerBase,
  163. IdIMAP4, //For some defines like TIdIMAP4ConnectionState
  164. IdMailBox,
  165. IdMessage,
  166. IdReply,
  167. IdReplyIMAP4,
  168. IdTCPConnection,
  169. IdYarn;
  170. const
  171. DEF_IMAP4_IMPLICIT_TLS = False;
  172. type
  173. TIMAP4CommandEvent = procedure(AContext: TIdContext; const ATag, ACmd: String) of object;
  174. TIdIMAP4CommandBeforeEvent = procedure(ASender: TIdCommandHandlers; var AData: string; AContext: TIdContext) of object;
  175. TIdIMAP4CommandBeforeSendEvent = procedure(AContext: TIdContext; AData: string) of object;
  176. //For default mechanisms..
  177. TIdIMAP4DefMech1 = function(ALoginName, AMailbox: string): Boolean of object;
  178. TIdIMAP4DefMech2 = function(ALoginName, AMailBoxName: string; AMailBox: TIdMailBox): Boolean of object;
  179. TIdIMAP4DefMech3 = function(ALoginName, AMailbox: string): string of object;
  180. TIdIMAP4DefMech4 = function(ALoginName, AOldMailboxName, ANewMailboxName: string): Boolean of object;
  181. TIdIMAP4DefMech5 = function(ALoginName, AMailBoxName: string; AMailBoxNames: TStrings; AMailBoxFlags: TStrings): Boolean of object;
  182. TIdIMAP4DefMech6 = function(ALoginName, AMailbox: string; AMessage: TIdMessage): Boolean of object;
  183. TIdIMAP4DefMech7 = function(ALoginName, ASourceMailBox, AMessageUID, ADestinationMailbox: string): Boolean of object;
  184. TIdIMAP4DefMech8 = function(ALoginName, AMailbox: string; AMessage: TIdMessage): Int64 of object;
  185. TIdIMAP4DefMech9 = function(ALoginName, AMailbox: string; AMessage, ATargetMessage: TIdMessage): Boolean of object;
  186. TIdIMAP4DefMech10 = function(ALoginName, AMailbox: string; AMessage: TIdMessage; ALines: TStrings): Boolean of object;
  187. TIdIMAP4DefMech11 = function(ASender: TIdCommand; AReadOnly: Boolean): Boolean of object;
  188. TIdIMAP4DefMech12 = function(AParams: TStrings; AMailBoxParam: Integer): Boolean of object;
  189. TIdIMAP4DefMech13 = function(ALoginName, AMailBoxName, ANewUIDNext: string): Boolean of object;
  190. TIdIMAP4DefMech14 = function(ALoginName, AMailBoxName, AUID: string): string of object;
  191. EIdIMAP4ServerException = class(EIdException);
  192. EIdIMAP4ImplicitTLSRequiresSSL = class(EIdIMAP4ServerException);
  193. { custom IMAP4 context }
  194. TIdIMAP4PeerContext = class(TIdServerContext)
  195. protected
  196. FConnectionState : TIdIMAP4ConnectionState;
  197. FLoginName: string;
  198. FMailBox: TIdMailBox;
  199. FIMAP4Tag: String;
  200. FLastCommand: TIdReplyIMAP4; //Used to record the client command we are currently processing
  201. function GetUsingTLS: Boolean;
  202. public
  203. constructor Create(
  204. AConnection: TIdTCPConnection;
  205. AYarn: TIdYarn;
  206. AList: TIdContextThreadList = nil
  207. ); override;
  208. destructor Destroy; override;
  209. property ConnectionState: TIdIMAP4ConnectionState read FConnectionState;
  210. property UsingTLS : Boolean read GetUsingTLS;
  211. property IMAP4Tag: String read FIMAP4Tag;
  212. property MailBox: TIdMailBox read FMailBox;
  213. property LoginName: string read FLoginName write FLoginName;
  214. end;
  215. { TIdIMAP4Server }
  216. TIdIMAP4Server = class(TIdExplicitTLSServer)
  217. protected
  218. //
  219. FSaferMode: Boolean; //See IMPLEMENTATION NOTES above
  220. FUseDefaultMechanismsForUnassignedCommands: Boolean; //See IMPLEMENTATION NOTES above
  221. FRootPath: string; //See IMPLEMENTATION NOTES above
  222. FDefaultPassword: string; //See IMPLEMENTATION NOTES above
  223. FMailBoxSeparator: Char;
  224. //
  225. fOnDefMechDoesImapMailBoxExist: TIdIMAP4DefMech1;
  226. fOnDefMechCreateMailBox: TIdIMAP4DefMech1;
  227. fOnDefMechDeleteMailBox: TIdIMAP4DefMech1;
  228. fOnDefMechIsMailBoxOpen: TIdIMAP4DefMech1;
  229. fOnDefMechSetupMailbox: TIdIMAP4DefMech2;
  230. fOnDefMechNameAndMailBoxToPath: TIdIMAP4DefMech3;
  231. fOnDefMechGetNextFreeUID: TIdIMAP4DefMech3;
  232. fOnDefMechRenameMailBox: TIdIMAP4DefMech4;
  233. fOnDefMechListMailBox: TIdIMAP4DefMech5;
  234. fOnDefMechDeleteMessage: TIdIMAP4DefMech6;
  235. fOnDefMechCopyMessage: TIdIMAP4DefMech7;
  236. fOnDefMechGetMessageSize: TIdIMAP4DefMech8;
  237. fOnDefMechGetMessageHeader: TIdIMAP4DefMech9;
  238. fOnDefMechGetMessageRaw: TIdIMAP4DefMech10;
  239. fOnDefMechOpenMailBox: TIdIMAP4DefMech11;
  240. fOnDefMechReinterpretParamAsMailBox: TIdIMAP4DefMech12;
  241. fOnDefMechUpdateNextFreeUID: TIdIMAP4DefMech13;
  242. fOnDefMechGetFileNameToWriteAppendMessage: TIdIMAP4DefMech14;
  243. //
  244. fOnBeforeCmd: TIdIMAP4CommandBeforeEvent;
  245. fOnBeforeSend: TIdIMAP4CommandBeforeSendEvent;
  246. fOnCommandCAPABILITY: TIMAP4CommandEvent;
  247. fONCommandNOOP: TIMAP4CommandEvent;
  248. fONCommandLOGOUT: TIMAP4CommandEvent;
  249. fONCommandAUTHENTICATE: TIMAP4CommandEvent;
  250. fONCommandLOGIN: TIMAP4CommandEvent;
  251. fONCommandSELECT: TIMAP4CommandEvent;
  252. fONCommandEXAMINE: TIMAP4CommandEvent;
  253. fONCommandCREATE: TIMAP4CommandEvent;
  254. fONCommandDELETE: TIMAP4CommandEvent;
  255. fONCommandRENAME: TIMAP4CommandEvent;
  256. fONCommandSUBSCRIBE: TIMAP4CommandEvent;
  257. fONCommandUNSUBSCRIBE: TIMAP4CommandEvent;
  258. fONCommandLIST: TIMAP4CommandEvent;
  259. fONCommandLSUB: TIMAP4CommandEvent;
  260. fONCommandSTATUS: TIMAP4CommandEvent;
  261. fONCommandAPPEND: TIMAP4CommandEvent;
  262. fONCommandCHECK: TIMAP4CommandEvent;
  263. fONCommandCLOSE: TIMAP4CommandEvent;
  264. fONCommandEXPUNGE: TIMAP4CommandEvent;
  265. fONCommandSEARCH: TIMAP4CommandEvent;
  266. fONCommandFETCH: TIMAP4CommandEvent;
  267. fONCommandSTORE: TIMAP4CommandEvent;
  268. fONCommandCOPY: TIMAP4CommandEvent;
  269. fONCommandUID: TIMAP4CommandEvent;
  270. fONCommandX: TIMAP4CommandEvent;
  271. fOnCommandError: TIMAP4CommandEvent;
  272. //
  273. function CreateExceptionReply: TIdReply; override;
  274. function CreateGreeting: TIdReply; override;
  275. function CreateHelpReply: TIdReply; override;
  276. function CreateMaxConnectionReply: TIdReply; override;
  277. function CreateReplyUnknownCommand: TIdReply; override;
  278. //
  279. //The following are internal commands that help support the IMAP protocol...
  280. procedure InitializeCommandHandlers; override;
  281. function GetReplyClass:TIdReplyClass; override;
  282. function GetRepliesClass:TIdRepliesClass; override;
  283. procedure SendGreeting(AContext: TIdContext; AGreeting: TIdReply); override;
  284. procedure SendWrongConnectionState(ASender: TIdCommand);
  285. procedure SendUnsupportedCommand(ASender: TIdCommand);
  286. procedure SendIncorrectNumberOfParameters(ASender: TIdCommand);
  287. procedure SendUnassignedDefaultMechanism(ASender: TIdCommand);
  288. procedure DoReplyUnknownCommand(AContext: TIdContext; AText: string); override;
  289. procedure SendErrorOpenedReadOnly(ASender: TIdCommand);
  290. procedure SendOkReply(ASender: TIdCommand; const AText: string);
  291. procedure SendBadReply(ASender: TIdCommand; const AText: string); overload;
  292. procedure SendBadReply(ASender: TIdCommand; const AFormat: string; const Args: array of const); overload;
  293. procedure SendNoReply(ASender: TIdCommand; const AText: string = ''); overload;
  294. procedure SendNoReply(ASender: TIdCommand; const AFormat: string; const Args: array of const); overload;
  295. //
  296. //The following are used internally by the default mechanism...
  297. function ExpungeRecords(ASender: TIdCommand): Boolean;
  298. function MessageSetToMessageNumbers(AUseUID: Boolean; ASender: TIdCommand; AMessageNumbers: TStrings; AMessageSet: string): Boolean;
  299. function GetRecordForUID(const AUID: String; AMailBox: TIdMailBox): Int64;
  300. procedure ProcessFetch(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  301. procedure ProcessCopy(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  302. function ProcessStore(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings): Boolean;
  303. procedure ProcessSearch(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  304. function FlagStringToFlagList(AFlagList: TStrings; AFlagString: string): Boolean;
  305. function StripQuotesIfNecessary(AName: string): string;
  306. function ReassembleParams(ASeparator: char; AParams: TStrings; AParamToReassemble: integer): Boolean;
  307. function ReinterpretParamAsMailBox(AParams: TStrings; AMailBoxParam: integer): Boolean;
  308. function ReinterpretParamAsFlags(AParams: TStrings; AFlagsParam: integer): Boolean;
  309. function ReinterpretParamAsQuotedStr(AParams: TStrings; AFlagsParam: integer): Boolean;
  310. function ReinterpretParamAsDataItems(AParams: TStrings; AFlagsParam: integer): Boolean;
  311. //
  312. //The following are used internally by our default mechanism and are copies of
  313. //the same function in TIdIMAP4 (move to a base class?)...
  314. function MessageFlagSetToStr(const AFlags: TIdMessageFlagsSet): String;
  315. //
  316. //DoBeforeCmd & DoSendReply are useful for a server to log all commands and
  317. //responses for debugging...
  318. procedure DoBeforeCmd(ASender: TIdCommandHandlers; var AData: string; AContext: TIdContext);
  319. procedure DoSendReply(AContext: TIdContext; const AData: string); overload;
  320. procedure DoSendReply(AContext: TIdContext; const AFormat: string; const Args: array of const); overload;
  321. //
  322. //Command handlers...
  323. procedure DoCmdHandlersException(ACommand: String; AContext: TIdContext);
  324. procedure DoCommandCAPABILITY(ASender: TIdCommand);
  325. procedure DoCommandNOOP(ASender: TIdCommand);
  326. procedure DoCommandLOGOUT(ASender: TIdCommand);
  327. procedure DoCommandAUTHENTICATE(ASender: TIdCommand);
  328. procedure DoCommandLOGIN(ASender: TIdCommand);
  329. procedure DoCommandSELECT(ASender: TIdCommand);
  330. procedure DoCommandEXAMINE(ASender: TIdCommand);
  331. procedure DoCommandCREATE(ASender: TIdCommand);
  332. procedure DoCommandDELETE(ASender: TIdCommand);
  333. procedure DoCommandRENAME(ASender: TIdCommand);
  334. procedure DoCommandSUBSCRIBE(ASender: TIdCommand);
  335. procedure DoCommandUNSUBSCRIBE(ASender: TIdCommand);
  336. procedure DoCommandLIST(ASender: TIdCommand);
  337. procedure DoCommandLSUB(ASender: TIdCommand);
  338. procedure DoCommandSTATUS(ASender: TIdCommand);
  339. procedure DoCommandAPPEND(ASender: TIdCommand);
  340. procedure DoCommandCHECK(ASender: TIdCommand);
  341. procedure DoCommandCLOSE(ASender: TIdCommand);
  342. procedure DoCommandEXPUNGE(ASender: TIdCommand);
  343. procedure DoCommandSEARCH(ASender: TIdCommand);
  344. procedure DoCommandFETCH(ASender: TIdCommand);
  345. procedure DoCommandSTORE(ASender: TIdCommand);
  346. procedure DoCommandCOPY(ASender: TIdCommand);
  347. procedure DoCommandUID(ASender: TIdCommand);
  348. procedure DoCommandX(ASender: TIdCommand);
  349. procedure DoCommandSTARTTLS(ASender: TIdCommand);
  350. // common code for command handlers
  351. procedure MustUseTLS(ASender: TIdCommand);
  352. //
  353. procedure InitComponent; override;
  354. public
  355. {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
  356. constructor Create(AOwner: TComponent); reintroduce; overload;
  357. {$ENDIF}
  358. destructor Destroy; override;
  359. published
  360. property DefaultPort default IdPORT_IMAP4;
  361. property SaferMode: Boolean read FSaferMode write FSaferMode default False;
  362. property UseDefaultMechanismsForUnassignedCommands: Boolean read FUseDefaultMechanismsForUnassignedCommands write FUseDefaultMechanismsForUnassignedCommands default True;
  363. property RootPath: string read FRootPath write FRootPath;
  364. property DefaultPassword: string read FDefaultPassword write FDefaultPassword;
  365. property MailBoxSeparator: Char read FMailBoxSeparator;
  366. {Default mechansisms}
  367. property OnDefMechDoesImapMailBoxExist: TIdIMAP4DefMech1 read fOnDefMechDoesImapMailBoxExist write fOnDefMechDoesImapMailBoxExist;
  368. property OnDefMechCreateMailBox: TIdIMAP4DefMech1 read fOnDefMechCreateMailBox write fOnDefMechCreateMailBox;
  369. property OnDefMechDeleteMailBox: TIdIMAP4DefMech1 read fOnDefMechDeleteMailBox write fOnDefMechDeleteMailBox;
  370. property OnDefMechIsMailBoxOpen: TIdIMAP4DefMech1 read fOnDefMechIsMailBoxOpen write fOnDefMechIsMailBoxOpen;
  371. property OnDefMechSetupMailbox: TIdIMAP4DefMech2 read fOnDefMechSetupMailbox write fOnDefMechSetupMailbox;
  372. property OnDefMechNameAndMailBoxToPath: TIdIMAP4DefMech3 read fOnDefMechNameAndMailBoxToPath write fOnDefMechNameAndMailBoxToPath;
  373. property OnDefMechGetNextFreeUID: TIdIMAP4DefMech3 read fOnDefMechGetNextFreeUID write fOnDefMechGetNextFreeUID;
  374. property OnDefMechRenameMailBox: TIdIMAP4DefMech4 read fOnDefMechRenameMailBox write fOnDefMechRenameMailBox;
  375. property OnDefMechListMailBox: TIdIMAP4DefMech5 read fOnDefMechListMailBox write fOnDefMechListMailBox;
  376. property OnDefMechDeleteMessage: TIdIMAP4DefMech6 read fOnDefMechDeleteMessage write fOnDefMechDeleteMessage;
  377. property OnDefMechCopyMessage: TIdIMAP4DefMech7 read fOnDefMechCopyMessage write fOnDefMechCopyMessage;
  378. property OnDefMechGetMessageSize: TIdIMAP4DefMech8 read fOnDefMechGetMessageSize write fOnDefMechGetMessageSize;
  379. property OnDefMechGetMessageHeader: TIdIMAP4DefMech9 read fOnDefMechGetMessageHeader write fOnDefMechGetMessageHeader;
  380. property OnDefMechGetMessageRaw: TIdIMAP4DefMech10 read fOnDefMechGetMessageRaw write fOnDefMechGetMessageRaw;
  381. property OnDefMechOpenMailBox: TIdIMAP4DefMech11 read fOnDefMechOpenMailBox write fOnDefMechOpenMailBox;
  382. property OnDefMechReinterpretParamAsMailBox: TIdIMAP4DefMech12 read fOnDefMechReinterpretParamAsMailBox write fOnDefMechReinterpretParamAsMailBox;
  383. property OnDefMechUpdateNextFreeUID: TIdIMAP4DefMech13 read fOnDefMechUpdateNextFreeUID write fOnDefMechUpdateNextFreeUID;
  384. property OnDefMechGetFileNameToWriteAppendMessage: TIdIMAP4DefMech14 read fOnDefMechGetFileNameToWriteAppendMessage write fOnDefMechGetFileNameToWriteAppendMessage;
  385. { Events }
  386. property OnBeforeCmd: TIdIMAP4CommandBeforeEvent read fOnBeforeCmd write fOnBeforeCmd;
  387. property OnBeforeSend: TIdIMAP4CommandBeforeSendEvent read fOnBeforeSend write fOnBeforeSend;
  388. property OnCommandCAPABILITY: TIMAP4CommandEvent read fOnCommandCAPABILITY write fOnCommandCAPABILITY;
  389. property OnCommandNOOP: TIMAP4CommandEvent read fONCommandNOOP write fONCommandNOOP;
  390. property OnCommandLOGOUT: TIMAP4CommandEvent read fONCommandLOGOUT write fONCommandLOGOUT;
  391. property OnCommandAUTHENTICATE: TIMAP4CommandEvent read fONCommandAUTHENTICATE write fONCommandAUTHENTICATE;
  392. property OnCommandLOGIN: TIMAP4CommandEvent read fONCommandLOGIN write fONCommandLOGIN;
  393. property OnCommandSELECT: TIMAP4CommandEvent read fONCommandSELECT write fONCommandSELECT;
  394. property OnCommandEXAMINE:TIMAP4CommandEvent read fOnCommandEXAMINE write fOnCommandEXAMINE;
  395. property OnCommandCREATE: TIMAP4CommandEvent read fONCommandCREATE write fONCommandCREATE;
  396. property OnCommandDELETE: TIMAP4CommandEvent read fONCommandDELETE write fONCommandDELETE;
  397. property OnCommandRENAME: TIMAP4CommandEvent read fOnCommandRENAME write fOnCommandRENAME;
  398. property OnCommandSUBSCRIBE: TIMAP4CommandEvent read fONCommandSUBSCRIBE write fONCommandSUBSCRIBE;
  399. property OnCommandUNSUBSCRIBE: TIMAP4CommandEvent read fONCommandUNSUBSCRIBE write fONCommandUNSUBSCRIBE;
  400. property OnCommandLIST: TIMAP4CommandEvent read fONCommandLIST write fONCommandLIST;
  401. property OnCommandLSUB: TIMAP4CommandEvent read fOnCommandLSUB write fOnCommandLSUB;
  402. property OnCommandSTATUS: TIMAP4CommandEvent read fONCommandSTATUS write fONCommandSTATUS;
  403. property OnCommandAPPEND: TIMAP4CommandEvent read fOnCommandAPPEND write fOnCommandAPPEND;
  404. property OnCommandCHECK: TIMAP4CommandEvent read fONCommandCHECK write fONCommandCHECK;
  405. property OnCommandCLOSE: TIMAP4CommandEvent read fOnCommandCLOSE write fOnCommandCLOSE;
  406. property OnCommandEXPUNGE: TIMAP4CommandEvent read fONCommandEXPUNGE write fONCommandEXPUNGE;
  407. property OnCommandSEARCH: TIMAP4CommandEvent read fOnCommandSEARCH write fOnCommandSEARCH;
  408. property OnCommandFETCH: TIMAP4CommandEvent read fONCommandFETCH write fONCommandFETCH;
  409. property OnCommandSTORE: TIMAP4CommandEvent read fOnCommandSTORE write fOnCommandSTORE;
  410. property OnCommandCOPY: TIMAP4CommandEvent read fOnCommandCOPY write fOnCommandCOPY;
  411. property OnCommandUID: TIMAP4CommandEvent read fONCommandUID write fONCommandUID;
  412. property OnCommandX: TIMAP4CommandEvent read fOnCommandX write fOnCommandX;
  413. property OnCommandError: TIMAP4CommandEvent read fOnCommandError write fOnCommandError;
  414. end;
  415. implementation
  416. uses
  417. IdGlobal,
  418. IdGlobalProtocols,
  419. IdMessageCollection,
  420. IdResourceStringsProtocols,
  421. IdSSL,
  422. SysUtils;
  423. function TIdIMAP4Server.GetReplyClass: TIdReplyClass;
  424. begin
  425. Result := TIdReplyIMAP4;
  426. end;
  427. function TIdIMAP4Server.GetRepliesClass: TIdRepliesClass;
  428. begin
  429. Result := TIdRepliesIMAP4;
  430. end;
  431. procedure TIdIMAP4Server.SendGreeting(AContext: TIdContext; AGreeting: TIdReply);
  432. begin
  433. if FSaferMode then begin
  434. DoSendReply(AContext, '* OK'); {Do not Localize}
  435. end else begin
  436. DoSendReply(AContext, '* OK Indy IMAP server version ' + GetIndyVersion); {Do not Localize}
  437. end;
  438. end;
  439. procedure TIdIMAP4Server.SendWrongConnectionState(ASender: TIdCommand);
  440. begin
  441. SendNoReply(ASender, 'Wrong connection state'); {Do not Localize}
  442. end;
  443. procedure TIdIMAP4Server.SendErrorOpenedReadOnly(ASender: TIdCommand);
  444. begin
  445. SendNoReply(ASender, 'Mailbox was opened read-only'); {Do not Localize}
  446. end;
  447. procedure TIdIMAP4Server.SendUnsupportedCommand(ASender: TIdCommand);
  448. begin
  449. SendBadReply(ASender, 'Unsupported command'); {Do not Localize}
  450. end;
  451. procedure TIdIMAP4Server.SendIncorrectNumberOfParameters(ASender: TIdCommand);
  452. begin
  453. SendBadReply(ASender, 'Incorrect number of parameters'); {Do not Localize}
  454. end;
  455. procedure TIdIMAP4Server.SendUnassignedDefaultMechanism(ASender: TIdCommand);
  456. begin
  457. SendBadReply(ASender, 'Server internal error: unassigned procedure'); {Do not Localize}
  458. end;
  459. procedure TIdIMAP4Server.SendOkReply(ASender: TIdCommand; const AText: string);
  460. begin
  461. DoSendReply(ASender.Context, TIdIMAP4PeerContext(ASender.Context).FLastCommand.SequenceNumber + ' OK ' + AText); {Do not Localize}
  462. end;
  463. procedure TIdIMAP4Server.SendBadReply(ASender: TIdCommand; const AText: string);
  464. begin
  465. DoSendReply(ASender.Context, TIdIMAP4PeerContext(ASender.Context).FLastCommand.SequenceNumber + ' BAD ' + AText); {Do not Localize}
  466. end;
  467. procedure TIdIMAP4Server.SendBadReply(ASender: TIdCommand; const AFormat: string; const Args: array of const);
  468. begin
  469. SendBadReply(ASender, IndyFormat(AFormat, Args));
  470. end;
  471. procedure TIdIMAP4Server.SendNoReply(ASender: TIdCommand; const AText: string = '');
  472. begin
  473. if AText <> '' then begin
  474. DoSendReply(ASender.Context, TIdIMAP4PeerContext(ASender.Context).FLastCommand.SequenceNumber + ' NO ' + AText); {Do not Localize}
  475. end else begin
  476. DoSendReply(ASender.Context, TIdIMAP4PeerContext(ASender.Context).FLastCommand.SequenceNumber + ' NO'); {Do not Localize}
  477. end;
  478. end;
  479. procedure TIdIMAP4Server.SendNoReply(ASender: TIdCommand; const AFormat: string; const Args: array of const);
  480. begin
  481. SendNoReply(ASender, IndyFormat(AFormat, Args));
  482. end;
  483. {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
  484. constructor TIdIMAP4Server.Create(AOwner: TComponent);
  485. begin
  486. inherited Create(AOwner);
  487. end;
  488. {$ENDIF}
  489. procedure TIdIMAP4Server.InitComponent;
  490. begin
  491. inherited InitComponent;
  492. //Todo: Not sure which number is appropriate. Should be tested
  493. FRegularProtPort := IdPORT_IMAP4;
  494. FImplicitTLSProtPort := IdPORT_IMAP4S; //Id_PORT_imap4_ssl_dp;
  495. FExplicitTLSProtPort := IdPORT_IMAP4;
  496. DefaultPort := IdPORT_IMAP4;
  497. ContextClass := TIdIMAP4PeerContext;
  498. FSaferMode := False;
  499. FUseDefaultMechanismsForUnassignedCommands := True;
  500. {$IFDEF UNIX}
  501. FRootPath := GPathDelim + 'var' + GPathDelim + 'imapmail'; {Do not Localize}
  502. {$ELSE}
  503. FRootPath := GPathDelim + 'imapmail'; {Do not Localize}
  504. {$ENDIF}
  505. FDefaultPassword := 'admin'; {Do not Localize}
  506. FMailBoxSeparator := '.'; {Do not Localize}
  507. end;
  508. destructor TIdIMAP4Server.Destroy;
  509. begin
  510. inherited Destroy;
  511. end;
  512. function TIdIMAP4Server.CreateExceptionReply: TIdReply;
  513. begin
  514. Result := TIdReplyIMAP4.CreateWithReplyTexts(nil, ReplyTexts);
  515. Result.SetReply(IMAP_BAD, 'Unknown Internal Error'); {do not localize}
  516. end;
  517. function TIdIMAP4Server.CreateGreeting: TIdReply;
  518. begin
  519. Result := TIdReplyIMAP4.CreateWithReplyTexts(nil, ReplyTexts);
  520. Result.SetReply(IMAP_OK, 'Welcome'); {do not localize}
  521. end;
  522. function TIdIMAP4Server.CreateHelpReply: TIdReply;
  523. begin
  524. Result := TIdReplyIMAP4.CreateWithReplyTexts(nil, ReplyTexts);
  525. Result.SetReply(IMAP_OK, 'Help follows'); {do not localize}
  526. end;
  527. function TIdIMAP4Server.CreateMaxConnectionReply: TIdReply;
  528. begin
  529. Result := TIdReplyIMAP4.CreateWithReplyTexts(nil, ReplyTexts);
  530. Result.SetReply(IMAP_BAD, 'Too many connections. Try again later.'); {do not localize}
  531. end;
  532. function TIdIMAP4Server.CreateReplyUnknownCommand: TIdReply;
  533. begin
  534. Result := TIdReplyIMAP4.CreateWithReplyTexts(nil, ReplyTexts);
  535. Result.SetReply(IMAP_BAD, 'Unknown command'); {do not localize}
  536. end;
  537. constructor TIdIMAP4PeerContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
  538. begin
  539. inherited Create(AConnection, AYarn, AList);
  540. FMailBox := TIdMailBox.Create;
  541. FLastCommand := TIdReplyIMAP4.Create(nil);
  542. FConnectionState := csAny;
  543. end;
  544. destructor TIdIMAP4PeerContext.Destroy;
  545. begin
  546. FreeAndNil(FLastCommand);
  547. FreeAndNil(FMailBox);
  548. inherited Destroy;
  549. end;
  550. function TIdIMAP4PeerContext.GetUsingTLS: Boolean;
  551. begin
  552. if Connection.IOHandler is TIdSSLIOHandlerSocketBase then begin
  553. Result := not TIdSSLIOHandlerSocketBase(Connection.IOHandler).PassThrough;
  554. end else begin
  555. Result := False;
  556. end;
  557. end;
  558. procedure TIdIMAP4Server.DoReplyUnknownCommand(AContext: TIdContext; AText: string);
  559. //AText is ignored by TIdIMAP4Server
  560. var
  561. LText: string;
  562. begin
  563. LText := TIdIMAP4PeerContext(AContext).FLastCommand.SequenceNumber;
  564. if LText = '' then begin
  565. //This should not happen!
  566. LText := '*'; {Do not Localize}
  567. end;
  568. DoSendReply(AContext, LText + ' NO Unknown command'); {Do not Localize}
  569. end;
  570. function TIdIMAP4Server.ExpungeRecords(ASender: TIdCommand): Boolean;
  571. var
  572. LN: integer;
  573. LMessage: TIdMessage;
  574. LContext: TIdIMAP4PeerContext;
  575. begin
  576. LContext := TIdIMAP4PeerContext(ASender.Context);
  577. //Delete all records that have the deleted flag set...
  578. LN := 0;
  579. Result := True;
  580. while LN < LContext.MailBox.MessageList.Count do begin
  581. LMessage := LContext.MailBox.MessageList.Messages[LN];
  582. if mfDeleted in LMessage.Flags then begin
  583. if not OnDefMechDeleteMessage(LContext.LoginName, LContext.MailBox.Name, LMessage) then
  584. begin
  585. Result := False;
  586. end;
  587. LContext.MailBox.MessageList.Delete(LN);
  588. LContext.MailBox.TotalMsgs := LContext.MailBox.TotalMsgs - 1;
  589. end else begin
  590. Inc(LN);
  591. end;
  592. end;
  593. end;
  594. function TIdIMAP4Server.MessageSetToMessageNumbers(AUseUID: Boolean; ASender: TIdCommand;
  595. AMessageNumbers: TStrings; AMessageSet: string): Boolean;
  596. {AMessageNumbers may be '7' or maybe '2:4' (2, 3 & 4) or maybe '2,4,6' (2, 4 & 6)
  597. or maybe '1:*'}
  598. var
  599. LPos: integer;
  600. LStart: Int64;
  601. LN: Int64;
  602. LEnd: Int64;
  603. LTemp: string;
  604. LContext: TIdIMAP4PeerContext;
  605. begin
  606. LContext := TIdIMAP4PeerContext(ASender.Context);
  607. AMessageNumbers.BeginUpdate;
  608. try
  609. AMessageNumbers.Clear;
  610. //See is it a sequence like 2:4 ...
  611. LPos := IndyPos(':', AMessageSet); {Do not Localize}
  612. if LPos > 0 then begin
  613. LTemp := Copy(AMessageSet, 1, LPos-1);
  614. LStart := IndyStrToInt64(LTemp);
  615. LTemp := Copy(AMessageSet, LPos+1, MAXINT);
  616. if LTemp = '*' then begin {Do not Localize}
  617. if AUseUID then begin
  618. LEnd := IndyStrToInt64(LContext.MailBox.UIDNext)-1;
  619. end else begin
  620. LEnd := LContext.MailBox.MessageList.Count;
  621. end;
  622. end else begin
  623. LEnd := IndyStrToInt64(LTemp);
  624. end;
  625. // RLebeau 2/4/2020: using a 'while' loop instead of a 'for' loop, because the
  626. // LN variable is an Int64 and Delphi prior to XE8 will fail to compile on it
  627. // with a "For loop control variable must have ordinal type" error...
  628. {
  629. for LN := LStart to LEnd do begin
  630. AMessageNumbers.Add(IntToStr(LN));
  631. end;
  632. }
  633. LN := LStart;
  634. while LN <= LEnd do begin
  635. AMessageNumbers.Add(IntToStr(LN));
  636. Inc(LN);
  637. end;
  638. end else begin
  639. //See is it a comma-separated list...
  640. LPos := IndyPos(',', AMessageSet); {Do not Localize}
  641. if LPos = 0 then begin
  642. AMessageNumbers.Add(AMessageSet);
  643. end else begin
  644. BreakApart(AMessageSet, ',', AMessageNumbers); {Do not Localize}
  645. end;
  646. end;
  647. finally
  648. AMessageNumbers.EndUpdate;
  649. end;
  650. Result := True;
  651. end;
  652. //Return -1 if not found
  653. function TIdIMAP4Server.GetRecordForUID(const AUID: String; AMailBox: TIdMailBox): Int64;
  654. var
  655. LN: Integer;
  656. LUID: Int64;
  657. begin
  658. // TODO: do string comparisons instead so that conversions are not needed?
  659. LUID := IndyStrToInt64(AUID);
  660. for LN := 0 to AMailBox.MessageList.Count-1 do begin
  661. if IndyStrToInt64(AMailBox.MessageList.Messages[LN].UID) = LUID then begin
  662. Result := LN;
  663. Exit;
  664. end;
  665. end;
  666. Result := -1;
  667. end;
  668. function TIdIMAP4Server.StripQuotesIfNecessary(AName: string): string;
  669. begin
  670. if Length(AName) > 0 then begin
  671. if (AName[1] = '"') and (AName[Length(Result)] = '"') then begin {Do not Localize}
  672. Result := Copy(AName, 2, Length(AName)-2);
  673. Exit;
  674. end;
  675. end;
  676. Result := AName;
  677. end;
  678. function TIdIMAP4Server.ReassembleParams(ASeparator: Char; AParams: TStrings;
  679. AParamToReassemble: Integer): Boolean;
  680. var
  681. LEndSeparator: char;
  682. LTemp: string;
  683. LN: integer;
  684. LReassembledParam: string;
  685. begin
  686. Result := False;
  687. case ASeparator of
  688. '(': LEndSeparator := ')'; {Do not Localize}
  689. '[': LEndSeparator := ']'; {Do not Localize}
  690. else LEndSeparator := ASeparator;
  691. end;
  692. LTemp := AParams[AParamToReassemble];
  693. if (LTemp = '') or (LTemp[1] <> ASeparator) then begin
  694. Exit;
  695. end;
  696. if LTemp[Length(LTemp)] = LEndSeparator then begin
  697. AParams[AParamToReassemble] := Copy(LTemp, 2, Length(LTemp)-2);
  698. Result := True;
  699. Exit;
  700. end;
  701. LReassembledParam := Copy(LTemp, 2, MAXINT);
  702. LN := AParamToReassemble + 1;
  703. repeat
  704. if LN >= AParams.Count - 1 then begin
  705. Result := False;
  706. Exit; //Error
  707. end;
  708. LTemp := AParams[LN];
  709. AParams.Delete(LN);
  710. if LTemp[Length(LTemp)] = LEndSeparator then begin
  711. AParams[AParamToReassemble] := LReassembledParam + ' ' + Copy(LTemp, 1, Length(LTemp)-1); {Do not Localize}
  712. Result := True;
  713. Exit; //This is example 1
  714. end;
  715. LReassembledParam := LReassembledParam + ' ' + LTemp; {Do not Localize}
  716. until False;
  717. end;
  718. //This reorganizes the parameter list on the basis that AMailBoxParam is a
  719. //mailbox name, which may (if enclosed in quotes) be in more than one param.
  720. //Example 1: '43' '"My' 'Documents"' '5' -> '43' 'My Documents' '5'
  721. //Example 2: '43' '"MyDocs"' '5' -> '43' 'MyDocs' '5'
  722. //Example 3: '43' 'MyDocs' '5' -> '43' 'MyDocs' '5'
  723. function TIdIMAP4Server.ReinterpretParamAsMailBox(AParams: TStrings; AMailBoxParam: Integer): Boolean;
  724. var
  725. LTemp: string;
  726. begin
  727. if (AMailBoxParam < 0) or (AMailBoxParam >= AParams.Count) then begin
  728. Result := False;
  729. Exit;
  730. end;
  731. LTemp := AParams[AMailBoxParam];
  732. if LTemp = '' then begin
  733. Result := False;
  734. Exit;
  735. end;
  736. if LTemp[1] <> '"' then begin {Do not Localize}
  737. Result := True;
  738. Exit; //This is example 3, no change.
  739. end;
  740. Result := ReassembleParams('"', AParams, AMailBoxParam); {Do not Localize}
  741. end;
  742. function TIdIMAP4Server.ReinterpretParamAsFlags(AParams: TStrings; AFlagsParam: Integer): Boolean;
  743. begin
  744. Result := ReassembleParams('(', AParams, AFlagsParam); {Do not Localize}
  745. end;
  746. function TIdIMAP4Server.ReinterpretParamAsQuotedStr(AParams: TStrings; AFlagsParam: integer): Boolean;
  747. begin
  748. Result := ReassembleParams('"', AParams, AFlagsParam); {Do not Localize}
  749. end;
  750. function TIdIMAP4Server.ReinterpretParamAsDataItems(AParams: TStrings; AFlagsParam: Integer): Boolean;
  751. begin
  752. Result := ReassembleParams('(', AParams, AFlagsParam); {Do not Localize}
  753. end;
  754. function TIdIMAP4Server.FlagStringToFlagList(AFlagList: TStrings; AFlagString: string): Boolean;
  755. var
  756. LTemp: string;
  757. begin
  758. AFlagList.BeginUpdate;
  759. try
  760. AFlagList.Clear;
  761. if (AFlagString <> '') and (AFlagString[1] = '(') and (AFlagString[Length(AFlagString)] = ')') then begin {Do not Localize}
  762. LTemp := Copy(AFlagString, 2, Length(AFlagString)-2);
  763. BreakApart(LTemp, ' ', AFlagList); {Do not Localize}
  764. Result := True;
  765. end else begin
  766. Result := False;
  767. end;
  768. finally
  769. AFlagList.EndUpdate;
  770. end;
  771. end;
  772. procedure TIdIMAP4Server.ProcessFetch(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  773. //There are a pile of options for this.
  774. var
  775. LMessageNumbers: TStringList;
  776. LDataItems: TStringList;
  777. LM: integer;
  778. LN: integer;
  779. LLO: integer;
  780. LRecord: Int64;
  781. LSize: Int64;
  782. LMessageToCheck, LMessageTemp: TIdMessage;
  783. LMessageRaw: TStringList;
  784. LTemp: string;
  785. LContext: TIdIMAP4PeerContext;
  786. begin
  787. LContext := TIdIMAP4PeerContext(ASender.Context);
  788. //First param is a message set, e.g. 41 or 2:5 (which is 2, 3, 4 & 5)
  789. LMessageNumbers := TStringList.Create;
  790. try
  791. if not MessageSetToMessageNumbers(AUseUID, ASender, LMessageNumbers, AParams[0]) then begin
  792. SendBadReply(ASender, 'Error in syntax of message set parameter'); {Do not Localize}
  793. Exit;
  794. end;
  795. if not ReinterpretParamAsDataItems(AParams, 1) then begin
  796. SendBadReply(ASender, 'Fetch data items parameter is invalid.'); {Do not Localize}
  797. Exit;
  798. end;
  799. LDataItems := TStringList.Create;
  800. try
  801. BreakApart(AParams[1], ' ', LDataItems);
  802. for LN := 0 to LMessageNumbers.Count-1 do begin
  803. if AUseUID then begin
  804. LRecord := GetRecordForUID(LMessageNumbers[LN], LContext.MailBox);
  805. if LRecord = -1 then begin //It is OK to skip non-existent UID records
  806. Continue;
  807. end;
  808. end else begin
  809. LRecord := IndyStrToInt64(LMessageNumbers[LN])-1;
  810. end;
  811. if (LRecord < 0) or (LRecord > LContext.MailBox.MessageList.Count) then begin
  812. SendBadReply(ASender, 'Message number %d does not exist', [LRecord+1]); {Do not Localize}
  813. Exit;
  814. end;
  815. LMessageToCheck := LContext.MailBox.MessageList.Messages[LRecord];
  816. for LLO := 0 to LDataItems.Count-1 do begin
  817. if TextIsSame(LDataItems[LLO], 'UID') then begin {Do not Localize}
  818. //Format:
  819. //C9 FETCH 490 (UID)
  820. //* 490 FETCH (UID 6545)
  821. //C9 OK Completed
  822. DoSendReply(ASender.Context, '* FETCH (UID %s)', [LMessageToCheck.UID]); {Do not Localize}
  823. end
  824. else if TextIsSame(LDataItems[LLO], 'FLAGS') then begin {Do not Localize}
  825. //Format:
  826. //C10 UID FETCH 6545 (FLAGS)
  827. //* 490 FETCH (FLAGS (\Recent) UID 6545)
  828. //C10 OK Completed
  829. if AUseUID then begin
  830. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s) UID %s)', {Do not Localize}
  831. [LRecord+1, MessageFlagSetToStr(LMessageToCheck.Flags), LMessageNumbers[LN]]);
  832. end else begin
  833. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s))', {Do not Localize}
  834. [LRecord+1, MessageFlagSetToStr(LMessageToCheck.Flags)]);
  835. end;
  836. end
  837. else if TextIsSame(LDataItems[LLO], 'RFC822.HEADER') then begin {Do not Localize}
  838. //Format:
  839. //C11 UID FETCH 6545 (RFC822.HEADER)
  840. //* 490 FETCH (UID 6545 RFC822.HEADER {1654}
  841. //Return-Path: <[email protected]>
  842. //...
  843. //Content-Type: multipart/alternative;
  844. // boundary="----=_NextPart_000_70BE_C8606D03.F4EA24EE"
  845. //C10 OK Completed
  846. //We don't want to thrash UIDs and flags in MailBox message, so load into LMessage
  847. LMessageTemp := TIdMessage.Create;
  848. try
  849. if not OnDefMechGetMessageHeader(LContext.LoginName, LContext.MailBox.Name, LMessageToCheck, LMessageTemp) then begin
  850. SendNoReply(ASender, 'Failed to get message header'); {Do not Localize}
  851. Exit;
  852. end;
  853. //Need to calculate the size of the headers...
  854. LSize := 0;
  855. for LM := 0 to LMessageTemp.Headers.Count-1 do begin
  856. Inc(LSize, Length(LMessageTemp.Headers.Strings[LM]) + 2); //Allow for CR+LF
  857. end;
  858. if AUseUID then begin
  859. DoSendReply(ASender.Context, '* %d FETCH (UID %s RFC822.HEADER {%d}', {Do not Localize}
  860. [LRecord+1, LMessageNumbers[LN], LSize]);
  861. end else begin
  862. DoSendReply(ASender.Context, '* %d FETCH (RFC822.HEADER {%d}', {Do not Localize}
  863. [LRecord+1, LSize]);
  864. end;
  865. for LM := 0 to LMessageTemp.Headers.Count-1 do begin
  866. DoSendReply(ASender.Context, LMessageTemp.Headers.Strings[LM]);
  867. end;
  868. DoSendReply(ASender.Context, ')'); {Do not Localize}
  869. //Finished with the headers, free the memory...
  870. finally
  871. FreeAndNil(LMessageTemp);
  872. end;
  873. end
  874. else if TextIsSame(LDataItems[LLO], 'RFC822.SIZE') then begin {Do not Localize}
  875. //Format:
  876. //C12 UID FETCH 6545 (RFC822.SIZE)
  877. //* 490 FETCH (UID 6545 RFC822.SIZE 3447)
  878. //C12 OK Completed
  879. LSize := OnDefMechGetMessageSize(LContext.LoginName, LContext.MailBox.Name, LMessageToCheck);
  880. if LSize = -1 then begin
  881. SendNoReply(ASender, 'Failed to get message size'); {Do not Localize}
  882. Exit;
  883. end;
  884. if AUseUID then begin
  885. DoSendReply(ASender.Context, '* %d FETCH (UID %s RFC822.SIZE %d)', {Do not Localize}
  886. [LRecord+1, LMessageNumbers[LN], LSize]);
  887. end else begin
  888. DoSendReply(ASender.Context, '* %d FETCH (RFC822.SIZE %d)', {Do not Localize}
  889. [LRecord+1, LSize]);
  890. end;
  891. end
  892. else if PosInStrArray(LDataItems[LLO], ['BODY.PEEK[]', 'BODY[]', 'RFC822', 'RFC822.PEEK'], False) <> -1 then {Do not Localize}
  893. begin
  894. //All are the same, except the return string is different...
  895. LMessageRaw := TStringList.Create;
  896. try
  897. if not OnDefMechGetMessageRaw(LContext.LoginName, LContext.MailBox.Name, LMessageToCheck, LMessageRaw) then
  898. begin
  899. SendNoReply(ASender, 'Failed to get raw message'); {Do not Localize}
  900. Exit;
  901. end;
  902. LSize := 0;
  903. for LM := 0 to LMessageToCheck.Headers.Count-1 do begin
  904. Inc(LSize, Length(LMessageRaw.Strings[LM]) + 2); //Allow for CR+LF
  905. end;
  906. Inc(LSize, 3); //The message terminator '.CRLF'
  907. LTemp := Copy(AParams[1], 2, Length(AParams[1])-2);
  908. if AUseUID then begin
  909. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s) UID %s %s {%d}', {Do not Localize}
  910. [LRecord+1, MessageFlagSetToStr(LMessageToCheck.Flags), LMessageNumbers[LN], LTemp, LSize]);
  911. end else begin
  912. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s) %s {%d}', {Do not Localize}
  913. [LRecord+1, MessageFlagSetToStr(LMessageToCheck.Flags), LTemp, LSize]);
  914. end;
  915. for LM := 0 to LMessageToCheck.Headers.Count-1 do begin
  916. DoSendReply(ASender.Context, LMessageRaw.Strings[LM]);
  917. end;
  918. DoSendReply(ASender.Context, '.'); {Do not Localize}
  919. DoSendReply(ASender.Context, ')'); {Do not Localize}
  920. //Free the memory...
  921. finally
  922. FreeAndNil(LMessageRaw);
  923. end;
  924. end
  925. else if TextIsSame(LDataItems[LLO], 'BODYSTRUCTURE') then begin {Do not Localize}
  926. //Format:
  927. //C49 UID FETCH 6545 (BODYSTRUCTURE)
  928. //* 490 FETCH (UID 6545 BODYSTRUCTURE (("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 290 8 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 1125 41 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY"
  929. //C12 OK Completed
  930. SendBadReply(ASender, 'Parameter not supported: ' + AParams[1]); {Do not Localize}
  931. end
  932. else if TextStartsWith(LDataItems[LLO], 'BODY[') or TextStartsWith(LDataItems[LLO], 'BODY.PEEK[') then begin {Do not Localize}
  933. //Format:
  934. //C50 UID FETCH 6545 (BODY[1])
  935. //* 490 FETCH (FLAGS (\Recent \Seen) UID 6545 BODY[1] {290}
  936. //...
  937. //)
  938. //C50 OK Completed
  939. SendBadReply(ASender, 'Parameter not supported: ' + AParams[1]); {Do not Localize}
  940. end
  941. else begin
  942. SendBadReply(ASender, 'Parameter not supported: ' + AParams[1]); {Do not Localize}
  943. Exit;
  944. end;
  945. end;
  946. end;
  947. finally
  948. FreeAndNil(LDataItems);
  949. end;
  950. finally
  951. FreeAndNil(LMessageNumbers);
  952. end;
  953. SendOkReply(ASender, 'Completed'); {Do not Localize}
  954. end;
  955. procedure TIdIMAP4Server.ProcessSearch(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  956. //if AUseUID is True, return UIDs rather than relative message numbers.
  957. var
  958. LSearchString: string;
  959. LN: Integer;
  960. LM: Integer;
  961. LItem: Integer;
  962. LMessageToCheck, LMessageTemp: TIdMessage;
  963. LHits: string;
  964. LContext: TIdIMAP4PeerContext;
  965. begin
  966. LContext := TIdIMAP4PeerContext(ASender.Context);
  967. //Watch out: you could become an old man trying to implement all the IMAP
  968. //search options, just do a subset.
  969. //Format:
  970. //C1065 UID SEARCH FROM "visible"
  971. //* SEARCH 5769 5878
  972. //C1065 OK Completed (2 msgs in 0.010 secs)
  973. if AParams.Count < 2 then begin //The only search options we support are 2-param ones
  974. SendIncorrectNumberOfParameters(ASender);
  975. //LParams.Free;
  976. Exit;
  977. end;
  978. LItem := PosInStrArray(AParams[0], ['FROM', 'TO', 'CC', 'BCC', 'SUBJECT'], False);
  979. if LItem = -1 then begin {Do not Localize}
  980. SendBadReply(ASender, 'Unsupported search method'); {Do not Localize}
  981. Exit;
  982. end;
  983. //Reassemble the other params into a line, because "Ciaran Costelloe" will be params 1 & 2...
  984. LSearchString := AParams[1];
  985. for LN := 2 to AParams.Count-1 do begin
  986. LSearchString := LSearchString + ' ' + AParams[LN]; {Do not Localize}
  987. end;
  988. if (LSearchString[1] = '"') and (LSearchString[Length(LSearchString)] = '"') then begin {Do not Localize}
  989. LSearchString := Copy(LSearchString, 2, Length(LSearchString)-2);
  990. end;
  991. LHits := '';
  992. LMessageTemp := TIdMessage.Create;
  993. try
  994. for LN := 0 to LContext.MailBox.MessageList.Count-1 do begin
  995. LMessageToCheck := LContext.MailBox.MessageList.Messages[LN];
  996. if not OnDefMechGetMessageHeader(LContext.LoginName, LContext.MailBox.Name, LMessageToCheck, LMessageTemp) then
  997. begin
  998. SendNoReply(ASender, 'Failed to get message header'); {Do not Localize}
  999. Exit;
  1000. end;
  1001. case LItem of
  1002. 0: // FROM {Do not Localize}
  1003. begin
  1004. if Pos(UpperCase(LSearchString), UpperCase(LMessageTemp.From.Address)) > 0 then begin
  1005. if AUseUID then begin
  1006. LHits := LHits + LMessageToCheck.UID + ' '; {Do not Localize}
  1007. end else begin
  1008. LHits := LHits + IntToStr(LN+1) + ' '; {Do not Localize}
  1009. end;
  1010. end;
  1011. end;
  1012. 1: // TO {Do not Localize}
  1013. begin
  1014. for LM := 0 to LMessageTemp.Recipients.Count-1 do begin
  1015. if Pos(UpperCase(LSearchString), UpperCase(LMessageTemp.Recipients.Items[LM].Address)) > 0 then begin
  1016. if AUseUID then begin
  1017. LHits := LHits + LMessageToCheck.UID + ' '; {Do not Localize}
  1018. end else begin
  1019. LHits := LHits + IntToStr(LN+1) + ' '; {Do not Localize}
  1020. end;
  1021. Break; //Don't want more than 1 hit on this record
  1022. end;
  1023. end;
  1024. end;
  1025. 2: // CC {Do not Localize}
  1026. begin
  1027. for LM := 0 to LMessageTemp.Recipients.Count-1 do begin
  1028. if Pos(UpperCase(LSearchString), UpperCase(LMessageTemp.CCList.Items[LM].Address)) > 0 then begin
  1029. if AUseUID then begin
  1030. LHits := LHits + LMessageToCheck.UID + ' '; {Do not Localize}
  1031. end else begin
  1032. LHits := LHits + IntToStr(LN+1) + ' '; {Do not Localize}
  1033. end;
  1034. Break; //Don't want more than 1 hit on this record
  1035. end;
  1036. end;
  1037. end;
  1038. 3: // BCC {Do not Localize}
  1039. begin
  1040. for LM := 0 to LMessageTemp.Recipients.Count-1 do begin
  1041. if Pos(UpperCase(LSearchString), UpperCase(LMessageTemp.BCCList.Items[LM].Address)) > 0 then begin
  1042. if AUseUID then begin
  1043. LHits := LHits + LMessageToCheck.UID + ' '; {Do not Localize}
  1044. end else begin
  1045. LHits := LHits + IntToStr(LN+1) + ' '; {Do not Localize}
  1046. end;
  1047. Break; //Don't want more than 1 hit on this record
  1048. end;
  1049. end;
  1050. end;
  1051. else // SUBJECT {Do not Localize}
  1052. begin
  1053. if Pos(UpperCase(LSearchString), UpperCase(LMessageTemp.Subject)) > 0 then begin
  1054. if AUseUID then begin
  1055. LHits := LHits + LMessageToCheck.UID + ' '; {Do not Localize}
  1056. end else begin
  1057. LHits := LHits + IntToStr(LN+1) + ' '; {Do not Localize}
  1058. end;
  1059. end;
  1060. end;
  1061. end;
  1062. end;
  1063. finally
  1064. FreeAndNil(LMessageTemp);
  1065. end;
  1066. DoSendReply(ASender.Context, '* SEARCH ' + TrimRight(LHits)); {Do not Localize}
  1067. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1068. end;
  1069. procedure TIdIMAP4Server.ProcessCopy(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings);
  1070. var
  1071. LMessageNumbers: TStringList;
  1072. LN: Integer;
  1073. LRecord: Int64;
  1074. LResult: Boolean;
  1075. LContext: TIdIMAP4PeerContext;
  1076. begin
  1077. LContext := TIdIMAP4PeerContext(ASender.Context);
  1078. //Format is "C1 COPY 2:4 MEETINGFOLDER"
  1079. if AParams.Count < 2 then begin
  1080. SendIncorrectNumberOfParameters(ASender);
  1081. Exit;
  1082. end;
  1083. if not OnDefMechReinterpretParamAsMailBox(AParams, 1) then begin
  1084. SendBadReply(ASender, 'Mailbox parameter is invalid.'); {Do not Localize}
  1085. Exit;
  1086. end;
  1087. //First param is a message set, e.g. 41 or 2:5 (which is 2, 3, 4 & 5)
  1088. LMessageNumbers := TStringList.Create;
  1089. try
  1090. if not MessageSetToMessageNumbers(AUseUID, ASender, LMessageNumbers, AParams[0]) then begin
  1091. SendBadReply(ASender, 'Error in syntax of message set parameter'); {Do not Localize}
  1092. Exit;
  1093. end;
  1094. if not Assigned(OnDefMechDoesImapMailBoxExist) then begin
  1095. SendUnassignedDefaultMechanism(ASender);
  1096. Exit;
  1097. end;
  1098. if not OnDefMechDoesImapMailBoxExist(LContext.LoginName, AParams[1]) then begin
  1099. SendNoReply(ASender, 'Mailbox does not exist.'); {Do not Localize}
  1100. Exit;
  1101. end;
  1102. LResult := True;
  1103. for LN := 0 to LMessageNumbers.Count-1 do begin
  1104. if AUseUID then begin
  1105. LRecord := GetRecordForUID(LMessageNumbers[LN], LContext.MailBox);
  1106. if LRecord = -1 then begin //It is OK to skip non-existent UID records
  1107. Continue;
  1108. end;
  1109. end else begin
  1110. LRecord := IndyStrToInt64(LMessageNumbers[LN])-1;
  1111. end;
  1112. if (LRecord < 0) or (LRecord >= LContext.MailBox.MessageList.Count) then begin
  1113. LResult := False;
  1114. end
  1115. else if not OnDefMechCopyMessage(LContext.LoginName, LContext.MailBox.Name,
  1116. LContext.MailBox.MessageList.Messages[LRecord].UID, AParams[1]) then
  1117. begin
  1118. LResult := False;
  1119. end;
  1120. end;
  1121. if LResult then begin
  1122. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1123. end else begin
  1124. SendNoReply(ASender, 'Copy failed for one or more messages'); {Do not Localize}
  1125. end;
  1126. finally
  1127. FreeAndNil(LMessageNumbers);
  1128. end;
  1129. end;
  1130. function TIdIMAP4Server.ProcessStore(AUseUID: Boolean; ASender: TIdCommand; AParams: TStrings): Boolean;
  1131. const
  1132. LCMsgFlags: array[0..4] of TIdMessageFlags = ( mfAnswered, mfFlagged, mfDeleted, mfDraft, mfSeen );
  1133. var
  1134. LMessageNumbers: TStringList;
  1135. LFlagList: TStringList;
  1136. LN: integer;
  1137. LM: integer;
  1138. LRecord: Int64;
  1139. LFlag: integer;
  1140. LTemp: string;
  1141. LStoreMethod: TIdIMAP4StoreDataItem;
  1142. LSilent: Boolean;
  1143. LMessage: TIdMessage;
  1144. LContext: TIdIMAP4PeerContext;
  1145. begin
  1146. LContext := TIdIMAP4PeerContext(ASender.Context);
  1147. //Format is:
  1148. //C53 UID STORE 6545,6544 +FLAGS.SILENT (\Deleted)
  1149. //C53 OK Completed
  1150. Result := False;
  1151. if AParams.Count < 3 then begin
  1152. SendIncorrectNumberOfParameters(ASender);
  1153. Exit;
  1154. end;
  1155. //First param is a message set, e.g. 41 or 2:5 (which is 2, 3, 4 & 5)
  1156. LMessageNumbers := TStringList.Create;
  1157. try
  1158. if not MessageSetToMessageNumbers(AUseUID, ASender, LMessageNumbers, AParams[0]) then begin
  1159. SendBadReply(ASender, 'Error in syntax of message set parameter'); {Do not Localize}
  1160. Exit;
  1161. end;
  1162. LTemp := AParams[1];
  1163. if LTemp[1] = '+' then begin {Do not Localize}
  1164. LStoreMethod := sdAdd;
  1165. LTemp := Copy(LTemp, 2, MaxInt);
  1166. end else if LTemp[1] = '-' then begin {Do not Localize}
  1167. LStoreMethod := sdRemove;
  1168. LTemp := Copy(LTemp, 2, MaxInt);
  1169. end else begin
  1170. LStoreMethod := sdReplace;
  1171. end;
  1172. if TextIsSame(LTemp, 'FLAGS') then begin {Do not Localize}
  1173. LSilent := False;
  1174. end else if TextIsSame(LTemp, 'FLAGS.SILENT') then begin {Do not Localize}
  1175. LSilent := True;
  1176. end else begin
  1177. SendBadReply(ASender, 'Error in syntax of FLAGS parameter'); {Do not Localize}
  1178. Exit;
  1179. end;
  1180. LFlagList := TStringList.Create;
  1181. try
  1182. //Assemble remaining flags back into a string...
  1183. LTemp := AParams[2];
  1184. for LN := 3 to AParams.Count-1 do begin
  1185. LTemp := LTemp + ' ' + AParams[LN]; {Do not Localize}
  1186. end;
  1187. if not FlagStringToFlagList(LFlagList, LTemp) then begin
  1188. SendBadReply(ASender, 'Error in syntax of flag set parameter'); {Do not Localize}
  1189. Exit;
  1190. end;
  1191. for LN := 0 to LMessageNumbers.Count-1 do begin
  1192. if AUseUID then begin
  1193. LRecord := GetRecordForUID(LMessageNumbers[LN], LContext.MailBox);
  1194. if LRecord = -1 then begin //It is OK to skip non-existent UID records
  1195. Continue;
  1196. end;
  1197. end else begin
  1198. LRecord := IndyStrToInt64(LMessageNumbers[LN])-1;
  1199. end;
  1200. if (LRecord < 0) or (LRecord > LContext.MailBox.MessageList.Count) then begin
  1201. SendBadReply(ASender, 'Message number %d does not exist', [LRecord+1]); {Do not Localize}
  1202. Exit;
  1203. end;
  1204. LMessage := LContext.MailBox.MessageList.Messages[LRecord];
  1205. if LStoreMethod = sdReplace then begin
  1206. LMessage.Flags := [];
  1207. end;
  1208. for LM := 0 to LFlagList.Count-1 do begin
  1209. //Support \Answered \Flagged \Deleted \Draft \Seen
  1210. LFlag := PosInStrArray(LFlagList[LM], ['\Answered', '\Flagged', '\Deleted', '\Draft', '\Seen'], False); {Do not Localize}
  1211. if LFlag = -1 then begin
  1212. Continue;
  1213. end;
  1214. case LStoreMethod of
  1215. sdAdd, sdReplace:
  1216. begin
  1217. LMessage.Flags := LMessage.Flags + [LCMsgFlags[LFlag]];
  1218. end;
  1219. sdRemove:
  1220. begin
  1221. LMessage.Flags := LMessage.Flags - [LCMsgFlags[LFlag]];
  1222. end;
  1223. end;
  1224. end;
  1225. if not LSilent then begin
  1226. //In this case, send to the client the current flags.
  1227. //The response is '* 43 FETCH (FLAGS (\Seen))' with the UID version
  1228. //being '* 43 FETCH (FLAGS (\Seen) UID 1234)'. Note the first number is the
  1229. //relative message number in BOTH cases.
  1230. if AUseUID then begin
  1231. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s) UID %s)', {Do not Localize}
  1232. [LRecord+1, MessageFlagSetToStr(LMessage.Flags), LMessageNumbers[LN]]);
  1233. end else begin
  1234. DoSendReply(ASender.Context, '* %d FETCH (FLAGS (%s))', {Do not Localize}
  1235. [LRecord+1, MessageFlagSetToStr(LMessage.Flags)]);
  1236. end;
  1237. end;
  1238. end;
  1239. SendOkReply(ASender, 'STORE Completed'); {Do not Localize}
  1240. finally
  1241. FreeAndNil(LFlagList);
  1242. end;
  1243. finally
  1244. FreeAndNil(LMessageNumbers);
  1245. end;
  1246. Result := True;
  1247. end;
  1248. procedure TIdIMAP4Server.InitializeCommandHandlers;
  1249. var
  1250. LCommandHandler: TIdCommandHandler;
  1251. begin
  1252. LCommandHandler := CommandHandlers.Add;
  1253. LCommandHandler.Command := 'CAPABILITY'; {do not localize}
  1254. LCommandHandler.OnCommand := DoCommandCAPABILITY;
  1255. LCommandHandler.NormalReply.Code := IMAP_OK;
  1256. LCommandHandler := CommandHandlers.Add;
  1257. LCommandHandler.Command := 'NOOP'; {do not localize}
  1258. LCommandHandler.OnCommand := DoCommandNOOP;
  1259. LCommandHandler.NormalReply.Code := IMAP_OK;
  1260. LCommandHandler := CommandHandlers.Add;
  1261. LCommandHandler.Command := 'LOGOUT'; {do not localize}
  1262. LCommandHandler.OnCommand := DoCommandLOGOUT;
  1263. LCommandHandler.NormalReply.Code := IMAP_OK;
  1264. LCommandHandler := CommandHandlers.Add;
  1265. LCommandHandler.Command := 'AUTHENTICATE'; {do not localize}
  1266. LCommandHandler.OnCommand := DoCommandAUTHENTICATE;
  1267. LCommandHandler.NormalReply.Code := IMAP_OK;
  1268. LCommandHandler := CommandHandlers.Add;
  1269. LCommandHandler.Command := 'LOGIN'; {do not localize}
  1270. LCommandHandler.OnCommand := DoCommandLOGIN;
  1271. LCommandHandler.NormalReply.Code := IMAP_OK;
  1272. LCommandHandler := CommandHandlers.Add;
  1273. LCommandHandler.Command := 'SELECT'; {do not localize}
  1274. LCommandHandler.OnCommand := DoCommandSELECT;
  1275. LCommandHandler.NormalReply.Code := IMAP_OK;
  1276. LCommandHandler := CommandHandlers.Add;
  1277. LCommandHandler.Command := 'EXAMINE'; {do not localize}
  1278. LCommandHandler.OnCommand := DoCommandEXAMINE;
  1279. LCommandHandler.NormalReply.Code := IMAP_OK;
  1280. LCommandHandler := CommandHandlers.Add;
  1281. LCommandHandler.Command := 'CREATE'; {do not localize}
  1282. LCommandHandler.OnCommand := DoCommandCREATE;
  1283. LCommandHandler.NormalReply.Code := IMAP_OK;
  1284. LCommandHandler := CommandHandlers.Add;
  1285. LCommandHandler.Command := 'DELETE'; {do not localize}
  1286. LCommandHandler.OnCommand := DoCommandDELETE;
  1287. LCommandHandler.NormalReply.Code := IMAP_OK;
  1288. LCommandHandler := CommandHandlers.Add;
  1289. LCommandHandler.Command := 'RENAME'; {do not localize}
  1290. LCommandHandler.OnCommand := DoCommandRENAME;
  1291. LCommandHandler.NormalReply.Code := IMAP_OK;
  1292. LCommandHandler := CommandHandlers.Add;
  1293. LCommandHandler.Command := 'SUBSCRIBE'; {do not localize}
  1294. LCommandHandler.OnCommand := DoCommandSUBSCRIBE;
  1295. LCommandHandler.NormalReply.Code := IMAP_OK;
  1296. LCommandHandler := CommandHandlers.Add;
  1297. LCommandHandler.Command := 'UNSUBSCRIBE'; {do not localize}
  1298. LCommandHandler.OnCommand := DoCommandUNSUBSCRIBE;
  1299. LCommandHandler.NormalReply.Code := IMAP_OK;
  1300. LCommandHandler := CommandHandlers.Add;
  1301. LCommandHandler.Command := 'LIST'; {do not localize}
  1302. LCommandHandler.OnCommand := DoCommandLIST;
  1303. LCommandHandler.NormalReply.Code := IMAP_OK;
  1304. LCommandHandler := CommandHandlers.Add;
  1305. LCommandHandler.Command := 'LSUB'; {do not localize}
  1306. LCommandHandler.OnCommand := DoCommandLSUB;
  1307. LCommandHandler.NormalReply.Code := IMAP_OK;
  1308. LCommandHandler := CommandHandlers.Add;
  1309. LCommandHandler.Command := 'STATUS'; {do not localize}
  1310. LCommandHandler.OnCommand := DoCommandSTATUS;
  1311. LCommandHandler.NormalReply.Code := IMAP_OK;
  1312. LCommandHandler := CommandHandlers.Add;
  1313. LCommandHandler.Command := 'APPEND'; {do not localize}
  1314. LCommandHandler.OnCommand := DoCommandAPPEND;
  1315. LCommandHandler.NormalReply.Code := IMAP_OK;
  1316. LCommandHandler := CommandHandlers.Add;
  1317. LCommandHandler.Command := 'CHECK'; {do not localize}
  1318. LCommandHandler.OnCommand := DoCommandCHECK;
  1319. LCommandHandler.NormalReply.Code := IMAP_OK;
  1320. LCommandHandler := CommandHandlers.Add;
  1321. LCommandHandler.Command := 'CLOSE'; {do not localize}
  1322. LCommandHandler.OnCommand := DoCommandCLOSE;
  1323. LCommandHandler.NormalReply.Code := IMAP_OK;
  1324. LCommandHandler := CommandHandlers.Add;
  1325. LCommandHandler.Command := 'EXPUNGE'; {do not localize}
  1326. LCommandHandler.OnCommand := DoCommandEXPUNGE;
  1327. LCommandHandler.NormalReply.Code := IMAP_OK;
  1328. LCommandHandler := CommandHandlers.Add;
  1329. LCommandHandler.Command := 'SEARCH'; {do not localize}
  1330. LCommandHandler.OnCommand := DoCommandSEARCH;
  1331. LCommandHandler.NormalReply.Code := IMAP_OK;
  1332. LCommandHandler := CommandHandlers.Add;
  1333. LCommandHandler.Command := 'FETCH'; {do not localize}
  1334. LCommandHandler.OnCommand := DoCommandFETCH;
  1335. LCommandHandler.NormalReply.Code := IMAP_OK;
  1336. LCommandHandler := CommandHandlers.Add;
  1337. LCommandHandler.Command := 'STORE'; {do not localize}
  1338. LCommandHandler.OnCommand := DoCommandSTORE;
  1339. LCommandHandler.NormalReply.Code := IMAP_OK;
  1340. LCommandHandler := CommandHandlers.Add;
  1341. LCommandHandler.Command := 'COPY'; {do not localize}
  1342. LCommandHandler.OnCommand := DoCommandCOPY;
  1343. LCommandHandler.NormalReply.Code := IMAP_OK;
  1344. LCommandHandler := CommandHandlers.Add;
  1345. LCommandHandler.Command := 'UID'; {do not localize}
  1346. LCommandHandler.OnCommand := DoCommandUID;
  1347. LCommandHandler.NormalReply.Code := IMAP_OK;
  1348. LCommandHandler := CommandHandlers.Add;
  1349. LCommandHandler.Command := 'X'; {do not localize}
  1350. LCommandHandler.OnCommand := DoCommandX;
  1351. LCommandHandler.NormalReply.Code := IMAP_OK;
  1352. LCommandHandler := CommandHandlers.Add;
  1353. LCommandHandler.Command := 'STARTTLS'; {do not localize}
  1354. LCommandHandler.OnCommand := DoCommandSTARTTLS;
  1355. LCommandHandler.NormalReply.Code := IMAP_OK;
  1356. FCommandHandlers.OnBeforeCommandHandler := DoBeforeCmd;
  1357. FCommandHandlers.OnCommandHandlersException := DoCmdHandlersException;
  1358. end;
  1359. //Command handlers
  1360. procedure TIdIMAP4Server.DoBeforeCmd(ASender: TIdCommandHandlers; var AData: string;
  1361. AContext: TIdContext);
  1362. begin
  1363. TIdIMAP4PeerContext(AContext).FLastCommand.ParseRequest(AData); //Main purpose is to get sequence number, like C11 from 'C11 CAPABILITY'
  1364. TIdIMAP4PeerContext(AContext).FIMAP4Tag := Fetch(AData, ' ');
  1365. AData := Trim(AData);
  1366. if Assigned(FOnBeforeCmd) then begin
  1367. FOnBeforeCmd(ASender, AData, AContext);
  1368. end;
  1369. end;
  1370. procedure TIdIMAP4Server.DoSendReply(AContext: TIdContext; const AData: string);
  1371. begin
  1372. if Assigned(FOnBeforeSend) then begin
  1373. FOnBeforeSend(AContext, AData);
  1374. end;
  1375. AContext.Connection.IOHandler.WriteLn(AData);
  1376. end;
  1377. procedure TIdIMAP4Server.DoSendReply(AContext: TIdContext; const AFormat: string; const Args: array of const);
  1378. begin
  1379. DoSendReply(AContext, IndyFormat(AFormat, Args));
  1380. end;
  1381. procedure TIdIMAP4Server.DoCmdHandlersException(ACommand: String; AContext: TIdContext);
  1382. var
  1383. LTag, LCmd: String;
  1384. begin
  1385. if Assigned(FOnCommandError) then begin
  1386. LTag := Fetch(ACommand, ' ');
  1387. LCmd := Fetch(ACommand, ' ');
  1388. OnCommandError(AContext, LTag, LCmd);
  1389. end;
  1390. end;
  1391. procedure TIdIMAP4Server.DoCommandCAPABILITY(ASender: TIdCommand);
  1392. begin
  1393. if Assigned(FOnCommandCAPABILITY) then begin
  1394. OnCommandCAPABILITY(ASender.Context, TIdIMAP4PeerContext(ASender.Context).IMAP4Tag, ASender.UnparsedParams);
  1395. Exit;
  1396. end;
  1397. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1398. Exit;
  1399. end;
  1400. {Tell the client our capabilities...}
  1401. DoSendReply(ASender.Context, '* CAPABILITY IMAP4rev1 AUTH=PLAIN'); {Do not Localize}
  1402. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1403. end;
  1404. procedure TIdIMAP4Server.DoCommandNOOP(ASender: TIdCommand);
  1405. begin
  1406. if Assigned(FOnCommandNOOP) then begin
  1407. OnCommandNOOP(ASender.Context, TIdIMAP4PeerContext(ASender.Context).IMAP4Tag, ASender.UnparsedParams);
  1408. Exit;
  1409. end;
  1410. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1411. Exit;
  1412. end;
  1413. {On most servers, this does nothing (they use a timeout to disconnect users,
  1414. irrespective of NOOP commands, so they always return OK. If you really
  1415. want to implement it, use a countdown timer to force disconnects but reset
  1416. the counter if ANY command received, including NOOP.}
  1417. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1418. end;
  1419. procedure TIdIMAP4Server.DoCommandLOGOUT(ASender: TIdCommand);
  1420. var
  1421. LContext: TIdIMAP4PeerContext;
  1422. begin
  1423. LContext := TIdIMAP4PeerContext(ASender.Context);
  1424. if Assigned(FOnCommandLOGOUT) then begin
  1425. OnCommandLOGOUT(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1426. Exit;
  1427. end;
  1428. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1429. Exit;
  1430. end;
  1431. {Be nice and say ByeBye first...}
  1432. DoSendReply(ASender.Context, '* BYE May your God go with you.'); {Do not Localize}
  1433. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1434. LContext.Connection.Disconnect(False);
  1435. LContext.MailBox.Clear;
  1436. LContext.RemoveFromList;
  1437. end;
  1438. procedure TIdIMAP4Server.DoCommandAUTHENTICATE(ASender: TIdCommand);
  1439. begin
  1440. if Assigned(FOnCommandAUTHENTICATE) then begin
  1441. {
  1442. Important, when usng TLS and FUseTLS=utUseRequireTLS, do not accept any authentication
  1443. information until TLS negotiation is completed. This insistance is a security feature.
  1444. Some networks should choose security over interoperability while other places may
  1445. sacrafice interoperability over security. It comes down to sensible administrative
  1446. judgement.
  1447. }
  1448. if (FUseTLS = utUseRequireTLS) and (not TIdIMAP4PeerContext(ASender.Context).UsingTLS) then begin
  1449. MustUseTLS(ASender);
  1450. end else begin
  1451. OnCommandAUTHENTICATE(ASender.Context, TIdIMAP4PeerContext(ASender.Context).IMAP4Tag, ASender.UnparsedParams);
  1452. end;
  1453. end;
  1454. end;
  1455. procedure TIdIMAP4Server.MustUseTLS(ASender: TIdCommand);
  1456. begin
  1457. DoSendReply(ASender.Context, 'NO ' + RSSMTPSvrReqSTARTTLS); {Do not Localize}
  1458. ASender.Disconnect := True;
  1459. end;
  1460. procedure TIdIMAP4Server.DoCommandLOGIN(ASender: TIdCommand);
  1461. var
  1462. LParams: TStringList;
  1463. LContext: TIdIMAP4PeerContext;
  1464. begin
  1465. LContext := TIdIMAP4PeerContext(ASender.Context);
  1466. if Assigned(fOnCommandLOGIN) then begin
  1467. {
  1468. Important, when using TLS and FUseTLS=utUseRequireTLS, do not accept any authentication
  1469. information until TLS negotiation is completed. This insistance is a security feature.
  1470. Some networks should choose security over interoperability while other places may
  1471. sacrafice interoperability over security. It comes down to sensible administrative
  1472. judgement.
  1473. }
  1474. if (FUseTLS = utUseRequireTLS) and (not TIdIMAP4PeerContext(ASender.Context).UsingTLS) then begin
  1475. MustUseTLS(ASender);
  1476. end else begin
  1477. OnCommandLOGIN(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1478. end;
  1479. Exit;
  1480. end;
  1481. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1482. Exit;
  1483. end;
  1484. if not Assigned(OnDefMechDoesImapMailBoxExist) then begin
  1485. SendUnassignedDefaultMechanism(ASender);
  1486. Exit;
  1487. end;
  1488. LParams := TStringList.Create;
  1489. try
  1490. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1491. if LParams.Count < 2 then begin
  1492. //Incorrect number of params...
  1493. if FSaferMode then begin
  1494. SendNoReply(ASender);
  1495. end else begin
  1496. SendIncorrectNumberOfParameters(ASender);
  1497. end;
  1498. Exit;
  1499. end;
  1500. //See if we have a directory under FRootPath of that user's name...
  1501. //if DoesImapMailBoxExist(LParams[0], '') = False then begin
  1502. if not OnDefMechDoesImapMailBoxExist(LParams[0], '') then begin
  1503. if FSaferMode then begin
  1504. SendNoReply(ASender);
  1505. end else begin
  1506. SendNoReply(ASender, 'Unknown username'); {Do not Localize}
  1507. end;
  1508. Exit;
  1509. end;
  1510. //See is it the correct password...
  1511. if not TextIsSame(FDefaultPassword, LParams[1]) then begin
  1512. if FSaferMode then begin
  1513. SendNoReply(ASender);
  1514. end else begin
  1515. SendNoReply(ASender, 'Incorrect password'); {Do not Localize}
  1516. end;
  1517. Exit;
  1518. end;
  1519. //Successful login, change context's state to logged in...
  1520. LContext.LoginName := LParams[0];
  1521. LContext.FConnectionState := csAuthenticated;
  1522. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1523. finally
  1524. FreeAndNil(LParams);
  1525. end;
  1526. end;
  1527. //SELECT and EXAMINE are the same except EXAMINE opens the mailbox read-only
  1528. procedure TIdIMAP4Server.DoCommandSELECT(ASender: TIdCommand);
  1529. var
  1530. LContext: TIdIMAP4PeerContext;
  1531. begin
  1532. LContext := TIdIMAP4PeerContext(ASender.Context);
  1533. if LContext.ConnectionState = csSelected then begin
  1534. LContext.MailBox.Clear;
  1535. LContext.FConnectionState := csAuthenticated;
  1536. end;
  1537. if LContext.ConnectionState <> csAuthenticated then begin
  1538. SendWrongConnectionState(ASender);
  1539. Exit;
  1540. end;
  1541. if Assigned(FOnCommandSELECT) then begin
  1542. OnCommandSELECT(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1543. Exit;
  1544. end;
  1545. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1546. Exit;
  1547. end;
  1548. if not Assigned(OnDefMechOpenMailBox) then begin
  1549. SendUnassignedDefaultMechanism(ASender);
  1550. Exit;
  1551. end;
  1552. if OnDefMechOpenMailBox(ASender, False) then begin //SELECT opens the mailbox read-write
  1553. LContext.FConnectionState := csSelected;
  1554. SendOkReply(ASender, '[READ-WRITE] Completed'); {Do not Localize}
  1555. end;
  1556. end;
  1557. //SELECT and EXAMINE are the same except EXAMINE opens the mailbox read-only
  1558. procedure TIdIMAP4Server.DoCommandEXAMINE(ASender: TIdCommand);
  1559. var
  1560. LContext: TIdIMAP4PeerContext;
  1561. begin
  1562. LContext := TIdIMAP4PeerContext(ASender.Context);
  1563. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1564. SendWrongConnectionState(ASender);
  1565. Exit;
  1566. end;
  1567. if Assigned(FOnCommandEXAMINE) then begin
  1568. OnCommandEXAMINE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1569. Exit;
  1570. end;
  1571. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1572. Exit;
  1573. end;
  1574. if not Assigned(OnDefMechOpenMailBox) then begin
  1575. SendUnassignedDefaultMechanism(ASender);
  1576. Exit;
  1577. end;
  1578. if OnDefMechOpenMailBox(ASender, True) then begin //EXAMINE opens the mailbox read-only
  1579. LContext.FConnectionState := csSelected;
  1580. SendOkReply(ASender, '[READ-ONLY] Completed'); {Do not Localize}
  1581. end;
  1582. end;
  1583. procedure TIdIMAP4Server.DoCommandCREATE(ASender: TIdCommand);
  1584. var
  1585. LParams: TStringList;
  1586. LContext: TIdIMAP4PeerContext;
  1587. begin
  1588. LContext := TIdIMAP4PeerContext(ASender.Context);
  1589. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1590. SendWrongConnectionState(ASender);
  1591. Exit;
  1592. end;
  1593. {
  1594. if LContext.MailBox.State = msReadOnly then begin
  1595. SendErrorOpenedReadOnly(ASender);
  1596. Exit;
  1597. end;
  1598. }
  1599. if Assigned(FOnCommandCREATE) then begin
  1600. OnCommandCREATE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1601. Exit;
  1602. end;
  1603. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1604. Exit;
  1605. end;
  1606. if (not Assigned(OnDefMechReinterpretParamAsMailBox))
  1607. or (not Assigned(OnDefMechDoesImapMailBoxExist))
  1608. or (not Assigned(OnDefMechCreateMailBox)) then
  1609. begin
  1610. SendUnassignedDefaultMechanism(ASender);
  1611. Exit;
  1612. end;
  1613. LParams := TStringList.Create;
  1614. try
  1615. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1616. if LParams.Count < 1 then begin
  1617. //Incorrect number of params...
  1618. SendIncorrectNumberOfParameters(ASender);
  1619. Exit;
  1620. end;
  1621. if not OnDefMechReinterpretParamAsMailBox(LParams, 0) then begin
  1622. SendBadReply(ASender, 'Mailbox parameter is invalid.'); {Do not Localize}
  1623. Exit;
  1624. end;
  1625. if OnDefMechDoesImapMailBoxExist(LContext.LoginName, LParams[0]) then begin
  1626. SendBadReply(ASender, 'Mailbox already exists.'); {Do not Localize}
  1627. Exit;
  1628. end;
  1629. if OnDefMechCreateMailBox(LContext.LoginName, LParams[0]) then begin
  1630. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1631. end else begin
  1632. SendNoReply(ASender, 'Create failed'); {Do not Localize}
  1633. end;
  1634. finally
  1635. FreeAndNil(LParams);
  1636. end;
  1637. end;
  1638. procedure TIdIMAP4Server.DoCommandDELETE(ASender: TIdCommand);
  1639. var
  1640. LParams: TStringList;
  1641. LContext: TIdIMAP4PeerContext;
  1642. begin
  1643. LContext := TIdIMAP4PeerContext(ASender.Context);
  1644. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1645. SendWrongConnectionState(ASender);
  1646. Exit;
  1647. end;
  1648. {
  1649. if LContext.MailBox.State = msReadOnly then begin
  1650. SendErrorOpenedReadOnly(ASender);
  1651. Exit;
  1652. end;
  1653. }
  1654. if Assigned(FOnCommandDELETE) then begin
  1655. OnCommandDELETE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1656. Exit;
  1657. end;
  1658. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1659. Exit;
  1660. end;
  1661. if (not Assigned(OnDefMechDoesImapMailBoxExist))
  1662. or (not Assigned(OnDefMechReinterpretParamAsMailBox))
  1663. or (not Assigned(OnDefMechDeleteMailBox))
  1664. or (not Assigned(OnDefMechIsMailBoxOpen)) then
  1665. begin
  1666. SendUnassignedDefaultMechanism(ASender);
  1667. Exit;
  1668. end;
  1669. //Make sure we don't have the mailbox open by anyone
  1670. LParams := TStringList.Create;
  1671. try
  1672. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1673. if LParams.Count < 1 then begin
  1674. //Incorrect number of params...
  1675. SendIncorrectNumberOfParameters(ASender);
  1676. Exit;
  1677. end;
  1678. if not OnDefMechReinterpretParamAsMailBox(LParams, 0) then begin
  1679. SendBadReply(ASender, 'Mailbox parameter is invalid.'); {Do not Localize}
  1680. Exit;
  1681. end;
  1682. if OnDefMechIsMailBoxOpen(LContext.LoginName, LParams[0]) then begin
  1683. SendNoReply(ASender, 'Mailbox is in use.'); {Do not Localize}
  1684. Exit;
  1685. end;
  1686. if not OnDefMechDoesImapMailBoxExist(LContext.LoginName, LParams[0]) then begin
  1687. SendNoReply(ASender, 'Mailbox does not exist.'); {Do not Localize}
  1688. Exit;
  1689. end;
  1690. if OnDefMechDeleteMailBox(LContext.LoginName, LParams[0]) then begin
  1691. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1692. end else begin
  1693. SendNoReply(ASender, 'Delete failed'); {Do not Localize}
  1694. end;
  1695. finally
  1696. FreeAndNil(LParams);
  1697. end;
  1698. end;
  1699. procedure TIdIMAP4Server.DoCommandRENAME(ASender: TIdCommand);
  1700. var
  1701. LParams: TStringList;
  1702. LContext: TIdIMAP4PeerContext;
  1703. begin
  1704. LContext := TIdIMAP4PeerContext(ASender.Context);
  1705. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1706. SendWrongConnectionState(ASender);
  1707. Exit;
  1708. end;
  1709. {
  1710. if LContext.MailBox.State = msReadOnly then begin
  1711. SendErrorOpenedReadOnly(ASender);
  1712. Exit;
  1713. end;
  1714. }
  1715. if Assigned(FOnCommandRENAME) then begin
  1716. OnCommandRENAME(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1717. Exit;
  1718. end;
  1719. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1720. Exit;
  1721. end;
  1722. if (not Assigned(OnDefMechDoesImapMailBoxExist))
  1723. or (not Assigned(OnDefMechReinterpretParamAsMailBox))
  1724. or (not Assigned(OnDefMechRenameMailBox))
  1725. or (not Assigned(OnDefMechIsMailBoxOpen)) then
  1726. begin
  1727. SendUnassignedDefaultMechanism(ASender);
  1728. Exit;
  1729. end;
  1730. //Make sure we don't have the mailbox open by anyone
  1731. LParams := TStringList.Create;
  1732. try
  1733. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1734. if LParams.Count < 2 then begin
  1735. //Incorrect number of params...
  1736. SendIncorrectNumberOfParameters(ASender);
  1737. Exit;
  1738. end;
  1739. if not OnDefMechReinterpretParamAsMailBox(LParams, 0) then begin
  1740. SendBadReply(ASender, 'First mailbox parameter is invalid.'); {Do not Localize}
  1741. Exit;
  1742. end;
  1743. if OnDefMechIsMailBoxOpen(LContext.LoginName, LParams[0]) then begin
  1744. SendNoReply(ASender, 'Mailbox is in use.'); {Do not Localize}
  1745. Exit;
  1746. end;
  1747. if not OnDefMechReinterpretParamAsMailBox(LParams, 1) then begin
  1748. SendBadReply(ASender, 'Second mailbox parameter is invalid.'); {Do not Localize}
  1749. Exit;
  1750. end;
  1751. if not OnDefMechDoesImapMailBoxExist(LContext.LoginName, LParams[0]) then begin
  1752. SendNoReply(ASender, 'Mailbox to be renamed does not exist.'); {Do not Localize}
  1753. Exit;
  1754. end;
  1755. if OnDefMechDoesImapMailBoxExist(LContext.LoginName, LParams[1]) then begin
  1756. SendNoReply(ASender, 'Destination mailbox already exists.'); {Do not Localize}
  1757. Exit;
  1758. end;
  1759. if OnDefMechRenameMailBox(LContext.LoginName, LParams[0], LParams[1]) then begin
  1760. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1761. end else begin
  1762. SendNoReply(ASender, 'Delete failed'); {Do not Localize}
  1763. end;
  1764. finally
  1765. FreeAndNil(LParams);
  1766. end;
  1767. end;
  1768. procedure TIdIMAP4Server.DoCommandSUBSCRIBE(ASender: TIdCommand);
  1769. var
  1770. LContext: TIdIMAP4PeerContext;
  1771. begin
  1772. LContext := TIdIMAP4PeerContext(ASender.Context);
  1773. if LContext.MailBox.State = msReadOnly then begin
  1774. SendErrorOpenedReadOnly(ASender);
  1775. Exit;
  1776. end;
  1777. if Assigned(FOnCommandSUBSCRIBE) then begin
  1778. OnCommandSUBSCRIBE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1779. Exit;
  1780. end;
  1781. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1782. Exit;
  1783. end;
  1784. {Not clear exactly what this would do in this sample mechanism...}
  1785. SendUnsupportedCommand(ASender);
  1786. end;
  1787. procedure TIdIMAP4Server.DoCommandUNSUBSCRIBE(ASender: TIdCommand);
  1788. var
  1789. LContext: TIdIMAP4PeerContext;
  1790. begin
  1791. LContext := TIdIMAP4PeerContext(ASender.Context);
  1792. if LContext.MailBox.State = msReadOnly then begin
  1793. SendErrorOpenedReadOnly(ASender);
  1794. Exit;
  1795. end;
  1796. if Assigned(FOnCommandUNSUBSCRIBE) then begin
  1797. OnCommandUNSUBSCRIBE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1798. Exit;
  1799. end;
  1800. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1801. Exit;
  1802. end;
  1803. {Not clear exactly what this would do in this sample mechanism...}
  1804. SendUnsupportedCommand(ASender);
  1805. end;
  1806. procedure TIdIMAP4Server.DoCommandLIST(ASender: TIdCommand);
  1807. var
  1808. LParams: TStringList;
  1809. LMailBoxNames: TStringList;
  1810. LMailBoxFlags: TStringList;
  1811. LN: integer;
  1812. LEntry: string;
  1813. LContext: TIdIMAP4PeerContext;
  1814. begin
  1815. LContext := TIdIMAP4PeerContext(ASender.Context);
  1816. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1817. SendWrongConnectionState(ASender);
  1818. Exit;
  1819. end;
  1820. if Assigned(FOnCommandLIST) then begin
  1821. OnCommandLIST(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1822. Exit;
  1823. end;
  1824. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1825. Exit;
  1826. end;
  1827. if not Assigned(OnDefMechListMailBox) then begin
  1828. SendUnassignedDefaultMechanism(ASender);
  1829. Exit;
  1830. end;
  1831. //The default mechanism only supports the following format:
  1832. // LIST "" *
  1833. LParams := TStringList.Create;
  1834. try
  1835. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1836. if LParams.Count < 2 then begin
  1837. //Incorrect number of params...
  1838. SendIncorrectNumberOfParameters(ASender);
  1839. Exit;
  1840. end;
  1841. if LParams[1] <> '*' then begin {Do not Localize}
  1842. SendBadReply(ASender, 'Parameter not supported, 2nd (last) parameter must be *'); {Do not Localize}
  1843. Exit;
  1844. end;
  1845. LMailBoxNames := TStringList.Create;
  1846. try
  1847. LMailBoxFlags := TStringList.Create;
  1848. try
  1849. if OnDefMechListMailBox(LContext.LoginName, LParams[0], LMailBoxNames, LMailBoxFlags) then begin
  1850. for LN := 0 to LMailBoxNames.Count-1 do begin
  1851. //Replies are of the form:
  1852. //* LIST (\HasNoChildren) "." "INBOX.CreatedFolder"
  1853. LEntry := '* LIST ('; {Do not Localize}
  1854. if LMailBoxFlags[LN] <> '' then begin
  1855. LEntry := LEntry + LMailBoxFlags[LN];
  1856. end;
  1857. LEntry := LEntry + ') "' + MailBoxSeparator + '" "' + LMailBoxNames[LN] + '"'; {Do not Localize}
  1858. DoSendReply(ASender.Context, LEntry); {Do not Localize}
  1859. end;
  1860. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1861. end else begin
  1862. SendNoReply(ASender, 'List failed'); {Do not Localize}
  1863. end;
  1864. finally
  1865. FreeAndNil(LMailBoxFlags);
  1866. end;
  1867. finally
  1868. FreeAndNil(LMailBoxNames);
  1869. end;
  1870. finally
  1871. FreeAndNil(LParams);
  1872. end;
  1873. end;
  1874. procedure TIdIMAP4Server.DoCommandLSUB(ASender: TIdCommand);
  1875. var
  1876. LParams: TStringList;
  1877. LMailBoxNames: TStringList;
  1878. LMailBoxFlags: TStringList;
  1879. LN: integer;
  1880. LEntry: string;
  1881. LContext: TIdIMAP4PeerContext;
  1882. begin
  1883. LContext := TIdIMAP4PeerContext(ASender.Context);
  1884. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1885. SendWrongConnectionState(ASender);
  1886. Exit;
  1887. end;
  1888. if Assigned(FOnCommandLSUB) then begin
  1889. OnCommandLSUB(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1890. Exit;
  1891. end;
  1892. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1893. Exit;
  1894. end;
  1895. if not Assigned(OnDefMechListMailBox) then begin
  1896. SendUnassignedDefaultMechanism(ASender);
  1897. Exit;
  1898. end;
  1899. //Treat this the same as LIST...
  1900. LParams := TStringList.Create;
  1901. try
  1902. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1903. if LParams.Count < 2 then begin
  1904. //Incorrect number of params...
  1905. SendIncorrectNumberOfParameters(ASender);
  1906. Exit;
  1907. end;
  1908. if LParams[1] <> '*' then begin {Do not Localize}
  1909. SendBadReply(ASender, 'Parameter not supported, 2nd (last) parameter must be *'); {Do not Localize}
  1910. Exit;
  1911. end;
  1912. LMailBoxNames := TStringList.Create;
  1913. try
  1914. LMailBoxFlags := TStringList.Create;
  1915. try
  1916. if OnDefMechListMailBox(LContext.LoginName, LParams[0], LMailBoxNames, LMailBoxFlags) then begin
  1917. for LN := 0 to LMailBoxNames.Count-1 do begin
  1918. //Replies are of the form:
  1919. //* LIST (\HasNoChildren) "." "INBOX.CreatedFolder"
  1920. LEntry := '* LIST ('; {Do not Localize}
  1921. if LMailBoxFlags[LN] <> '' then begin
  1922. LEntry := LEntry + LMailBoxFlags[LN];
  1923. end;
  1924. LEntry := LEntry + ') "' + MailBoxSeparator + '" "' + LMailBoxNames[LN] + '"'; {Do not Localize}
  1925. DoSendReply(ASender.Context, LEntry); {Do not Localize}
  1926. end;
  1927. SendOkReply(ASender, 'Completed'); {Do not Localize}
  1928. end else begin
  1929. SendNoReply(ASender, 'List failed'); {Do not Localize}
  1930. end;
  1931. finally
  1932. FreeAndNil(LMailBoxFlags);
  1933. end;
  1934. finally
  1935. FreeAndNil(LMailBoxNames);
  1936. end;
  1937. finally
  1938. FreeAndNil(LParams);
  1939. end;
  1940. end;
  1941. procedure TIdIMAP4Server.DoCommandSTATUS(ASender: TIdCommand);
  1942. var
  1943. LMailBox: TIdMailBox;
  1944. LN: integer;
  1945. LParams: TStringList;
  1946. LTemp: string;
  1947. LAnswer: string;
  1948. LContext: TIdIMAP4PeerContext;
  1949. begin
  1950. LContext := TIdIMAP4PeerContext(ASender.Context);
  1951. if not (LContext.ConnectionState in [csAuthenticated, csSelected]) then begin
  1952. SendWrongConnectionState(ASender);
  1953. Exit;
  1954. end;
  1955. if Assigned(FOnCommandSTATUS) then begin
  1956. OnCommandSTATUS(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  1957. Exit;
  1958. end;
  1959. if not FUseDefaultMechanismsForUnassignedCommands then begin
  1960. Exit;
  1961. end;
  1962. if (not Assigned(OnDefMechDoesImapMailBoxExist))
  1963. or (not Assigned(OnDefMechReinterpretParamAsMailBox))
  1964. or (not Assigned(OnDefMechSetupMailbox)) then
  1965. begin
  1966. SendUnassignedDefaultMechanism(ASender);
  1967. Exit;
  1968. end;
  1969. //This can be issued for ANY mailbox, not just the currently selected one.
  1970. //The format is:
  1971. //C5 STATUS "INBOX" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)
  1972. //* STATUS INBOX (MESSAGES 490 RECENT 132 UIDNEXT 6546 UIDVALIDITY 1065090323 UNSEEN 167)
  1973. //C5 OK Completed
  1974. LParams := TStringList.Create;
  1975. try
  1976. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  1977. if LParams.Count < 1 then begin
  1978. //Incorrect number of params...
  1979. SendIncorrectNumberOfParameters(ASender);
  1980. Exit;
  1981. end;
  1982. if not OnDefMechReinterpretParamAsMailBox(LParams, 0) then begin
  1983. SendBadReply(ASender, 'Mailbox parameter is invalid.'); {Do not Localize}
  1984. Exit;
  1985. end;
  1986. if not OnDefMechDoesImapMailBoxExist(LContext.LoginName, LParams[0]) then begin
  1987. SendNoReply(ASender, 'Mailbox does not exist.'); {Do not Localize}
  1988. Exit;
  1989. end;
  1990. {Get everything you need for this mailbox...}
  1991. LMailBox := TIdMailBox.Create;
  1992. try
  1993. OnDefMechSetupMailbox(LContext.LoginName, LParams[0], LMailBox);
  1994. {Send the stats...}
  1995. LAnswer := '* STATUS ' + LParams[0] + ' ('; {Do not Localize}
  1996. for LN := 1 to LParams.Count-1 do begin
  1997. LTemp := LParams[LN];
  1998. if LTemp <> '' then begin
  1999. //Strip brackets (will be on 1st & last param)
  2000. if LTemp[1] = '(' then begin {Do not Localize}
  2001. LTemp := Copy(LTemp, 2, MaxInt);
  2002. end;
  2003. if (LTemp <> '') and (LTemp[Length(LTemp)] = ')') then begin {Do not Localize}
  2004. LTemp := Copy(LTemp, 1, Length(LTemp)-1);
  2005. end;
  2006. case PosInStrArray(LTemp, ['MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'], False) of
  2007. 0: // MESSAGES {Do not Localize}
  2008. begin
  2009. LAnswer := LAnswer + LTemp + ' ' + IntToStr(LMailBox.TotalMsgs) + ' '; {Do not Localize}
  2010. end;
  2011. 1: // RECENT {Do not Localize}
  2012. begin
  2013. LAnswer := LAnswer + LTemp + ' ' + IntToStr(LMailBox.RecentMsgs) + ' '; {Do not Localize}
  2014. end;
  2015. 2: // UIDNEXT {Do not Localize}
  2016. begin
  2017. LAnswer := LAnswer + LTemp + ' ' + LMailBox.UIDNext + ' '; {Do not Localize}
  2018. end;
  2019. 3: // UIDVALIDITY {Do not Localize}
  2020. begin
  2021. LAnswer := LAnswer + LTemp + ' ' + LMailBox.UIDValidity + ' '; {Do not Localize}
  2022. end;
  2023. 4: // UNSEEN {Do not Localize}
  2024. begin
  2025. LAnswer := LAnswer + LTemp + ' ' + IntToStr(LMailBox.UnseenMsgs) + ' '; {Do not Localize}
  2026. end;
  2027. else
  2028. begin
  2029. SendBadReply(ASender, 'Parameter not supported: ' + LTemp); {Do not Localize}
  2030. Exit;
  2031. end;
  2032. end;
  2033. end;
  2034. end;
  2035. if LAnswer[Length(LAnswer)] = ' ' then begin {Do not Localize}
  2036. LAnswer := Copy(LAnswer, 1, Length(LAnswer)-1);
  2037. end;
  2038. LAnswer := LAnswer + ')'; {Do not Localize}
  2039. DoSendReply(ASender.Context, LAnswer);
  2040. SendOkReply(ASender, 'Completed'); {Do not Localize}
  2041. finally
  2042. FreeAndNil(LMailBox);
  2043. end;
  2044. finally
  2045. FreeAndNil(LParams);
  2046. end;
  2047. end;
  2048. procedure TIdIMAP4Server.DoCommandAPPEND(ASender: TIdCommand);
  2049. var
  2050. LUID: string;
  2051. LStream: TStream;
  2052. LFile: string;
  2053. LTemp: string;
  2054. LParams: TStringList;
  2055. LParams2: TStringList;
  2056. LFlagsList: TStringList;
  2057. LSize: Int64;
  2058. LFlags, LInternalDateTime: string;
  2059. LN: integer;
  2060. LMessage: TIdMessage;
  2061. LContext: TIdIMAP4PeerContext;
  2062. begin
  2063. LContext := TIdIMAP4PeerContext(ASender.Context);
  2064. //You do NOT need to be in selected state for this.
  2065. if LContext.ConnectionState <> csAuthenticated then begin
  2066. SendWrongConnectionState(ASender);
  2067. Exit;
  2068. end;
  2069. if LContext.MailBox.State = msReadOnly then begin
  2070. SendErrorOpenedReadOnly(ASender);
  2071. Exit;
  2072. end;
  2073. if Assigned(FOnCommandAPPEND) then begin
  2074. OnCommandAPPEND(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2075. Exit;
  2076. end;
  2077. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2078. Exit;
  2079. end;
  2080. if (not Assigned(OnDefMechGetNextFreeUID))
  2081. or (not Assigned(OnDefMechReinterpretParamAsMailBox))
  2082. or (not Assigned(OnDefMechUpdateNextFreeUID))
  2083. or (not Assigned(OnDefMechDeleteMessage)) //Needed to reverse out a save if setting flags fail
  2084. or (not Assigned(OnDefMechGetFileNameToWriteAppendMessage)) then
  2085. begin
  2086. SendUnassignedDefaultMechanism(ASender);
  2087. Exit;
  2088. end;
  2089. //Format (the flags and date/time are optional):
  2090. //C323 APPEND "INBOX.Sent" (\Seen) "internal date/time" {1876}
  2091. //+ go ahead
  2092. //...
  2093. //C323 OK [APPENDUID 1065095982 105] Completed
  2094. LParams := TStringList.Create;
  2095. try
  2096. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2097. if LParams.Count < 2 then begin
  2098. //Incorrect number of params...
  2099. SendIncorrectNumberOfParameters(ASender);
  2100. Exit;
  2101. end;
  2102. if not OnDefMechReinterpretParamAsMailBox(LParams, 0) then begin
  2103. SendBadReply(ASender, 'Mailbox parameter is invalid.'); {Do not Localize}
  2104. Exit;
  2105. end;
  2106. LFlags := '';
  2107. LInternalDateTime := '';
  2108. LN := 1;
  2109. LTemp := LParams[Ln];
  2110. if TextStartsWith(LTemp, '(') then begin {Do not Localize}
  2111. if not ReinterpretParamAsFlags(LParams, Ln) then begin
  2112. SendBadReply(ASender, 'Flags parameter is invalid.'); {Do not Localize}
  2113. Exit;
  2114. end;
  2115. LFlags := LParams[Ln];
  2116. Inc(Ln);
  2117. end
  2118. else if TextIsSame(LTemp, 'NIL') then begin {Do not Localize}
  2119. Inc(Ln);
  2120. end;
  2121. LTemp := LParams[Ln];
  2122. if TextStartsWith(LTemp, '"') then begin {Do not Localize}
  2123. if not ReinterpretParamAsQuotedStr(LParams, Ln) then begin
  2124. SendBadReply(ASender, 'InternalDateTime parameter is invalid.'); {Do not Localize}
  2125. Exit;
  2126. end;
  2127. LInternalDateTime := LParams[Ln];
  2128. end;
  2129. LTemp := LParams[LParams.Count-1];
  2130. if not TextStartsWith(LTemp, '{') then begin {Do not Localize}
  2131. SendBadReply(ASender, 'Size parameter is invalid.'); {Do not Localize}
  2132. Exit;
  2133. end;
  2134. LSize := IndyStrToInt64(Copy(LTemp, 2, Length(LTemp)-2));
  2135. //Grab the next UID...
  2136. LUID := OnDefMechGetNextFreeUID(LContext.LoginName, LParams[0]);
  2137. //Get the message...
  2138. LFile := OnDefMechGetFileNameToWriteAppendMessage(LContext.LoginName, LContext.MailBox.Name, LUID);
  2139. LStream := TIdFileCreateStream.Create(LFile);
  2140. try
  2141. ASender.Context.Connection.IOHandler.ReadStream(LStream, LSize);
  2142. if LFlags = '' then begin
  2143. SendOkReply(ASender, 'Completed'); {Do not Localize}
  2144. end else begin
  2145. //Update the (optional) flags...
  2146. LParams2 := TStringList.Create;
  2147. try
  2148. LParams2.Add(LUID);
  2149. LParams2.Add('FLAGS.SILENT'); {Do not Localize}
  2150. {
  2151. for LN := 1 to LParams.Count-2 do begin
  2152. LParams2.Add(LParams[LN]);
  2153. end;
  2154. }
  2155. //The flags are in a string, need to reassemble...
  2156. LFlagsList := TStringList.Create;
  2157. try
  2158. BreakApart(LFlags, ' ', LFlagsList); {Do not Localize}
  2159. for LN := 0 to LFlagsList.Count-1 do begin
  2160. LTemp := LFlagsList[LN];
  2161. if LN = 0 then begin
  2162. LTemp := '(' + LTemp; {Do not Localize}
  2163. end;
  2164. if LN = LFlagsList.Count-1 then begin
  2165. LTemp := LTemp + ')'; {Do not Localize}
  2166. end;
  2167. LParams2.Add(LTemp);
  2168. end;
  2169. if not ProcessStore(True, ASender, LParams2) then begin
  2170. //Have to reverse out our changes if ANYTHING fails..
  2171. LMessage := TIdMessage.Create(Self);
  2172. try
  2173. LMessage.UID := LUID; //This is all we need for deletion
  2174. OnDefMechDeleteMessage(LContext.LoginName, LContext.MailBox.Name, LMessage);
  2175. finally
  2176. FreeAndNil(LMessage);
  2177. end;
  2178. Exit;
  2179. end;
  2180. finally
  2181. FreeAndNil(LFlagsList);
  2182. end;
  2183. finally
  2184. FreeAndNil(LParams2);
  2185. end;
  2186. end;
  2187. //Update the next free UID in the .uid file...
  2188. OnDefMechUpdateNextFreeUID(LContext.LoginName, LContext.MailBox.Name, IntToStr(IndyStrToInt64(LUID)+1));
  2189. // TODO: implement this
  2190. {
  2191. if LInternalDateTime <> '' then
  2192. begin
  2193. // what to do here?
  2194. end;
  2195. }
  2196. finally
  2197. FreeAndNil(LStream);
  2198. end;
  2199. finally
  2200. FreeAndNil(LParams);
  2201. end;
  2202. end;
  2203. procedure TIdIMAP4Server.DoCommandCHECK(ASender: TIdCommand);
  2204. var
  2205. LContext: TIdIMAP4PeerContext;
  2206. begin
  2207. LContext := TIdIMAP4PeerContext(ASender.Context);
  2208. if LContext.ConnectionState <> csSelected then begin
  2209. SendWrongConnectionState(ASender);
  2210. Exit;
  2211. end;
  2212. if Assigned(fOnCommandCHECK) then begin
  2213. OnCommandCHECK(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2214. Exit;
  2215. end;
  2216. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2217. Exit;
  2218. end;
  2219. {On most servers, this does nothing, they always return OK...}
  2220. SendOkReply(ASender, 'Completed'); {Do not Localize}
  2221. end;
  2222. procedure TIdIMAP4Server.DoCommandCLOSE(ASender: TIdCommand);
  2223. var
  2224. LResult: Boolean;
  2225. LContext: TIdIMAP4PeerContext;
  2226. begin
  2227. LContext := TIdIMAP4PeerContext(ASender.Context);
  2228. if LContext.ConnectionState <> csSelected then begin
  2229. SendWrongConnectionState(ASender);
  2230. Exit;
  2231. end;
  2232. if Assigned(fOnCommandCLOSE) then begin
  2233. OnCommandCLOSE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2234. Exit;
  2235. end;
  2236. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2237. Exit;
  2238. end;
  2239. if not Assigned(OnDefMechDeleteMessage) then begin //Used by ExpungeRecords
  2240. SendUnassignedDefaultMechanism(ASender);
  2241. Exit;
  2242. end;
  2243. {This is an implicit expunge...}
  2244. LResult := ExpungeRecords(ASender);
  2245. {Now close it...}
  2246. LContext.MailBox.Clear;
  2247. LContext.FConnectionState := csAuthenticated;
  2248. if LResult then begin
  2249. SendOkReply(ASender, 'Completed'); {Do not Localize}
  2250. end else begin
  2251. SendNoReply(ASender, 'Implicit expunge failed for one or more messages'); {Do not Localize}
  2252. end;
  2253. end;
  2254. procedure TIdIMAP4Server.DoCommandEXPUNGE(ASender: TIdCommand);
  2255. var
  2256. LContext: TIdIMAP4PeerContext;
  2257. begin
  2258. LContext := TIdIMAP4PeerContext(ASender.Context);
  2259. if LContext.ConnectionState <> csSelected then begin
  2260. SendWrongConnectionState(ASender);
  2261. Exit;
  2262. end;
  2263. if LContext.MailBox.State = msReadOnly then begin
  2264. SendErrorOpenedReadOnly(ASender);
  2265. Exit;
  2266. end;
  2267. if Assigned(FOnCommandEXPUNGE) then begin
  2268. OnCommandEXPUNGE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2269. Exit;
  2270. end;
  2271. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2272. Exit;
  2273. end;
  2274. if not Assigned(OnDefMechDeleteMessage) then begin //Used by ExpungeRecords
  2275. SendUnassignedDefaultMechanism(ASender);
  2276. Exit;
  2277. end;
  2278. if ExpungeRecords(ASender) then begin
  2279. SendOkReply(ASender, 'Completed'); {Do not Localize}
  2280. end else begin
  2281. SendNoReply(ASender, 'Expunge failed for one or more messages'); {Do not Localize}
  2282. end;
  2283. end;
  2284. procedure TIdIMAP4Server.DoCommandSEARCH(ASender: TIdCommand);
  2285. var
  2286. LParams: TStringList;
  2287. LContext: TIdIMAP4PeerContext;
  2288. begin
  2289. LContext := TIdIMAP4PeerContext(ASender.Context);
  2290. if LContext.ConnectionState <> csSelected then begin
  2291. SendWrongConnectionState(ASender);
  2292. Exit;
  2293. end;
  2294. if Assigned(fOnCommandSEARCH) then begin
  2295. OnCommandSEARCH(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2296. Exit;
  2297. end;
  2298. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2299. Exit;
  2300. end;
  2301. if not Assigned(OnDefMechGetMessageHeader) then begin //Used by ProcessSearch
  2302. SendUnassignedDefaultMechanism(ASender);
  2303. Exit;
  2304. end;
  2305. LParams := TStringList.Create;
  2306. try
  2307. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2308. ProcessSearch(False, ASender, LParams);
  2309. finally
  2310. FreeAndNil(LParams);
  2311. end;
  2312. end;
  2313. procedure TIdIMAP4Server.DoCommandFETCH(ASender: TIdCommand);
  2314. var
  2315. LParams: TStringList;
  2316. LContext: TIdIMAP4PeerContext;
  2317. begin
  2318. LContext := TIdIMAP4PeerContext(ASender.Context);
  2319. if LContext.ConnectionState <> csSelected then begin
  2320. SendWrongConnectionState(ASender);
  2321. Exit;
  2322. end;
  2323. if Assigned(FOnCommandFETCH) then begin
  2324. OnCommandFETCH(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2325. Exit;
  2326. end;
  2327. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2328. Exit;
  2329. end;
  2330. if (not Assigned(OnDefMechGetMessageHeader)) //Used by ProcessFetch
  2331. or (not Assigned(OnDefMechGetMessageSize))
  2332. or (not Assigned(OnDefMechGetMessageRaw)) then
  2333. begin
  2334. SendUnassignedDefaultMechanism(ASender);
  2335. Exit;
  2336. end;
  2337. LParams := TStringList.Create;
  2338. try
  2339. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2340. ProcessFetch(False, ASender, LParams);
  2341. finally
  2342. FreeAndNil(LParams);
  2343. end;
  2344. end;
  2345. procedure TIdIMAP4Server.DoCommandSTORE(ASender: TIdCommand);
  2346. var
  2347. LParams: TStringList;
  2348. LContext: TIdIMAP4PeerContext;
  2349. begin
  2350. LContext := TIdIMAP4PeerContext(ASender.Context);
  2351. if LContext.ConnectionState <> csSelected then begin
  2352. SendWrongConnectionState(ASender);
  2353. Exit;
  2354. end;
  2355. if LContext.MailBox.State = msReadOnly then begin
  2356. SendErrorOpenedReadOnly(ASender);
  2357. Exit;
  2358. end;
  2359. if Assigned(fOnCommandSTORE) then begin
  2360. OnCommandSTORE(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2361. Exit;
  2362. end;
  2363. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2364. Exit;
  2365. end;
  2366. LParams := TStringList.Create;
  2367. try
  2368. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2369. ProcessStore(False, ASender, LParams);
  2370. finally
  2371. FreeAndNil(LParams);
  2372. end;
  2373. end;
  2374. function TIdIMAP4Server.MessageFlagSetToStr(const AFlags: TIdMessageFlagsSet): String;
  2375. begin
  2376. Result := '';
  2377. if mfAnswered in AFlags then begin
  2378. Result := Result + MessageFlags[mfAnswered] + ' '; {Do not Localize}
  2379. end;
  2380. if mfFlagged in AFlags then begin
  2381. Result := Result + MessageFlags[mfFlagged] + ' '; {Do not Localize}
  2382. end;
  2383. if mfDeleted in AFlags then begin
  2384. Result := Result + MessageFlags[mfDeleted] + ' '; {Do not Localize}
  2385. end;
  2386. if mfDraft in AFlags then begin
  2387. Result := Result + MessageFlags[mfDraft] + ' '; {Do not Localize}
  2388. end;
  2389. if mfSeen in AFlags then begin
  2390. Result := Result + MessageFlags[mfSeen] + ' '; {Do not Localize}
  2391. end;
  2392. if Result <> '' then begin
  2393. Result := TrimRight(Result);
  2394. end;
  2395. end;
  2396. procedure TIdIMAP4Server.DoCommandCOPY(ASender: TIdCommand);
  2397. var
  2398. LParams: TStringList;
  2399. LContext: TIdIMAP4PeerContext;
  2400. begin
  2401. LContext := TIdIMAP4PeerContext(ASender.Context);
  2402. if LContext.ConnectionState <> csSelected then begin
  2403. SendWrongConnectionState(ASender);
  2404. Exit;
  2405. end;
  2406. if LContext.MailBox.State = msReadOnly then begin
  2407. SendErrorOpenedReadOnly(ASender);
  2408. Exit;
  2409. end;
  2410. if Assigned(FOnCommandCOPY) then begin
  2411. OnCommandCOPY(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2412. Exit;
  2413. end;
  2414. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2415. Exit;
  2416. end;
  2417. //Format is COPY 2:4 DestinationMailBoxName
  2418. if (not Assigned(OnDefMechReinterpretParamAsMailBox))
  2419. or (not Assigned(OnDefMechCopyMessage)) then //Needed for ProcessCopy
  2420. begin
  2421. SendUnassignedDefaultMechanism(ASender);
  2422. Exit;
  2423. end;
  2424. LParams := TStringList.Create;
  2425. try
  2426. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2427. ProcessCopy(False, ASender, LParams);
  2428. finally
  2429. FreeAndNil(LParams);
  2430. end;
  2431. end;
  2432. {UID before COPY, FETCH or STORE means the record numbers are UIDs.
  2433. UID before SEARCH means SEARCH is to _return_ UIDs rather than relative numbers.}
  2434. procedure TIdIMAP4Server.DoCommandUID(ASender: TIdCommand);
  2435. var
  2436. LParams: TStringList;
  2437. LContext: TIdIMAP4PeerContext;
  2438. begin
  2439. LContext := TIdIMAP4PeerContext(ASender.Context);
  2440. if LContext.ConnectionState <> csSelected then begin
  2441. SendWrongConnectionState(ASender);
  2442. Exit;
  2443. end;
  2444. if Assigned(fOnCommandUID) then begin
  2445. OnCommandUID(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2446. Exit;
  2447. end;
  2448. if not FUseDefaultMechanismsForUnassignedCommands then begin
  2449. Exit;
  2450. end;
  2451. LParams := TStringList.Create;
  2452. try
  2453. BreakApart(ASender.UnparsedParams, ' ', LParams); {Do not Localize}
  2454. if LParams.Count < 1 then begin
  2455. //Incorrect number of params...
  2456. SendIncorrectNumberOfParameters(ASender);
  2457. Exit;
  2458. end;
  2459. //Map the commands to the general handler but remove the FETCH or whatever...
  2460. case PosInStrArray(LParams[0], ['FETCH', 'COPY', 'STORE', 'SEARCH'], False) of
  2461. 0: // FETCH {Do not Localize}
  2462. begin
  2463. if (not Assigned(OnDefMechGetMessageHeader)) //Used by ProcessFetch
  2464. or (not Assigned(OnDefMechGetMessageSize))
  2465. or (not Assigned(OnDefMechGetMessageRaw)) then
  2466. begin
  2467. SendUnassignedDefaultMechanism(ASender);
  2468. Exit;
  2469. end;
  2470. LParams.Delete(0);
  2471. ProcessFetch(True, ASender, LParams);
  2472. end;
  2473. 1: // COPY {Do not Localize}
  2474. begin
  2475. if (not Assigned(OnDefMechReinterpretParamAsMailBox))
  2476. or (not Assigned(OnDefMechCopyMessage)) then //Needed for ProcessCopy
  2477. begin
  2478. SendUnassignedDefaultMechanism(ASender);
  2479. Exit;
  2480. end;
  2481. LParams.Delete(0);
  2482. ProcessCopy(True, ASender, LParams);
  2483. end;
  2484. 2: // STORE {Do not Localize}
  2485. begin
  2486. LParams.Delete(0);
  2487. ProcessStore(True, ASender, LParams);
  2488. end;
  2489. 3: // SEARCH {Do not Localize}
  2490. begin
  2491. if not Assigned(OnDefMechGetMessageHeader) then begin //Used by ProcessSearch
  2492. SendUnassignedDefaultMechanism(ASender);
  2493. Exit;
  2494. end;
  2495. LParams.Delete(0);
  2496. ProcessSearch(True, ASender, LParams);
  2497. end;
  2498. else
  2499. begin
  2500. SendUnsupportedCommand(ASender);
  2501. end;
  2502. end;
  2503. finally
  2504. FreeAndNil(LParams);
  2505. end;
  2506. end;
  2507. procedure TIdIMAP4Server.DoCommandX(ASender: TIdCommand);
  2508. begin
  2509. if not Assigned(fOnCommandX) then begin
  2510. OnCommandX(ASender.Context, TIdIMAP4PeerContext(ASender.Context).IMAP4Tag, ASender.UnparsedParams);
  2511. end else if FUseDefaultMechanismsForUnassignedCommands then begin
  2512. SendUnsupportedCommand(ASender);
  2513. end;
  2514. end;
  2515. procedure TIdIMAP4Server.DoCommandSTARTTLS(ASender: TIdCommand);
  2516. var
  2517. LContext: TIdIMAP4PeerContext;
  2518. begin
  2519. LContext := TIdIMAP4PeerContext(ASender.Context);
  2520. if (not (IOHandler is TIdServerIOHandlerSSLBase)) or (not (FUseTLS in ExplicitTLSVals)) then begin
  2521. OnCommandError(ASender.Context, LContext.IMAP4Tag, ASender.UnparsedParams);
  2522. Exit;
  2523. end;
  2524. if LContext.UsingTLS then begin // we are already using TLS
  2525. DoSendReply(ASender.Context, 'BAD %s', [RSIMAP4SvrNotPermittedWithTLS]); {do not localize}
  2526. Exit;
  2527. end;
  2528. // TODO: STARTTLS may only be issued in auth-state
  2529. DoSendReply(ASender.Context, 'OK %s', [RSIMAP4SvrBeginTLSNegotiation]); {do not localize}
  2530. (ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
  2531. end;
  2532. end.