Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Indy: POP3Server

Posted on 2004-09-14
7
Medium Priority
?
649 Views
Last Modified: 2010-04-05
I need a complete code example for a simple POP3 server with following requirements:
- server is installed on a server computer serving 50 workstations
- POP3 server validates users (user name and password are in local database)
- the mail messages are generated by the POP3 server (just a simple 'Hi ' + UserName will do)
thank you
0
Comment
Question by:graga
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 3
7 Comments
 
LVL 10

Expert Comment

by:Jacco
ID: 12061502
What backend do you want to use? Where/how do you want to store the user credentials and messages?
0
 

Author Comment

by:graga
ID: 12061705
Jacco,

I will be using MySQL but for this purpose anything will do, MS Access probably will be the simplest for the prototype.
I have put together a prototype, but, being a newbie with Indy, I'm not sure how the threads work and how to make it work under multiuser load.

So, my idea of the prototype whould be something along these lines:

- User checks email from their email client with UserName and Password
  - this is done by setting POP3 server to 127.0.0.1 in each user's email client

- The POP3 server checks that UserName and Password exist in the user table
  - this is done in CheckUser event evaluating LThread - looking up the table returns some sort of key, let's say User_Number (integer)

- The POP server generates an email to that user but in the Body of the message embeded is UserName - this will ensure that the right message goes to the right recipient.
  - this is what I don't know how to do. How do I know in STAT, RETR and other command events what the User_Number is? What happens when all 50 users try to check email at the same time?

graga
0
 
LVL 1

Accepted Solution

by:
NickRyan earned 600 total points
ID: 12063255
There is a Data property in the Thread object that is passed to all of the command events in the pop3 server. This is there so you can store relevant session data. You should create a suitable session object class which stores all of the information that you need, such as the user number, the database connection and anything else you might need. You should create this session object in the OnConnect event and free it in the OnDisconnect event. For example:

type
  TSessionObject = class(TObject)
    User_Number:integer;
  end; {class}

procedure TForm1.IdPOP3Server1Connect( AThread:TIdPeerThread );
begin
  AThread.Data := TSessionObject.Create;
end;

procedure TForm1.IdPOP3Server1Disconnect( AThread:TIdPeerThread );
begin
  AThread.Data.Free;
  AThread.Data := NIL;
end;

You always have access to this Thread object, however it's not passed directly to the command handlers, instead you need to access the thread object using code like this:

procedure TForm1.IdPOP3Server1LIST( ASender:TIdCommand; AMessageNum:Integer );
var
  SessionObject:TSessionObject;
begin
  SessionObject := TSessionObject(ASender.Thread);
  ...
end;

You next problem will be concurrent access to whatever database you're using to store the data in. The solution to this is that you must either serialise access to a shared database access object (or access code) or create a new connection to the database for each POP3 session. In this case, extend the session object to manage the individual database connections - for example:

TSessionObject = class(TObject)
  Database:TDatabase;
  User_Number:integer;
  constructor Create;
  destructor Destroy; override;
end; {class}

constructor TSessionObject.Create;
begin
  Database := TDatabase.Create( NIL );
  [connect to database]
end;

destructor TSessionObject.Destroy;
begin
  FreeAndNIL( Database );
end;

Now when you need to access the backend database, eg. in the command handler code, you can create the local query object, link it to the session's database object and do whatever you need to do. For example:

procedure TForm1.IdPOP3Server1LIST( ASender:TIdCommand; AMessageNum:Integer );
var
  SessionObject:TSessionObject;
  Query:TQuery;
begin
  SessionObject := TSessionObject(ASender.Thread);
  Query := TQuery.Create( NIL );
  try
    Query.Database := SessionObject.Database;
    Query.SQL.Text := 'select * from sometable where user_number = ' + IntToStr(Session.User_Number);
    [execute query and do your processing]
  finally
    Query.Free;
  end; {try}
end;

There are ways to optimise the code using pooled database connections and a certain amount of serialised access but the value of these optimisations will depend heavily on the number of simultaneous database connections your database can handle, the connection time and the number of users who are going to connect at exactly the same time.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 10

Assisted Solution

by:Jacco
Jacco earned 400 total points
ID: 12070375
Hi there,

I created a sample using a custom server side client thread. I implemented this simply using the file system (which is by default thread-safe). In the directory of the project there must be a file "Users.txt" with the following layout:

user1=pass1
user2=pass2

In subdirectories for each user (with the users name) there can be any number of message files.

!! Make sure Pop3 is not active in the IDE (otherwise the user defined thread class will not be used) !!

I tested this server sample with Outlook and it worked fine :)

If you want to use a DataBase backend you need to make a connection inside the thread. (If you use the BDE this means creating TSession/TDatabase/TQuery components). For MySQL I don't know what you need exactly).

Have fun!! Let me know if this is what you need.

Regards Jacco

P.S. You could optimize not reading the user file on every login. But reading it in only once would not handle alterations of the file.

*** start of code ***
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdBaseComponent, IdComponent, IdTCPServer, IdPOP3Server, SyncObjs;

