SMTP_Session.cs
上传用户:horngjaan
上传日期:2009-12-12
资源大小:2882k
文件大小:32k
源码类别:

Email服务器

开发平台:

C#

  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Threading;
  6. using System.Collections;
  7. using System.Text;
  8. namespace LumiSoft.MailServer.SMTP
  9. {
  10. /// <summary>
  11. /// SMTP Session.
  12. /// </summary>
  13. internal class SMTP_Session
  14. {
  15. private Socket      m_pClientSocket       = null;    // Referance to client Socket.
  16. private SMTP_Server m_pSMTP_Server        = null;    // Referance to SMTP server.
  17. private string      m_SessionID           = "";      // Holds session ID.
  18. private string      m_ConnectedIp         = "";      // Holds connected computer's IP.
  19. private string      m_ConnectedHostName   = "";      // Holds connected computer's name.
  20. private string      m_Reverse_path        = "";      // Holds sender's reverse path.
  21. private Hashtable   m_Forward_path        = null;    // Holds Mail to.
  22. private bool        m_Authenticated       = false;   // Holds authentication flag.
  23. private int         m_BadCmdCount         = 0;       // Holds number of bad commands.
  24. private SMTP_Cmd_Validator m_CmdValidator = null;
  25. private _LogWriter  m_pLogWriter          = null;
  26. // Events
  27. internal ValidateIPHandler       ValidateIPAddress       = null;
  28. internal AuthUserEventHandler    AuthUser                = null;
  29. internal ValidateMailFromHandler ValidateMailFrom        = null;
  30. internal ValidateMailToHandler   ValidateMailTo          = null;
  31. internal ValidateMailboxSize     ValidateMailboxSize = null;
  32. internal NewMailEventHandler     NewMailEvent            = null;
  33. /// <summary>
  34. /// Default constructor.
  35. /// </summary>
  36. /// <param name="clientSocket">Referance to socket.</param>
  37. /// <param name="server">Referance to SMTP server.</param>
  38. /// <param name="sessionID">Session ID which is assigned to this session.</param>
  39. /// <param name="logWriter">Log writer.</param>
  40. public SMTP_Session(Socket clientSocket,SMTP_Server server,string sessionID,_LogWriter logWriter)
  41. {
  42. m_pClientSocket = clientSocket;
  43. m_pSMTP_Server  = server;
  44. m_SessionID     = sessionID;
  45. m_Forward_path  = new Hashtable();
  46. m_CmdValidator  = new SMTP_Cmd_Validator();
  47. m_pLogWriter    = logWriter;
  48. }
  49. #region function StartProcessing
  50. /// <summary>
  51. /// Starts session processing.
  52. /// </summary>
  53. public void StartProcessing()
  54. {
  55. try
  56. {
  57. // Store client ip and host name.
  58. m_ConnectedIp = Core.ParseIP_from_EndPoint(m_pClientSocket.RemoteEndPoint.ToString());
  59. m_ConnectedHostName = Core.GetHostName(m_ConnectedIp);
  60. // Check if ip is allowed to connect this computer
  61. if(OnValidate_IpAddress(m_ConnectedIp)){
  62.  
  63. // Notify that server is ready
  64. SendData("220 " + Dns.GetHostName() + " Service readyrn");
  65. //------ Create command loop --------------------------------//
  66. // Loop while QUIT cmd or Session TimeOut.
  67. int lastCmdTime = Environment.TickCount;
  68. string lastCmd  = "";
  69. while(true)
  70. {
  71. // If there is any data available, begin command reading.
  72. if(m_pClientSocket.Available > 0){
  73. ReadReplyCode replyCode = Core.ReadLineFromSocket(m_pClientSocket,out lastCmd,m_pSMTP_Server.CommandIdleTimeOut);
  74. if(replyCode == ReadReplyCode.Ok){
  75. if(SwitchCommand(lastCmd)){
  76. break;
  77. }
  78. }
  79. //----- There was bad command (usually cmd isn't terminated with '<CRLF>') -------------//
  80. else{
  81. //---- Check that maximum bad commands count isn't exceeded ---------------//
  82. if(m_BadCmdCount > m_pSMTP_Server.MaxBadCommands-1){
  83. SendData("421 Too many bad commands, closing transmission channelrn");
  84. break;
  85. }
  86. m_BadCmdCount++;
  87. //-------------------------------------------------------------------------//
  88. switch(replyCode)
  89. {
  90. case ReadReplyCode.LenghtExceeded:
  91. SendData("500 Line too long.rn");
  92. break;
  93. case ReadReplyCode.TimeOut:
  94. SendData("500 Command timeout.rn");
  95. break;
  96. case ReadReplyCode.UnKnownError:
  97. SendData("500 UnKnown Error.rn");
  98. m_pSMTP_Server.OnSysError(new Exception("ReadReplyCode.UnKnownError"),new System.Diagnostics.StackTrace());
  99. break;
  100. }
  101. }
  102. //------- End of bad command ------------------------------------------------------------//
  103. // reset last command time
  104. lastCmdTime = Environment.TickCount;
  105. }
  106. //----- Session timeout stuff ------------------------------------------------//
  107. if(m_pClientSocket.Available == 0){
  108. if(Environment.TickCount > lastCmdTime + m_pSMTP_Server.SessionIdleTimeOut){
  109. // Notify for closing
  110. SendData("421 Session timeout, closing transmission channelrn");
  111. break;
  112. }
  113. // Wait 100ms to save CPU, otherwise while loop may take 100% CPU. 
  114. Thread.Sleep(100);
  115. }
  116. //---------------------------------------------------------------------------//
  117. }
  118. }
  119. }
  120. catch(ThreadInterruptedException e){
  121. }
  122. catch(Exception x){
  123. /* RFC 2821 3.9
  124. NOTE:
  125. An SMTP server which is forcibly shut down via external means SHOULD
  126. attempt to send a line containing a 421 response code to the SMTP
  127. client before exiting.  The SMTP client will normally read the 421
  128. response code after sending its next command.
  129. */
  130. if(m_pClientSocket.Connected){
  131. SendData("421 Service not available, closing transmission channelrn");
  132. return;
  133. }
  134. if(!m_pClientSocket.Connected){
  135. m_pLogWriter.AddEntry("Connection is aborted by client machine",this.SessionID,m_ConnectedIp,"x");
  136. return;
  137. }
  138. m_pSMTP_Server.OnSysError(x,new System.Diagnostics.StackTrace());
  139. }
  140. finally{
  141. m_pSMTP_Server.RemoveSession(this.SessionID,m_pLogWriter);
  142. m_pClientSocket.Close();
  143. // Write logs to log file, if needed
  144. if(m_pSMTP_Server.LogCommands){
  145. m_pLogWriter.Flush();
  146. }
  147. }
  148. }
  149. #endregion
  150. #region function SwitchCommand
  151. /// <summary>
  152. /// Executes SMTP command.
  153. /// </summary>
  154. /// <param name="SMTP_commandTxt">Original command text.</param>
  155. /// <returns>Returns true if must end session(command loop).</returns>
  156. private bool SwitchCommand(string SMTP_commandTxt)
  157. {
  158. try
  159. {
  160. //---- Parse command --------------------------------------------------//
  161. string[] cmdParts = SMTP_commandTxt.TrimStart().Split(new char[]{' '});
  162. string SMTP_command = cmdParts[0];
  163.    SMTP_command = SMTP_command.ToUpper();
  164. //---------------------------------------------------------------------//
  165. //----- loging stuff --------------------------------------------------------------------------//
  166. if(m_pSMTP_Server.LogCommands){
  167. string lCmdTxt = SMTP_commandTxt.Replace("rn","<CRLF>");
  168. m_pLogWriter.AddEntry(lCmdTxt,this.SessionID,m_ConnectedIp,"C");
  169. }
  170. //---- End of loging ---------------------------------------------------------------------------//
  171. switch(SMTP_command)
  172. {
  173. case "HELO":
  174. HELO();
  175. break;
  176. case "EHLO":
  177. EHLO();
  178. break;
  179. case "AUTH":
  180. AUTH(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  181. break;
  182. case "MAIL":
  183. MAIL(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  184. break;
  185. case "RCPT":
  186. RCPT(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  187. break;
  188. case "DATA":
  189. DATA(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  190. break;
  191. case "RSET":
  192. RSET(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  193. break;
  194. case "VRFY":
  195. VRFY();
  196. break;
  197. case "EXPN":
  198. EXPN();
  199. break;
  200. case "HELP":
  201. HELP();
  202. break;
  203. case "NOOP":
  204. NOOP();
  205. return true;
  206. case "QUIT":
  207. QUIT(Core.GetArgsText(SMTP_commandTxt,SMTP_command));
  208. return true;
  209. default:
  210. SendData("500 command unrecognizedrn");
  211. //---- Check that maximum bad commands count isn't exceeded ---------------//
  212. if(m_BadCmdCount > m_pSMTP_Server.MaxBadCommands-1){
  213. SendData("421 Too many bad commands, closing transmission channelrn");
  214. return true;
  215. }
  216. m_BadCmdCount++;
  217. //-------------------------------------------------------------------------//
  218. // Core.WriteLog(m_ErrorFile,"SMTP SwitchCommand default:" + SMTP_command + " IP:" + m_ConnectedIp);
  219. break;
  220. }
  221. }
  222. catch(Exception x)
  223. {
  224. // Connection lost
  225. if(!m_pClientSocket.Connected){
  226. return true;
  227. }
  228. SendData("500 Unkown temp errorrn");
  229. m_pSMTP_Server.OnSysError(x,new System.Diagnostics.StackTrace());
  230. }
  231. return false;
  232. }
  233. #endregion
  234. #region function HELO
  235. private void HELO()
  236. {
  237. /* Rfc 2821 4.1.1.1
  238. These commands, and a "250 OK" reply to one of them, confirm that
  239. both the SMTP client and the SMTP server are in the initial state,
  240. that is, there is no transaction in progress and all state tables and
  241. buffers are cleared.
  242. Syntax:
  243.  "HELO" SP Domain CRLF
  244. */
  245. SendData("250 " + Dns.GetHostName() + " Hello [" + m_ConnectedIp + "]rn");
  246. m_CmdValidator.Helo_ok = true;
  247. }
  248. #endregion
  249. #region function EHLO
  250. private void EHLO()
  251. {
  252. /* Rfc 2821 4.1.1.1
  253. These commands, and a "250 OK" reply to one of them, confirm that
  254. both the SMTP client and the SMTP server are in the initial state,
  255. that is, there is no transaction in progress and all state tables and
  256. buffers are cleared.
  257. */
  258. string reply = "" +
  259. "250-" + Dns.GetHostName() + " Hello [" + m_ConnectedIp + "]rn" +
  260. "250-SIZE " + m_pSMTP_Server.MaxMessageSize + "rn" +
  261. // "250-DSNrn"  +
  262. // "250-HELPrn" +
  263. "250-AUTH LOGINrn" +
  264.     "250 Okrn";
  265. SendData(reply);
  266. m_CmdValidator.Helo_ok = true;
  267. }
  268. #endregion
  269. #region function AUTH
  270. private void AUTH(string argsText)
  271. {
  272. /* Rfc 2554 AUTH --------------------------------------------------//
  273. Restrictions:
  274.          After an AUTH command has successfully completed, no more AUTH
  275.  commands may be issued in the same session.  After a successful
  276.  AUTH command completes, a server MUST reject any further AUTH
  277.  commands with a 503 reply.
  278. */
  279. if(m_Authenticated){
  280. SendData("503 already authenticatedrn");
  281. return;
  282. }
  283. //------ Parse parameters -------------------------------------//
  284. string userName = "";
  285. string password = "";
  286. string[] param = argsText.Split(new char[]{' '});
  287. switch(param[0].ToUpper())
  288. {
  289. case "PLAIN":
  290. SendData("504 Unrecognized authentication type.rn");
  291. break;
  292. case "LOGIN":
  293. #region LOGIN authentication
  294.     //---- AUTH = LOGIN ------------------------------
  295. // Note: all strings are base64 strings eg. VXNlcm5hbWU6 = UserName.
  296.     // Query UserName
  297. SendData("334 VXNlcm5hbWU6rn");
  298. string userNameLine = "";
  299. if(Core.ReadLineFromSocket(m_pClientSocket,out userNameLine,m_pSMTP_Server.CommandIdleTimeOut) == ReadReplyCode.Ok){
  300. // Encode username from base64
  301. userName = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(userNameLine));
  302. //----- loging stuff --------------------------------------------------------------------------//
  303. if(m_pSMTP_Server.LogCommands){
  304. string uLogTxt = userNameLine.Replace("rn","<CRLF>");
  305. m_pLogWriter.AddEntry(uLogTxt,this.SessionID,m_ConnectedIp,"C");
  306. }
  307. //---- End of loging ---------------------------------------------------------------------------//
  308. // Query Password
  309. SendData("334 UGFzc3dvcmQ6rn");
  310. string passwordLine = "";
  311. if(Core.ReadLineFromSocket(m_pClientSocket,out passwordLine,m_pSMTP_Server.CommandIdleTimeOut) == ReadReplyCode.Ok){
  312. // Encode password from base64
  313. password = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(passwordLine));
  314. //----- loging stuff --------------------------------------------------------------------------//
  315. if(m_pSMTP_Server.LogCommands){
  316. string pLogTxt = passwordLine.Replace("rn","<CRLF>");
  317. m_pLogWriter.AddEntry(pLogTxt,this.SessionID,m_ConnectedIp,"C");
  318. }
  319. //---- End of loging ---------------------------------------------------------------------------//
  320. }
  321. else{
  322. SendData("500 Syntax error, command unrecognizedrn");
  323. }
  324. }
  325. else{
  326. SendData("500 Syntax error, command unrecognizedrn");
  327. }
  328. if(OnAuthUser(userName,password)){
  329. SendData("235 Authentication successful.rn");
  330. m_Authenticated = true;
  331. }
  332. else{
  333. SendData("535 Authentication failedrn");
  334. }
  335. #endregion
  336. break;
  337. case "DIGEST-MD5":
  338. SendData("504 Unrecognized authentication type.rn");
  339. break;
  340. default:
  341. SendData("504 Unrecognized authentication type.rn");
  342. break;
  343. }
  344. //-----------------------------------------------------------------//
  345. /* Rfc 2554 AUTH
  346. Remarks: 
  347. If an AUTH command fails, the server MUST behave the same as if
  348. the client had not issued the AUTH command.
  349. */
  350. }
  351. #endregion
  352. #region function MAIL
  353. private void MAIL(string argsText)
  354. {
  355. /* RFC 2821 3.3
  356. NOTE:
  357. This command tells the SMTP-receiver that a new mail transaction is
  358. starting and to reset all its state tables and buffers, including any
  359. recipients or mail data.  The <reverse-path> portion of the first or
  360. only argument contains the source mailbox (between "<" and ">"
  361. brackets), which can be used to report errors (see section 4.2 for a
  362. discussion of error reporting).  If accepted, the SMTP server returns
  363.  a 250 OK reply.
  364.  
  365. MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
  366. reverse-path = "<" [ A-d-l ":" ] Mailbox ">"
  367. Mailbox = Local-part "@" Domain
  368. Examples:
  369. C: MAIL FROM:<ned@thor.innosoft.com>
  370. C: MAIL FROM:<ned@thor.innosoft.com> SIZE=500000
  371. */
  372. if(!m_CmdValidator.MayHandle_MAIL){
  373. if(m_CmdValidator.MailFrom_ok){
  374. SendData("503 Sender already specifiedrn");
  375. }
  376. else{
  377. SendData("503 Bad sequence of commandsrn");
  378. }
  379. return;
  380. }
  381. //------ Parse parameters -------------------------------------------------------------------//
  382. string reverse_path = "";
  383. string senderEmail  = "";
  384. long   messageSize  = 0;
  385. bool   isFromParam  = false;
  386. _Parameter[] param = _ParamParser.Parse(argsText,new string[]{"FROM:","SIZE="},new string[]{":","="});
  387. foreach(_Parameter parameter in param){
  388. // Possible params:
  389. // FROM:
  390. // SIZE=
  391. switch(parameter.ParamName.ToUpper()) // paramInf[0] because of param syntax: pramName =/: value
  392. {
  393. //------ Required paramters -----//
  394. case "FROM:":
  395. if(parameter.ParamValue.Length == 0){
  396. SendData("501 Sender address isn't specified. Syntax:{MAIL FROM:<address> [SIZE=msgSize]}rn");
  397. return;
  398. }
  399. else{
  400. reverse_path = parameter.ParamValue;
  401. isFromParam = true;
  402. }
  403. break;
  404. //------ Optional parameters ---------------------//
  405. case "SIZE=":
  406. if(parameter.ParamValue.Length == 0){
  407. SendData("501 Size parameter isn't specified. Syntax:{MAIL FROM:<address> [SIZE=msgSize]}rn");
  408. return;
  409. }
  410. else{
  411. if(Core.IsNumber(parameter.ParamValue)){
  412. messageSize = Convert.ToInt64(parameter.ParamValue);
  413. }
  414. }
  415. break;
  416. default:
  417. SendData("501 Invalid parameter. Syntax:{MAIL FROM:<address> [SIZE=msgSize]}rn");
  418. return;
  419. }
  420. }
  421. // If required parameter 'FROM:' is missing
  422. if(!isFromParam){
  423. SendData("501 Required param FROM: is missing. Syntax:{MAIL FROM:<address> [SIZE=msgSize]}rn");
  424. return;
  425. }
  426. // Parse sender's email address
  427. senderEmail = Core.ParseEmailFromPath(reverse_path);
  428. //---------------------------------------------------------------------------------------------//
  429. // Check message size
  430. if(m_pSMTP_Server.MaxMessageSize > messageSize){
  431. // Check if sender is ok
  432. if(OnValidate_MailFrom(reverse_path,senderEmail)){
  433. SendData("250 OK <" + senderEmail + "> Sender okrn");
  434. m_Reverse_path = reverse_path;
  435. // See note above
  436. ResetState();
  437. m_CmdValidator.MailFrom_ok = true;
  438. }
  439. else{
  440. SendData("550 You are refused to send mail herern");
  441. }
  442. }
  443. else{
  444. SendData("552 Message exceeds allowed sizern");
  445. }
  446. }
  447. #endregion
  448. #region function RCPT
  449. private void RCPT(string argsText)
  450. {
  451. /* RFC 2821 4.1.1.3 RCPT
  452. NOTE:
  453. This command is used to identify an individual recipient of the mail
  454. data; multiple recipients are specified by multiple use of this
  455. command.  The argument field contains a forward-path and may contain
  456. optional parameters.
  457. Relay hosts SHOULD strip or ignore source routes, and
  458. names MUST NOT be copied into the reverse-path.  
  459. Example:
  460. RCPT TO:<@hosta.int,@jkl.org:userc@d.bar.org>
  461. will normally be sent directly on to host d.bar.org with envelope
  462. commands
  463. RCPT TO:<userc@d.bar.org>
  464. RCPT TO:<userc@d.bar.org> SIZE=40000
  465. RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
  466. */
  467. /* RFC 2821 3.3
  468. If a RCPT command appears without a previous MAIL command, 
  469. the server MUST return a 503 "Bad sequence of commands" response.
  470. */
  471. if(!m_CmdValidator.MayHandle_RCPT){
  472. SendData("503 Bad sequence of commandsrn");
  473. return;
  474. }
  475. // Check that recipient count isn't exceeded
  476. if(m_Forward_path.Count > m_pSMTP_Server.MaxRecipients){
  477. SendData("452 Too many recipientsrn");
  478. return;
  479. }
  480. //------ Parse parameters -------------------------------------------------------------------//
  481. string forward_path   = "";
  482. string recipientEmail = "";
  483. long   messageSize    = 0;
  484. bool   isToParam      = false;
  485. _Parameter[] param = _ParamParser.Parse(argsText,new string[]{"TO:","SIZE="},new string[]{":","="});
  486. foreach(_Parameter parameter in param){
  487. // Possible params:
  488. // TO:
  489. // SIZE=
  490. switch(parameter.ParamName.ToUpper()) // paramInf[0] because of param syntax: pramName =/: value
  491. {
  492. //------ Required paramters -----//
  493. case "TO:":
  494. if(parameter.ParamValue.Length == 0){
  495. SendData("501 Recipient address isn't specified. Syntax:{RCPT TO:<address> [SIZE=msgSize]}rn");
  496. return;
  497. }
  498. else{
  499. forward_path = parameter.ParamValue;
  500. isToParam = true;
  501. }
  502. break;
  503. //------ Optional parameters ---------------------//
  504. case "SIZE=":
  505. if(parameter.ParamValue.Length == 0){
  506. SendData("501 Size parameter isn't specified. Syntax:{RCPT TO:<address> [SIZE=msgSize]}rn");
  507. return;
  508. }
  509. else{
  510. if(Core.IsNumber(parameter.ParamValue)){
  511. messageSize = Convert.ToInt64(parameter.ParamValue);
  512. }
  513. }
  514. break;
  515. default:
  516. SendData("501 Invalid parameter. Syntax:{RCPT TO:<address> [SIZE=msgSize]}rn");
  517. return;
  518. }
  519. }
  520. // If required parameter 'TO:' is missing
  521. if(!isToParam){
  522. SendData("501 Required param TO: is missing. Syntax:<RCPT TO:{address> [SIZE=msgSize]}rn");
  523. return;
  524. }
  525. // Parse recipient's email address
  526. recipientEmail = Core.ParseEmailFromPath(forward_path);
  527. //---------------------------------------------------------------------------------------------//
  528. // Check message size
  529. if(m_pSMTP_Server.MaxMessageSize > messageSize){
  530. // Check if email address is ok
  531. if(OnValidate_MailTo(forward_path,recipientEmail)){
  532. // Check if mailbox size isn't exceeded
  533. if(Validate_MailBoxSize(recipientEmail,messageSize)){
  534. // Store reciptient
  535. if(!m_Forward_path.Contains(recipientEmail)){
  536. m_Forward_path.Add(recipientEmail,forward_path);
  537. }
  538. SendData("250 OK <" + recipientEmail + "> Recipient okrn");
  539. m_CmdValidator.RcptTo_ok = true;
  540. }
  541. else{
  542. SendData("552 Mailbox size limit exceededrn");
  543. }
  544. }
  545. else{
  546. SendData("550 <" + recipientEmail + "> No such user herern");
  547. }
  548. }
  549. else{
  550. SendData("552 Message exceeds allowed sizern");
  551. }
  552. }
  553. #endregion
  554. #region function DATA
  555. private void DATA(string argsText)
  556. {
  557. /* RFC 2821 4.1.1
  558. NOTE:
  559. Several commands (RSET, DATA, QUIT) are specified as not permitting
  560. parameters.  In the absence of specific extensions offered by the
  561. server and accepted by the client, clients MUST NOT send such
  562. parameters and servers SHOULD reject commands containing them as
  563. having invalid syntax.
  564. */
  565. if(argsText.Length > 0){
  566. SendData("500 Syntax error. Syntax:{DATA}rn");
  567. return;
  568. }
  569. /* RFC 2821 4.1.1.4 DATA
  570. NOTE:
  571. If accepted, the SMTP server returns a 354 Intermediate reply and
  572. considers all succeeding lines up to but not including the end of
  573. mail data indicator to be the message text.  When the end of text is
  574. successfully received and stored the SMTP-receiver sends a 250 OK
  575. reply.
  576. The mail data is terminated by a line containing only a period, that
  577. is, the character sequence "<CRLF>.<CRLF>" (see section 4.5.2).  This
  578. is the end of mail data indication.
  579. When the SMTP server accepts a message either for relaying or for
  580. final delivery, it inserts a trace record (also referred to
  581. interchangeably as a "time stamp line" or "Received" line) at the top
  582. of the mail data.  This trace record indicates the identity of the
  583. host that sent the message, the identity of the host that received
  584. the message (and is inserting this time stamp), and the date and time
  585. the message was received.  Relayed messages will have multiple time
  586. stamp lines.  Details for formation of these lines, including their
  587. syntax, is specified in section 4.4.
  588.    
  589. */
  590. /* RFC 2821 DATA
  591. NOTE:
  592. If there was no MAIL, or no RCPT, command, or all such commands
  593. were rejected, the server MAY return a "command out of sequence"
  594. (503) or "no valid recipients" (554) reply in response to the DATA
  595. command.
  596. */
  597. if(!m_CmdValidator.MayHandle_DATA){
  598. SendData("503 Bad sequence of commandsrn");
  599. return;
  600. }
  601. // reply: 354 Start mail input
  602. SendData("354 Start mail input; end with <CRLF>.<CRLF>rn");
  603. //---- Construct server headers -------------------------------------------------------------------//
  604. byte[] headers = null;
  605. string header  = "Received: from " + this.m_ConnectedHostName + " (" + this.m_ConnectedIp + ")rn"; 
  606.    header += "tby " + Dns.GetHostName() + " with SMTP; " + Core.GetDateTimeNow() + "rn";
  607.     
  608. headers = System.Text.Encoding.ASCII.GetBytes(header.ToCharArray());
  609. //-------------------------------------------------------------------------------------------------//
  610. MemoryStream reply = null;
  611. ReadReplyCode replyCode = Core.ReadReplyFromSocket(m_pClientSocket,out reply,headers,m_pSMTP_Server.MaxMessageSize,m_pSMTP_Server.CommandIdleTimeOut,"rn.rn",".rn",false);
  612. if(replyCode == ReadReplyCode.Ok){
  613.                 
  614. //------- Do period handling and raise store event  --------//
  615. // If line starts with '.', mail client adds additional '.',
  616. // remove them.
  617. using(MemoryStream msgStream = Core.DoPeriodHandling(reply,false)){
  618. reply.Close();
  619. // Raise NewMail event
  620. OnNewMail(msgStream);
  621. // Send ok - got message
  622. SendData("250 OKrn");
  623. }
  624. //----------------------------------------------------------//
  625. /* RFC 2821 4.1.1.4 DATA
  626. NOTE:
  627. Receipt of the end of mail data indication requires the server to
  628. process the stored mail transaction information.  This processing
  629. consumes the information in the reverse-path buffer, the forward-path
  630. buffer, and the mail data buffer, and on the completion of this
  631. command these buffers are cleared.
  632. */
  633. ResetState();
  634. }
  635. else{
  636. if(replyCode == ReadReplyCode.LenghtExceeded){
  637. SendData("552 Requested mail action aborted: exceeded storage allocationrn");
  638. }
  639. else{
  640. SendData("500 Error mail not terminated with '.'rn");
  641. }
  642. }
  643. }
  644. #endregion
  645. #region function RSET
  646. private void RSET(string argsText)
  647. {
  648. /* RFC 2821 4.1.1
  649. NOTE:
  650. Several commands (RSET, DATA, QUIT) are specified as not permitting
  651. parameters.  In the absence of specific extensions offered by the
  652. server and accepted by the client, clients MUST NOT send such
  653. parameters and servers SHOULD reject commands containing them as
  654. having invalid syntax.
  655. */
  656. if(argsText.Length > 0){
  657. SendData("500 Syntax error. Syntax:{RSET}rn");
  658. return;
  659. }
  660. /* RFC 2821 4.1.1.5 RESET (RSET)
  661. NOTE:
  662. This command specifies that the current mail transaction will be
  663. aborted.  Any stored sender, recipients, and mail data MUST be
  664. discarded, and all buffers and state tables cleared.  The receiver
  665. MUST send a "250 OK" reply to a RSET command with no arguments.
  666. */
  667. ResetState();
  668. SendData("250 OKrn");
  669. }
  670. #endregion
  671. #region function VRFY
  672. private void VRFY()
  673. {
  674. /* RFC 821 VRFY 
  675. Example:
  676. S: VRFY Lumi
  677. R: 250 Ivar Lumi <ivx@lumisoft.ee>
  678. S: VRFY lum
  679. R: 550 String does not match anything.  
  680. */
  681. // ToDo: Parse user, add new event for cheking user
  682. // SendData("250 OKrn");
  683. SendData("502 Command not implementedrn");
  684. }
  685. #endregion
  686. #region function NOOP
  687. private void NOOP()
  688. {
  689. /* RFC 2821 4.1.1.9 NOOP (NOOP)
  690. NOTE:
  691. This command does not affect any parameters or previously entered
  692. commands.  It specifies no action other than that the receiver send
  693. an OK reply.
  694. */
  695. SendData("250 OKrn");
  696. }
  697. #endregion
  698. #region function QUIT
  699. private void QUIT(string argsText)
  700. {
  701. /* RFC 2821 4.1.1
  702. NOTE:
  703. Several commands (RSET, DATA, QUIT) are specified as not permitting
  704. parameters.  In the absence of specific extensions offered by the
  705. server and accepted by the client, clients MUST NOT send such
  706. parameters and servers SHOULD reject commands containing them as
  707. having invalid syntax.
  708. */
  709. if(argsText.Length > 0){
  710. SendData("500 Syntax error. Syntax:<QUIT>rn");
  711. return;
  712. }
  713. /* RFC 2821 4.1.1.10 QUIT (QUIT)
  714. NOTE:
  715. This command specifies that the receiver MUST send an OK reply, and
  716. then close the transmission channel.
  717. */
  718. // reply: 221 - Close transmission cannel
  719. SendData("221 Service closing transmission channelrn");
  720. }
  721. #endregion
  722. //---- Optional commands
  723. #region function EXPN
  724. private void EXPN()
  725. {
  726. /* RFC 821 EXPN 
  727. NOTE:
  728. This command asks the receiver to confirm that the argument
  729. identifies a mailing list, and if so, to return the
  730. membership of that list.  The full name of the users (if
  731. known) and the fully specified mailboxes are returned in a
  732. multiline reply.
  733. Example:
  734. S: EXPN lsAll
  735. R: 250-ivar lumi <ivx@lumisoft.ee>
  736. R: 250-<willy@lumisoft.ee>
  737. R: 250 <kaido@lumisoft.ee>
  738. */
  739. // SendData("250 OKrn");
  740. SendData("502 Command not implementedrn");
  741. }
  742. #endregion
  743. #region function HELP
  744. private void HELP()
  745. {
  746. /* RFC 821 HELP
  747. NOTE:
  748. This command causes the receiver to send helpful information
  749. to the sender of the HELP command.  The command may take an
  750. argument (e.g., any command name) and return more specific
  751. information as a response.
  752. */
  753. // SendData("250 OKrn");
  754. SendData("502 Command not implementedrn");
  755. }
  756. #endregion
  757. #region function ResetState
  758. private void ResetState()
  759. {
  760. //--- Reset variables
  761. m_Forward_path.Clear();
  762. m_Reverse_path  = "";
  763. // m_Authenticated = false; // ??? must clear or not, no info.
  764. m_CmdValidator.Reset();
  765. m_CmdValidator.Helo_ok = true;
  766. }
  767. #endregion
  768. #region function SendData
  769. /// <summary>
  770. /// Sends data to socket.
  771. /// </summary>
  772. /// <param name="data">String data wich to send.</param>
  773. private void SendData(string data)
  774. {
  775. Byte[] byte_data = System.Text.Encoding.ASCII.GetBytes(data.ToCharArray());
  776. int nCount = m_pClientSocket.Send(byte_data,byte_data.Length,0);
  777. if(m_pSMTP_Server.LogCommands){
  778. data = data.Replace("rn","<CRLF>");
  779. m_pLogWriter.AddEntry(data,this.SessionID,m_ConnectedIp,"S");
  780. }
  781. }
  782. #endregion
  783. #region Properties Implementation
  784. /// <summary>
  785. /// Gets session ID.
  786. /// </summary>
  787. public string SessionID
  788. {
  789. get{ return m_SessionID; }
  790. }
  791. #endregion
  792. #region Events Implementaion
  793. #region function OnValidate_IpAddress
  794. /// <summary>
  795. /// 
  796. /// </summary>
  797. /// <param name="ConnectedIP"></param>
  798. /// <returns></returns>
  799. protected virtual bool OnValidate_IpAddress(string ConnectedIP) 
  800. {
  801. ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(ConnectedIP);
  802. if(this.ValidateIPAddress != null){
  803. this.ValidateIPAddress(this, oArg);
  804. }
  805. return oArg.Validated;
  806. }
  807. #endregion
  808. #region function OnAuthUser
  809. /// <summary>
  810. /// 
  811. /// </summary>
  812. /// <param name="userName"></param>
  813. /// <param name="password"></param>
  814. /// <returns></returns>
  815. protected bool OnAuthUser(string userName,string password)
  816. {
  817. AuthUser_EventArgs oArgs = new AuthUser_EventArgs(m_ConnectedIp,userName,password,"",AuthType.Plain);
  818. if(this.AuthUser != null){
  819. this.AuthUser(this,oArgs);
  820. }
  821. return oArgs.Validated;
  822. }
  823. #endregion
  824. #region function OnValidate_MailFrom
  825. /// <summary>
  826. /// 
  827. /// </summary>
  828. /// <param name="reverse_path"></param>
  829. /// <param name="email"></param>
  830. /// <returns></returns>
  831. protected virtual bool OnValidate_MailFrom(string reverse_path,string email) 
  832. {
  833. ValidateSender_EventArgs oArg = new ValidateSender_EventArgs(m_ConnectedIp,email);
  834. if(this.ValidateMailFrom != null){
  835. this.ValidateMailFrom(this, oArg);
  836. }
  837. return true;
  838. }
  839. #endregion
  840. #region function OnValidate_MailTo
  841. /// <summary>
  842. /// 
  843. /// </summary>
  844. /// <param name="forward_path"></param>
  845. /// <param name="email"></param>
  846. /// <returns></returns>
  847. protected virtual bool OnValidate_MailTo(string forward_path,string email) 
  848. {
  849. ValidateRecipient_EventArgs oArg = new ValidateRecipient_EventArgs(m_ConnectedIp,email,m_Authenticated);
  850. if(this.ValidateMailTo != null){
  851. this.ValidateMailTo(this, oArg);
  852. }
  853. return oArg.Validated;
  854. }
  855. #endregion
  856. #region function Validate_MailBoxSize
  857. /// <summary>
  858. /// 
  859. /// </summary>
  860. /// <param name="eAddress"></param>
  861. /// <param name="messageSize"></param>
  862. /// <returns></returns>
  863. protected bool Validate_MailBoxSize(string eAddress,long messageSize)
  864. {
  865. ValidateMailboxSize_EventArgs oArgs = new ValidateMailboxSize_EventArgs(eAddress,messageSize);
  866. if(this.ValidateMailboxSize != null){
  867. this.ValidateMailboxSize(this,oArgs);
  868. }
  869. return oArgs.IsValid;
  870. }
  871. #endregion
  872. #region function OnNewMail
  873. /// <summary>
  874. /// 
  875. /// </summary>
  876. /// <param name="msgStream"></param>
  877. protected virtual void OnNewMail(MemoryStream msgStream) 
  878. {
  879. string[] to = new string[m_Forward_path.Count];
  880. m_Forward_path.Values.CopyTo(to,0);
  881. // New mail event; 
  882. NewMail_EventArgs oArg = new NewMail_EventArgs(m_ConnectedIp,m_ConnectedHostName,m_Reverse_path,to,msgStream);
  883. if(this.NewMailEvent != null){
  884. this.NewMailEvent(this, oArg);
  885. }
  886. }
  887. #endregion
  888. #endregion
  889. }
  890. }