SmtpServer.pas
上传用户:dh8233980
上传日期:2014-10-16
资源大小:1015k
文件大小:52k
源码类别:

Email服务器

开发平台:

Delphi

  1. unit SmtpServer;
  2. (******************************************************************************)
  3. (*                                                                            *)
  4. (* SMTP Server Objects                                                        *)
  5. (* Part of Hermes SMTP/POP3 Server.                                           *)
  6. (* Copyright(C) 2000 by Alexander J. Fanti, All Rights Reserver Worldwide.    *)
  7. (*                                                                            *)
  8. (* Contains: TSmtpServer, TSmtpConnection                                     *)
  9. (*                                                                            *)
  10. (* Created January 12, 2000 by Alexander J. Fanti.  See License.txt           *)
  11. (*                                                                            *)
  12. (* Depends on: MailRouting (ListServer, MailDeliver)                          *)
  13. (*             DataU1                                                         *)
  14. (*             UtilU1                                                         *)
  15. (*                                                                            *)
  16. (* Also Uses: WSocket (Francois Piette Internet Component Suite)              *)
  17. (*                                                                            *)
  18. (* Used by: Main                                                              *)
  19. (*                                                                            *)
  20. (* Description:                                                               *)
  21. (* TSmtpServer - This server object manages the Smtp connections, controls    *)
  22. (*               listening for connections, and accepts them.                 *)
  23. (* TSmtpConnection - The Connection object is the real server.  It handles    *)
  24. (*                   the individual Smtp connection, and any requests by the  *)
  25. (*                   connected user.                                          *)
  26. (*                                                                            *)
  27. (* Revisions: 1/14/2000  AJF  Commented                                       *)
  28. (*            1/21/2000  AJF  Commented                                       *)
  29. (*            2/12/2000  AJF  Added Access control to Smtp Server to deter    *)
  30. (*                            spam                                            *)
  31. (*            2/13/2000  AJF  Re-worked anti-spam to be more user friendly    *)
  32. (*                                                                            *)
  33. (******************************************************************************)
  34. interface
  35. uses Classes, SysUtils, FileCtrl, ExtCtrls, INIFiles, Messages, Windows,
  36.      WSocket,
  37.      MailRouting, {TMessageInformation, TListServer, TDeliverMail}
  38.      DataU1;
  39. const
  40.   CRLF = #13 + #10;
  41.   WM_FREECONNECTION = WM_USER + 100;
  42. type
  43.   TSmtpConnection = class;  // Forward declaration of later object
  44.   TSmtpServer_ErrorCode = (sec_CantListen, sec_Bad_Mailbox_Path,
  45.                            sec_SocketError);
  46.   TSmtpServer_StateChange = procedure(Sender : TObject; Active : Boolean;
  47.                                       OpenConnections : Longint) of Object;
  48.   TSmtpServer_StatusUpdate = procedure(Sender : TObject; Status : String;
  49.                                        Level : Integer) of Object;
  50.   TSmtpServer_Error = procedure(Sender : TObject;
  51.                                 ErrorCode : TSmtpServer_ErrorCode) of Object;
  52.   TSmtpServer = class(TWHComponent)
  53.   private
  54.     FSocket : TWSocket;        // Socket for Listening for Smtp requests
  55.     ConnectionList : TList;    // List of TSmtpConnection objects currently open
  56.     // Bind address, Port, server name, mailboxpath, queuepath and other
  57.     // Smtp Server settings come straight from global INI object (DataU1)
  58.     FActive : Boolean;   // True when server is listening.  This flag is used
  59.                          // because there are more socket states than listen
  60.                          // and not.  we want to reduce them to 2, a binary.
  61.     FOnStateChange : TSmtpServer_StateChange;   // Event Ptr for OnChangeState
  62.     FOnStatusUpdate : TSmtpServer_StatusUpdate; // Event Ptr for StatusUpdate
  63.     FOnError : TSmtpServer_Error;               // Event Ptr for Server Error
  64.     // We use this to let the connection object tell us when it's finished
  65.     // so we can free it!
  66.     procedure WindowsMessage(Sender : TObject; Msg: TMessage);
  67.     function GetActive : Boolean;            // Read if server's active
  68.     function GetConnectionCount : Longint;   // Read SmtpConnection Count
  69.     // Socket procedures for Listening socket
  70.     procedure SocketSessionAvailable(Sender: TObject; Error: Word);
  71.     procedure SocketChangeState(Sender: TObject;
  72.                                 OldState, NewState: TSocketState);
  73.     procedure SocketSessionClosed(Sender: TObject; Error: Word);
  74.     procedure SocketError(Sender: TObject);
  75.     procedure SocketBgException(Sender: TObject; E: Exception;
  76.                                 var CanClose: Boolean);
  77.     // Triggered when TSmtpConnection fires a StatusUpdate event
  78.     procedure SmtpConnectionStatusUpdate(Sender : TObject; Status : String;
  79.                                          Level : Integer);
  80.     // Triggered when TSmtpConnection fires a ConnectionClosed event
  81.     procedure SmtpConnectionClosed(Sender : TObject);
  82.     procedure CloseConnection(AConnection : TSmtpConnection);
  83.     procedure CloseAllConnections;
  84.   public
  85.     constructor Create(AOwner : TComponent); Override;
  86.     destructor Destroy; Override;
  87.     // Methods
  88.     procedure Listen;   // Listen for Smtp Connections
  89.     procedure Stop;     // Stop listening for Smtp Connections
  90.     procedure Shutdown; // Stop listening and close all open Smtp Connections
  91.     procedure ConnectionInactivityTimeout(Minutes : Integer);
  92.     // Properties
  93.     property Active : Boolean read GetActive;
  94.     function PublicServerName : String;
  95.     property Count : Longint read GetConnectionCount;
  96.     // Events
  97.     property OnStateChange : TSmtpServer_StateChange    // Fired on Server State
  98.              read FOnStateChange write FOnStateChange;  // Change (Active)
  99.     property OnStatusUpdate : TSmtpServer_StatusUpdate  // Fired on Status
  100.              read FOnStatusUpdate write FOnStatusUpdate;// Update
  101.     property OnError : TSmtpServer_Error                // Fired on Server Error
  102.              read FOnError write FOnError;
  103.   end;
  104.   TSmtpConnection_State = (scs_IDENTIFICATION, scs_WAITCOMMAND,
  105.                            scs_RECEIVINGMAIL, scs_RECEIVINGMAILTO,
  106.                            scs_RECEIVINGMAILDATA);
  107.   TSmtpConnection_StatusUpdate = procedure(Sender : TObject; Status : String;
  108.                                            Level : Integer) of Object;
  109.   TSmtpConnection = class(TComponent)
  110.   private
  111.     FServer : TSmtpServer;
  112.     FSocket : TWSocket;        // Socket for Talking to Smtp User
  113.     FBufferStr : String;       // To buffer commands from Socket
  114.     FState  : TSmtpConnection_State;  // State of Smtp Connection
  115.     FLastActivity : TDateTime; // Time of last activity (for timeout)
  116.     AccCtrl_FromAcceptedAddress : Boolean;  // MAIL FROM is from an Accepted
  117.                                             // IP Address
  118.     AccCtrl_FromBannedAddress   : Boolean;  // MAIL FROM or connection is from
  119.                                             // a banned IP Address
  120.     AccCtrl_FromIPAddress : String; // IP Address of connected sender
  121.     FUserID : String;  // The Machine Address given when they connect
  122.     FMessageInfo : TSmtpMessageInformation;   // Smtp Message information user
  123.                                               // sends us through the connection
  124.     FOnStatusUpdate : TSmtpConnection_StatusUpdate; // Event Ptr for StatusUpd.
  125.     procedure StatusUpdate(Status : String; Level : Integer);
  126.                  // Used internally to Trigger Status Update
  127.     procedure ProcessRequest(UserRequest : String);  // Process Smtp Request
  128.     // Socket Procedures for Connected Socket
  129.     procedure SocketDataAvailable(Sender: TObject; Error: Word);
  130.     procedure SocketDataSent(Sender: TObject; Error: Word);
  131.     procedure SocketSessionClosed(Sender: TObject; Error: Word);
  132.     procedure SocketError(Sender: TObject);
  133.     procedure SocketBgException(Sender: TObject; E: Exception;
  134.                                 var CanClose: Boolean);
  135.     procedure Close;                          // Close connection and terminate
  136.   public
  137.     constructor Create(AOwner : TSmtpServer);
  138.     destructor Destroy; Override;
  139.     procedure Accept(SocketHandle : Integer); // Smtp Connection
  140.     property LastActivity : TDateTime read FLastActivity;
  141.     property OnStatusUpdate : TSmtpConnection_StatusUpdate // Fired on Status
  142.              read FOnStatusUpdate write FOnStatusUpdate;   // Update
  143.   end;
  144. implementation
  145. uses UtilU1; {For Domain Name formatting}
  146. (******************************************************************************)
  147. (*                                                                            *)
  148. (*  START SMTP Server Object                                                  *)
  149. (*                                                                            *)
  150. (* This Object listens for connections, accepts them and tracks them.  It     *)
  151. (* also reports on them, and can drop them.                                   *)
  152. (*                                                                            *)
  153. (******************************************************************************)
  154. constructor TSmtpServer.Create(AOwner : TComponent);
  155. begin
  156.   inherited Create(AOwner);
  157.   OnWindowsMessage := WindowsMessage;
  158.   // Initialize variables
  159.   ConnectionList := TList.Create;
  160.   FActive := False;
  161.   // Listening Socket Create and Setup
  162.   FSocket := TWSocket.Create(Self);
  163.   FSocket.OnSessionAvailable := SocketSessionAvailable;
  164.   FSocket.OnChangeState      := SocketChangeState;
  165.   FSocket.OnSessionClosed    := SocketSessionClosed;
  166.   FSocket.OnError            := SocketError;
  167.   FSocket.OnBgException      := SocketBgException;
  168. end;
  169. procedure TSmtpServer.WindowsMessage(Sender : TObject; Msg: TMessage);
  170. begin
  171.   if Msg.Msg = WM_FREECONNECTION then
  172.     CloseConnection(TSmtpConnection(Msg.WParam));
  173. end;
  174. destructor TSmtpServer.Destroy;
  175. begin
  176.   if Assigned(FSocket) then begin
  177.     FSocket.Destroy;
  178.     FSocket := nil;
  179.   end;
  180.   CloseAllConnections;
  181.   ConnectionList.Free;
  182.   inherited Destroy;
  183. end;
  184. procedure TSmtpServer.CloseConnection(AConnection : TSmtpConnection);
  185. var
  186.   x : Longint;
  187.   Connection : TSmtpConnection;
  188. begin
  189.   for x := ConnectionList.Count -1 downto 0 do begin
  190.     Connection := TSmtpConnection(ConnectionList[x]);
  191.     if Connection = AConnection then begin
  192.       ConnectionList.Delete(x);
  193.       AConnection.Free;
  194.       if Assigned(FOnStateChange) then
  195.         OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  196.     end;
  197.   end;
  198. end;
  199. procedure TSmtpServer.CloseAllConnections;
  200. var
  201.   x : Longint;
  202.   Connection : TSmtpConnection;
  203. begin
  204.   for x := ConnectionList.Count -1 downto 0 do begin
  205.     Connection := TSmtpConnection(ConnectionList[x]);
  206.     CloseConnection(Connection);
  207.   end;
  208. end;
  209. procedure TSmtpServer.ConnectionInactivityTimeout(Minutes : Integer);
  210. const
  211.   HOUR = 0.04167;
  212.   MINUTE = 0.00069;
  213. var
  214.   x : Longint;
  215.   Connection : TSmtpConnection;
  216.   DT : TDateTime;
  217. begin
  218.   // See if there's a connection that hasn't been active for a while
  219.   if Minutes > 0 then begin
  220.     for x := ConnectionList.Count -1 downto 0 do begin
  221.       Connection := TSmtpConnection(ConnectionList[x]);
  222.       DT := Now - Connection.LastActivity;
  223.       if DT > (Minutes * MINUTE) then begin
  224.         if Assigned(FOnStatusUpdate) then
  225.           OnStatusUpdate(Self, '<SMTP ID ' + IntToStr(Integer(Connection)) + '> ' +
  226.                          'Closing due to inactivity (' + IntToStr(Minutes) + ' minutes).',
  227.                          STAT_SERVERERROR);
  228.         CloseConnection(Connection);
  229.       end;
  230.     end;
  231.   end;
  232. end;
  233. function TSmtpServer.GetActive : Boolean;
  234. begin
  235.   FActive := FSocket.State = wsListening;
  236.   Result := FActive;
  237. end;
  238. function TSmtpServer.PublicServerName : String;
  239. begin
  240.   Result := FormatedDomain(INI.ServerName);
  241. end;
  242. function TSmtpServer.GetConnectionCount : Longint;
  243. begin
  244.   Result := ConnectionList.Count;
  245. end;
  246. procedure TSmtpServer.Listen;
  247. begin
  248.   FSocket.Close;
  249.   FSocket.Addr := INI.Smtp_BindAddress;
  250.   FSocket.Port := IntToStr(INI.Smtp_Port);
  251.   FSocket.Proto := 'TCP';
  252.   FSocket.Listen;
  253. end;
  254. procedure TSmtpServer.Stop;
  255. begin
  256.   FSocket.Close;
  257. end;
  258. procedure TSmtpServer.Shutdown;
  259. begin
  260.   Stop;  // Stop listening for new connections
  261.   CloseAllConnections;
  262. end;
  263. procedure TSmtpServer.SocketSessionAvailable(Sender: TObject; Error: Word);
  264. var
  265.   Connection : TSmtpConnection;
  266. begin
  267.   // Smtp Session request
  268.   // Create a new Smtp Connection and accept the request to it...
  269.   Connection := TSmtpConnection.Create(Self);
  270.   Connection.OnStatusUpdate := SmtpConnectionStatusUpdate;
  271.   ConnectionList.Add(Connection);  // Keep track of the connection (add to list)
  272.   if Assigned(FOnStateChange) then
  273.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  274.   Connection.Accept(FSocket.Accept);
  275. end;
  276. procedure TSmtpServer.SocketChangeState(Sender: TObject;
  277.                                         OldState, NewState: TSocketState);
  278. var
  279.   OldActive : Boolean;
  280. begin
  281.   OldActive := FActive;
  282.   FActive := FSocket.State = wsListening;
  283.   if (OldActive <> FActive) and Assigned(FOnStateChange) then
  284.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  285. end;
  286. procedure TSmtpServer.SocketSessionClosed(Sender: TObject; Error: Word);
  287. begin
  288.   if Assigned(FOnStateChange) then
  289.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  290. end;
  291. procedure TSmtpServer.SocketError(Sender: TObject);
  292. var
  293.   Error : Integer;
  294. begin
  295.   // Socket error.  This could be because the listening socket can't bind to
  296.   // the port (10048) or something else.  In fact, bind failure is so common,
  297.   // I'll treat it seperately.
  298.   Error := FSocket.LastError;
  299.   if Error = 10048 then begin // unable to bind to port
  300.     if Assigned(FOnStatusUpdate) then
  301.       OnStatusUpdate(Self, 'Can''t Bind to ' + INI.Smtp_BindAddress +':' +
  302.                            IntToStr(INI.Smtp_Port), STAT_SERVERERROR);
  303.     if Assigned(FOnError) then OnError(Self, sec_CantListen);
  304.   end else begin  // other error
  305.     if Assigned(FOnStatusUpdate) then
  306.       OnStatusUpdate(Self, 'Error #' + IntToStr(FSocket.LastError),
  307.                            STAT_SERVERERROR);
  308.     if Assigned(FOnError) then OnError(Self, sec_SocketError);
  309.   end;
  310. end;
  311. procedure TSmtpServer.SocketBgException(Sender: TObject; E: Exception;
  312.                                         var CanClose: Boolean);
  313. begin
  314.   // Critical Socket Error...
  315.   // This is because something caused an Exception during the socket's
  316.   // processing while it was in an event handler.
  317.   // If the program is good, this will never happen... but...
  318.   CanClose := False;
  319.   if Assigned(FOnStatusUpdate) then
  320.     OnStatusUpdate(Self, 'Unknown BG Exception', STAT_CRITICALERROR);
  321.   if Assigned(FOnError) then OnError(Self, sec_SocketError);
  322. end;
  323. procedure TSmtpServer.SmtpConnectionStatusUpdate(Sender : TObject;
  324.                                                  Status : String;
  325.                                                  Level : Integer);
  326. begin
  327.   // the SmtpConnection has something to report... I'll pass it on,
  328.   // but add where I got it from...
  329.   if Assigned(FOnStatusUpdate) then
  330.     OnStatusUpdate(Self, '<SMTP ID ' +IntToStr(Integer(Sender)) + '> ' +
  331.                    Status, Level);
  332. end;
  333. procedure TSmtpServer.SmtpConnectionClosed(Sender : TObject);
  334. var
  335.   x : Longint;
  336.   Connection : TSmtpConnection;
  337. begin
  338.   // SmtpConnection is letting us know it's destroying itself.
  339.   // we need to remove it from the connection list
  340.   if Sender is TSmtpConnection then
  341.     for x := ConnectionList.Count -1 downto 0 do begin
  342.       Connection := TSmtpConnection(ConnectionList[x]);
  343.       if Connection = Sender then ConnectionList.Delete(x);
  344.     end;
  345.   if Assigned(FOnStateChange) then
  346.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  347. end;
  348. (******************************************************************************)
  349. (*                                                                            *)
  350. (*  STOP  SMTP Server Object                                                  *)
  351. (*                                                                            *)
  352. (******************************************************************************)
  353. (******************************************************************************)
  354. (*                                                                            *)
  355. (*  START SMTP Connection Object                                              *)
  356. (*                                                                            *)
  357. (* The actual Smtp server protocols are implemented here.  This is the real   *)
  358. (* Smtp server code. We try to handle all Smtp requests here and act on       *)
  359. (* whatever we get.                                                           *)
  360. (*                                                                            *)
  361. (* Note, while all commands are implemented, not all replies are possible.    *)
  362. (* this is because we won't do certain things.  For instance, if we can't     *)
  363. (* accept a RCPT TO address, we'll never suggest an alternate route, cause    *)
  364. (* we don't store routing information.  We do DNS every time.                 *)
  365. (*                                                                            *)
  366. (******************************************************************************)
  367. constructor TSmtpConnection.Create(AOwner : TSmtpServer);
  368. begin
  369.   inherited Create(AOwner);
  370.   FServer := TSmtpServer(AOwner);
  371.   // Certain things cannot be done from event handlers.  This is because an
  372.   // event handler is basically a function call.  That means you can't free
  373.   // the sender in the handler, for example. If event handlers were activated
  374.   // by Windows messages, you could, because the code would be
  375.   // object-independant, but then you'd have messages flying everywhere...
  376.   // not pretty.
  377.   //
  378.   // Unfortunately, because of the nature of socket communications, there
  379.   // are some things you really want to do on socket events.  For example,
  380.   // free an object that contains a socket when the socket closes...
  381.   // after all, we don't need it anymore.
  382.   //
  383.   // Well, we need to do it with a Windows message to divorce the object
  384.   // from the event.  I was using TTimers, but this means every connection
  385.   // needs one.  Not too efficient.  So I made a Component that just has a
  386.   // Windows Handle so I could send messages to it, and derived the servers
  387.   // from them.  Now the connections send messages to their servers to close.
  388.   // Initialize variables
  389.   FState := scs_IDENTIFICATION;
  390.   FUserID := '';
  391.   FMessageInfo := TSmtpMessageInformation.Create;
  392.   // Connecting Socket Create and Setup
  393.   FSocket := TWSocket.Create(Self);
  394.   FSocket.OnDataAvailable    := SocketDataAvailable;
  395.   FSocket.OnDataSent         := SocketDataSent;
  396.   FSocket.OnSessionClosed    := SocketSessionClosed;
  397.   FSocket.OnError            := SocketError;
  398.   FSocket.OnBgException      := SocketBgException;
  399.   FBufferStr := '';
  400. end;
  401. destructor TSmtpConnection.Destroy;
  402. begin
  403.   FMessageInfo.Free;
  404.   if Assigned(FSocket) then begin
  405.     FSocket.Destroy;
  406.     FSocket := nil;
  407.   end;
  408.   inherited Destroy;
  409. end;
  410. procedure TSmtpConnection.StatusUpdate(Status : String; Level : Integer);
  411. begin
  412.   if Assigned(FOnStatusUpdate) then OnStatusUpdate(Self, Status, Level);
  413. end;
  414. procedure TSmtpConnection.Accept(SocketHandle : Integer);
  415. var
  416.   Accept : Boolean;
  417. begin
  418.   FSocket.Dup(SocketHandle);
  419.   StatusUpdate('Accepted', STAT_CONNECTIONEVENT);
  420.   // Initialize Connection State
  421.   FState := scs_IDENTIFICATION;
  422.   FMessageInfo.Initialize;
  423.   AccCtrl_FromAcceptedAddress := False;
  424.   AccCtrl_FromBannedAddress   := False;
  425.   AccCtrl_FromIPAddress       := '';
  426.   if FSocket.State = wsConnected then begin
  427.     StatusUpdate('Connected', STAT_CONNECTIONEVENT);
  428.     Accept := True;
  429.     AccCtrl_FromIPAddress := FSocket.GetPeerAddr;
  430.     AccCtrl_FromBannedAddress
  431.       := INI.Smtp_Access_IsThisAddressBanned(AccCtrl_FromIPAddress);
  432.     AccCtrl_FromAcceptedAddress
  433.       := INI.Smtp_Access_IsThisDomainAccepted(AccCtrl_FromIPAddress);
  434.     if INI.Smtp_Access_BanAddresses and AccCtrl_FromBannedAddress then
  435.       Accept := False;
  436.     if Accept then begin
  437.       // Send Greeting
  438.       case INI.Banner_Level of
  439.         bannerlevel_NameVersionService : FSocket.SendStr('220 ' + FServer.PublicServerName + ' Hermes ' + AppVersion + ' SMTP Ready.' + CRLF);
  440.         bannerlevel_NameService        : FSocket.SendStr('220 ' + FServer.PublicServerName + ' Hermes SMTP Ready.' + CRLF);
  441.         bannerlevel_Service            : FSocket.SendStr('220 ' + FServer.PublicServerName + ' SMTP Ready.' + CRLF);
  442.         else FSocket.SendStr('220 ' + FServer.PublicServerName + ' Hermes SMTP Ready.' + CRLF);
  443.       end;
  444.     end else begin
  445.       FSocket.SendStr('221 ' + FServer.PublicServerName +
  446.                       ' Closing Channel, Address banned.' + CRLF);
  447.       FSocket.Close;
  448.       StatusUpdate('Address ' + FUserID + ' BANNED', STAT_COMMANDERROR);
  449.     end;
  450.     // The SMTP specification allows other responses... 221 or 421, for example.
  451.     // These are to let the connected user know that while they connected, they
  452.     // can't talk to us because we're closing, or something.  We won't do that.
  453.     // We simply don't accept a connection if we don't want to talk to a user.
  454.     // If they got this far... we're willing and able to talk.
  455.   end else begin
  456.     //
  457.     // DEBUG
  458.     //
  459.     // Is there ever a time we could accept the connection and then not be
  460.     // connected?  If this happened, we assume the SessionClosed event would
  461.     // fire, thereby closing our connection and object.
  462.     //
  463.     // But we should check this out.
  464.     // This is also seen in the TPop3Connection Object
  465.   end;
  466. end;
  467. procedure TSmtpConnection.Close;
  468. begin
  469.   // We want to close.
  470.   // If the socket is open, close it... if not,
  471.   // send the message that will free this connection object
  472.   if FSocket.State <> wsClosed then FSocket.Close
  473.     else PostMessage(FServer.Handle, WM_FREECONNECTION, Integer(Self), 0);
  474. end;
  475. // procedure SocketSessionConnected(Sender: TObject; Error: Word);
  476. // Not called on accept
  477. procedure TSmtpConnection.SocketDataAvailable(Sender: TObject; Error: Word);
  478. var
  479.   x : Longint;
  480.   len : Integer;                    // Length of data accepted from the socket
  481.   Buffer : Array[0..1023] of Char;  // buffer of data we'll accept from socket
  482.                                     // we add this data to the socket's command
  483.                                     // buffer (FBufferStr) and then parse it
  484.                                     // for CRLF to seperate out commands we
  485.                                     // need to act on.
  486.   UserRequest : String;             // the command we got from the buffer
  487. begin
  488.   // Data is available from the socket for processing.
  489.   // we'll receive the data, and buffer it until we get a CRLF,
  490.   // indicating the end of some sort of command from the client
  491.   len := FSocket.Receive(@Buffer[0], 1024);
  492.   FLastActivity := Now;
  493.   // add to Command Buffer (FBufferStr)
  494.   for x := 0 to len -1 do FBufferStr := FBufferStr + Buffer[x];
  495.   // Process buffer (look for CRLF) and process each command
  496.   while Pos(CRLF, FBufferStr) > 0 do begin
  497.     UserRequest := Copy(FBufferStr, 1, Pos(CRLF, FBufferStr) -1);
  498.     FBufferStr := Copy(FBufferStr, Pos(CRLF, FBufferStr) +2,
  499.                        Length(FBufferStr));
  500.     // Process a command
  501.     ProcessRequest(UserRequest);
  502.   end;
  503. end;
  504. procedure TSmtpConnection.SocketDataSent(Sender: TObject; Error: Word);
  505. begin
  506.   FLastActivity := Now;
  507. end;
  508. procedure TSmtpConnection.ProcessRequest(UserRequest : String);
  509. var
  510.   Command, SubCommand : String;   // User Command and sub-command
  511.   Parameter : String;             // Possible Command Parameter
  512.   x : Longint;
  513.   Accept : Boolean;               // I use this to decide to accept various
  514.                                   // commands from a client (after I've
  515.                                   // considered them, of course!)
  516.   RejectReason : String;  // Reason a mail was rejected!
  517.   AliasID, AliasUser : String;    // When considering who the mail is for,
  518.                                   // we'll use these to determine if it's a
  519.                                   // mail alias, which user it belongs to
  520.   UserInfo : TPop3UserInformation;  // We use this to VeRiFY a user is here
  521.                                     // on the server
  522.   ListInfo : TMailListInformation;      // we use these to EXPaNd a mail list
  523.   ListMember : PMailListMemberInfoRec;  // to its membership if requested
  524.   SL : TStringList;
  525.   ToRoute   : TMessageRouteInformation; // Used to analyze the RCPT TO command
  526.                                         // parameter to decide if we want to
  527.                                         // accept mail bound for the destination
  528.   FromRoute : TMessageRouteInformation; // Used to analyze the MAIL FROM command
  529.                                         // parameter to decide if we want to
  530.                                         // accept mail from the source
  531.   Route : TMessageRouteInformation;
  532.   Deliverer : TDeliverMail;  // Object to handle actual delivery of mail
  533. begin
  534.   // Are we receiving mail data (message data)?  If we are, go to mail
  535.   // data processing, but if we're not, then we're accepting cammands
  536.   if FState <> scs_RECEIVINGMAILDATA then begin
  537.     // Accepting Smtp Commands
  538.     if UserRequest <> '' then begin
  539.       // Seperate out command from parameters to command
  540.       Command := UpperCase(Trim(UserRequest));
  541.       Parameter := '';
  542.       if Pos(' ', UserRequest) > 0 then begin
  543.         Command := UpperCase(Trim(Copy(UserRequest, 1,
  544.                                        Pos(' ', UserRequest))));
  545.         Parameter := Trim(Copy(UserRequest, Pos(' ', UserRequest),
  546.                                Length(UserRequest)));
  547.       end;
  548.       StatusUpdate('Command: ' + Command + ' (' + Parameter + ')',
  549.                    STAT_CONNECTIONEVENT);
  550.       // Process each command
  551.       if (Command = 'HELO') and                    // HELO machine-ID
  552.          (FState = scs_IDENTIFICATION) and
  553.          (FSocket.State = wsConnected) then begin
  554.         // User wants to identify self... Parameter is their ID
  555.         FUserID := Parameter;
  556.         // Here I could decide to accept or not...
  557.         Accept := True;
  558.         // Accept
  559.         if Accept then begin
  560.           // Accept connection request from the user.
  561.           FMessageInfo.Initialize;  // Initialize message buffer
  562.           FState := scs_WAITCOMMAND;
  563.           FSocket.SendStr('250 ' + FServer.PublicServerName + CRLF);
  564.           StatusUpdate('User ' + FUserID + ' at ' + FSocket.GetPeerAddr + ' OK',
  565.                        STAT_COMMANDEVENT);
  566.         end else begin
  567.           // Reject requesting user/machine
  568.           FSocket.SendStr('421 ' + FServer.PublicServerName +
  569.                           ' Closing Channel, Address banned.' + CRLF);
  570.           FSocket.Close;
  571.           StatusUpdate('User ' + FUserID + ' BAD', STAT_COMMANDERROR);
  572.         end;
  573.       end else
  574.       if (Command = 'MAIL') and                   // MAIL FROM:<reverse-path>
  575.          (FState = scs_WAITCOMMAND) and
  576.          (FSocket.State = wsConnected) then begin
  577.         // Verify the command is well-formed (has a FROM:)
  578.         if Pos(':', Parameter) > 0 then begin
  579.           SubCommand := UpperCase(Trim(Copy(Parameter, 1,
  580.                                             Pos(':', Parameter) -1)));
  581.           Parameter := Trim(Copy(Parameter, Pos(':', Parameter) +1,
  582.                                  Length(Parameter)));
  583.           if SubCommand = 'FROM' then begin
  584.             // Analize From (Parameter)
  585.             FromRoute := TMessageRouteInformation.Create(mrte_From);
  586.             x := FromRoute.ParseRoute(Parameter);
  587.             if (x = 0) or (x = 1) then begin
  588.               // X = 1 allows me to accept empty return routes...
  589.               // These are usually failure notices... they're OK
  590.               FMessageInfo.AccCtrl_ToLocalUser        := False;
  591.               FMessageInfo.AccCtrl_ToLocalCount       := 0;
  592.               FMessageInfo.AccCtrl_FromLocalDomain
  593.                 := INI.Domain_IsThisOneOfMine(FromRoute.Domain);
  594.               FMessageInfo.AccCtrl_FromLocalUser :=
  595.                 FMessageInfo.AccCtrl_FromLocalDomain and
  596.                 INI.Mailbox_IsThisOneOfMine(FromRoute.Mailbox);
  597.               FMessageInfo.AccCtrl_FromAcceptedDomain
  598.                 := INI.Smtp_Access_IsThisDomainAccepted(FromRoute.Domain);
  599.               FMessageInfo.AccCtrl_FromBannedDomain
  600.                 := INI.Smtp_Access_IsThisDomainBanned(FromRoute.Domain);
  601.               FMessageInfo.AccCtrl_FromBannedMailbox
  602.                 := INI.Smtp_Access_IsThisMailboxBanned(FromRoute.Mailbox);
  603.               AccCtrl_FromBannedAddress
  604.                 := INI.Smtp_Access_IsThisAddressBanned(FromRoute.Domain);
  605.               FMessageInfo.AccCtrl_MessgaeSizeInBytes := 0;
  606.               Accept := True;
  607.               // Here I'll apply my rejection/Acceptance criteria!
  608.               if INI.Smtp_Access_BanDomains and
  609.                  FMessageInfo.AccCtrl_FromBannedDomain then begin
  610.                 Accept := False;
  611.                 RejectReason := 'Domain (' + FromRoute.Domain + ') Banned';
  612.               end;
  613.               if INI.Smtp_Access_BanMailboxes and
  614.                  FMessageInfo.AccCtrl_FromBannedMailbox then begin
  615.                   Accept := False;
  616.                   RejectReason := 'Mailbox (' + FromRoute.Mailbox + ') Banned';
  617.               end;
  618.               if INI.Smtp_Access_BanAddresses and
  619.                  AccCtrl_FromBannedAddress then begin
  620.                   Accept := False;
  621.                   RejectReason := 'Address (' + FromRoute.Domain + ') Banned';
  622.               end;
  623.               // Here I could reject mail originating from any domain
  624.               // other than one of mine
  625. //              if not INI.Smtp_Forward then
  626. //                Accept := INI.Domain_IsThisOneOfMine(FromRoute.Domain);
  627.               // But I want to get mail from other people...
  628.               // so I'll accept any mail from anywhere...
  629.               if Accept then begin
  630.                 // Store the Reverse-Path Route
  631.                 FMessageInfo.ReverseRoute.ParseRoute(Parameter);
  632.                 // Now I'm ready to accept RCPT TOs
  633.                 FState := scs_RECEIVINGMAILTO;
  634.                 FSocket.SendStr('250 Sender Accepted' + CRLF);
  635.                 StatusUpdate('Mail From (' +
  636.                              FMessageInfo.ReverseRoute.BuildRoute +
  637.                              ') accepted', STAT_COMMANDEVENT);
  638.               end else begin
  639.                 // I didn't like the sender, so I'm gonna reject them
  640.                 // They can still send other MAIL FROM commands...
  641.                 FMessageInfo.Initialize;  // Initialize message buffer
  642.                 FState := scs_WAITCOMMAND;
  643.                 FSocket.SendStr('550 Sender Not Accepted: ' +
  644.                                 RejectReason + CRLF);
  645.                 StatusUpdate('Sender BAD (' + RejectReason + ')',
  646.                              STAT_COMMANDERROR);
  647.               end;
  648.             end else begin
  649.               // I can't parse the reverse-path specified...
  650.               // I have to reject the MAIL FROM request
  651.               FSocket.SendStr('501 Reverse-Path not understood' + CRLF);
  652.               StatusUpdate('Bad Reverse Path', STAT_COMMANDERROR);
  653.             end;
  654.             FromRoute.Free;
  655.           end else begin
  656.             // there's no FROM in the Mail command... again it's not correct
  657.             FSocket.SendStr('501 Mail From?' + CRLF);
  658.             StatusUpdate('Missing FROM', STAT_COMMANDERROR);
  659.           end;
  660.         end else begin
  661.           // No : in command... it's not correctly formed
  662.           FSocket.SendStr('501 Mail From?' + CRLF);
  663.           StatusUpdate('Missing :', STAT_COMMANDERROR);
  664.         end;
  665.       end else
  666.       if (Command = 'RCPT') and                  // RCPT TO:<forward-path>
  667.          (FSocket.State = wsConnected) then begin
  668.         // Are we willing to accept RCPT TOs?  Did we accept a MAIL FROM?
  669.         if (FState = scs_RECEIVINGMAILTO) then begin
  670.           FState := scs_RECEIVINGMAILTO;
  671.           // verify the command is well-formed (has a TO:)
  672.           if Pos(':', Parameter) > 0 then begin
  673.             SubCommand := UpperCase(Trim(Copy(Parameter, 1,
  674.                                               Pos(':', Parameter) -1)));
  675.             Parameter := Trim(Copy(Parameter, Pos(':', Parameter) +1,
  676.                                               Length(Parameter)));
  677.             if SubCommand = 'TO' then begin
  678.           // if accept (local or forwardable) 250(local)
  679.           //                               or 251 <forward-path> (forward)
  680.               // Analize To (Parameter)
  681.               ToRoute := TMessageRouteInformation.Create(mrte_To);
  682.               x := ToRoute.ParseRoute(Parameter);
  683.               if x = 0 then begin
  684.                 // Here I can reject mail based on who it's going to...
  685.                 // I'm going to accept mail for other domains unconditionally...
  686.                 // So local users can send mail to non-locals
  687.                 Accept := True;
  688.                 // But mail for my domain myst have a valid user mailbox...
  689.                 if INI.Domain_IsThisOneOfMine(ToRoute.Domain) then begin
  690.                   Accept := False;
  691.                   // then I check lists, users, and aliases
  692.                   if INI.Alias_Exists(ToRoute.Mailbox) or
  693.                      INI.Alias_Exists(ToRoute.Mailbox + '@' + ToRoute.Domain) or // The addressed user could be a fullt qualified mail alias!
  694.                      INI.User_Exists(ToRoute.Mailbox) or
  695.                      INI.List_Exists(ToRoute.Mailbox) then begin
  696.                        StatusUpdate('Mail To is Local', STAT_PROCESSINGEVENT);
  697.                        FMessageInfo.AccCtrl_ToLocalUser := True;
  698.                        Accept := True;
  699.                   end;
  700.                   // I could go further here...
  701.                   // UserInfo : TPop3UserInformation;
  702.                   // I could open the list or user and find out weather the
  703.                   // message was too big, or other stuff if I cared to...
  704.                 end;
  705.                 if Accept then begin
  706.                   Inc(FMessageInfo.AccCtrl_ToLocalCount);
  707.                   FState := scs_RECEIVINGMAILTO;
  708.                   // Still willing to accept RCPT TOs
  709.                   // Store this Receipient
  710.                   FMessageInfo.AddForwardRoute(Parameter);
  711.                   FSocket.SendStr('250 Destination Accepted' + CRLF);
  712.                   StatusUpdate('Mail To accepted', STAT_COMMANDEVENT);
  713.                 end else begin
  714.                   FState := scs_RECEIVINGMAILTO;
  715.                   // Still willing to accept RCPT TOs
  716.                   // I couldn't understand the forward-path route
  717.                   // but that's OK... I'll let then send more...
  718.                   FSocket.SendStr('550 Destination Not Accepted' + CRLF);
  719.                   StatusUpdate('Receiver BAD', STAT_COMMANDERROR);
  720.                 end;
  721.               end else begin
  722.                 FState := scs_RECEIVINGMAILTO;
  723.                 // I couldn't understand the forward-path route
  724.                 // but that's OK... I'll let then send more...
  725.                 FSocket.SendStr('501 Forward-Path not understood' + CRLF);
  726.                 StatusUpdate('Bad Forward Path', STAT_COMMANDERROR);
  727.               end;
  728.               ToRoute.Free;
  729.             end else begin
  730.               FState := scs_WAITCOMMAND;
  731.               // I'm resetting to wait for MAIL FROM again...
  732.               // there's no TO in the Rcpt command... again it's not correct
  733.               FSocket.SendStr('501 Mail To?' + CRLF);
  734.               StatusUpdate('Missing TO', STAT_COMMANDERROR);
  735.             end;
  736.           end else begin
  737.             FState := scs_WAITCOMMAND;
  738.             // I'm resetting to wait for MAIL FROM again...
  739.             // No : in command... it's not correctly formed
  740.             FSocket.SendStr('501 Mail To?' + CRLF);
  741.             StatusUpdate('Missing :', STAT_COMMANDERROR);
  742.           end;
  743.         end else begin
  744.           FState := scs_WAITCOMMAND;
  745.           // I'm resetting to wait for MAIL FROM again...
  746.           // I havn't accepted the MAIL FROM command yet.  This is premature
  747.           FSocket.SendStr('503 Please Mail From before RCPT' + CRLF);
  748.           StatusUpdate('No From yet, how can we have Tos?', STAT_COMMANDERROR);
  749.         end;
  750.       end else
  751.       if (Command = 'DATA') and                  // DATA
  752.          (FSocket.State = wsConnected) then begin
  753.         // User wants to send me some mail data (actual message)
  754.         // THey had better already have given me one Rcpt To route
  755.         if (FState = scs_RECEIVINGMAILTO) and
  756.            (FMessageInfo.ForwardRouteCount > 0) then begin
  757.           Accept := True;
  758.           if INI.Smtp_Access_Restricted then begin
  759.             if ((not AccCtrl_FromAcceptedAddress) and
  760.                 (not FMessageInfo.AccCtrl_FromAcceptedDomain) and
  761.                 (not FMessageInfo.AccCtrl_FromLocalUser) and
  762.                 (not FMessageInfo.AccCtrl_ToLocalUser)) then begin
  763.               Accept := False;
  764.               if FMessageInfo.AccCtrl_ToLocalUser then
  765.                 RejectReason := 'NOT addressed to local user.'
  766.               else
  767.                 RejectReason := 'Domain or user NOT Accepted';
  768.             end;
  769.           end;
  770.           if INI.Smtp_Access_OnlyForUnderXUsers and
  771.              (INI.Smtp_Access_OnlyForUsersCount > 0) and
  772.              (FMessageInfo.AccCtrl_ToLocalCount >
  773.               INI.Smtp_Access_OnlyForUsersCount) then begin
  774.             Accept := False;
  775.             RejectReason := 'Too Many Recipients (' +
  776.                             IntToStr(FMessageInfo.AccCtrl_ToLocalCount) + ' > '
  777.                             + IntToStr(INI.Smtp_Access_OnlyForUsersCount) + ')';
  778.           end;
  779.           if Accept then begin
  780.             FState := scs_RECEIVINGMAILDATA;
  781.             FSocket.SendStr('354 Start Mail Input, end with <CRLF>.<CRLF>' + CRLF);
  782.             StatusUpdate('Ready to accept mail', STAT_COMMANDEVENT);
  783.           end else begin
  784.             FMessageInfo.Initialize;  // Initialize message buffer
  785.             FState := scs_WAITCOMMAND;
  786.             FSocket.SendStr('451 Error in processing: ' + RejectReason + CRLF);
  787.             StatusUpdate('Mail Rejected: ' + RejectReason, STAT_COMMANDERROR);
  788.           end;
  789.         end else begin
  790.           // I've never gotten a rcpt to route...
  791.           FSocket.SendStr('503 Please RCPT TO before DATA' + CRLF);
  792.           StatusUpdate('No To yet, how can we have Data?', STAT_COMMANDERROR);
  793.         end;
  794.       end else
  795.       if (Command = 'RSET') and                  // RSET
  796. //         (FState = scs_WAITCOMMAND) and
  797.          (FSocket.State = wsConnected) then begin
  798.         // Reset connection and drop mail in progress
  799.         // OK.
  800.         FState := scs_WAITCOMMAND;
  801.         FMessageInfo.Initialize;  // Initialize message buffer
  802.         FSocket.SendStr('250 Ready.' + CRLF);
  803.         StatusUpdate('Connection Reset', STAT_COMMANDEVENT);
  804.       end else
  805.       if (Command = 'SEND') and                  // SEND FROM:<reverse-path>
  806.          (FState = scs_WAITCOMMAND) and
  807.          (FSocket.State = wsConnected) then begin
  808.         // Send to user terminal... no terminals, so not implemented
  809.         FState := scs_WAITCOMMAND;
  810.         FSocket.SendStr('502 SEND not implemented.' + CRLF);
  811.         StatusUpdate('Command not implemented', STAT_COMMANDERROR);
  812.       end else
  813.       if (Command = 'SOML') and                  // SOML FROM:<reverse-path>
  814.          (FState = scs_WAITCOMMAND) and
  815.          (FSocket.State = wsConnected) then begin
  816.         // Send to user terminal... OR Mail...
  817.         // Not implemented.  I could mail here... but
  818.         // I'm not gonna.  Let them MAIL if they want to MAIL
  819.         FState := scs_WAITCOMMAND;
  820.         FSocket.SendStr('502 SOML not implemented.' + CRLF);
  821.         StatusUpdate('Command not implemented', STAT_COMMANDERROR);
  822.       end else
  823.       if (Command = 'SAML') and                  // SAML FROM:<reverse-path>
  824.          (FState = scs_WAITCOMMAND) and
  825.          (FSocket.State = wsConnected) then begin
  826.         // Send to user terminal... AND Mail...
  827.         // Not implemented.  I could mail here... but
  828.         // I'm not gonna.  Let them MAIL if they want to MAIL
  829.         FState := scs_WAITCOMMAND;
  830.         FSocket.SendStr('502 SAML not implemented.' + CRLF);
  831.         StatusUpdate('Command not implemented', STAT_COMMANDERROR);
  832.       end else
  833.       if (Command = 'VRFY') and                  // VRFY <user>
  834.          (FState = scs_WAITCOMMAND) and
  835.          (FSocket.State = wsConnected) then begin
  836.         // Verify a user is valid...
  837.         // see if it's an alias, and if so, recover real UserID
  838.         if INI.Alias_Exists(Parameter) then begin
  839.           Parameter := INI.Alias_Find(Parameter);
  840.           INI.Alias_Parse(Parameter, AliasID, AliasUser);
  841.           Parameter := AliasUser;
  842.           StatusUpdate('Alias Converted', STAT_COMMANDEVENT);
  843.         end;
  844.         // Parameter is UserID
  845.         if INI.User_Exists(Parameter) then begin
  846.           // The user is a valid user on this system...
  847.           UserInfo := TPop3UserInformation.Create;
  848.           UserInfo.LoadFromFile(Parameter);
  849.           // we can decide how to reply now
  850.           // Maybe user wants to hide himself?
  851.           Accept := not UserInfo.UB_DoNotReportUserExists_SMTP;
  852.           if Accept then begin
  853.             // DEBUG
  854.             // It needs to be a routeable response...
  855.             // I think this'll work but I'm not certain...
  856.             FSocket.SendStr('250 ' + UserInfo.RealName + ' <' +
  857.                             FormatedAddress(Parameter, INI.ServerName) +
  858.                             '>' + CRLF);
  859.             StatusUpdate('User Reported', STAT_COMMANDEVENT);
  860.           end else begin
  861.             // Couldn't report on user... they want to stay hidden
  862.             FSocket.SendStr('550 User Unknown' + CRLF);
  863.             StatusUpdate('User is Hidden', STAT_COMMANDERROR);
  864.           end;
  865.           UserInfo.Free;
  866.         end else begin
  867.           // Couldn't find user...
  868.           FSocket.SendStr('550 User Unknown' + CRLF);
  869.           StatusUpdate('User is Unknown', STAT_COMMANDERROR);
  870.         end;
  871.       end else
  872.       if (Command = 'EXPN') and                  // EXPN <list>
  873.          (FState = scs_WAITCOMMAND) and
  874.          (FSocket.State = wsConnected) then begin
  875.         // User wants information on a Mail List...
  876.         // the list name is given in Parameter
  877.         if INI.List_Exists(Parameter) then begin
  878.           // The list exists here, not get list information
  879.           ListInfo := TMailListInformation.Create;
  880.           ListInfo.LoadFromFile(Parameter);
  881.           // we can decide how to reply now
  882.           // Maybe List wants to hide members?
  883.           Accept := not ListInfo.LB_DoNotReportListMembers_SMTP;
  884.           if Accept then begin
  885.             // Reply with list membership
  886.             // Build a StringList of the responses...
  887.             // We want to limit our responses to "Active" and non-"Hidden"
  888.             // list members with routeable addresses.
  889.             SL := TStringList.Create;
  890.             Route := TMessageRouteInformation.Create(mrte_Unknown);
  891.             for x := 0 to ListInfo.MemberCount -1 do begin
  892.               ListMember := ListInfo.Members[x];
  893.               if (ListMember.Active) and (not ListMember.Hidden) and
  894.                  (Route.ParseRoute(ListMember.EMail) = 0) then
  895.                 SL.Add(Route.BuildRoute);
  896.             end;
  897.             Route.Free;
  898.             if SL.Count = 0 then begin
  899.               // DEBUG
  900.               // List exists, but has no visible (active and not hidden)
  901.               // members... I'll call this an errer, but I'm not sure it
  902.               // really is...
  903.               FSocket.SendStr('550 List is Empty' + CRLF);
  904.               StatusUpdate('List is Empty', STAT_COMMANDERROR);
  905.             end else
  906.             if SL.Count = 1 then begin
  907.               // The list has one active and visible member... let's report
  908.               FSocket.SendStr('250 ' + SL[0] + CRLF);
  909.               StatusUpdate('List Entry Reported', STAT_COMMANDEVENT);
  910.             end else begin
  911.               // The list has several active and visible members, here they are
  912.               for x := 0 to SL.Count -2 do
  913.                 FSocket.SendStr('250-' + SL[x] + CRLF);
  914.               FSocket.SendStr('250 ' + SL[SL.Count -1] + CRLF);
  915.               StatusUpdate('List Entries Reported', STAT_COMMANDEVENT);
  916.             end;
  917.             SL.Free;
  918.           end else begin
  919.             // we didn't accept the request for some reason
  920.             // (like the list is hidden or hiding its membership)
  921.             FSocket.SendStr('550 List Unknown' + CRLF);
  922.             StatusUpdate('List is Hidden', STAT_COMMANDERROR);
  923.           end;
  924.           ListInfo.Free;
  925.         end else begin
  926.           // The listname is unknown.
  927.           FSocket.SendStr('550 List Unknown' + CRLF);
  928.           StatusUpdate('List Unknown', STAT_COMMANDERROR);
  929.         end;
  930.       end else
  931.       if (Command = 'HELP') and                  // HELP <?, optional>
  932.          (FState = scs_WAITCOMMAND) and
  933.          (FSocket.State = wsConnected) then begin
  934.         // User wants some help... yeah right ;-)
  935.         // I've got no help to offer... maybe I'll add this later, but
  936.         // realistically, this is a holdover from the days when the user
  937.         // might have been a human... now they never are.
  938.         FState := scs_WAITCOMMAND;
  939.         FSocket.SendStr('502 No Help Available' + CRLF);
  940.         StatusUpdate('Command not implemented', STAT_COMMANDEVENT);
  941.       end else
  942.       if (Command = 'NOOP') and                  // NOOP
  943.          (FState = scs_WAITCOMMAND) and
  944.          (FSocket.State = wsConnected) then begin
  945.         // User wants to do a "Noop"...
  946.         // No problaemo...
  947.         FState := scs_WAITCOMMAND;
  948.         FSocket.SendStr('250 OK' + CRLF);
  949.         StatusUpdate('No Operation', STAT_COMMANDEVENT);
  950.       end else
  951.       if (Command = 'TURN') and                  // TURN
  952.          (FState = scs_WAITCOMMAND) and
  953.          (FSocket.State = wsConnected) then begin
  954.         // User wants to switch roles.  no thanks.
  955.         // This implementation is not capeable of reversing
  956.         // course because it's implemented as two seperate
  957.         // parts (Server and Agent)
  958.         FState := scs_WAITCOMMAND;
  959.         FSocket.SendStr('502 TURN not permitted.' + CRLF);
  960.         StatusUpdate('Command not implemented', STAT_COMMANDERROR);
  961.       end else
  962.       if (Command = 'QUIT') and                  // QUIT
  963.          (FState = scs_WAITCOMMAND) and
  964.          (FSocket.State = wsConnected) then begin
  965.         // User wants to Quit
  966.         FState := scs_WAITCOMMAND;
  967. //        FSocket.SendStr('250 OK' + CRLF);
  968.         FSocket.SendStr('221 OK, Closed' + CRLF);
  969.         // Mail is processed on receipt of .
  970.         // If I never got it, then mail gets dropped
  971.         StatusUpdate('Closing connection', STAT_CONNECTIONEVENT);
  972.         Close;
  973.       end else
  974.       begin
  975.         // This is not a command I understand
  976.         if FState <> scs_IDENTIFICATION then FState := scs_WAITCOMMAND;
  977.         FSocket.SendStr('500 Command not recognized.' + CRLF);
  978.         StatusUpdate('Command not recognized', STAT_COMMANDERROR);
  979.       end;
  980.     end;
  981.   end else begin
  982.     // We're accepting Mail (message) data....
  983.     if UserRequest = '.' then begin  // end of data
  984.       // if the user sent us just a period (.) then this is the sign that
  985.       // the mail data is finished, and we should try to process it.
  986.       StatusUpdate('Processing Incoming Mail...', STAT_COMMANDEVENT);
  987.       // I need to record the fact that I received this mail in the mail header
  988.       // before trying to deliver it to anybody...
  989.       StatusUpdate('Adding Received to Message Header', STAT_PROCESSINGEVENT);
  990.       FMessageInfo.InsertReceived;
  991.       // Here's the format:
  992.       // Received: from HOST by HOST ; DD MON YY HH:MM:SS ZONE
  993.       // Now I can try to deliver the mail... anyway, this step is successful
  994.       // If there's a delivery failure, then the Smtp Agent will take care of
  995.       // that when it's processing the mail for future delivery...
  996.       Deliverer := TDeliverMail.Create(FMessageInfo);
  997.       Deliverer.OnStatusUpdate := FOnStatusUpdate;
  998.       Deliverer.Deliver;
  999.       Deliverer.Free;
  1000.       FMessageInfo.Initialize;
  1001.       // Added 5-22-2000 to fix the multi-mail to one mail (compounding) bug!
  1002.       // Now I'm ready to accept more commands...
  1003.       FState := scs_WAITCOMMAND;
  1004.       StatusUpdate('Incoming Mail Processed', STAT_COMMANDEVENT);
  1005.       FSocket.SendStr('250 Mail Queued for Delivery' + CRLF);
  1006.     end else begin
  1007.       // the data the user sent us must be added to the mail message for
  1008.       // delivery but if the line starts with a period (.) (but is longer
  1009.       // than a period) then we know they padded it to send to us, and we
  1010.       // need to remove the first period.
  1011.       if Copy(UserRequest, 1, 1) = '.' then  // Remove padded period (.)
  1012.         UserRequest := Copy(UserRequest, 2, Length(UserRequest));
  1013.       // Add data to message data already stored
  1014.       FMessageInfo.Data_AppendLine(UserRequest);
  1015.       if INI.LogSpyMessageContent then
  1016.         StatusUpdate('Data: ' + UserRequest, STAT_PROCESSINGEVENT);
  1017.     end;
  1018.   end;
  1019. end;
  1020. procedure TSmtpConnection.SocketSessionClosed(Sender: TObject; Error: Word);
  1021. begin
  1022.   StatusUpdate('Closed', STAT_CONNECTIONEVENT);
  1023.   // Socket is closed, we must tell Server Object
  1024.   // to free this connection.  There's no point to
  1025.   // carrying on without a connection, now is there?
  1026.   PostMessage(FServer.Handle, WM_FREECONNECTION, Integer(Self), 0);
  1027. end;
  1028. procedure TSmtpConnection.SocketError(Sender: TObject);
  1029. begin
  1030.   StatusUpdate('Socket Error', STAT_CONNECTIONERROR);
  1031.   // We had a socket error. This isn't a protocol error like the user
  1032.   // typed the wrong command, it's a dropped connection, or something else.
  1033.   // we'll close on this too, because we don't know how to recover from it.
  1034.   // the user can open a new connection if they really want.
  1035.   Close; // Close on Error
  1036. end;
  1037. procedure TSmtpConnection.SocketBgException(Sender: TObject; E: Exception;
  1038.                                             var CanClose: Boolean);
  1039. begin
  1040.   CanClose := False;
  1041.   StatusUpdate('Background Exception Error', STAT_CRITICALERROR);
  1042.   // We had a background exception.  This is like a socket error in that
  1043.   // we don't know what happened, and we don't know how to recover, so
  1044.   // we'd better just close this connection.
  1045.   Close; // Close on Error
  1046. end;
  1047. (******************************************************************************)
  1048. (*                                                                            *)
  1049. (*  STOP  SMTP Connection Object                                              *)
  1050. (*                                                                            *)
  1051. (******************************************************************************)
  1052. (* Bugs Fixed *)
  1053. {  // This is not a command I understand
  1054.    Was: FState := scs_WAITCOMMAND;
  1055.    Now: if FState <> scs_IDENTIFICATION then FState := scs_WAITCOMMAND;
  1056.    // Changed because a bad HELO caused us to skip the Identification state.
  1057.    // Thanks to "Vassilis Stathopoulos" <vstath@irismedia.gr> on 2/1/00
  1058. }
  1059. end.