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

Email服务器

开发平台:

Delphi

  1. unit Pop3Server;
  2. (******************************************************************************)
  3. (*                                                                            *)
  4. (* Pop3 Server Objects                                                        *)
  5. (* Part of Hermes SMTP/POP3 Server.                                           *)
  6. (* Copyright(C) 2000 by Alexander J. Fanti, All Rights Reserver Worldwide.    *)
  7. (*                                                                            *)
  8. (* Contains: TPop3Server, TPop3Connection                                     *)
  9. (*                                                                            *)
  10. (* Created January 11, 2000 by Alexander J. Fanti.  See License.txt           *)
  11. (*                                                                            *)
  12. (* Depends on: DataU1 (Pop3 User Information, Pop3 Mail Information Object)   *)
  13. (*                                                                            *)
  14. (* Also Uses: WSocket, MD5 (Francois Piette Internet Component Suite)         *)
  15. (*                                                                            *)
  16. (* Used by: Main                                                              *)
  17. (*                                                                            *)
  18. (* Implememtation Note: No support for UIDL.  This requires persistant state  *)
  19. (*                                            info for messages I don't want  *)
  20. (*                                            to store                        *)
  21. (*                      APOP support depends on Francois' MD5 code.           *)
  22. (*                      Refer to RFC 1725                                     *)
  23. (* Description:                                                               *)
  24. (* TPop3Server - This server object manages the Pop3 connections, controls    *)
  25. (*               listening for connections, and accepts them.                 *)
  26. (* TPop3Connection - The Connection object is the real server.  It handles    *)
  27. (*                   the individual Pop3 connection, and any requests by the  *)
  28. (*                   connected user.                                          *)
  29. (*                                                                            *)
  30. (*  Revisions: 1/12/2000  AJF  Commented                                      *)
  31. (*  Revisions: 1/25/2000  AJF  Commented                                      *)
  32. (*                                                                            *)
  33. (******************************************************************************)
  34. interface
  35. uses Classes, SysUtils, FileCtrl, ExtCtrls, INIFiles, Messages, Windows,
  36.      WSocket, MD5, {Francois Components, MD5 for APOP only}
  37.      DataU1;
  38. const
  39.   CRLF = #13 + #10;
  40.   WM_FREECONNECTION = WM_USER + 100;
  41. type
  42.   TPop3Connection = class;
  43.   TPop3Server_ErrorCode = (pec_Bad_Mailbox_Path, pec_CantListen,
  44.                            pec_SocketError);
  45.   TPop3Server_StateChange = procedure(Sender : TObject; Active : Boolean;
  46.                                       OpenConnections : Longint) of Object;
  47.   TPop3Server_StatusUpdate = procedure(Sender : TObject; Status : String;
  48.                                        Level : Integer) of Object;
  49.   TPop3Server_Error = procedure(Sender : TObject;
  50.                                 ErrorCode : TPop3Server_ErrorCode) of Object;
  51.   TPop3Server = class(TWHComponent)
  52.   private
  53.     FSocket : TWSocket;        // Socket for Listening for Pop3 requests
  54.     ConnectionList : TList;    // List of TPop3Connection objects currently open
  55.     // Bind address, Port, server name, mailboxpath and other Pop3 server
  56.     // settings come straight from global INI object
  57.     FActive : Boolean;                          // True when server is listening
  58.     FOnStateChange : TPop3Server_StateChange;   // Event Ptr for OnChangeState
  59.     FOnStatusUpdate : TPop3Server_StatusUpdate; // Event Ptr for StatusUpdate
  60.     FOnError : TPop3Server_Error;               // Event Ptr for Server Error
  61.     // We use this to let the connection object tell us when it's finished
  62.     // so we can free it!
  63.     procedure WindowsMessage(Sender : TObject; Msg: TMessage);
  64.     function GetActive : Boolean;            // Read if server's active
  65.     function GetConnectionCount : Longint;   // Read Pop3Connection Count
  66.     // Socket procedures for Listening socket
  67.     procedure SocketSessionAvailable(Sender: TObject; Error: Word);
  68.     procedure SocketChangeState(Sender: TObject;
  69.                                 OldState, NewState: TSocketState);
  70.     procedure SocketSessionClosed(Sender: TObject; Error: Word);
  71.     procedure SocketError(Sender: TObject);
  72.     procedure SocketBgException(Sender: TObject; E: Exception;
  73.                                 var CanClose: Boolean);
  74.     // Triggered when TPop3Connection fires a StatusUpdate event
  75.     procedure Pop3ConnectionStatusUpdate(Sender : TObject; Status : String;
  76.                                          Level : Integer);
  77.     procedure CloseConnection(AConnection : TPop3Connection);
  78.     procedure CloseAllConnections;
  79.   public
  80.     constructor Create(AOwner : TComponent); Override;
  81.     destructor Destroy; Override;
  82.     // Methods
  83.     procedure Listen;   // Listen for Pop3 Connections
  84.     procedure Stop;     // Stop listening for Pop3 Connections
  85.     procedure Shutdown; // Stop listening and close all open Pop3 Connections
  86.     // Used by TPop3Connection to see if "MailBox Locked" meaning someone is
  87.     // already talking to the mailbox (that user's logged in from elsewhere)
  88.     function IsUserAlreadyConnected(OpenConn : TPop3Connection;
  89.                                     User : String) : Boolean;
  90.     procedure ConnectionInactivityTimeout(Minutes : Integer);
  91.               // Check all connections for inactivity
  92.     // Properties
  93.     property Active : Boolean read GetActive;
  94.     property Count : Longint read GetConnectionCount;
  95.     // Events
  96.     property OnStateChange : TPop3Server_StateChange    // Fired on Server State
  97.              read FOnStateChange write FOnStateChange;  // Change (Active)
  98.     property OnStatusUpdate : TPop3Server_StatusUpdate    // Fired on Status
  99.              read FOnStatusUpdate write FOnStatusUpdate;  // Update
  100.     property OnError : TPop3Server_Error                // Fired on Server Error
  101.              read FOnError write FOnError;
  102.   end;
  103.   TPop3Connection_State = (pcs_AUTHENTICATION_WAITUSER,
  104.                            pcs_AUTHENTICATION_WAITPASS,
  105.                            pcs_TRANSACTION, pcs_UPDATE);
  106.   TPop3Connection_StatusUpdate = procedure(Sender : TObject; Status : String;
  107.                                            Level : Integer) of Object;
  108.   TPop3Connection = class(TComponent)
  109.   private
  110.     FServer : TPop3Server;
  111.     FSocket : TWSocket;        // Socket for Talking to Pop3 User
  112.     FBufferStr : String;       // To buffer commands from Socket
  113.     FState  : TPop3Connection_State;  // State of Pop3 Connection
  114.     FLastActivity : TDateTime; // Time of last activity (for timeout)
  115.     FServerMD5ID : String;            // The String send on connect (for APOP)
  116.     FUserID   : String;               // User ID (of user logged in)
  117.     FUserInfo : TPop3UserInformation; // Pop3 User Information (like password)
  118.                                       // See DataU1
  119.     FMailInfo : TPop3MailInformation; // User's Pop3 Mail information (count...)
  120.                                       // See DataU1
  121.     FOnStatusUpdate : TPop3Connection_StatusUpdate; // Event Ptr for StatusUpd.
  122.     procedure StatusUpdate(Status : String; Level : Integer);
  123.               // Used internally to Trigger Status Update
  124.     procedure ProcessRequest(UserRequest : String);  // Process Pop3 Request
  125.     // Socket Procedures for Connected Socket
  126.     procedure SocketDataAvailable(Sender: TObject; Error: Word);
  127.     procedure SocketDataSent(Sender: TObject; Error: Word);
  128.     procedure SocketSessionClosed(Sender: TObject; Error: Word);
  129.     procedure SocketError(Sender: TObject);
  130.     procedure SocketBgException(Sender: TObject; E: Exception;
  131.                                 var CanClose: Boolean);
  132.     procedure Close;                          // Close connection and terminate
  133.   public
  134.     constructor Create(AOwner : TPop3Server);
  135.     destructor Destroy; Override;
  136.     procedure Accept(SocketHandle : Integer); // Pop3 Connection
  137.     property User : String read FUserID;      // Connected User
  138.     property LastActivity : TDateTime read FLastActivity;
  139.     property OnStatusUpdate : TPop3Connection_StatusUpdate  // Fired on Status
  140.              read FOnStatusUpdate write FOnStatusUpdate;    // Update
  141.   end;
  142. implementation
  143. (******************************************************************************)
  144. (*                                                                            *)
  145. (*  START POP3 Server Object                                                  *)
  146. (*                                                                            *)
  147. (* This Object listens for connections, accepts them and tracks them.  It     *)
  148. (* also reports on them, and can drop them.                                   *)
  149. (*                                                                            *)
  150. (******************************************************************************)
  151. constructor TPop3Server.Create(AOwner : TComponent);
  152. begin
  153.   inherited Create(AOwner);
  154.   OnWindowsMessage := WindowsMessage;
  155.   // Initialize variables
  156.   ConnectionList := TList.Create;
  157.   FActive := False;
  158.   // Listening Socket Create and Setup
  159.   FSocket := TWSocket.Create(Self);
  160.   FSocket.OnSessionAvailable := SocketSessionAvailable;
  161.   FSocket.OnChangeState      := SocketChangeState;
  162.   FSocket.OnSessionClosed    := SocketSessionClosed;
  163.   FSocket.OnError            := SocketError;
  164.   FSocket.OnBgException      := SocketBgException;
  165. end;
  166. procedure TPop3Server.WindowsMessage(Sender : TObject; Msg: TMessage);
  167. begin
  168.   if Msg.Msg = WM_FREECONNECTION then
  169.     CloseConnection(TPop3Connection(Msg.WParam));
  170. end;
  171. destructor TPop3Server.Destroy;
  172. begin
  173.   if Assigned(FSocket) then begin
  174.     FSocket.Destroy;
  175.     FSocket := nil;
  176.   end;
  177.   CloseAllConnections;
  178.   ConnectionList.Free;
  179.   inherited Destroy;
  180. end;
  181. procedure TPop3Server.CloseConnection(AConnection : TPop3Connection);
  182. var
  183.   x : Longint;
  184.   Connection : TPop3Connection;
  185. begin
  186.   for x := ConnectionList.Count -1 downto 0 do begin
  187.     Connection := TPop3Connection(ConnectionList[x]);
  188.     if Connection = AConnection then begin
  189.       ConnectionList.Delete(x);
  190.       AConnection.Free;
  191.       if Assigned(FOnStateChange) then
  192.         OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  193.     end;
  194.   end;
  195. end;
  196. procedure TPop3Server.CloseAllConnections;
  197. var
  198.   x : Longint;
  199.   Connection : TPop3Connection;
  200. begin
  201.   for x := ConnectionList.Count -1 downto 0 do begin
  202.     Connection := TPop3Connection(ConnectionList[x]);
  203.     CloseConnection(Connection);
  204.   end;
  205. end;
  206. function TPop3Server.GetActive : Boolean;
  207. begin
  208.   FActive := FSocket.State = wsListening;
  209.   Result := FActive;
  210. end;
  211. function TPop3Server.GetConnectionCount : Longint;
  212. begin
  213.   Result := ConnectionList.Count;
  214. end;
  215. function TPop3Server.IsUserAlreadyConnected(OpenConn : TPop3Connection;
  216.                                             User : String) : Boolean;
  217. var
  218.   x : Longint;
  219.   Connection : TPop3Connection;
  220. begin
  221.   Result := False;
  222.   // See if there's a connection (other than the requesting one) for this user
  223.   // This is how we determine if the mailbox is "locked"
  224.   for x := ConnectionList.Count -1 downto 0 do begin
  225.     Connection := TPop3Connection(ConnectionList[x]);
  226.     if (LowerCase(Connection.User) = LowerCase(User)) and
  227.        (Connection <> OpenConn) then Result := True;
  228.   end;
  229. end;
  230. procedure TPop3Server.ConnectionInactivityTimeout(Minutes : Integer);
  231. const
  232.   HOUR = 0.04167;
  233.   MINUTE = 0.00069;
  234. var
  235.   x : Longint;
  236.   Connection : TPop3Connection;
  237.   DT : TDateTime;
  238. begin
  239.   // See if there's a connection that hasn't been active for a while
  240.   if Minutes > 0 then begin
  241.     for x := ConnectionList.Count -1 downto 0 do begin
  242.       Connection := TPop3Connection(ConnectionList[x]);
  243.       DT := Now - Connection.LastActivity;
  244.       if DT > (Minutes * MINUTE) then begin
  245.         if Assigned(FOnStatusUpdate) then
  246.           OnStatusUpdate(Self, '<POP3 ID ' + IntToStr(Integer(Connection)) + '> ' +
  247.                          'Closing due to inactivity (' + IntToStr(Minutes) + ' minutes).',
  248.                          STAT_SERVERERROR);
  249.         CloseConnection(Connection);
  250.       end;
  251.     end;
  252.   end;
  253. end;
  254. procedure TPop3Server.Listen;
  255. begin
  256.   FSocket.Close;
  257.   FSocket.Addr := INI.Pop3_BindAddress;
  258.   FSocket.Port := IntToStr(INI.Pop3_Port);
  259.   FSocket.Proto := 'TCP';
  260.   FSocket.Listen;
  261. end;
  262. procedure TPop3Server.Stop;
  263. begin
  264.   FSocket.Close;
  265. end;
  266. procedure TPop3Server.Shutdown;
  267. begin
  268.   Stop;  // Stop listening for new connections
  269.   CloseAllConnections;
  270. end;
  271. procedure TPop3Server.SocketSessionAvailable(Sender: TObject; Error: Word);
  272. var
  273.   Connection : TPop3Connection;
  274. begin
  275.   // Pop3 Seccion Request
  276.   // Create a new Pop3 Connection and accept the request to it...
  277.   Connection := TPop3Connection.Create(Self);
  278.   Connection.OnStatusUpdate := Pop3ConnectionStatusUpdate;
  279.   ConnectionList.Add(Connection);  // Keep track of the connection (add to list)
  280.   if Assigned(FOnStateChange) then
  281.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  282.   Connection.Accept(FSocket.Accept);
  283. end;
  284. procedure TPop3Server.SocketChangeState(Sender: TObject;
  285.                                         OldState, NewState: TSocketState);
  286. var
  287.   OldActive : Boolean;
  288. begin
  289.   OldActive := FActive;
  290.   FActive := FSocket.State = wsListening;
  291.   if (OldActive <> FActive) and Assigned(FOnStateChange) then
  292.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  293. end;
  294. procedure TPop3Server.SocketSessionClosed(Sender: TObject; Error: Word);
  295. begin
  296.   if Assigned(FOnStateChange) then
  297.     OnStateChange(Self, FSocket.State = wsListening, ConnectionList.Count);
  298. end;
  299. procedure TPop3Server.SocketError(Sender: TObject);
  300. var
  301.   Error : Integer;
  302. begin
  303.   // Socket error.  This could be because the listening socket can't bind to
  304.   // the port (10048) or something else.  In fact, bind failure is so common,
  305.   // I'll treat it seperately.
  306.   Error := FSocket.LastError;
  307.   if Error = 10048 then begin  // Unable to bind to port
  308.     if Assigned(FOnStatusUpdate) then
  309.       OnStatusUpdate(Self, 'Can''t Bind to ' + INI.Pop3_BindAddress +':' +
  310.                            IntToStr(INI.Pop3_Port), STAT_SERVERERROR);
  311.     if Assigned(FOnError) then OnError(Self, pec_CantListen);
  312.   end else begin      // Other Error
  313.     if Assigned(FOnStatusUpdate) then
  314.       OnStatusUpdate(Self, 'Error #' + IntToStr(FSocket.LastError),
  315.                            STAT_SERVERERROR);
  316.     if Assigned(FOnError) then OnError(Self, pec_SocketError);
  317.   end;
  318. end;
  319. procedure TPop3Server.SocketBgException(Sender: TObject; E: Exception;
  320.                                         var CanClose: Boolean);
  321. begin
  322.   // Critical Socket Error...
  323.   // This is because something caused an Exception during the socket's
  324.   // processing while it was in an event handler.
  325.   // If the program is good, this will never happen... but...
  326.   CanClose := False;
  327.   if Assigned(FOnStatusUpdate) then
  328.     OnStatusUpdate(Self, 'Unknown BG Exception', STAT_CRITICALERROR);
  329.   if Assigned(FOnError) then OnError(Self, pec_SocketError);
  330. end;
  331. procedure TPop3Server.Pop3ConnectionStatusUpdate(Sender : TObject;
  332.                                                  Status : String;
  333.                                                  Level : Integer);
  334. begin
  335.   // the Pop3Connection has something to report... I'll pass it on,
  336.   // but add where I got it from...
  337.   if Assigned(FOnStatusUpdate) then
  338.     OnStatusUpdate(Self, '<POP3 ID ' + IntToStr(Integer(Sender)) + '> ' +
  339.                          Status, Level);
  340. end;
  341. (******************************************************************************)
  342. (*                                                                            *)
  343. (*  STOP  POP3 Server Object                                                  *)
  344. (*                                                                            *)
  345. (******************************************************************************)
  346. (******************************************************************************)
  347. (*                                                                            *)
  348. (*  START POP3 Connection Object                                              *)
  349. (*                                                                            *)
  350. (* The actual Pop3 server protocols are implemented here.  This is the real   *)
  351. (* Pop3 server code. We try to handle all Pop3 requests here and act on       *)
  352. (* whatever we get.                                                           *)
  353. (*                                                                            *)
  354. (* Note: not all commands are implemented.                                    *)
  355. (*                                                                            *)
  356. (******************************************************************************)
  357. constructor TPop3Connection.Create(AOwner : TPop3Server);
  358. begin
  359.   inherited Create(AOwner);
  360.   FServer := TPop3Server(AOwner);
  361.   // Initialize variables
  362.   FLastActivity := Now;
  363.   FState := pcs_AUTHENTICATION_WAITUSER;
  364.   FServerMD5ID := '';
  365.   FUserID := '';
  366.   FUserInfo := TPop3UserInformation.Create;
  367.   FMailInfo := TPop3MailInformation.Create;
  368.   // Connecting Socket Create and Setup
  369.   FSocket := TWSocket.Create(Self);
  370.   FSocket.OnDataAvailable    := SocketDataAvailable;
  371.   FSocket.OnDataSent         := SocketDataSent;
  372.   FSocket.OnSessionClosed    := SocketSessionClosed;
  373.   FSocket.OnError            := SocketError;
  374.   FSocket.OnBgException      := SocketBgException;
  375.   FBufferStr := '';
  376. end;
  377. destructor TPop3Connection.Destroy;
  378. begin
  379.   FUserInfo.Free;
  380.   FMailInfo.Free;
  381.   if Assigned(FSocket) then begin
  382.     FSocket.Destroy;
  383.     FSocket := nil;
  384.   end;
  385.   inherited Destroy;
  386. end;
  387. procedure TPop3Connection.StatusUpdate(Status : String; Level : Integer);
  388. begin
  389.   if Assigned(FOnStatusUpdate) then OnStatusUpdate(Self, Status, Level);
  390. end;
  391. procedure TPop3Connection.Accept(SocketHandle : Integer);
  392. begin
  393.   FSocket.Dup(SocketHandle);
  394.   StatusUpdate('Accepted', STAT_CONNECTIONEVENT);
  395.   // Initialize Connection State
  396.   FState := pcs_AUTHENTICATION_WAITUSER;
  397.   if FSocket.State = wsConnected then begin
  398.     StatusUpdate('Connected', STAT_CONNECTIONEVENT);
  399.     // Determine Server Info for possible APOP command (MD5 ID)
  400.     FServerMD5ID := '<' + IntToStr(Integer(Self)) + '.' +
  401.                     FloatToStr(Now) + '@' + INI.ServerName + '>';
  402.     // Send Greeting
  403.     case INI.Banner_Level of
  404.       bannerlevel_NameVersionService : FSocket.SendStr('+OK ' + 'Hermes ' + AppVersion + ' POP3 Ready. ' + FServerMD5ID + CRLF);
  405.       bannerlevel_NameService        : FSocket.SendStr('+OK ' + 'Hermes POP3 Ready. ' + FServerMD5ID + CRLF);
  406.       bannerlevel_Service            : FSocket.SendStr('+OK ' + 'POP3 Ready. ' + FServerMD5ID + CRLF);
  407.       else FSocket.SendStr('+OK ' + 'Hermes POP3 Ready. ' + FServerMD5ID + CRLF);
  408.     end;
  409.   end else begin
  410.     //
  411.     // DEBUG
  412.     //
  413.     // Is there ever a time we could accept the connection and then not be
  414.     // connected?  If this happened, we assume the SessionClosed event would
  415.     // fire, thereby closing our connection and object.
  416.     //
  417.     // This is also seen in the TSmtpConnection Object
  418.   end;
  419. end;
  420. procedure TPop3Connection.Close;
  421. begin
  422.   // We want to close.
  423.   // If the socket is open, close it... if not,
  424.   // send the message that will free this connection object
  425.   if FSocket.State <> wsClosed then FSocket.Close
  426.     else PostMessage(FServer.Handle, WM_FREECONNECTION, Integer(Self), 0);
  427. end;
  428. // procedure SocketSessionConnected(Sender: TObject; Error: Word);
  429. // This is not called when we accept a connection
  430. procedure TPop3Connection.SocketDataAvailable(Sender: TObject; Error: Word);
  431. var
  432.   x : Longint;
  433.   len : Integer;                    // Length of data accepted from the socket
  434.   Buffer : Array[0..1023] of Char;  // buffer of data we'll accept from socket
  435.                                     // we add this data to the socket's command
  436.                                     // buffer (FBufferStr) and then parse it
  437.                                     // for CRLF to seperate out commands we
  438.                                     // need to act on.
  439.   UserRequest : String;             // the command we got from the buffer
  440. begin
  441.   // Data is available from the socket for processing.
  442.   // we'll receive the data, and buffer it until we get a CRLF,
  443.   // indicating the end of some sort of command from the client
  444.   len := FSocket.Receive(@Buffer[0], 1024);
  445.   FLastActivity := Now;  // We have activity...
  446.   // add to Command Buffer (FBufferStr)
  447.   for x := 0 to len -1 do FBufferStr := FBufferStr + Buffer[x];
  448.   // Process buffer (look for CRLF) and process each command
  449.   while Pos(CRLF, FBufferStr) > 0 do begin
  450.     UserRequest := Copy(FBufferStr, 1, Pos(CRLF, FBufferStr) -1);
  451.     FBufferStr := Copy(FBufferStr, Pos(CRLF, FBufferStr) +2,
  452.                        Length(FBufferStr));
  453.     // Process a command
  454.     ProcessRequest(UserRequest);
  455.   end;
  456. end;
  457. procedure TPop3Connection.SocketDataSent(Sender: TObject; Error: Word);
  458. begin
  459.   FLastActivity := Now;  // We have activity...
  460. end;
  461. procedure TPop3Connection.ProcessRequest(UserRequest : String);
  462. var
  463.   Command : String;                            // User Command
  464.   Parameter1, Parameter2 : String;             // Possible Command Parameters
  465.   Para_MessageID, Para_LineCount : Longint;            // Parameters specific
  466.   Para_UserName, Para_Password, Para_Digest : String;  // to given commands
  467.   x, y : Longint;
  468.   UserValid, PasswordValid : Boolean;  // Is the requested user / pwd valid?
  469.   HeaderBreak : Longint;  // The Index of the blank line of the header (for TOP)
  470.   MailItem : PPop3MailEntry;  // Pointer to a mail item (see DataU1)
  471.   SL : TStringList;  // Temporary string list for holding message to send
  472. begin
  473.   if UserRequest <> '' then begin
  474.     // Seperate out command from parameters to command
  475.     Command := UpperCase(Trim(UserRequest));
  476.     Parameter1 := '';
  477.     if Pos(' ', UserRequest) > 0 then begin
  478.       Command := UpperCase(Trim(Copy(UserRequest, 1, Pos(' ', UserRequest))));
  479.       Parameter1 := Trim(Copy(UserRequest, Pos(' ', UserRequest),
  480.                               Length(UserRequest)));
  481.     end;
  482.     StatusUpdate('Command: ' + Command, STAT_CONNECTIONEVENT);
  483.     if (Command = 'USER') and
  484.        (FState = pcs_AUTHENTICATION_WAITUSER) and
  485.        (FSocket.State = wsConnected) then begin
  486.       // User wants to log in... giving us User ID
  487.       // Determine User ID
  488.       Para_UserName := Parameter1;
  489.       UserValid := INI.User_Exists(Para_UserName);
  490.       // Do we create user on demand?
  491.       if (not UserValid) and (INI.Pop3_CreateUserOnDemand) then
  492.         if INI.User_Create(Para_UserName) then UserValid := True;
  493.       if UserValid then begin
  494.         FUserID := Para_UserName;
  495.         // Fetch user information (password and whatnot)
  496.         FUserInfo.LoadFromFile(FUserID);
  497.         FState := pcs_AUTHENTICATION_WAITPASS;
  498.         FSocket.SendStr('+OK Send Password' + CRLF);
  499.         StatusUpdate('User ' + FUserID + ' OK', STAT_COMMANDEVENT);
  500.       end else begin
  501.         FSocket.SendStr('-ERR Unknown User' + CRLF);
  502.         StatusUpdate('User ' + FUserID + ' BAD', STAT_COMMANDERROR);
  503.       end;
  504.     end else
  505.     if (Command = 'PASS') and
  506.        (FState = pcs_AUTHENTICATION_WAITPASS) and
  507.        (FSocket.State = wsConnected) then begin
  508.       // User whats to finish logging in... give us password
  509.       // Determine user password
  510.       Para_Password := Parameter1;
  511.       PasswordValid := (FUserInfo.Password = Para_Password) and
  512.                        (FUserInfo.Password <> '');
  513.       // Do we create password on demand?
  514.       if (not PasswordValid) and
  515.          (INI.Pop3_CreateUserPasswordOnDemand) then begin
  516.         FUserInfo.Password := Para_Password;
  517.         FUserInfo.SaveToFile(FUserID);
  518.         PasswordValid := True;
  519.       end;
  520.       if PasswordValid then begin
  521.         // Be sure we havn't got another connection to this user already!
  522.         if FServer.IsUserAlreadyConnected(Self, FUserID) then begin
  523.           // Mailbox locked by same user on different connection
  524.           FState := pcs_AUTHENTICATION_WAITUSER;
  525.           FSocket.SendStr('-ERR Cannot Lock Mailbox' + CRLF);
  526.           StatusUpdate('User Password OK, but mailbox locked.',
  527.                        STAT_COMMANDERROR);
  528.         end else begin
  529.           // Mailbox available, Get Mailbox information
  530.           FState := pcs_TRANSACTION;
  531.           FMailInfo.ReadFolder(FUserID);
  532.           FSocket.SendStr('+OK ' + IntToStr(FMailInfo.Count) + ' ' +
  533.                           IntToStr(FMailInfo.ByteCount) + CRLF);
  534.           StatusUpdate('User Password OK', STAT_COMMANDEVENT);
  535.         end;
  536.       end else begin
  537.         // User Password not good
  538.         FState := pcs_AUTHENTICATION_WAITUSER;
  539.         FSocket.SendStr('-ERR Password Invalid' + CRLF);
  540.         StatusUpdate('User Password BAD', STAT_COMMANDERROR);
  541.       end;
  542.     end else
  543.     if (Command = 'APOP') and                            // Optional
  544.        (FState = pcs_AUTHENTICATION_WAITUSER) and
  545.        (FSocket.State = wsConnected) then begin
  546.       // User wants to login with APOP
  547.       // Get User ID and Digest
  548.       Para_UserName := Parameter1;
  549.       Para_Digest := '';
  550.       if Pos(' ', Parameter1) > 0 then begin
  551.         Para_UserName := UpperCase(Trim(Copy(Parameter1, 1,
  552.                                              Pos(' ', Parameter1))));
  553.         Para_Digest := Trim(Copy(Parameter1, Pos(' ', Parameter1),
  554.                                  Length(Parameter1)));
  555.       end;
  556.       if (Para_UserName <> '') and (Para_Digest <> '') then begin
  557.         UserValid := DirectoryExists(INI.MailBoxPath + '' + Para_UserName);
  558.         if UserValid then begin
  559.           FUserID := LowerCase(Para_UserName);
  560.           // Can't create a user on demand without a password
  561.           FUserInfo.LoadFromFile(FUserID);
  562.           // Check digest
  563.           if StrMD5(FServerMD5ID + FUserInfo.Password) = Para_Digest then
  564.           begin
  565.             // Be sure we havn't got another connection to this user already!
  566.             if FServer.IsUserAlreadyConnected(Self, FUserID) then begin
  567.               // Mailbox locked by same user on different connection
  568.               FState := pcs_AUTHENTICATION_WAITUSER;
  569.               FSocket.SendStr('-ERR Cannot Lock Mailbox' + CRLF);
  570.               StatusUpdate('User Password OK, but mailbox locked.',
  571.                            STAT_COMMANDERROR);
  572.             end else begin
  573.               // Mailbox available, Get Mailbox information
  574.               FState := pcs_TRANSACTION;
  575.               FMailInfo.ReadFolder(FUserID);
  576.               FSocket.SendStr('+OK ' + IntToStr(FMailInfo.Count) + ' ' +
  577.                               IntToStr(FMailInfo.ByteCount) + CRLF);
  578.               StatusUpdate('User Password OK', STAT_COMMANDEVENT);
  579.             end;
  580.           end else begin
  581.             // MD5 digest didn't match... user not accepted
  582.             FState := pcs_AUTHENTICATION_WAITUSER;
  583.             FSocket.SendStr('-ERR Bad Digest' + CRLF);
  584.             StatusUpdate('User Digest BAD', STAT_COMMANDERROR);
  585.           end;
  586.         end else begin
  587.           // User Name not recognized
  588.           FSocket.SendStr('-ERR Unknown User' + CRLF);
  589.           StatusUpdate('User ' + Para_UserName + ' BAD', STAT_COMMANDERROR);
  590.         end;
  591.       end else begin
  592.         // We need a name and digest
  593.         FSocket.SendStr('-ERR Command Incomplete' + CRLF);
  594.         StatusUpdate('Command missing parameters', STAT_COMMANDERROR);
  595.       end;
  596.     end else
  597.     if (Command = 'QUIT') and
  598.        (FSocket.State = wsConnected) then begin
  599.       if FState = pcs_TRANSACTION then begin
  600.         // Enter "Update" state and Delete marked messages
  601.         x := FMailInfo.DeleteMarkedMessages;
  602.         FSocket.SendStr('+OK ' + IntToStr(x) + ' Messages Deleted.  Bye.' +
  603.                         CRLF);
  604.         StatusUpdate('Updated', STAT_CONNECTIONEVENT);
  605.       end else begin
  606.         // No update, just goodbye
  607.         FSocket.SendStr('+OK Signing Off' + CRLF);
  608.         StatusUpdate('NOT Updated', STAT_CONNECTIONEVENT);
  609.       end;
  610.       // Close socket, this will eventually terminate the component
  611.       FSocket.Close;
  612.     end else
  613.     if (Command = 'STAT') and
  614.        (FState = pcs_TRANSACTION) and
  615.        (FSocket.State = wsConnected) then begin
  616.       // Get status of mailbox
  617.       FSocket.SendStr('+OK ' + IntToStr(FMailInfo.Count) + ' ' +
  618.                       IntToStr(FMailInfo.ByteCount) + CRLF);
  619.       StatusUpdate('Status: ' + IntToStr(FMailInfo.Count) + ' ' +
  620.                    IntToStr(FMailInfo.ByteCount), STAT_COMMANDEVENT);
  621.     end else
  622.     if (Command = 'LIST') and
  623.        (FState = pcs_TRANSACTION) and
  624.        (FSocket.State = wsConnected) then begin
  625.       // List info on one or more messages
  626.       // Get message number if avaiable
  627.       if Parameter1 <> '' then begin
  628.         // We want info on a single message
  629.         try
  630.           Para_MessageID := StrToInt(Parameter1);
  631.         except
  632.           on E: Exception do Para_MessageID := -1
  633.         end;
  634.         // Find the message
  635.         MailItem := FMailInfo.Find(Para_MessageID);
  636.         if MailItem = nil then begin
  637.           FSocket.SendStr('-ERR No Such Message' + CRLF);
  638.           StatusUpdate('No such message', STAT_COMMANDERROR);
  639.         end else
  640.         if MailItem.MarkForDelete then begin
  641.           FSocket.SendStr('-ERR Message Deleted' + CRLF);
  642.           StatusUpdate('Message Deleted', STAT_COMMANDERROR);
  643.         end else begin
  644.           FSocket.SendStr('+OK ' + IntToSTr(MailItem.Number) + ' ' +
  645.                           IntToSTr(MailItem.FileSize) + CRLF);
  646.           StatusUpdate('Info: ' + IntToSTr(MailItem.Number) + ' ' +
  647.                        IntToSTr(MailItem.FileSize), STAT_COMMANDEVENT);
  648.         end;
  649.       end else begin
  650.         FSocket.SendStr('+OK ' + IntToStr(FMailInfo.Count) + ' Messages ' +
  651.                         IntToStr(FMailInfo.ByteCount) + ' octets' + CRLF);
  652.         StatusUpdate('Info: ' + IntToSTr(FMailInfo.Count) + ' Messages ' +
  653.                      IntToSTr(FMailInfo.ByteCount) + ' octets',
  654.                      STAT_COMMANDEVENT);
  655.         for x := 0 to FMailInfo.Count -1 do begin
  656.           MailItem := FMailInfo.Mail[x];
  657.           FSocket.SendStr(IntToStr(MailItem.Number) + ' ' +
  658.                           IntToStr(MailItem.FileSize) + CRLF);
  659.         end;
  660.         FSocket.SendStr('.' + CRLF);  // multi-line listings must end with '.'
  661.       end;
  662.     end else
  663.     if (Command = 'RETR') and
  664.        (FState = pcs_TRANSACTION) and
  665.        (FSocket.State = wsConnected) then begin
  666.       // User wants a message
  667.       // Which message does the user want?
  668.       if Parameter1 <> '' then begin
  669.         try
  670.           Para_MessageID := StrToInt(Parameter1);
  671.         except
  672.           on E: Exception do Para_MessageID := -1
  673.         end;
  674.         // Find the message
  675.         MailItem := FMailInfo.Find(Para_MessageID);
  676.         if MailItem = nil then begin
  677.           FSocket.SendStr('-ERR No Such Message' + CRLF);
  678.           StatusUpdate('No Such Message', STAT_COMMANDERROR);
  679.         end else
  680.         if MailItem.MarkForDelete then begin
  681.           FSocket.SendStr('-ERR Message Deleted' + CRLF);
  682.           StatusUpdate('Message Deleted', STAT_COMMANDERROR);
  683.         end else begin
  684.           FSocket.SendStr('+OK ' + IntToSTr(MailItem.FileSize) + ' octets' +
  685.                           CRLF);
  686.           StatusUpdate('Message: ' + IntToSTr(MailItem.FileSize) + ' octets',
  687.                        STAT_COMMANDEVENT);
  688.           // Message itself
  689.           SL := TStringList.Create;
  690.           SL.LoadFromFile(INI.MailBoxPath + FUserID + '' + MailItem.Filename);
  691.           // Stuff . byte
  692.           for x := 0 to SL.Count -1 do begin
  693.             if Copy(SL[x], 1, 1) = '.' then
  694.               FSocket.SendStr('.' + SL[x] + CRLF)
  695.             else
  696.               FSocket.SendStr(SL[x] + CRLF);
  697.             if x mod 700 = 0 then
  698.               StatusUpdate('Sent: ' + IntToSTr(x) + ' of ' +
  699.                            IntToStr(SL.Count) + ' lines.', STAT_PROCESSINGEVENT);
  700.           end;
  701.           SL.Free;
  702.           // Send terminator
  703.           StatusUpdate('Sent: Terminator.', STAT_PROCESSINGEVENT);
  704.           FSocket.SendStr('.' + CRLF);
  705.         end;
  706.       end else begin
  707.         FSocket.SendStr('-ERR No Message Specified' + CRLF);
  708.         StatusUpdate('No Message Specified', STAT_COMMANDERROR);
  709.       end;
  710.     end else
  711.     if (Command = 'DELE') and
  712.        (FState = pcs_TRANSACTION) and
  713.        (FSocket.State = wsConnected) then begin
  714.       // Mark a message for delete
  715.       // which message?
  716.       if Parameter1 <> '' then begin
  717.         try
  718.           Para_MessageID := StrToInt(Parameter1);
  719.         except
  720.           on E: Exception do Para_MessageID := -1
  721.         end;
  722.         // Find message
  723.         MailItem := FMailInfo.Find(Para_MessageID);
  724.         if MailItem = nil then begin
  725.           FSocket.SendStr('-ERR No Such Message' + CRLF);
  726.           StatusUpdate('No Such Message', STAT_COMMANDERROR);
  727.         end else
  728.         if MailItem.MarkForDelete then begin
  729.           FSocket.SendStr('-ERR Message Already Deleted' + CRLF);
  730.           StatusUpdate('Message Already Deleted', STAT_COMMANDERROR);
  731.         end else begin
  732.           MailItem.MarkForDelete := True;
  733.           FSocket.SendStr('+OK Message ' + IntToSTr(MailItem.Number) +
  734.                           ' Deleted' + CRLF);
  735.           StatusUpdate('Message ' + IntToSTr(MailItem.Number) + ' Deleted',
  736.                        STAT_COMMANDEVENT);
  737.         end;
  738.       end else begin
  739.         FSocket.SendStr('-ERR No Message Specified' + CRLF);
  740.         StatusUpdate('No Message Specified', STAT_COMMANDERROR);
  741.       end;
  742.     end else
  743.     if (Command = 'NOOP') and
  744.        (FState = pcs_TRANSACTION) and
  745.        (FSocket.State = wsConnected) then begin
  746.       // No operation
  747.       FSocket.SendStr('+OK' + CRLF);
  748.     end else
  749.     if (Command = 'RSET') and
  750.        (FState = pcs_TRANSACTION) and
  751.        (FSocket.State = wsConnected) then begin
  752.       // un-mark messages marked for delete
  753.       for x := 0 to FMailInfo.Count -1 do begin
  754.         MailItem := FMailInfo.Mail[x];
  755.         MailItem.MarkForDelete := False;
  756.       end;
  757.       FSocket.SendStr('+OK' + CRLF);
  758.       StatusUpdate('Message(s) Un-Deleted', STAT_COMMANDEVENT);
  759.     end else
  760.     if (Command = 'TOP') and                       // Optional
  761.        (FState = pcs_TRANSACTION) and
  762.        (FSocket.State = wsConnected) then begin
  763.       // User wants top n lines of message
  764.       // Find message number and number of lines
  765.       if Parameter1 <> '' then begin
  766.         Parameter2 := '';
  767.         if Pos(' ', Parameter1) > 0 then begin
  768.           Parameter2 := Trim(Copy(Parameter1, Pos(' ', Parameter1),
  769.                                   Length(Parameter1)));
  770.           Parameter1 := Trim(Copy(Parameter1, 1, Pos(' ', Parameter1)));
  771.         end;
  772.         try
  773.           Para_MessageID := StrToInt(Parameter1);
  774.         except
  775.           on E: Exception do Para_MessageID := -1
  776.         end;
  777.         try
  778.           Para_LineCount := StrToInt(Parameter2);
  779.         except
  780.           on E: Exception do Para_LineCount := -1
  781.         end;
  782.         // Find message
  783.         MailItem := FMailInfo.Find(Para_MessageID);
  784.         if MailItem = nil then begin
  785.           FSocket.SendStr('-ERR No Such Message' + CRLF);
  786.           StatusUpdate('No Such Message', STAT_COMMANDERROR);
  787.         end else
  788.         if MailItem.MarkForDelete then begin
  789.           FSocket.SendStr('-ERR Message Deleted' + CRLF);
  790.           StatusUpdate('Message Deleted', STAT_COMMANDERROR);
  791.         end else begin
  792.           FSocket.SendStr('+OK' + CRLF);
  793.           // Message itself
  794.           SL := TStringList.Create;
  795.           SL.LoadFromFile(INI.MailBoxPath + FUserID + '' + MailItem.Filename);
  796.           // Find where the header stops!
  797.           HeaderBreak := SL.Count -1;
  798.           for x := SL.Count -1 downto 0 do if SL[x] = '' then HeaderBreak := x;
  799.           // Write Header (do we byte stuff the header?  I will)
  800.           for x := 0 to HeaderBreak do
  801.             if Copy(SL[x], 1, 1) = '.' then
  802.               FSocket.SendStr('.' + SL[x] + CRLF)
  803.               else FSocket.SendStr(SL[x] + CRLF);
  804.           // Write Body
  805.           y := HeaderBreak + Para_LineCount;
  806.           if y > SL.Count then y := SL.Count;
  807.           // Stuff . byte
  808.           for x := (HeaderBreak +1) to y do
  809.             if Copy(SL[x], 1, 1) = '.' then
  810.               FSocket.SendStr('.' + SL[x] + CRLF)
  811.               else FSocket.SendStr(SL[x] + CRLF);
  812.           SL.Free;
  813.           // Send terminator
  814.           FSocket.SendStr('.' + CRLF);
  815.           StatusUpdate('Sent ' + IntToStr(y) + ' lines', STAT_COMMANDEVENT);
  816.         end;
  817.       end else begin
  818.         FSocket.SendStr('-ERR No Message Specified' + CRLF);
  819.         StatusUpdate('No Message Specified', STAT_COMMANDERROR);
  820.       end;
  821.     end else
  822.     if (Command = 'UIDL') and                        // Optional
  823.        (FState = pcs_TRANSACTION) and
  824.        (FSocket.State = wsConnected) then begin
  825.       // We don't support the UID!!!
  826.       FSocket.SendStr('-ERR Command Not Supported' + CRLF);
  827.       StatusUpdate('Command Not Supported', STAT_COMMANDERROR);
  828.     end else
  829.     begin // Unknown command
  830.       if FSocket.State = wsConnected then
  831.         FSocket.SendStr('-ERR Unrecognized Command' + CRLF);
  832.     end;
  833.   end;
  834. end;
  835. procedure TPop3Connection.SocketSessionClosed(Sender: TObject; Error: Word);
  836. begin
  837.   StatusUpdate('Closed', STAT_CONNECTIONEVENT);
  838.   // Socket is closed, we must tell Server Object
  839.   // to free this connection.  There's no point to
  840.   // carrying on without a connection, now is there?
  841.   PostMessage(FServer.Handle, WM_FREECONNECTION, Integer(Self), 0);
  842. end;
  843. procedure TPop3Connection.SocketError(Sender: TObject);
  844. begin
  845.   StatusUpdate('Socket Error', STAT_CONNECTIONERROR);
  846.   // We had a socket error. This isn't a protocol error like the user
  847.   // typed the wrong command, it's a dropped connection, or something else.
  848.   // we'll close on this too, because we don't know how to recover from it.
  849.   // the user can open a new connection if they really want.
  850.   Close; // Close on Error
  851. end;
  852. procedure TPop3Connection.SocketBgException(Sender: TObject; E: Exception;
  853.                                             var CanClose: Boolean);
  854. begin
  855.   StatusUpdate('Background Exception Error', STAT_CRITICALERROR);
  856.   // We had a background exception.  This is like a socket error in that
  857.   // we don't know what happened, and we don't know how to recover, so
  858.   // we'd better just close this connection.
  859.   Close; // Close on Error
  860. end;
  861. (******************************************************************************)
  862. (*                                                                            *)
  863. (*  STOP  POP3 Connection Object                                              *)
  864. (*                                                                            *)
  865. (******************************************************************************)
  866. end.