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, continued (Page 4 of 5 )
Next is the RETR [msgNo] command. This command retrieves all messages or a specific message based on its msgNo provided it is not marked for deletion. Here's a quick example:
C: RETR 1
S: +OK 120 octets
S: <the POP3 server sends the entire message here>
S: .
And now for the code:
procedure TForm1.popsRetrieve(aCmd: TIdCommand; AMsgNo: Integer); var // using a TStringList so that Indy can automatically ensure // that any lines beginning with '.' does not mess up the response Data: TStringList; n2:integer; begin Data := TStringList.Create; try n2:=0;//not marked for deletion q2.Close; //get all msgs that is not marked for deletion... q2.SQL.text:='SELECT * from email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n2; q2.open; //Check to see if msg number is valid... if (AMsgNo < 1) or (AMsgNo > q2.RecordCount) then raise Exception.Create('invalid message number'); //Set the table marker to the msg number i.e., if the msg# is 2 then set the tbl record to #2 q2.RecNo := AMsgNo; //add msg details to the data object... Data.Add('From: ' + q2.FieldByName('from').Text); Data.Add('To: ' + q2.FieldByName('to').Text); Data.Add('Subject: ' + q2.FieldByName ('subject').Text); Data.Add(''); Data.Add(q2.FieldByName('mbody').AsString); aCmd.Reply.SetReply(OK, 'message follows'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA aCmd.Context.Connection.WriteRFCStrings(Data); // <-- INCLUDESTHE TERMINATING '.' LINE finally FreeAndNil(Data); end; end;
Next the STAT command lists the number of messages and their combined sizes. An example:
C: STAT S: +OK 2 320 <- two messages with a combined size of 320
And here's the code:
procedure TForm1.popsStat(aCmd: TIdCommand; out oCount, oSize: Integer); var msgnum,n2:integer; begin n2:=0; q2.Close; //Get msg sizes that is unmarked.. q2.SQL.text:='SELECT SUM(msize) AS tsize from email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n2; q2.open; osize:=q2.Fieldbyname('tsize').AsInteger; q2.Close; q2.SQL.text:='SELECT * from email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n2; q2.open; //Get number of msgs ocount:=q2.RecordCount; aCmd.Reply.SetReply(OK, inttostr(ocount)+' '+inttostr(osize)); end;
Next the QUIT command disconnects the pop3client and deletes all messages marked for deletion. Here's the code:
procedure TForm1.popsQuit(aCmd: TIdCommand); var n1:integer; begin n1:=1; q2.Close; //Delete all marked messages q2.SQL.text:='DELETE * FROM email WHERE ismarked =:num'; q2.Parameters.ParamByName('num').Value:=n1; q2.ExecSQL; aCmd.Reply.SetReply(OK, 'Server signing off (maildrop empty)'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA end;
Next the TOP [msgNo msgLines] command sends the headers of the message, the blank line separating the headers from the body, and then the number of lines of the indicated message's body. Here's an example:
C: TOP 1 10 S: +OK S: <the POP3 server sends the headers of the message, a blank line, and the first 10 lines of the body of the message> S: . ... C: TOP 100 3 S: -ERR no such message
And here's the code:
procedure TForm1.popsTop(aCmd: TIdCommand; aMsgNo, aLines: Integer); var thestr:string; Data:TStringList; begin Data := TStringList.Create; try ado1.Open; if (AMsgNo < 1) or (AMsgNo > ado1.RecordCount) then raise Exception.Create('invalid message number'); ado1.RecNo := AMsgNo; //check if number of lines are specified.. if alines > 0 then begin thestr:= Copy(ado1.Fieldbyname('mbody').AsString, 1, aLines); end else begin thestr:=ado1.Fieldbyname('mbody').AsString; end; Data.Add('From: ' + ado1.FieldByName('from').Text); Data.Add('To: ' + ado1.FieldByName('to').Text); Data.Add('Subject: ' + ado1.FieldByName ('subject').Text); Data.Add(''); Data.Add(thestr); aCmd.Reply.SetReply(OK, 'top of message follows'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA aCmd.Context.Connection.WriteRFCStrings(Data); // <-- INCLUDESTHE TERMINATING '.' LINE finally FreeAndNil(Data); end; end;
Sometimes the command is issued without the number of lines specified, in which case you have to show the entire body of the message. This is demonstrated by the code above.
The next command is UIDL [msgNo]. When this command is issued the POP3 server responds with a line containing information for that message. This line is called a "unique-id listing" for that message. The unique number can be made up of anything, as long as it is unique. Below is an example of the UIDL command:
C: UIDL S: +OK S: 1 whqtswO00WBw418f9t5JxYwZ S: 2 QhdPYR:00WBw1Ph7x7 S: . ... C: UIDL 2 S: +OK 2 QhdPYR:00WBw1Ph7x7 ... C: UIDL 3 S: -ERR no such message, only 2 messages in maildrop
And here is the code:
procedure TForm1.popsUIDL(aCmd: TIdCommand; AMsgNo: Integer); var Data:TStringList; begin Data := TStringList.Create; try ado1.Open; if (AMsgNo < 1) or (AMsgNo > ado1.RecordCount) then raise Exception.Create('invalid message number'); ado1.RecNo := AMsgNo; Data.Add(inttostr(ado1.RecNo)+' '+'whqtswO00WB'+inttostr(ado1.FieldByName('mid').AsInteger) +'w418f9t5JxYwZ'); aCmd.Reply.SetReply(OK, 'unique-id listing follows'); aCmd.SendReply; // <-- YOU MUST DO THIS BEFORE SENDING THE DATA aCmd.Context.Connection.WriteRFCStrings(Data); // <-- INCLUDESTHE TERMINATING '.' LINE finally FreeAndNil(Data); end; end;
The above code creates a unique number by adding the msgnumber to "whqtswO00WBw418f9t5JxYwZ" as you can see from the above code. You can create/add anything you want to make this a unique number.
Double click on "OnException" and add the following code:
procedure TForm1.popsException(AContext: TIdContext; AException: Exception); begin AContext.Connection.IOHandler.Write( AException.Message); end; //the code below activates the pop3 Server procedure TForm1.Button1Click(Sender: TObject); begin if not pops.Active then begin pops.Active:=true; label1.Caption:='Connected'; end; end;
Double click on "OnExecute" and add the following code:
procedure TForm1.popsExecute(AContext: TIdContext); begin logfile.DoLogWriteString(acontext.Connection.IOHandler.ReadLn); end; end.