IdIMAP4Server.pas 101 KB

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