A POP3 server is a protocol that follows the RFC 1939 specification. It responds to a set of commands issued by a pop3 client. A POP3 Server is basically a go between, between a pop3 Client and a mail server. In this article, we will create the code to respond to those commands issued by a pop3 client such as Microsoft Outlook or Eudora.
Creating a POP3 Server - Implementing the Commands (Page 3 of 5 )
Once you have all of the components in place and connected, click on pops and go to the object inspector. Click on the events tab, double click on "OnCheckUser" and add the following code:
procedure TForm1.popsCheckUser(aContext: TIdContext; aServerContext: TIdPOP3ServerContext); begin coinitialize(nil); q2.Close; //Search for the username and password in "users" table.. q2.SQL.Text := 'SELECT * from users WHERE uname=:user AND upass=:pwd'; q2.Parameters.ParamByName('user').Value := aServerContext.Username; q2.Parameters.ParamByName('pwd').Value := aServerContext.Password; q2.open; //if the user is not found, display an error... if q2.RecordCount = 0 then raise Exception.Create('Authentication failed'); couninitialize; end;
The above procedure checks whether the username and password exist in the database.
Next, we deal with the DELE(TE) command. The delete command is in the following format: DELE msgNo. Below is a example C(lient) request the S(erver) to delete msg numbers 1 and 2:
C: DELE 1 S: +OK message 1 deleted ... C: DELE 2 S: -ERR message 2 already deleted
Double click on OnDelete and add the following code:
procedure TForm1.popsDelete(aCmd: TIdCommand; AMsgNo: Integer); var n1,n2:integer; Data:TStringList; begin Data := TStringList.Create; try ado1.Open; //Check if a valid message number has been requested... if (AMsgNo < 1) or (AMsgNo > ado1.RecordCount) then raise Exception.Create('invalid message number'); ado1.RecNo := AMsgNo; n2:=ado1.Fieldbyname('mid').AsInteger; n1:=1; //mark the message for deletion... q2.Close; q2.SQL.text:='UPDATE email SET ismarked=:num WHERE mid =:msgnum'; q2.Parameters.ParamByName('num').Value:=n1; q2.Parameters.ParamByName('msgnum').Value:=n2; q2.ExecSQL; //Send a reply confirming that message has been deleted aCmd.Reply.SetReply(OK, 'message'+inttostr(amsgno) +'deleted'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA //aCmd.Context.Connection.WriteRFCStrings(Data); // <-- INCLUDESTHE TERMINATING '.' LINE finally FreeAndNil(Data); end;
Now, you do not actually delete the messages at this point. All that you do is mark them for deletion when the pop3client disconnects from the server. In the above code, to mark a message for deletion I insert "1" in the ismarked column of the table, so, when the pop3 client quits or disconnects, the server will automatically search for messages where the column ismarked is set to 1 for deletion. The way I checked the validity of the msgNo is by comparing the msgNo by the number of records (ado1.RecordCount) in the database. If the message number is greater than the number of records, then an exception will be raised.
Next is the RSET command. This resets (unmarks) any messages previously marked for deletion in this session so that the QUIT command will not delete them. Below is a short example:
C: RSET S: +OK maildrop has 2 messages (320 octets)
That is the end of the example. Now for some real code:
procedure TForm1.popsReset(aCmd: TIdCommand); var n1,n2,i:integer; begin n1:=1;//marked for deletion n2:=0;//not marked q2.Close; q2.SQL.text:='select * FROM email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n1; q2.open; //there are messages marked for deletion if q2.RecordCount > 0 then begin for i:= 1 to q2.RecordCount do begin //reset unmark them q2.Close; q2.SQL.text:='UPDATE email SET ismarked=:num'; q2.Parameters.ParamByName('num').Value:=n2; q2.ExecSQL; end; end; q2.Close; //get the count of message that is NOT marked q2.SQL.text:='select * FROM email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n2; q2.open; //Sent off the count of messages that is not marked aCmd.Reply.SetReply(OK, inttostr(q2.RecordCount)+' messages available'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA end;
The LIST command lists all the messages with their message ID and the message size. Below is an example of the LIST command:
C: LIST S: +OK 2 messages (320 octets) S: 1 120 S: 2 200 S: . ... C: LIST 2 S: +OK 2 200 ... C: LIST 3 S: -ERR no such message, only 2 messages in maildrop
That is the end of that example; now for some more code:
procedure TForm1.popsList(aCmd: TIdCommand; AMsgNo: Integer); var msgnum,thesize:integer; begin CoInitialize(nil); q2.Close; q2.SQL.text:='SELECT SUM(msize) AS tsize from email'; q2.open; //get the size of the total messages... thesize:=q2.fieldbyname('tsize').AsInteger; q2.Close; q2.SQL.text:='SELECT * from email'; q2.open; //Get total number of messages msgnum:= q2.recordcount; //now, send a reply ... aCmd.Reply.SetReply(OK, inttostr(msgnum) + ' messages '+'('+inttostr(thesize) + ' octets)'); aCmd.SendReply; q2.Close; q2.SQL.text:='SELECT * from email'; q2.open; q2.First; while not q2.EOF do begin with aCmd.Context.Connection.IOHandler do begin WriteLn(inttostr(q2.RecNo{.Fieldbyname('mid').Value}) +' '+ inttostr(q2.Fieldbyname('msize').Value)); q2.Next; end;//cmd end;//w aCmd.Context.Connection.IOHandler.writeln('.'); CoUnInitialize; end;
Sometimes pop3 clients include a msgNo with this command, in which case you will have to retrieve that specific message based on its msgNo. But generally this won't be the case.