PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/module/ASC.Mail/ASC.Mail.Core/Net/POP3/Server/POP3_Session.cs

https://github.com/dc0d/ONLYOFFICE-Server
C# | 1723 lines | 898 code | 196 blank | 629 comment | 136 complexity | ca7810115608a400a473c49691872559 MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. (c) Copyright Ascensio System SIA 2010-2014
  3. This program is a free software product.
  4. You can redistribute it and/or modify it under the terms
  5. of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
  6. Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
  7. to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
  8. any third-party rights.
  9. This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty
  10. of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
  11. the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
  12. You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
  13. The interactive user interfaces in modified source and object code versions of the Program must
  14. display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
  15. Pursuant to Section 7(b) of the License you must retain the original Product logo when
  16. distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
  17. trademark law for use of our trademarks.
  18. All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
  19. content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
  20. International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
  21. */
  22. using System.Security.Principal;
  23. using ASC.Mail.Net.IO;
  24. using ASC.Mail.Net.TCP;
  25. namespace ASC.Mail.Net.POP3.Server
  26. {
  27. #region usings
  28. using System;
  29. using System.IO;
  30. using System.Net.Sockets;
  31. using System.Text;
  32. using AUTH;
  33. #endregion
  34. /// <summary>
  35. /// POP3 Session.
  36. /// </summary>
  37. public class POP3_Session : TCP_ServerSession
  38. {
  39. #region Members
  40. private readonly POP3_MessageCollection m_POP3_Messages;
  41. private readonly POP3_Server m_pServer;
  42. private int m_BadCmdCount; // Holds number of bad commands.
  43. private string m_MD5_prefix = ""; // Session MD5 prefix for APOP command
  44. private GenericIdentity m_pUser;
  45. #endregion
  46. #region Constructor
  47. public string UserName
  48. {
  49. get
  50. {
  51. if (AuthenticatedUserIdentity!=null)
  52. {
  53. return AuthenticatedUserIdentity.Name;
  54. }
  55. return string.Empty;
  56. }
  57. }
  58. #endregion
  59. #region Methods
  60. #endregion
  61. #region Overrides
  62. protected override void OnTimeout()
  63. {
  64. base.OnTimeout();
  65. WriteLine("-ERR Session timeout, closing transmission channel");
  66. }
  67. private POP3_Server Pop3Server { get { return Server as POP3_Server; } }
  68. private void AddWriteEntry(string cmd)
  69. {
  70. // Log
  71. if (Pop3Server.Logger != null)
  72. {
  73. Pop3Server.Logger.AddWrite(ID, AuthenticatedUserIdentity, cmd.Length, cmd, LocalEndPoint, RemoteEndPoint);
  74. }
  75. }
  76. protected void WriteLine(string line)
  77. {
  78. try
  79. {
  80. AddWriteEntry(line);
  81. TcpStream.WriteLine(line);
  82. }
  83. catch (Exception x)
  84. {
  85. OnError(x);
  86. }
  87. }
  88. #endregion
  89. #region Utility methods
  90. protected internal override void Start()
  91. {
  92. base.Start();
  93. try
  94. {
  95. // Check if ip is allowed to connect this computer
  96. if (m_pServer.OnValidate_IpAddress(LocalEndPoint, RemoteEndPoint))
  97. {
  98. // Notify that server is ready
  99. m_MD5_prefix = "<" + Guid.NewGuid().ToString().ToLower() + ">";
  100. if (m_pServer.GreetingText == "")
  101. {
  102. WriteLine(string.Format("+OK {0} POP3 Server ready {1}", LocalHostName, m_MD5_prefix));
  103. }
  104. else
  105. {
  106. WriteLine(string.Format("+OK {0} {1}", m_pServer.GreetingText, m_MD5_prefix));
  107. }
  108. BeginRecieveCmd();
  109. }
  110. else
  111. {
  112. EndSession();
  113. }
  114. }
  115. catch (Exception x)
  116. {
  117. OnError(x);
  118. }
  119. }
  120. /// <summary>
  121. /// Starts session.
  122. /// </summary>
  123. //private void StartSession()
  124. //{
  125. // // Add session to session list
  126. // m_pServer.AddSession(this);
  127. // try
  128. // {
  129. // // Check if ip is allowed to connect this computer
  130. // if (m_pServer.OnValidate_IpAddress(LocalEndPoint, RemoteEndPoint))
  131. // {
  132. // //--- Dedicated SSL connection, switch to SSL -----------------------------------//
  133. // if (BindInfo.SslMode == SslMode.SSL)
  134. // {
  135. // try
  136. // {
  137. // Socket.SwitchToSSL(BindInfo.Certificate);
  138. // if (Socket.Logger != null)
  139. // {
  140. // Socket.Logger.AddTextEntry("SSL negotiation completed successfully.");
  141. // }
  142. // }
  143. // catch (Exception x)
  144. // {
  145. // if (Socket.Logger != null)
  146. // {
  147. // Socket.Logger.AddTextEntry("SSL handshake failed ! " + x.Message);
  148. // EndSession();
  149. // return;
  150. // }
  151. // }
  152. // }
  153. // //-------------------------------------------------------------------------------//
  154. // // Notify that server is ready
  155. // m_MD5_prefix = "<" + Guid.NewGuid().ToString().ToLower() + ">";
  156. // if (m_pServer.GreetingText == "")
  157. // {
  158. // Socket.WriteLine("+OK " + Net_Utils.GetLocalHostName(BindInfo.HostName) +
  159. // " POP3 Server ready " + m_MD5_prefix);
  160. // }
  161. // else
  162. // {
  163. // Socket.WriteLine("+OK " + m_pServer.GreetingText + " " + m_MD5_prefix);
  164. // }
  165. // BeginRecieveCmd();
  166. // }
  167. // else
  168. // {
  169. // EndSession();
  170. // }
  171. // }
  172. // catch (Exception x)
  173. // {
  174. // OnError(x);
  175. // }
  176. //}
  177. /// <summary>
  178. /// Ends session, closes socket.
  179. /// </summary>
  180. private void EndSession()
  181. {
  182. Disconnect();
  183. }
  184. /// <summary>
  185. /// Is called when error occures.
  186. /// </summary>
  187. /// <param name="x"></param>
  188. private new void OnError(Exception x)
  189. {
  190. try
  191. {
  192. // We must see InnerException too, SocketException may be as inner exception.
  193. SocketException socketException = null;
  194. if (x is SocketException)
  195. {
  196. socketException = (SocketException) x;
  197. }
  198. else if (x.InnerException != null && x.InnerException is SocketException)
  199. {
  200. socketException = (SocketException) x.InnerException;
  201. }
  202. if (socketException != null)
  203. {
  204. // Client disconnected without shutting down
  205. if (socketException.ErrorCode == 10054 || socketException.ErrorCode == 10053)
  206. {
  207. EndSession();
  208. // Exception handled, return
  209. return;
  210. }
  211. }
  212. m_pServer.OnSysError("", x);
  213. }
  214. catch (Exception ex)
  215. {
  216. m_pServer.OnSysError("", ex);
  217. }
  218. }
  219. /// <summary>
  220. /// Starts recieveing command.
  221. /// </summary>
  222. private void BeginRecieveCmd()
  223. {
  224. ReadAsync(ReciveCmdComleted);
  225. }
  226. private void AddReadEntry(string cmd)
  227. {
  228. // Log
  229. if (Pop3Server.Logger != null)
  230. {
  231. Pop3Server.Logger.AddRead(ID, AuthenticatedUserIdentity, cmd.Length, cmd, LocalEndPoint, RemoteEndPoint);
  232. }
  233. }
  234. internal void BeginWriteLine(string line)
  235. {
  236. if (!line.EndsWith("\r\n"))
  237. {
  238. line += "\r\n";
  239. }
  240. byte[] buffer = Encoding.Default.GetBytes(line);
  241. TcpStream.BeginWrite(buffer, 0, buffer.Length, EndSend, null);
  242. }
  243. private void EndSend(IAsyncResult ar)
  244. {
  245. try
  246. {
  247. TcpStream.EndWrite(ar);
  248. BeginRecieveCmd();
  249. }
  250. catch (Exception x)
  251. {
  252. OnError(x);
  253. }
  254. }
  255. internal void ReadAsync(EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>> completed)
  256. {
  257. SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength],
  258. SizeExceededAction.
  259. JunkAndThrowException);
  260. args.Completed += completed;
  261. TcpStream.ReadLine(args, true);
  262. }
  263. private void ReciveCmdComleted(object sender, EventArgs<SmartStream.ReadLineAsyncOP> e)
  264. {
  265. if (e.Value.IsCompleted && e.Value.Error == null)
  266. {
  267. //Call
  268. bool sessionEnd = false;
  269. try
  270. {
  271. string cmdLine = e.Value.LineUtf8;
  272. AddReadEntry(cmdLine);
  273. // Exceute command
  274. sessionEnd = SwitchCommand(cmdLine);
  275. if (sessionEnd)
  276. {
  277. // Session end, close session
  278. EndSession();
  279. }
  280. }
  281. catch (Exception ex)
  282. {
  283. WriteLine("-ERR " + ex.Message);
  284. if (!sessionEnd)
  285. {
  286. BeginRecieveCmd();
  287. }
  288. }
  289. }
  290. else if (e.Value.Error != null)
  291. {
  292. OnError(e.Value.Error);
  293. }
  294. }
  295. /// <summary>
  296. /// Parses and executes POP3 commmand.
  297. /// </summary>
  298. /// <param name="POP3_commandTxt">POP3 command text.</param>
  299. /// <returns>Returns true,if session must be terminated.</returns>
  300. private bool SwitchCommand(string POP3_commandTxt)
  301. {
  302. //---- Parse command --------------------------------------------------//
  303. string[] cmdParts = POP3_commandTxt.TrimStart().Split(new[] {' '});
  304. string POP3_command = cmdParts[0].ToUpper().Trim();
  305. string argsText = Core.GetArgsText(POP3_commandTxt, POP3_command);
  306. //---------------------------------------------------------------------//
  307. bool getNextCmd = true;
  308. switch (POP3_command)
  309. {
  310. case "USER":
  311. USER(argsText);
  312. getNextCmd = false;
  313. break;
  314. case "PASS":
  315. PASS(argsText);
  316. getNextCmd = false;
  317. break;
  318. case "STAT":
  319. STAT();
  320. getNextCmd = false;
  321. break;
  322. case "LIST":
  323. LIST(argsText);
  324. getNextCmd = false;
  325. break;
  326. case "RETR":
  327. RETR(argsText);
  328. getNextCmd = false;
  329. break;
  330. case "DELE":
  331. DELE(argsText);
  332. getNextCmd = false;
  333. break;
  334. case "NOOP":
  335. NOOP();
  336. getNextCmd = false;
  337. break;
  338. case "RSET":
  339. RSET();
  340. getNextCmd = false;
  341. break;
  342. case "QUIT":
  343. QUIT();
  344. getNextCmd = false;
  345. return true;
  346. //----- Optional commands ----- //
  347. case "UIDL":
  348. UIDL(argsText);
  349. getNextCmd = false;
  350. break;
  351. case "APOP":
  352. APOP(argsText);
  353. getNextCmd = false;
  354. break;
  355. case "TOP":
  356. TOP(argsText);
  357. getNextCmd = false;
  358. break;
  359. case "AUTH":
  360. AUTH(argsText);
  361. getNextCmd = false;
  362. break;
  363. case "CAPA":
  364. CAPA(argsText);
  365. getNextCmd = false;
  366. break;
  367. case "STLS":
  368. STLS(argsText);
  369. getNextCmd = false;
  370. break;
  371. default:
  372. WriteLine("-ERR Invalid command");
  373. //---- Check that maximum bad commands count isn't exceeded ---------------//
  374. if (m_BadCmdCount > m_pServer.MaxBadCommands - 1)
  375. {
  376. WriteLine("-ERR Too many bad commands, closing transmission channel");
  377. return true;
  378. }
  379. m_BadCmdCount++;
  380. //-------------------------------------------------------------------------//
  381. break;
  382. }
  383. if (getNextCmd)
  384. {
  385. BeginRecieveCmd();
  386. }
  387. return false;
  388. }
  389. public override GenericIdentity AuthenticatedUserIdentity
  390. {
  391. get
  392. {
  393. if (IsDisposed)
  394. {
  395. throw new ObjectDisposedException(GetType().Name);
  396. }
  397. return m_pUser;
  398. }
  399. }
  400. private void SetUserName(string userName)
  401. {
  402. m_pUser = new GenericIdentity(UserName);
  403. }
  404. private void USER(string argsText)
  405. {
  406. /* RFC 1939 7. USER
  407. Arguments:
  408. a string identifying a mailbox (required), which is of
  409. significance ONLY to the server
  410. NOTE:
  411. If the POP3 server responds with a positive
  412. status indicator ("+OK"), then the client may issue
  413. either the PASS command to complete the authentication,
  414. or the QUIT command to terminate the POP3 session.
  415. */
  416. if (IsAuthenticated)
  417. {
  418. BeginWriteLine("-ERR You are already authenticated");
  419. return;
  420. }
  421. if (UserName.Length > 0)
  422. {
  423. BeginWriteLine("-ERR username is already specified, please specify password");
  424. return;
  425. }
  426. if ((m_pServer.SupportedAuthentications & SaslAuthTypes.Plain) == 0)
  427. {
  428. BeginWriteLine("-ERR USER/PASS command disabled");
  429. return;
  430. }
  431. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  432. // There must be only one parameter - userName
  433. if (argsText.Length > 0 && param.Length == 1)
  434. {
  435. string userName = param[0];
  436. // Check if user isn't logged in already
  437. if (!m_pServer.IsUserLoggedIn(userName))
  438. {
  439. SetUserName(userName);
  440. // Send this line last, because it issues a new command and any assignments done
  441. // after this method may not become wisible to next command.
  442. BeginWriteLine("+OK User:'" + userName + "' ok");
  443. }
  444. else
  445. {
  446. BeginWriteLine("-ERR User:'" + userName + "' already logged in");
  447. }
  448. }
  449. else
  450. {
  451. BeginWriteLine("-ERR Syntax error. Syntax:{USER username}");
  452. }
  453. }
  454. private void PASS(string argsText)
  455. {
  456. /* RFC 7. PASS
  457. Arguments:
  458. a server/mailbox-specific password (required)
  459. Restrictions:
  460. may only be given in the AUTHORIZATION state immediately
  461. after a successful USER command
  462. NOTE:
  463. When the client issues the PASS command, the POP3 server
  464. uses the argument pair from the USER and PASS commands to
  465. determine if the client should be given access to the
  466. appropriate maildrop.
  467. Possible Responses:
  468. +OK maildrop locked and ready
  469. -ERR invalid password
  470. -ERR unable to lock maildrop
  471. */
  472. if (IsAuthenticated)
  473. {
  474. BeginWriteLine("-ERR You are already authenticated");
  475. return;
  476. }
  477. if (UserName.Length == 0)
  478. {
  479. BeginWriteLine("-ERR please specify username first");
  480. return;
  481. }
  482. if ((m_pServer.SupportedAuthentications & SaslAuthTypes.Plain) == 0)
  483. {
  484. BeginWriteLine("-ERR USER/PASS command disabled");
  485. return;
  486. }
  487. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  488. // There may be only one parameter - password
  489. if (param.Length == 1)
  490. {
  491. string password = param[0];
  492. // Authenticate user
  493. AuthUser_EventArgs aArgs = m_pServer.OnAuthUser(this, UserName, password, "", AuthType.Plain);
  494. // There is custom error, return it
  495. if (aArgs.ErrorText != null)
  496. {
  497. BeginWriteLine("-ERR " + aArgs.ErrorText);
  498. return;
  499. }
  500. if (aArgs.Validated)
  501. {
  502. SetUserName(UserName);
  503. // Get user messages info.
  504. m_pServer.OnGetMessagesInfo(this, m_POP3_Messages);
  505. BeginWriteLine("+OK Password ok");
  506. }
  507. else
  508. {
  509. BeginWriteLine("-ERR UserName or Password is incorrect");
  510. SetUserName(""); // Reset userName !!!
  511. }
  512. }
  513. else
  514. {
  515. BeginWriteLine("-ERR Syntax error. Syntax:{PASS userName}");
  516. }
  517. }
  518. private void STAT()
  519. {
  520. /* RFC 1939 5. STAT
  521. NOTE:
  522. The positive response consists of "+OK" followed by a single
  523. space, the number of messages in the maildrop, a single
  524. space, and the size of the maildrop in octets.
  525. Note that messages marked as deleted are not counted in
  526. either total.
  527. Example:
  528. C: STAT
  529. S: +OK 2 320
  530. */
  531. if (!IsAuthenticated)
  532. {
  533. BeginWriteLine("-ERR You must authenticate first");
  534. return;
  535. }
  536. BeginWriteLine(
  537. string.Format("+OK {0} {1}", m_POP3_Messages.Count, m_POP3_Messages.GetTotalMessagesSize()));
  538. }
  539. private void LIST(string argsText)
  540. {
  541. /* RFC 1939 5. LIST
  542. Arguments:
  543. a message-number (optional), which, if present, may NOT
  544. refer to a message marked as deleted
  545. NOTE:
  546. If an argument was given and the POP3 server issues a
  547. positive response with a line containing information for
  548. that message.
  549. If no argument was given and the POP3 server issues a
  550. positive response, then the response given is multi-line.
  551. Note that messages marked as deleted are not listed.
  552. Examples:
  553. C: LIST
  554. S: +OK 2 messages (320 octets)
  555. S: 1 120
  556. S: 2 200
  557. S: .
  558. ...
  559. C: LIST 2
  560. S: +OK 2 200
  561. ...
  562. C: LIST 3
  563. S: -ERR no such message, only 2 messages in maildrop
  564. */
  565. if (!IsAuthenticated)
  566. {
  567. BeginWriteLine("-ERR You must authenticate first");
  568. return;
  569. }
  570. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  571. // Argument isn't specified, multiline response.
  572. if (argsText.Length == 0)
  573. {
  574. StringBuilder reply = new StringBuilder();
  575. reply.Append("+OK " + m_POP3_Messages.Count + " messages\r\n");
  576. // Send message number and size for each message
  577. for (int i = 0; i < m_POP3_Messages.Count; i++)
  578. {
  579. POP3_Message message = m_POP3_Messages[i];
  580. if (!message.MarkedForDelete)
  581. {
  582. reply.Append((i + 1) + " " + message.Size + "\r\n");
  583. }
  584. }
  585. // ".<CRLF>" - means end of list
  586. reply.Append(".\r\n");
  587. BeginWriteLine(reply.ToString());
  588. }
  589. else
  590. {
  591. // If parameters specified,there may be only one parameter - messageNr
  592. if (param.Length == 1)
  593. {
  594. // Check if messageNr is valid
  595. if (Core.IsNumber(param[0]))
  596. {
  597. int messageNr = Convert.ToInt32(param[0]);
  598. if (m_POP3_Messages.MessageExists(messageNr))
  599. {
  600. POP3_Message msg = m_POP3_Messages[messageNr - 1];
  601. BeginWriteLine(string.Format("+OK {0} {1}", messageNr, msg.Size));
  602. }
  603. else
  604. {
  605. BeginWriteLine("-ERR no such message, or marked for deletion");
  606. }
  607. }
  608. else
  609. {
  610. BeginWriteLine("-ERR message-number is invalid");
  611. }
  612. }
  613. else
  614. {
  615. BeginWriteLine("-ERR Syntax error. Syntax:{LIST [messageNr]}");
  616. }
  617. }
  618. }
  619. private void RETR(string argsText)
  620. {
  621. /* RFC 1939 5. RETR
  622. Arguments:
  623. a message-number (required) which may NOT refer to a
  624. message marked as deleted
  625. NOTE:
  626. If the POP3 server issues a positive response, then the
  627. response given is multi-line. After the initial +OK, the
  628. POP3 server sends the message corresponding to the given
  629. message-number, being careful to byte-stuff the termination
  630. character (as with all multi-line responses).
  631. Example:
  632. C: RETR 1
  633. S: +OK 120 octets
  634. S: <the POP3 server sends the entire message here>
  635. S: .
  636. */
  637. if (!IsAuthenticated)
  638. {
  639. BeginWriteLine("-ERR You must authenticate first");
  640. }
  641. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  642. // There must be only one parameter - messageNr
  643. if (argsText.Length > 0 && param.Length == 1)
  644. {
  645. // Check if messageNr is valid
  646. if (Core.IsNumber(param[0]))
  647. {
  648. int messageNr = Convert.ToInt32(param[0]);
  649. if (m_POP3_Messages.MessageExists(messageNr))
  650. {
  651. POP3_Message msg = m_POP3_Messages[messageNr - 1];
  652. // Raise Event, request message
  653. POP3_eArgs_GetMessageStream eArgs = m_pServer.OnGetMessageStream(this, msg);
  654. if (eArgs.MessageExists && eArgs.MessageStream != null)
  655. {
  656. WriteLine("+OK " + eArgs.MessageSize + " octets");
  657. // Send message asynchronously to client
  658. TcpStream.WritePeriodTerminated(eArgs.MessageStream);
  659. BeginRecieveCmd();
  660. }
  661. else
  662. {
  663. BeginWriteLine("-ERR no such message");
  664. }
  665. }
  666. else
  667. {
  668. BeginWriteLine("-ERR no such message");
  669. }
  670. }
  671. else
  672. {
  673. BeginWriteLine("-ERR message-number is invalid");
  674. }
  675. }
  676. else
  677. {
  678. BeginWriteLine("-ERR Syntax error. Syntax:{RETR messageNr}");
  679. }
  680. }
  681. private void DELE(string argsText)
  682. {
  683. /* RFC 1939 5. DELE
  684. Arguments:
  685. a message-number (required) which may NOT refer to a
  686. message marked as deleted
  687. NOTE:
  688. The POP3 server marks the message as deleted. Any future
  689. reference to the message-number associated with the message
  690. in a POP3 command generates an error. The POP3 server does
  691. not actually delete the message until the POP3 session
  692. enters the UPDATE state.
  693. */
  694. if (!IsAuthenticated)
  695. {
  696. BeginWriteLine("-ERR You must authenticate first");
  697. return;
  698. }
  699. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  700. // There must be only one parameter - messageNr
  701. if (argsText.Length > 0 && param.Length == 1)
  702. {
  703. // Check if messageNr is valid
  704. if (Core.IsNumber(param[0]))
  705. {
  706. int nr = Convert.ToInt32(param[0]);
  707. if (m_POP3_Messages.MessageExists(nr))
  708. {
  709. POP3_Message msg = m_POP3_Messages[nr - 1];
  710. msg.MarkedForDelete = true;
  711. BeginWriteLine("+OK marked for delete");
  712. }
  713. else
  714. {
  715. BeginWriteLine("-ERR no such message");
  716. }
  717. }
  718. else
  719. {
  720. BeginWriteLine("-ERR message-number is invalid");
  721. }
  722. }
  723. else
  724. {
  725. BeginWriteLine("-ERR Syntax error. Syntax:{DELE messageNr}");
  726. }
  727. }
  728. private void NOOP()
  729. {
  730. /* RFC 1939 5. NOOP
  731. NOTE:
  732. The POP3 server does nothing, it merely replies with a
  733. positive response.
  734. */
  735. if (!IsAuthenticated)
  736. {
  737. BeginWriteLine("-ERR You must authenticate first");
  738. return;
  739. }
  740. BeginWriteLine("+OK");
  741. }
  742. private void RSET()
  743. {
  744. /* RFC 1939 5. RSET
  745. Discussion:
  746. If any messages have been marked as deleted by the POP3
  747. server, they are unmarked. The POP3 server then replies
  748. with a positive response.
  749. */
  750. if (!IsAuthenticated)
  751. {
  752. BeginWriteLine("-ERR You must authenticate first");
  753. return;
  754. }
  755. Reset();
  756. // Raise SessionResetted event
  757. m_pServer.OnSessionResetted(this);
  758. BeginWriteLine("+OK");
  759. }
  760. private void QUIT()
  761. {
  762. /* RFC 1939 6. QUIT
  763. NOTE:
  764. The POP3 server removes all messages marked as deleted
  765. from the maildrop and replies as to the status of this
  766. operation. If there is an error, such as a resource
  767. shortage, encountered while removing messages, the
  768. maildrop may result in having some or none of the messages
  769. marked as deleted be removed. In no case may the server
  770. remove any messages not marked as deleted.
  771. Whether the removal was successful or not, the server
  772. then releases any exclusive-access lock on the maildrop
  773. and closes the TCP connection.
  774. */
  775. Update();
  776. WriteLine("+OK POP3 server signing off");
  777. }
  778. //--- Optional commands
  779. private void TOP(string argsText)
  780. {
  781. /* RFC 1939 7. TOP
  782. Arguments:
  783. a message-number (required) which may NOT refer to to a
  784. message marked as deleted, and a non-negative number
  785. of lines (required)
  786. NOTE:
  787. If the POP3 server issues a positive response, then the
  788. response given is multi-line. After the initial +OK, the
  789. POP3 server sends the headers of the message, the blank
  790. line separating the headers from the body, and then the
  791. number of lines of the indicated message's body, being
  792. careful to byte-stuff the termination character (as with
  793. all multi-line responses).
  794. Examples:
  795. C: TOP 1 10
  796. S: +OK
  797. S: <the POP3 server sends the headers of the
  798. message, a blank line, and the first 10 lines
  799. of the body of the message>
  800. S: .
  801. ...
  802. C: TOP 100 3
  803. S: -ERR no such message
  804. */
  805. if (!IsAuthenticated)
  806. {
  807. BeginWriteLine("-ERR You must authenticate first");
  808. }
  809. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  810. // There must be at two parameters - messageNr and nrLines
  811. if (param.Length == 2)
  812. {
  813. // Check if messageNr and nrLines is valid
  814. if (Core.IsNumber(param[0]) && Core.IsNumber(param[1]))
  815. {
  816. int messageNr = Convert.ToInt32(param[0]);
  817. if (m_POP3_Messages.MessageExists(messageNr))
  818. {
  819. POP3_Message msg = m_POP3_Messages[messageNr - 1];
  820. byte[] lines = m_pServer.OnGetTopLines(this, msg, Convert.ToInt32(param[1]));
  821. if (lines != null)
  822. {
  823. WriteLine("+OK " + lines.Length + " octets");
  824. // Send message asynchronously to client
  825. TcpStream.WritePeriodTerminated(new MemoryStream(lines));
  826. BeginRecieveCmd();
  827. }
  828. else
  829. {
  830. BeginWriteLine("-ERR no such message");
  831. }
  832. }
  833. else
  834. {
  835. BeginWriteLine("-ERR no such message");
  836. }
  837. }
  838. else
  839. {
  840. BeginWriteLine("-ERR message-number or number of lines is invalid");
  841. }
  842. }
  843. else
  844. {
  845. BeginWriteLine("-ERR Syntax error. Syntax:{TOP messageNr nrLines}");
  846. }
  847. }
  848. private void UIDL(string argsText)
  849. {
  850. /* RFC 1939 UIDL [msg]
  851. Arguments:
  852. a message-number (optional), which, if present, may NOT
  853. refer to a message marked as deleted
  854. NOTE:
  855. If an argument was given and the POP3 server issues a positive
  856. response with a line containing information for that message.
  857. If no argument was given and the POP3 server issues a positive
  858. response, then the response given is multi-line. After the
  859. initial +OK, for each message in the maildrop, the POP3 server
  860. responds with a line containing information for that message.
  861. Examples:
  862. C: UIDL
  863. S: +OK
  864. S: 1 whqtswO00WBw418f9t5JxYwZ
  865. S: 2 QhdPYR:00WBw1Ph7x7
  866. S: .
  867. ...
  868. C: UIDL 2
  869. S: +OK 2 QhdPYR:00WBw1Ph7x7
  870. ...
  871. C: UIDL 3
  872. S: -ERR no such message
  873. */
  874. if (!IsAuthenticated)
  875. {
  876. BeginWriteLine("-ERR You must authenticate first");
  877. return;
  878. }
  879. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  880. // Argument isn't specified, multiline response.
  881. if (argsText.Length == 0)
  882. {
  883. StringBuilder reply = new StringBuilder();
  884. reply.Append("+OK\r\n");
  885. // Send message number and size for each message
  886. for (int i = 0; i < m_POP3_Messages.Count; i++)
  887. {
  888. POP3_Message message = m_POP3_Messages[i];
  889. if (!message.MarkedForDelete)
  890. {
  891. reply.Append((i + 1) + " " + message.UID + "\r\n");
  892. }
  893. }
  894. // ".<CRLF>" - means end of list
  895. reply.Append(".\r\n");
  896. BeginWriteLine(reply.ToString());
  897. }
  898. else
  899. {
  900. // If parameters specified,there may be only one parameter - messageID
  901. if (param.Length == 1)
  902. {
  903. // Check if messageNr is valid
  904. if (Core.IsNumber(param[0]))
  905. {
  906. int messageNr = Convert.ToInt32(param[0]);
  907. if (m_POP3_Messages.MessageExists(messageNr))
  908. {
  909. POP3_Message msg = m_POP3_Messages[messageNr - 1];
  910. BeginWriteLine(string.Format("+OK {0} {1}", messageNr, msg.UID));
  911. }
  912. else
  913. {
  914. BeginWriteLine("-ERR no such message");
  915. }
  916. }
  917. else
  918. {
  919. BeginWriteLine("-ERR message-number is invalid");
  920. }
  921. }
  922. else
  923. {
  924. BeginWriteLine("-ERR Syntax error. Syntax:{UIDL [messageNr]}");
  925. }
  926. }
  927. }
  928. private void APOP(string argsText)
  929. {
  930. /* RFC 1939 7. APOP
  931. Arguments:
  932. a string identifying a mailbox and a MD5 digest string
  933. (both required)
  934. NOTE:
  935. A POP3 server which implements the APOP command will
  936. include a timestamp in its banner greeting. The syntax of
  937. the timestamp corresponds to the `msg-id' in [RFC822], and
  938. MUST be different each time the POP3 server issues a banner
  939. greeting.
  940. Examples:
  941. S: +OK POP3 server ready <1896.697170952@dbc.mtview.ca.us>
  942. C: APOP mrose c4c9334bac560ecc979e58001b3e22fb
  943. S: +OK maildrop has 1 message (369 octets)
  944. In this example, the shared secret is the string `tan-
  945. staaf'. Hence, the MD5 algorithm is applied to the string
  946. <1896.697170952@dbc.mtview.ca.us>tanstaaf
  947. which produces a digest value of
  948. c4c9334bac560ecc979e58001b3e22fb
  949. */
  950. if (IsAuthenticated)
  951. {
  952. BeginWriteLine("-ERR You are already authenticated");
  953. return;
  954. }
  955. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  956. // There must be two params
  957. if (param.Length == 2)
  958. {
  959. string userName = param[0];
  960. string md5HexHash = param[1];
  961. // Check if user isn't logged in already
  962. if (m_pServer.IsUserLoggedIn(userName))
  963. {
  964. BeginWriteLine(string.Format("-ERR User:'{0}' already logged in", userName));
  965. return;
  966. }
  967. // Authenticate user
  968. AuthUser_EventArgs aArgs = m_pServer.OnAuthUser(this,
  969. userName,
  970. md5HexHash,
  971. m_MD5_prefix,
  972. AuthType.APOP);
  973. if (aArgs.Validated)
  974. {
  975. SetUserName(userName);
  976. // Get user messages info.
  977. m_pServer.OnGetMessagesInfo(this, m_POP3_Messages);
  978. BeginWriteLine("+OK authentication was successful");
  979. }
  980. else
  981. {
  982. BeginWriteLine("-ERR authentication failed");
  983. }
  984. }
  985. else
  986. {
  987. BeginWriteLine("-ERR syntax error. Syntax:{APOP userName md5HexHash}");
  988. }
  989. }
  990. private string ReadLine()
  991. {
  992. SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[Workaround.Definitions.MaxStreamLineLength],
  993. SizeExceededAction.
  994. JunkAndThrowException);
  995. if (TcpStream.ReadLine(args, false))
  996. {
  997. return args.LineUtf8;
  998. }
  999. return string.Empty;
  1000. }
  1001. private void AUTH(string argsText)
  1002. {
  1003. /* Rfc 1734
  1004. AUTH mechanism
  1005. Arguments:
  1006. a string identifying an IMAP4 authentication mechanism,
  1007. such as defined by [IMAP4-AUTH]. Any use of the string
  1008. "imap" used in a server authentication identity in the
  1009. definition of an authentication mechanism is replaced with
  1010. the string "pop".
  1011. Possible Responses:
  1012. +OK maildrop locked and ready
  1013. -ERR authentication exchange failed
  1014. Restrictions:
  1015. may only be given in the AUTHORIZATION state
  1016. Discussion:
  1017. The AUTH command indicates an authentication mechanism to
  1018. the server. If the server supports the requested
  1019. authentication mechanism, it performs an authentication
  1020. protocol exchange to authenticate and identify the user.
  1021. Optionally, it also negotiates a protection mechanism for
  1022. subsequent protocol interactions. If the requested
  1023. authentication mechanism is not supported, the server
  1024. should reject the AUTH command by sending a negative
  1025. response.
  1026. The authentication protocol exchange consists of a series
  1027. of server challenges and client answers that are specific
  1028. to the authentication mechanism. A server challenge,
  1029. otherwise known as a ready response, is a line consisting
  1030. of a "+" character followed by a single space and a BASE64
  1031. encoded string. The client answer consists of a line
  1032. containing a BASE64 encoded string. If the client wishes
  1033. to cancel an authentication exchange, it should issue a
  1034. line with a single "*". If the server receives such an
  1035. answer, it must reject the AUTH command by sending a
  1036. negative response.
  1037. A protection mechanism provides integrity and privacy
  1038. protection to the protocol session. If a protection
  1039. mechanism is negotiated, it is applied to all subsequent
  1040. data sent over the connection. The protection mechanism
  1041. takes effect immediately following the CRLF that concludes
  1042. the authentication exchange for the client, and the CRLF of
  1043. the positive response for the server. Once the protection
  1044. mechanism is in effect, the stream of command and response
  1045. octets is processed into buffers of ciphertext. Each
  1046. buffer is transferred over the connection as a stream of
  1047. octets prepended with a four octet field in network byte
  1048. order that represents the length of the following data.
  1049. The maximum ciphertext buffer length is defined by the
  1050. protection mechanism.
  1051. The server is not required to support any particular
  1052. authentication mechanism, nor are authentication mechanisms
  1053. required to support any protection mechanisms. If an AUTH
  1054. command fails with a negative response, the session remains
  1055. in the AUTHORIZATION state and client may try another
  1056. authentication mechanism by issuing another AUTH command,
  1057. or may attempt to authenticate by using the USER/PASS or
  1058. APOP commands. In other words, the client may request
  1059. authentication types in decreasing order of preference,
  1060. with the USER/PASS or APOP command as a last resort.
  1061. Should the client successfully complete the authentication
  1062. exchange, the POP3 server issues a positive response and
  1063. the POP3 session enters the TRANSACTION state.
  1064. Examples:
  1065. S: +OK POP3 server ready
  1066. C: AUTH KERBEROS_V4
  1067. S: + AmFYig==
  1068. C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT
  1069. +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd
  1070. WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh
  1071. S: + or//EoAADZI=
  1072. C: DiAF5A4gA+oOIALuBkAAmw==
  1073. S: +OK Kerberos V4 authentication successful
  1074. ...
  1075. C: AUTH FOOBAR
  1076. S: -ERR Unrecognized authentication type
  1077. */
  1078. if (IsAuthenticated)
  1079. {
  1080. BeginWriteLine("-ERR already authenticated");
  1081. return;
  1082. }
  1083. //------ Parse parameters -------------------------------------//
  1084. string userName = "";
  1085. string password = "";
  1086. AuthUser_EventArgs aArgs = null;
  1087. string[] param = TextUtils.SplitQuotedString(argsText, ' ', true);
  1088. switch (param[0].ToUpper())
  1089. {
  1090. case "PLAIN":
  1091. BeginWriteLine("-ERR Unrecognized authentication type.");
  1092. break;
  1093. case "LOGIN":
  1094. #region LOGIN authentication
  1095. //---- AUTH = LOGIN ------------------------------
  1096. /* Login
  1097. C: AUTH LOGIN-MD5
  1098. S: + VXNlcm5hbWU6
  1099. C: username_in_base64
  1100. S: + UGFzc3dvcmQ6
  1101. C: password_in_base64
  1102. VXNlcm5hbWU6 base64_decoded= USERNAME
  1103. UGFzc3dvcmQ6 base64_decoded= PASSWORD
  1104. */
  1105. // Note: all strings are base64 strings eg. VXNlcm5hbWU6 = UserName.
  1106. // Query UserName
  1107. WriteLine("+ VXNlcm5hbWU6");
  1108. string userNameLine = ReadLine();
  1109. // Encode username from base64
  1110. if (userNameLine.Length > 0)
  1111. {
  1112. userName = Encoding.Default.GetString(Convert.FromBase64String(userNameLine));
  1113. }
  1114. // Query Password
  1115. WriteLine("+ UGFzc3dvcmQ6");
  1116. string passwordLine = ReadLine();
  1117. // Encode password from base64
  1118. if (passwordLine.Length > 0)
  1119. {
  1120. password = Encoding.Default.GetString(Convert.FromBase64String(passwordLine));
  1121. }
  1122. aArgs = m_pServer.OnAuthUser(this, userName, password, "", AuthType.Plain);
  1123. // There is custom error, return it
  1124. if (aArgs.ErrorText != null)
  1125. {
  1126. BeginWriteLine("-ERR " + aArgs.ErrorText);
  1127. return;
  1128. }
  1129. if (aArgs.Validated)
  1130. {
  1131. SetUserName(userName);
  1132. // Get user messages info.
  1133. m_pServer.OnGetMessagesInfo(this, m_POP3_Messages);
  1134. BeginWriteLine("+OK Authentication successful.");
  1135. }
  1136. else
  1137. {
  1138. BeginWriteLine("-ERR Authentication failed");
  1139. }
  1140. #endregion
  1141. break;
  1142. case "CRAM-MD5":
  1143. #region CRAM-MD5 authentication
  1144. /* Cram-M5
  1145. C: AUTH CRAM-MD5
  1146. S: + <md5_calculation_hash_in_base64>
  1147. C: base64(decoded:username password_hash)
  1148. */
  1149. string md5Hash = "<" + Guid.NewGuid().ToString().ToLower() + ">";
  1150. WriteLine("+ " + Convert.ToBase64String(Encoding.ASCII.GetBytes(md5Hash)));
  1151. string reply = ReadLine();
  1152. reply = Encoding.Default.GetString(Convert.FromBase64String(reply));
  1153. string[] replyArgs = reply.Split(' ');
  1154. userName = replyArgs[0];
  1155. aArgs = m_pServer.OnAuthUser(this, userName, replyArgs[1], md5Hash, AuthType.CRAM_MD5);
  1156. // There is custom error, return it
  1157. if (aArgs.ErrorText != null)
  1158. {
  1159. BeginWriteLine("-ERR " + aArgs.ErrorText);
  1160. return;
  1161. }
  1162. if (aArgs.Validated)
  1163. {
  1164. SetUserName(userName);
  1165. // Get user messages info.
  1166. m_pServer.OnGetMessagesInfo(this, m_POP3_Messages);
  1167. BeginWriteLine("+OK Authentication successful.");
  1168. }
  1169. else
  1170. {
  1171. BeginWriteLine("-ERR Authentication failed");
  1172. }
  1173. #endregion
  1174. break;
  1175. case "DIGEST-MD5":
  1176. #region DIGEST-MD5 authentication
  1177. /* RFC 2831 AUTH DIGEST-MD5
  1178. *
  1179. * Example:
  1180. *
  1181. * C: AUTH DIGEST-MD5
  1182. * S: + base64(realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",algorithm=md5-sess)
  1183. * C: base64(username="chris",realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",
  1184. * nc=00000001,cnonce="OA6MHXh6VqTrRk",digest-uri="imap/elwood.innosoft.com",
  1185. * response=d388dad90d4bbd760a152321f2143af7,qop=auth)
  1186. * S: + base64(rspauth=ea40f60335c427b5527b84dbabcdfffd)
  1187. * C:
  1188. * S: +OK Authentication successful.
  1189. */
  1190. string realm = LocalHostName;
  1191. string nonce = AuthHelper.GenerateNonce();
  1192. WriteLine("+ " +
  1193. AuthHelper.Base64en(AuthHelper.Create_Digest_Md5_ServerResponse(realm,
  1194. nonce)));
  1195. string clientResponse = AuthHelper.Base64de(ReadLine());
  1196. // Check that realm and nonce in client response are same as we specified
  1197. if (clientResponse.IndexOf("realm=\"" + realm + "\"") > - 1 &&
  1198. clientResponse.IndexOf("nonce=\"" + nonce + "\"") > - 1)
  1199. {
  1200. // Parse user name and password compare value
  1201. // string userName = "";
  1202. string passwData = "";
  1203. string cnonce = "";
  1204. foreach (string clntRespParam in clientResponse.Split(','))
  1205. {
  1206. if (clntRespParam.StartsWith("username="))
  1207. {
  1208. userName = clntRespParam.Split(new[] {'='}, 2)[1].Replace("\"", "");
  1209. }
  1210. else if (clntRespParam.StartsWith("response="))
  1211. {
  1212. passwData = clntRespParam.Split(new[] {'='}, 2)[1];
  1213. }
  1214. else if (clntRespParam.StartsWith("cnonce="))
  1215. {
  1216. cnonce = clntRespParam.Split(new[] {'='}, 2)[1].Replace("\"", "");
  1217. }
  1218. }
  1219. aArgs = m_pServer.OnAuthUser(this,
  1220. userName,
  1221. passwData,
  1222. clientResponse,
  1223. AuthType.DIGEST_MD5);
  1224. // There is custom error, return it
  1225. if (aArgs.ErrorText != null)
  1226. {
  1227. BeginWriteLine("-ERR " + aArgs.ErrorText);
  1228. return;
  1229. }
  1230. if (aArgs.Validated)
  1231. {
  1232. // Send server computed password hash
  1233. WriteLine("+ " + AuthHelper.Base64en("rspauth=" + aArgs.ReturnData));
  1234. // We must got empty line here
  1235. clientResponse = ReadLine();
  1236. if (clientResponse == "")
  1237. {
  1238. SetUserName(userName);
  1239. m_pServer.OnGetMessagesInfo(this, m_POP3_Messages);
  1240. BeginWriteLine("+OK Authentication successful.");
  1241. }
  1242. else
  1243. {
  1244. BeginWriteLine("-ERR Authentication failed");
  1245. }
  1246. }
  1247. else
  1248. {

Large files files are truncated, but you can click here to view the full file