type
  TForm1 = class(TForm)
    Pop3: TIdPOP3Server;
    procedure FormCreate(Sender: TObject);
    procedure Pop3CheckUser(AThread: TIdPeerThread; LThread: TIdPOP3ServerThread);
    procedure Pop3STAT(ASender: TIdCommand);
    procedure Pop3LIST(ASender: TIdCommand; AMessageNum: Integer);
    procedure Pop3RETR(ASender: TIdCommand; AMessageNum: Integer);
    procedure Pop3DELE(ASender: TIdCommand; AMessageNum: Integer);
  private
    fUserVerificationSection: TCriticalSection;
  public
    function VerifyUserLogin(const aUserName, aPassword: string): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TPop3Thread = class(TIdPOP3ServerThread)
  private
    fMessages: TStringList;
    fTotal: Integer;
  public
    constructor Create(ACreateSuspended: Boolean = True); override;
    destructor Destroy; override;
    procedure GetMessages;
  end;

{ TPop3Thread }

constructor TPop3Thread.Create(ACreateSuspended: Boolean);
begin
  inherited Create(ACreateSuspended);
  fMessages := TStringList.Create;
end;

destructor TPop3Thread.Destroy;
begin
  fMessages.Free;
  inherited Destroy;
end;

procedure TPop3Thread.GetMessages;
var
  lSR: TSearchRec;
begin
  fTotal := 0;
  if FindFirst(Username + '\*.txt', faAnyFile, lSR) = 0 then
  begin
    repeat
      fMessages.AddObject(Username + '\' + lSR.Name, Pointer(lSR.Size));
      Inc(fTotal, lSR.Size);
    until FindNext(lSR) <> 0;
    FindClose(lSR);
  end;
end;

{ TForm1 }

procedure TForm1.Pop3CheckUser(AThread: TIdPeerThread; LThread: TIdPOP3ServerThread);
begin
  if VerifyUserLogin(LThread.Username, LThread.Password) then
  begin
    LThread.State := Trans;
    TPop3Thread(AThread).GetMessages;
  end;
end;

function TForm1.VerifyUserLogin(const aUserName, aPassword: string): Boolean;
var
  lUsers: TStringList;
begin
  Result := False;
  lUsers := TStringList.Create;
  try
    lUsers.LoadFromFile('Users.txt');
    Result := lUsers.Values[aUserName] = aPassword;
  finally
    lUsers.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Pop3.ThreadClass := TPop3Thread;
  Pop3.Active := True;
end;

procedure TForm1.Pop3STAT(ASender: TIdCommand);
begin
  with TPop3Thread(ASender.Thread) do
    Connection.WriteLn('+OK ' + IntToStr(fMessages.Count) + ' ' + IntToStr(fTotal));
end;

procedure TForm1.Pop3LIST(ASender: TIdCommand; AMessageNum: Integer);
var
  liMessage: Integer;
begin
  with TPop3Thread(ASender.Thread) do
  begin
    Connection.WriteLn('+OK ' + IntToStr(fMessages.Count) + ' messages (' + IntToStr(fTotal) + ' octets)');
    for liMessage := 0 to fMessages.Count-1 do
      Connection.WriteLn(IntToStr(Succ(liMessage)) + ' ' + IntToStr(Integer(fMessages.Objects[liMessage])));
    Connection.WriteLn('.');
  end;
end;

procedure TForm1.Pop3RETR(ASender: TIdCommand; AMessageNum: Integer);
var
  lMessage: TStringList;
  liLine: Integer;
begin
  with TPop3Thread(ASender.Thread) do
  begin
    if AMessageNum <= fMessages.Count then
    begin
      Connection.WriteLn('+OK ' + IntToStr(Integer(fMessages.Objects[AMessageNum-1])) + ' octets');
      lMessage := TStringList.Create;
      try
        lMessage.LoadFromFile(fMessages[AMessageNum-1]);
        for liLine := 0 to lMessage.Count-1 do
          if lMessage[liLine] = '.' then
            Connection.WriteLn('..')
          else
            Connection.WriteLn(lMessage[liLine]);
          Connection.WriteLn('.');
      finally
        lMessage.Free;
      end;
    end else
      Connection.WriteLn('-Message ' + IntToStr(AMessageNum) + ' not found!');
  end;
end;

procedure TForm1.Pop3DELE(ASender: TIdCommand; AMessageNum: Integer);
begin
  with TPop3Thread(ASender.Thread) do
  begin
    if AMessageNum <= fMessages.Count then
    begin
      DeleteFile(fMessages[AMessageNum-1]);
    end;
  end;
end;

end.
*** end of code ***
0
 
LVL 10

Expert Comment

by:Jacco
ID: 12070384
Messages should be with "txt" extension (its coded that way...)
0
 

Author Comment

by:graga
ID: 12070999
Thank you. Now it's all clear.
I have accepted Nicks answer, but Jacko has also put a lot of work in and I will use his code for other purpose. You guys are champions!
0
 

Author Comment

by:graga
ID: 12071053
I have also posted another Indy question a couple of days ago which is worth 500 points. You may want to have a look.
Thanks
0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Visualize your data even better in Access queries. Given a date and a value, this lesson shows how to compare that value with the previous value, calculate the difference, and display a circle if the value is the same, an up triangle if it increased…
In response to a need for security and privacy, and to continue fostering an environment members can turn to for support, solutions, and education, Experts Exchange has created anonymous question capabilities. This new feature is available to our Pr…
Suggested Courses

650 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question