PROGRAMOWANIE W ŚRODWISKU SIECIOWYM I


 

ĆWICZENIE III

 

Protokół SMTP

/// <summary>
/// Easy mail component that only needs to be droped on
/// your form to be used.
/// </summary>
public partial class EasyMail : Component
{
  // All the fields that are needed
  private string mMailFrom;
  private string mMailTo;
  private string mMailSubject;
  private string mMailBody;
  private string mSMTPServer;
  private int mSMTPPort;
  private string mSMTPUsername;
  private string mSMTPPassword;
  private bool mSMTPSSL;
  private MailMessage MailObject;
 
  // Properties
  public string MailFrom { set { mMailFrom = value; }
                           get { return mMailFrom; } }
  public string MailTo { set { mMailTo = value; }
                         get { return mMailTo; } }
  public string MailSubject
      { set { mMailSubject = value; }
        get { return mMailSubject; } }
  public string MailBody { set { mMailBody = value; }
                           get { return mMailBody; } }
  public string SMTPServer { set { mSMTPServer = value; }
                             get { return mSMTPServer; } }
  public int SMTPPort { set { mSMTPPort = value; }
                        get { return mSMTPPort; } }
  public string SMTPUsername
      { set { mSMTPUsername = value; }
        get { return mSMTPUsername; } }
  public string SMTPPassword {
        set { mSMTPPassword = value; }
        get { return mSMTPPassword; } }
  public Boolean SMTPSSL { set { mSMTPSSL = value; }
                           get { return mSMTPSSL; } }
 
 
  // Functions
  public Boolean Send()
  {
   // build the email message
   MailMessage Email = new MailMessage();
   MailAddress MailFrom = 
     new MailAddress(mMailFrom,mMailFrom);
   Email.From = MailFrom;
   Email.To.Add(mMailTo);
 
   Email.Subject = mMailSubject;
   Email.Body = mMailBody;
 
   // Smtp Client
   SmtpClient SmtpMail = 
    new SmtpClient(mSMTPServer,mSMTPPort);
   SmtpMail.Credentials = 
    new NetworkCredential(mSMTPUsername,mSMTPPassword);
   SmtpMail.EnableSsl = mSMTPSSL;
 
   Boolean bTemp = true;
       
   try
    {
     SmtpMail.Send(Email);
     return true;
    }
   catch (SmtpFailedRecipientsException ex)
    {
     MessageBox.Show("The message was not sent!!!");
     bTemp = false;
    }
   return bTemp;
  }
       
  // Constructor
  public EasyMail()
        {
            InitializeComponent();
 
            MailObject = new MailMessage();
            mMailFrom = "";
            mMailTo = "";
            mMailSubject = "";
            mMailBody = "";
            mSMTPServer = "";
            mSMTPPort = 25;
            mSMTPUsername = "";
            mSMTPPassword = "";
            mSMTPSSL = false;
        }
 
  public EasyMail(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
        }
    }

 

 

 

1.2 SMTP Commands

The SMTP commands are used to send mail through the server. The necessary commands are listed below, including their syntax and the normal reply code. You can try this in the telnet window. When connected to an SMTP server, the default command order is:

 

S: 220 myserver.com welcome to the myserver.com SMTP email server

C: "HELO myserver.com" <CRLF>

S: "250 Requested mail action okay, completed"

C: "MAIL FROM: myemail@myserver.com" <CRLF>

S: "250 Requested mail action okay, completed"

C: "RCPT TO: myrecipient@hisserver.com" <CRLF>

S: "250 Requested mail action okay, completed"

C: "DATA" <CRLF>

S: "354 Start mail input; end with <CRLF>.<CRLF>"

C: <Mail Message, see chapter 4.4> <CRLF>.<CRLF>

S: "250 Requested mail action okay, completed"

C: "QUIT" <CRLF>

S: 221 myserver.com service closing transmission channel

 

There are some commands that can be issued before or during this standard command order. See the details for each command below. The reply message can differ from each server; however, the reply codes cannot. Therefore, when dealing with an SMTP component, we only work with the reply codes. For a detailed description of all the reply codes, see the section "MAIL (FROM)".

1.2.1 HELO
The HELO command is short for hello; it states a friendly greeting from the client with identification. This command is the first in line after a TCP connection has been established. After the HELO command, the receiving server sets up a reverse-path, forward-path, and mail data buffer to start receiving e-mail data.

Syntax: HELO myserver.com CRLF
Normal Reply Code: 250 Requested mail action okay, completed
Standard Timeout: 5 minutes (After the initial 220 message)

1.2.2 EHLO
The EHLO command is the extended version of the HELO command giving the client the possibility to issue advanced commands. These commands are not used within the SMTP component. More information about the EHLO command can be found in RFC 2821.

1.2.3 MAIL (FROM)
This command is used to specify the source of the message. Here the sender needs to identify itself by giving his/her e-mail address. The SMTP server uses this information to verify if the sender is authorized to send messages. However, the MAIL FROM command isn't used within the mail message itself. For the recipient to view the sender, the From header must be set in the message data. The MAIL FROM command can only be issued once.

Syntax: MAIL FROM: myemail@myserver.com CRLF
Normal Reply Code: 250 Requested mail action okay, completed
Standard Timeout: 5 minutes

1.2.4 RCPT TO
The Recipient To (RCPT TO) command is used to identify the recipients. This command can be issued more than once and has to be issued for all To, Cc and Bcc recipients. The first action taken by the SMTP server on receiving this command is to verify whether the recipient is a local mailbox. If not, the SMTP server will either throw an error, or, if allowed, try to relay the message. Note that an SMTP server limits the amount of recipients. The RFC standard is 100 recipients per message.

Syntax: RCPT TO: myrecipient@hotmail.com CRLF
Normal Reply Code: 250 Requested mail action okay, completed
Standard Timeout: 5 minutes

1.2.5 DATA
Sending this command means the server will start receiving the actual mail message. The DATA command has 2 parts. The first part is the command with a CRLF. The server then will respond that it is ready to receive the message. After that, the message has to be sent. Information about the message format will be described in the next article. To let the server know when the message is finished, a <CRLF>.<CRLF> has to be sent.(Note that after issuing and executing a DATA command without error [reply code 250], the server accepts responsibility for sending the message or returning a failure message to the sender address as specified in the MAIL command. The process of sending can no longer be aborted within the context of the TCP connection.)

 

Syntax: DATA CRLF

<message> CRLF.CRLF

Normal Reply Code: 354 Start mail input; end with <CRLF>.<CRLF>

                                              250 Requested mail action okay, completed

Standard Timeout: 2 minutes (DATA Initiation)

                                      3 minutes (Per DATA Block)

                                      10 minutes (After DATA Termination, i.e. 220 reply code)

 

1.2.6 RSET
The RSET (Reset) command can be used to clear all the SMTP server buffers. It clears all stored data (MAIL FROM, RCPT TO,DATA,HELO), and the server will be ready to restart the sending process. However, if the goal is to start over with the sending of an e-mail, you should issue a HELO command after the reset. (Note that this method is redundant, since issuing a HELO command will also reset the previous data; therefore, a RSET command is unnecessary, but not harmful)

Syntax: RSET CRLF
Normal Reply Code: 250 Requested mail action okay, completed
Standard Timeout: N/A (Server Default)

1.2.7 VRFY
If your goal is to simply verify an e-mail address, use the VRFY command. However, it only verifies whether the e-mail address is a valid local mailbox.

 

Syntax: VRFY myrecipient@hotmail.com CRLF

Normal Reply Code: 250 "myrecipient@hotmail.com" <myrecipient@>

                                              252 Cannot VRFY user, but will accept message

Standard Timeout: N/A (Server Default)

 

1.2.8 HELP
Although not always implemented, the HELP command should return a list of commands supported by the SMTP server. When supplying a command name as parameter, the server should return extra information about that command.

Syntax: HELP <command name> CRLF
Normal Reply Code: 214 <Custom Help Information, depends per server>
Standard Timeout: N/A (Server Default)

1.2.9 NOOP
The NOOP command accepts no parameters and can only return a 250 response code. It is implemented only to test the client/server connection. The command can be issued at any time during the session.

Syntax: NOOP CRLF
Normal Reply Code: 250 Requested mail action okay, completed
Standard Timeout: N/A (Server Default)

1.2.10 QUIT
The QUIT command is the opposite to the HELO command; it tells the SMTP server that the client is leaving. In most cases the server will then disconnect the TCP connection. If not it will wait for a new HELO command. The QUIT command also indicates to the server that the client is done transmitting the message, and after issuing this command, the server will start processing any received data.

Syntax: QUIT CRLF
Normal Reply Code: 221 <domain> service closing transmission channel
Standard Timeout: N/A (Connection Closed)

(Note: The RFC recommends SMTP servers to have a default timeout of 5 minutes)

1.3 SMTP Reply Codes

When issuing a command, the server will respond with one of the reply codes listed below. As said before, the text associated with the reply code can differ from each server; however, the syntax of the reply defaults to <Reply Code> <Text> <CRLF>

In case of multiple lines, the syntax of the "text-only" lines is <Reply Code>-<Text> <CRLF>. The last line is that of the default syntax, so the client understands the server is done with the reply.

The reply codes can be divided into five values for the first digit (out of 3):

1xy   Positive Preliminary reply (only for Extended SMTP commands)
2xy   Positive Completion reply (The command has been accepted)
3xy   Positive Intermediate reply (There should be further information [i.e. DATA])
4xy   Transient Negative Completion reply (Command Failed, but can be repeated)
5xy   Permanent Negative Completion reply (Command Failed)

The detailed information of the reply codes is listed below

1.3.1 2xy Reply codes

1.3.1.1 System status, or system help reply (211)
This reply code is followed by information concerning the server, the server status, the server system, or server help.

Reply Code: 211
Issued Command(s): HELP
Standard Timeout: N/A (Server Default)

1.3.1.2 Help message (214)
This reply code is followed by information on how to use the receiver or the meaning of a command. Usually it provides a list of commands accepted by the server.

Reply Code: 214
Issued Command(s): HELP
Standard Timeout: N/A (Server Default)

1.3.1.3 <Domain> Service ready (220)
The 220 reply code is used to confirm connection establishment. It is the primary command after making a TCP connection

Reply Code: 220
Issued Command(s): N/A
Standard Timeout: 5 minutes

1.3.1.4 <Domain> Service Closing Transmission Channel (221)
After issuing the Quit command, the server replies with the 221 code, immediately closing the TCP connection. Therefore, this is the final reply code (unless the connection is terminated before closing the session).

Reply Code: 221
Issued Command(s): QUIT
Standard Timeout: N/A (Connection Closed)

1.3.1.5 Requested mail action okay, completed (250)
This is the most commonly used reply code when a command is accepted and executed without errors. After the 250 reply code the server waits until the next command is issued, or a timeout occurs.

Reply Code: 250
Issued Command(s): HELO, MAIL, RCPT, DATA, RSET, VRFY, NOOP
Standard Timeout: See command description

1.3.1.6 User not local; will forward to <forward-path> (251)
When the server receives a RCPT or VRFY command, it will try to verify the specified user with the local mailboxes. When the user doesn't exists or has a forward path, the server will reply with the 251 code saying it will accept the user and relay the message.

(Please refer to section 1.3.5 for more information about the 251 and 551 reply codes)

Reply Code: 251
Issued Command(s): RCPT, VRFY
Standard Timeout: 5 Minutes (RCPT command)

1.3.1.7 Cannot VRFY user, but will accept message and attempt delivery (252)
This code works the same as the 251 reply code; however, it will be issued only in response to the VRFY command.

Reply Code: 252
Issued Command(s): VRFY
Standard Timeout: N/A (Server Default)

1.3.2 3xy Reply codes

1.3.2.1 Start mail input; end with <CRLF>.<CRLF> (354)
Issued after the DATA command, the 354 code is used to state that more information is required, and thus the server is waiting for the message body.

Reply Code: 354
Issued Command(s): DATA
Standard Timeout: 2 Minutes (DATA command), 3 Minutes (DATA block), 10 Minutes (DATA Termination)

1.3.3 4xy Reply codes

1.3.3.1 <Domain> Service not available, closing transmission channel (421)
This reply code is given when the server is temporarily unavailable for accepting connections. This would be the case when the server is in the process of shutting down. This code can be replied during the entire client session if unusual circumstances are encountered

Reply Code: 421
Issued Command(s): N/A
Standard Timeout: N/A (Connection Closed)

1.3.3.2 Requested mail action not taken: mailbox unavailable (450)
When a user mailbox is busy because of administrative work (i.e. backup), the 450 reply code is generated when the RCPT command is issued. It states that the message cannot be delivered to that user at this time. The client can repeat the command at a later time.

Reply Code: 450
Issued Command(s): RCPT
Standard Timeout: 5 Minutes (RCPT Command)

1.3.3.3 Requested action aborted: local error in processing (451)
This reply code specifies that an error has occurred while processing the command locally. However, the error is transient, allowing the user to repeat the command after an unspecified period of time.

Reply Code: 451
Issued Command(s): MAIL, RCPT, DATA
Standard Timeout: See command description

1.3.3.4 Requested action not taken: insufficient system storage (452)
The 452 reply code specifies that the SMTP server system has insufficient storage to process the message. Most SMTP server alerts the system administrator when this happens allowing them to solve the problem and thus giving the client the possibility to repeat the command after an unspecified period of time. This reply code can also be issued when there are too many recipients. See the RCPT command for further details.

Reply Code: 452
Issued Command(s): MAIL, RCPT, DATA
Standard Timeout: See command description

1.3.4 5xy Reply codes

1.3.4.1 Syntax error, command unrecognized (500)
This reply code is issued when the SMTP server doesn't recognize an SMTP command. It can be issued at any time during the client session. Note that all SMTP servers compliant to the RFC accept the standard commands necessary for sending an e-mail (HELO, MAIL, RCPT, DATA, QUIT). The 500 reply code is also issued when the command line is too long

Reply Code: 500
Issued Command(s): All
Standard Timeout: See command description

1.3.4.2 Syntax error in parameters or arguments (501)
When a command is recognized but doesn't comply to the specified parameters or arguments, the 501 reply code is returned. This reply code can be issued at any time during the client session. In default, it will be returned when the DATA, RSET or QUIT command have parameters or arguments while no EHLO extensions are available.

Reply Code: 501
Issued Command(s): All
Standard Timeout: N/A (Server Default)

1.3.4.3 Command not implemented (502)
The 502 Reply code is related to the 500 command with the only difference that this reply code is issued when the server does recognize the command, but didn't implement it, so it cannot be executed. Again, the 502 reply code may not be issued when handling the standard commands necessary for sending an e-mail.

Reply Code: 502
Issued Command(s): VRFY, EXPN, HELP
Standard Timeout: N/A (Server Default)

1.3.4.4 Bad sequence of commands (503)
When sending e-mail, a default sequence is specified (as shown in 2.2). Not complying with this sequence can result in the 503 reply code. When this code is returned, the server will wait for a new command, preserving the data already supplied.

Reply Code: 503
Issued Command(s): MAIL, RCPT, DATA
Standard Timeout: See command description

1.3.4.5 Command parameter not implemented (504)
There are a few commands which don't allow command parameters to be supplied. Ignoring this rule will result in a 504 reply code. Below is a list of commands on which this rule applies.

Reply Code: 504
Issued Command(s): HELO, VRFY, EXPN, HELP
Standard Timeout: N/A (Server Default)

1.3.4.6 Requested action not taken: mailbox unavailable (550) Although similar to the 450 reply code (1.3.3.2), there is one difference. When the 550 reply code is issued by the server, the mailbox or command is permanently unavailable. With the 450 reply code, the client can repeat the command at a later time. The 550 reply code is issued when a mailbox cannot be found, access is prohibited, or the command is rejected based on security/company policies.

Reply Code: 550
Issued Command(s): HELO, MAIL, RCPT, VRFY, EXPN
Standard Timeout: See command description

1.3.4.7 User not local; please try <forward-path> (551)
Although similar to the 251 reply code (1.3.1.6), the main difference between them is that the server will reject the address when returning a 551 reply code and attempt to deliver the message when returning a 251 reply code.

(Please refer to section 1.3.5 for more information about the 251 and 551 reply codes)

Reply Code: 551
Issued Command(s): RCPT, VRFY
Standard Timeout: See command description

1.3.4.8 Requested mail action aborted: exceeded storage allocation (552)
The 552 reply code is a remaining instance of the 821 RFC published in 1982. In this RFC, the 552 reply code was designed to alert clients exceeding the number of recipients allowed by the server. However, in the 2821 RFC, the 452 reply code became the appropriate command. Since there is a possibility that there are SMTP servers active that were designed on the 821 RFC, clients should treat the 552 reply code as a 452 temporary failure instead of permanently based on the 5xy logic.

Reply Code: 552
Issued Command(s): RCPT
Standard Timeout: 5 Minutes (RCPT command)

1.3.4.9 Requested action not taken: mailbox name not allowed (553)
The 553 reply code is issued when an e-mail address parameter of a command is invalid. This can occur because the syntax of the parameter is invalid or because of security/company policies.

Reply Code: 553
Issued Command(s): MAIL, RCPT, VRFY
Standard Timeout: See command description

1.3.4.10 Transaction failed OR No SMTP service here (554)
When connecting to a SMTP server, the server initiates an SMTP session. In this session the client can issue commands to the server. However, an SMTP server can formally reject an SMTP transaction. If so, the SMTP server will, instead of the normal 220 reply code upon establishing the connection, issue a 554 reply code. The server will maintain the SMTP session until a QUIT command is issued, the connection times out, or the client closed the connection. When receiving a command, with the exception of the QUIT command, after a 554 reply code has been issued, a server will reply with a 503 reply code (1.3.4.4).

Reply Code: 554
Issued Command(s): N/A
Standard Timeout: 5 Minutes

 

 

 

class Program {

    static void Main(string[] args) {

      Console.WriteLine("POP3 Mail Client Demo");

      Console.WriteLine("=====================");

      Console.WriteLine();

      try {

        //prepare pop client

        // TODO: Replace username and password with your own credentials.

        Pop3.Pop3MailClient DemoClient = new Pop3.Pop3MailClient("pop.gmail.com", 995, true, "Username@gmail.com", "password");

        DemoClient.IsAutoReconnect = true;

 

        //remove the following line if no tracing is needed

        DemoClient.Trace += new Pop3.TraceHandler(Console.WriteLine);

        DemoClient.ReadTimeout = 60000; //give pop server 60 seconds to answer

 

        //establish connection

        DemoClient.Connect();

 

        //get mailbox statistics

        int NumberOfMails, MailboxSize;

        DemoClient.GetMailboxStats(out NumberOfMails, out MailboxSize);

 

        //get a list of mails

        List<int> EmailIds;

        DemoClient.GetEmailIdList(out EmailIds);

 

        //get a list of unique mail ids

        List<Pop3.EmailUid> EmailUids;

        DemoClient.GetUniqueEmailIdList(out EmailUids);

 

        //get email size

        DemoClient.GetEmailSize(1);

 

        //get email

        string Email;

        DemoClient.GetRawEmail(1, out Email);

       

        //delete email

        DemoClient.DeleteEmail(1);

 

        //get a list of mails

        List<int> EmailIds2;

        DemoClient.GetEmailIdList(out EmailIds2);

 

        //undelete all emails

        DemoClient.UndeleteAllEmails();

 

        //ping server

        DemoClient.NOOP();

 

        //test some error conditions

        DemoClient.GetRawEmail(1000000, out Email);

        DemoClient.DeleteEmail(1000000);

       

 

        //close connection

        DemoClient.Disconnect();

 

      } catch (Exception ex) {

        Console.WriteLine();

        Console.WriteLine("Run Time Error Occured:");

        Console.WriteLine(ex.Message);

        Console.WriteLine(ex.StackTrace);

      }

 

        Console.WriteLine();

        Console.WriteLine("======== Press Enter to end program");

        Console.ReadLine();

    }

  }

 

 

 

// POP3 Client

// ===========

//

// copyright by Peter Huber, Singapore, 2006

// this code is provided as is, bugs are probable, free for any use, no responsibility accepted :-)

//

// based on POP3 Client as a C# Class, by Bill Dean, http://www.codeproject.com/csharp/Pop3MailClient.asp

// based on Retrieve Mail From a POP3 Server Using C#, by Agus Kurniawan, http://www.codeproject.com/csharp/popapp.asp

// based on Post Office Protocol - Version 3, http://www.ietf.org/rfc/rfc1939.txt

 

using System;

using System.Collections.Generic;

using System.IO;

using System.Net;

using System.Net.Sockets;

using System.Net.Security;

using System.Text;

 

 

namespace Pop3 {

  // Supporting classes and structs

  // ==============================

 

  /// <summary>

  /// Combines Email ID with Email UID for one email

  /// The POP3 server assigns to each message a unique Email UID, which will not change for the life time

  /// of the message and no other message should use the same.

  ///

  /// Exceptions:

  /// Throws Pop3Exception if there is a serious communication problem with the POP3 server, otherwise

  ///

  /// </summary>

  public struct EmailUid {

    /// <summary>

    /// used in POP3 commands to indicate which message (only valid in the present session)

    /// </summary>

    public int EmailId;

    /// <summary>

    /// Uid is always the same for a message, regardless of session

    /// </summary>

    public string Uid;

 

    /// <summary>

    ///

    /// </summary>

    /// <param name="EmailId"></param>

    /// <param name="Uid"></param>

    public EmailUid(int EmailId, string Uid) {

      this.EmailId = EmailId;

      this.Uid = Uid;

    }

  }

 

 

  /// <summary>

  /// If anything goes wrong within Pop3MailClient, a Pop3Exception is raised

  /// </summary>

  public class Pop3Exception:ApplicationException {

    /// <summary>

    ///

    /// </summary>

    public Pop3Exception() { }

    /// <summary>

    ///

    /// </summary>

    /// <param name="ErrorMessage"></param>

    public Pop3Exception(string ErrorMessage) : base(ErrorMessage) { }

  }

 

 

  /// <summary>

  /// A pop 3 connection goes through the following states:

  /// </summary>

  public enum Pop3ConnectionStateEnum {

    /// <summary>

    /// undefined

    /// </summary>

    None=0,

    /// <summary>

    /// not connected yet to POP3 server

    /// </summary>

    Disconnected,

    /// <summary>

    /// TCP connection has been opened and the POP3 server has sent the greeting. POP3 server expects user name and password

    /// </summary>

    Authorization,

    /// <summary>

    /// client has identified itself successfully with the POP3, server has locked all messages

    /// </summary>

    Connected,

    /// <summary>

    /// QUIT command was sent, the server has deleted messages marked for deletion and released the resources

    /// </summary>

    Closed

  }

 

 

  // Delegates for Pop3MailClient

  // ============================

 

  /// <summary>

  /// If POP3 Server doesn't react as expected or this code has a problem, but

  /// can continue with the execution, a Warning is called.

  /// </summary>

  /// <param name="WarningText"></param>

  /// <param name="Response">string received from POP3 server</param>

  public delegate void WarningHandler(string WarningText, string Response);

 

 

  /// <summary>

  /// Traces all the information exchanged between POP3 client and POP3 server plus some

  /// status messages from POP3 client.

  /// Helpful to investigate any problem.

  /// Console.WriteLine() can be used

  /// </summary>

  /// <param name="TraceText"></param>

  public delegate void TraceHandler(string TraceText);

 

 

  // Pop3MailClient Class

  // ==================== 

 

  /// <summary>

  /// provides access to emails on a POP3 Server

  /// </summary>

  public class Pop3MailClient {

 

    //Events

    //------

 

    /// <summary>

    /// Called whenever POP3 server doesn't react as expected, but no runtime error is thrown.

    /// </summary>

    public event WarningHandler Warning;

 

    /// <summary>

    /// call warning event

    /// </summary>

    /// <param name="methodName">name of the method where warning is needed</param>

    /// <param name="response">answer from POP3 server causing the warning</param>

    /// <param name="warningText">explanation what went wrong</param>

    /// <param name="warningParameters"></param>

    protected void CallWarning(string methodName, string response, string warningText, params object[] warningParameters) {

      warningText = string.Format(warningText, warningParameters);

      if (Warning!=null) {

        Warning(methodName + ": " + warningText, response);

      }

      CallTrace("!! {0}", warningText);

    }

 

 

    /// <summary>

    /// Shows the communication between PopClient and PopServer, including warnings

    /// </summary>

    public event TraceHandler Trace;

 

    /// <summary>

    /// call Trace event

    /// </summary>

    /// <param name="text">string to be traced</param>

    /// <param name="parameters"></param>

    protected void CallTrace(string text, params object[] parameters) {

      if (Trace!=null) {

        Trace(DateTime.Now.ToString("hh:mm:ss ") + popServer + " " + string.Format(text, parameters));

      }

    }

 

    /// <summary>

    /// Trace information received from POP3 server

    /// </summary>

    /// <param name="text">string to be traced</param>

    /// <param name="parameters"></param>

    protected void TraceFrom(string text, params object[] parameters) {

      if (Trace!=null) {

        CallTrace("   " + string.Format(text, parameters));

      }

    }

 

 

    //Properties

    //----------

 

    /// <summary>

    /// Get POP3 server name

    /// </summary>

    public string PopServer {

      get { return popServer; }

    }

    /// <summary>

    /// POP3 server name

    /// </summary>

    protected string popServer;

 

 

    /// <summary>

    /// Get POP3 server port

    /// </summary>

    public int Port {

      get { return port; }

    }

    /// <summary>

    /// POP3 server port

    /// </summary>

    protected int port;

 

 

    /// <summary>

    /// Should SSL be used for connection with POP3 server ?

    /// </summary>

    public bool UseSSL {

      get { return useSSL; }

    }

    /// <summary>

    /// Should SSL be used for connection with POP3 server ?

    /// </summary>

    private bool useSSL;

 

 

    /// <summary>

    /// should Pop3MailClient automatically reconnect if POP3 server has dropped the

    /// connection due to a timeout ?

    /// </summary>

    public bool IsAutoReconnect {

      get { return isAutoReconnect; }

      set { isAutoReconnect = value; }

    }

    private bool isAutoReconnect = false;

    //timeout has occured, we try to perform an autoreconnect

    private bool isTimeoutReconnect = false;

 

 

 

    /// <summary>

    /// Get / set read timeout (miliseconds)

    /// </summary>

    public int ReadTimeout {

      get { return readTimeout; }

      set {

        readTimeout = value;

        if (pop3Stream!=null && pop3Stream.CanTimeout) {

          pop3Stream.ReadTimeout = readTimeout;

        }

      }

    }

    /// <summary>

    /// POP3 server read timeout

    /// </summary>

    protected int readTimeout = -1;

 

 

    /// <summary>

    /// Get owner name of mailbox on POP3 server

    /// </summary>

    public string Username {

      get { return username; }

    }

    /// <summary>

    /// Owner name of mailbox on POP3 server

    /// </summary>

    protected string username;

 

 

    /// <summary>

    /// Get password for mailbox on POP3 server

    /// </summary>

    public string Password {

      get { return password; }

    }

    /// <summary>

    /// Password for mailbox on POP3 server

    /// </summary>

    protected string password;

 

 

    /// <summary>

    /// Get connection status with POP3 server

    /// </summary>

    public Pop3ConnectionStateEnum Pop3ConnectionState {

      get { return pop3ConnectionState; }

    }

    /// <summary>

    /// connection status with POP3 server

    /// </summary>

    protected Pop3ConnectionStateEnum pop3ConnectionState = Pop3ConnectionStateEnum.Disconnected;

 

 

    // Methods

    // -------

 

    /// <summary>

    /// set POP3 connection state

    /// </summary>

    /// <param name="State"></param>

    protected void setPop3ConnectionState(Pop3ConnectionStateEnum State) {

      pop3ConnectionState = State;

      CallTrace("   Pop3MailClient Connection State {0} reached", State);

    }

 

    /// <summary>

    /// throw exception if POP3 connection is not in the required state

    /// </summary>

    /// <param name="requiredState"></param>

    protected void EnsureState(Pop3ConnectionStateEnum requiredState) {

      if (pop3ConnectionState!=requiredState) {

        // wrong connection state

        throw new Pop3Exception("GetMailboxStats only accepted during connection state: " + requiredState.ToString() +

              "\n The connection to server "+ popServer + " is in state " + pop3ConnectionState.ToString());

      }

    }

 

 

    //private fields

    //--------------

    /// <summary>

    /// TCP to POP3 server

    /// </summary>

    private TcpClient serverTcpConnection;

    /// <summary>

    /// Stream from POP3 server with or without SSL

    /// </summary>

    private Stream pop3Stream;

    /// <summary>

    /// Reader for POP3 message

    /// </summary>

    protected StreamReader pop3StreamReader;

    /// <summary>

    /// char 'array' for carriage return / line feed

    /// </summary>

    protected string CRLF = "\r\n";

 

 

    //public methods

    //--------------

 

    /// <summary>

    /// Make POP3 client ready to connect to POP3 server

    /// </summary>

    /// <param name="PopServer"><example>pop.gmail.com</example></param>

    /// <param name="Port"><example>995</example></param>

    /// <param name="useSSL">True: SSL is used for connection to POP3 server</param>

    /// <param name="Username"><example>abc@gmail.com</example></param>

    /// <param name="Password">Secret</param>

    public Pop3MailClient(string PopServer, int Port, bool useSSL, string Username, string Password) {

      this.popServer = PopServer;

      this.port = Port;

      this.useSSL = useSSL;

      this.username = Username;

      this.password = Password;

    }

 

 

    /// <summary>

    /// Connect to POP3 server

    /// </summary>

    public void Connect() {

      if (pop3ConnectionState!=Pop3ConnectionStateEnum.Disconnected &&

        pop3ConnectionState!=Pop3ConnectionStateEnum.Closed &&

        !isTimeoutReconnect) {

        CallWarning("connect", "", "Connect command received, but connection state is: " + pop3ConnectionState.ToString());

      } else {

        //establish TCP connection

        try {

          CallTrace("   Connect at port {0}", port);

          serverTcpConnection = new TcpClient(popServer, port);

        } catch (Exception ex) {

          throw new Pop3Exception("Connection to server "+ popServer + ", port " + port + " failed.\nRuntime Error: "+ex.ToString());

        }

 

        if (useSSL) {

          //get SSL stream

          try {

            CallTrace("   Get SSL connection");

            pop3Stream = new SslStream(serverTcpConnection.GetStream(), false);

            pop3Stream.ReadTimeout = readTimeout;

          } catch (Exception ex) {

            throw new Pop3Exception("Server " + popServer + " found, but cannot get SSL data stream.\nRuntime Error: "+ex.ToString());

          }

 

          //perform SSL authentication

          try {

            CallTrace("   Get SSL authentication");

            ((SslStream)pop3Stream).AuthenticateAsClient(popServer);

          } catch (Exception ex) {

            throw new Pop3Exception("Server " + popServer + " found, but problem with SSL Authentication.\nRuntime Error: " + ex.ToString());

          }

        } else {

          //create a stream to POP3 server without using SSL

          try {

            CallTrace("   Get connection without SSL");

            pop3Stream = serverTcpConnection.GetStream();

            pop3Stream.ReadTimeout = readTimeout;

          } catch (Exception ex) {

            throw new Pop3Exception("Server " + popServer + " found, but cannot get data stream (without SSL).\nRuntime Error: "+ex.ToString());

          }

        }

        //get stream for reading from pop server

        //POP3 allows only US-ASCII. The message will be translated in the proper encoding in a later step

        try {

          pop3StreamReader= new StreamReader(pop3Stream, Encoding.ASCII);

        } catch (Exception ex) {

          if (useSSL) {

            throw new Pop3Exception("Server " + popServer + " found, but cannot read from SSL stream.\nRuntime Error: " + ex.ToString());

          } else {

            throw new Pop3Exception("Server " + popServer + " found, but cannot read from stream (without SSL).\nRuntime Error: " + ex.ToString());

          }

        }

 

        //ready for authorisation

        string response;

        if (!readSingleLine(out response)) {

          throw new Pop3Exception("Server " + popServer + " not ready to start AUTHORIZATION.\nMessage: " + response);

        }

        setPop3ConnectionState(Pop3ConnectionStateEnum.Authorization);

 

        //send user name

        if (!executeCommand("USER "+ username, out response)) {

          throw new Pop3Exception("Server " + popServer + " doesn't accept username '" + username + "'.\nMessage: " + response);

        }

 

        //send password

        if (!executeCommand("PASS " + password, out response)) {

          throw new Pop3Exception("Server " + popServer + " doesn't accept password '" + password + "' for user '" + username + "'.\nMessage: " + response);

        }

 

        setPop3ConnectionState(Pop3ConnectionStateEnum.Connected);

      }

    }

 

 

    /// <summary>

    /// Disconnect from POP3 Server

    /// </summary>

    public void Disconnect() {

      if (pop3ConnectionState==Pop3ConnectionStateEnum.Disconnected ||

        pop3ConnectionState==Pop3ConnectionStateEnum.Closed) {

        CallWarning("disconnect", "", "Disconnect received, but was already disconnected.");

      } else {

        //ask server to end session and possibly to remove emails marked for deletion

        try {

          string response;

          if (executeCommand("QUIT", out response)) {

            //server says everything is ok

            setPop3ConnectionState(Pop3ConnectionStateEnum.Closed);

          } else {

            //server says there is a problem

            CallWarning("Disconnect", response, "negative response from server while closing connection: " + response);

            setPop3ConnectionState(Pop3ConnectionStateEnum.Disconnected);

          }

        } finally {

          //close connection

          if (pop3Stream!=null) {

            pop3Stream.Close();

          }

 

          pop3StreamReader.Close();

        }

      }

    }

 

 

    /// <summary>

    /// Delete message from server.

    /// The POP3 server marks the message as deleted.  Any future

    /// reference to the message-number associated with the message

    /// in a POP3 command generates an error.  The POP3 server does

    /// not actually delete the message until the POP3 session

    /// enters the UPDATE state.

    /// </summary>

    /// <param name="msg_number"></param>

    /// <returns></returns>

    public bool DeleteEmail(int msg_number) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      string response;

      if (!executeCommand("DELE " + msg_number.ToString(), out response)) {

        CallWarning("DeleteEmail", response, "negative response for email (Id: {0}) delete request", msg_number);

        return false;

      }

      return true;

    }

 

 

    /// <summary>

    /// Get a list of all Email IDs available in mailbox

    /// </summary>

    /// <returns></returns>

    public bool GetEmailIdList(out List<int> EmailIds) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      EmailIds = new List<int>();

 

      //get server response status line

      string response;

      if (!executeCommand("LIST", out response)) {

        CallWarning("GetEmailIdList", response, "negative response for email list request");

        return false;

      }

 

      //get every email id

      int EmailId;

      while (readMultiLine(out response)) {

        if (int.TryParse(response.Split(' ')[0], out EmailId)) {

          EmailIds.Add(EmailId);

        } else {

          CallWarning("GetEmailIdList", response, "first characters should be integer (EmailId)");

        }

      }

      TraceFrom("{0} email ids received", EmailIds.Count);

      return true;

    }

 

 

    /// <summary>

    /// get size of one particular email

    /// </summary>

    /// <param name="msg_number"></param>

    /// <returns></returns>

    public int GetEmailSize(int msg_number) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      string response;

      executeCommand("LIST " + msg_number.ToString(), out response);

      int EmailSize = 0;

      string[] responseSplit = response.Split(' ');

      if (responseSplit.Length<2 || !int.TryParse(responseSplit[2], out EmailSize)) {

        CallWarning("GetEmailSize", response, "'+OK int int' format expected (EmailId, EmailSize)");

      }

 

      return EmailSize;

    }

 

 

    /// <summary>

    /// Get a list with the unique IDs of all Email available in mailbox.

    ///

    /// Explanation:

    /// EmailIds for the same email can change between sessions, whereas the unique Email id

    /// never changes for an email.

    /// </summary>

    /// <param name="EmailIds"></param>

    /// <returns></returns>

    public bool GetUniqueEmailIdList(out List<EmailUid> EmailIds) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      EmailIds = new List<EmailUid>();

 

      //get server response status line

      string response;

      if (!executeCommand("UIDL ", out response)) {

        CallWarning("GetUniqueEmailIdList", response, "negative response for email list request");

        return false;

      }

 

      //get every email unique id

      int EmailId;

      while (readMultiLine(out response)) {

        string[] responseSplit = response.Split(' ');

        if (responseSplit.Length<2) {

          CallWarning("GetUniqueEmailIdList", response, "response not in format 'int string'");

        } else if (!int.TryParse(responseSplit[0], out EmailId)) {

          CallWarning("GetUniqueEmailIdList", response, "first charaters should be integer (Unique EmailId)");

        } else {

          EmailIds.Add(new EmailUid(EmailId, responseSplit[1]));

        }

      }

      TraceFrom("{0} unique email ids received", EmailIds.Count);

      return true;

    }

 

 

    /// <summary>

    /// get a list with all currently available messages and the UIDs

    /// </summary>

    /// <param name="EmailIds">EmailId Uid list</param>

    /// <returns>false: server sent negative response (didn't send list)</returns>

    public bool GetUniqueEmailIdList(out SortedList<string, int> EmailIds) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      EmailIds = new SortedList<string, int>();

 

      //get server response status line

      string response;

      if (!executeCommand("UIDL", out response)) {

        CallWarning("GetUniqueEmailIdList", response, "negative response for email list request");

        return false;

      }

 

      //get every email unique id

      int EmailId;

      while (readMultiLine(out response)) {

        string[] responseSplit = response.Split(' ');

        if (responseSplit.Length<2) {

          CallWarning("GetUniqueEmailIdList", response, "response not in format 'int string'");

        } else if (!int.TryParse(responseSplit[0], out EmailId)) {

          CallWarning("GetUniqueEmailIdList", response, "first charaters should be integer (Unique EmailId)");

        } else {

          EmailIds.Add(responseSplit[1], EmailId);

        }

      }

      TraceFrom("{0} unique email ids received", EmailIds.Count);

      return true;

    }

 

 

    /// <summary>

    /// get size of one particular email

    /// </summary>

    /// <param name="msg_number"></param>

    /// <returns></returns>

    public int GetUniqueEmailId(EmailUid msg_number) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      string response;

      executeCommand("LIST " + msg_number.ToString(), out response);

      int EmailSize = 0;

      string[] responseSplit = response.Split(' ');

      if (responseSplit.Length<2 || !int.TryParse(responseSplit[2], out EmailSize)) {

        CallWarning("GetEmailSize", response, "'+OK int int' format expected (EmailId, EmailSize)");

      }

 

      return EmailSize;

    }

 

 

    /// <summary>

    /// Sends an 'empty' command to the POP3 server. Server has to respond with +OK

    /// </summary>

    /// <returns>true: server responds as expected</returns>

    public bool NOOP() {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      string response;

      if (!executeCommand("NOOP", out response)) {

        CallWarning("NOOP", response, "negative response for NOOP request");

        return false;

      }

      return true;

    }

 

    /// <summary>

    /// Should the raw content, the US-ASCII code as received, be traced

    /// GetRawEmail will switch it on when it starts and off once finished

    ///

    /// Inheritors might use it to get the raw email

    /// </summary>

    protected bool isTraceRawEmail = false;

 

 

    /// <summary>

    /// contains one MIME part of the email in US-ASCII, needs to be translated in .NET string (Unicode)

    /// contains the complete email in US-ASCII, needs to be translated in .NET string (Unicode)

    /// For speed reasons, reuse StringBuilder

    /// </summary>

    protected StringBuilder RawEmailSB;

 

 

    /// <summary>

    /// Reads the complete text of a message

    /// </summary>

    /// <param name="MessageNo">Email to retrieve</param>

    /// <param name="EmailText">ASCII string of complete message</param>

    /// <returns></returns>

    public bool GetRawEmail(int MessageNo, out string EmailText) {

      //send 'RETR int' command to server

      if (!SendRetrCommand(MessageNo)) {

        EmailText = null;

        return false;

      }

 

      //get the lines

      string response;

      int LineCounter = 0;

      //empty StringBuilder

      if (RawEmailSB==null) {

        RawEmailSB = new StringBuilder(100000);

      } else {

        RawEmailSB.Length = 0;

      }

      isTraceRawEmail = true;

      while (readMultiLine(out response)) {

        LineCounter += 1;

      }

      EmailText = RawEmailSB.ToString();

      TraceFrom("email with {0} lines,  {1} chars received", LineCounter.ToString(), EmailText.Length);

      return true;

    }

 

 

    ///// <summary>

    ///// Requests from POP3 server a specific email and returns a stream with the message content (header and body)

    ///// </summary>

    ///// <param name="MessageNo"></param>

    ///// <param name="EmailStreamReader"></param>

    ///// <returns>false: POP3 server cannot provide this message</returns>

    //public bool GetEmailStream(int MessageNo, out StreamReader EmailStreamReader) {

    //  //send 'RETR int' command to server

    //  if (!SendRetrCommand(MessageNo)) {

    //    EmailStreamReader = null;

    //    return false;

    //  }

    //  EmailStreamReader = sslStreamReader;

    //  return true;

    //}

 

 

    /// <summary>

    /// Unmark any emails from deletion. The server only deletes email really

    /// once the connection is properly closed.

    /// </summary>

    /// <returns>true: emails are unmarked from deletion</returns>

    public bool UndeleteAllEmails() {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      string response;

      return executeCommand("RSET", out response);

    }

 

 

    /// <summary>

    /// Get mailbox statistics

    /// </summary>

    /// <param name="NumberOfMails"></param>

    /// <param name="MailboxSize"></param>

    /// <returns></returns>

    public bool GetMailboxStats(out int NumberOfMails, out int MailboxSize) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

 

      //interpret response

      string response;

      NumberOfMails = 0;

      MailboxSize =0;

      if (executeCommand("STAT", out response)) {

        //got a positive response

        string[] responseParts = response.Split(' ');

        if (responseParts.Length<2) {

          //response format wrong

          throw new Pop3Exception("Server " + popServer + " sends illegally formatted response." +

            "\nExpected format: +OK int int" +

            "\nReceived response: " + response);

        }

        NumberOfMails = int.Parse(responseParts[1]);

        MailboxSize   = int.Parse(responseParts[2]);

        return true;

      }

      return false;

    }

 

 

    /// <summary>

    /// Send RETR command to POP 3 server to fetch one particular message

    /// </summary>

    /// <param name="MessageNo">ID of message required</param>

    /// <returns>false: negative server respond, message not delivered</returns>

    protected bool SendRetrCommand(int MessageNo) {

      EnsureState(Pop3ConnectionStateEnum.Connected);

      // retrieve mail with message number

      string response;

      if (!executeCommand("RETR "+ MessageNo.ToString(), out response)) {

        CallWarning("GetRawEmail", response, "negative response for email (ID: {0}) request", MessageNo);

        return false;

      }

      return true;

    }

 

 

    //Helper methodes

    //---------------

 

    /// <summary>

    /// sends the 4 letter command to POP3 server (adds CRLF) and waits for the

    /// response of the server

    /// </summary>

    /// <param name="command">command to be sent to server</param>

    /// <param name="response">answer from server</param>

    /// <returns>false: server sent negative acknowledge, i.e. server could not execute command</returns>

 

    public bool isDebug = false;

    private bool executeCommand(string command, out string response) {

      //send command to server

      byte[] commandBytes = System.Text.Encoding.ASCII.GetBytes((command + CRLF).ToCharArray());

      CallTrace("Tx '{0}'", command);

      bool isSupressThrow = false;

      try {

        pop3Stream.Write(commandBytes, 0, commandBytes.Length);

        if (isDebug) {

          isDebug=false;

          throw new IOException("Test", new SocketException(10053));

        }

      } catch (IOException ex) {

        //Unable to write data to the transport connection. Check if reconnection should be tried

        isSupressThrow = executeReconnect(ex, command, commandBytes);

        if (!isSupressThrow) {

          throw;

        }

      }

      pop3Stream.Flush();

 

      //read response from server

      response = null;

      try {

        response = pop3StreamReader.ReadLine();

      } catch (IOException ex) {

        //Unable to write data to the transport connection. Check if reconnection should be tried

        isSupressThrow = executeReconnect(ex, command, commandBytes);

        if (isSupressThrow) {

          //wait for response one more time

          response = pop3StreamReader.ReadLine();

        } else {

          throw;

        }

      }

      if (response==null) {

        throw new Pop3Exception("Server "+ popServer + " has not responded, timeout has occured.");

      }

      CallTrace("Rx '{0}'", response);

      return (response.Length>0 && response[0]=='+');

    }

 

 

    /// <summary>

    /// reconnect, if there is a timeout exception and isAutoReconnect is true

    ///

    /// </summary>

   private bool executeReconnect(IOException ex, string command, byte[] commandBytes) {

      if (ex.InnerException!=null && ex.InnerException is SocketException) {

        //SocketException

        SocketException innerEx = (SocketException)ex.InnerException;

        if (innerEx.ErrorCode==10053) {

          //probably timeout: An established connection was aborted by the software in your host machine.

          CallWarning("ExecuteCommand", "", "probably timeout occured");

          if (isAutoReconnect) {

            //try to reconnect and send one more time

            isTimeoutReconnect = true;

            try {

              CallTrace("   try to auto reconnect");

              Connect();

 

              CallTrace("   reconnect successful, try to resend command");

              CallTrace("Tx '{0}'", command);

              pop3Stream.Write(commandBytes, 0, commandBytes.Length);

              pop3Stream.Flush();

              return true;

            } finally {

              isTimeoutReconnect = false;

            }

 

          }

        }

      }

      return false;

    }

 

 

    /*

        /// <summary>

        /// sends the 4 letter command to POP3 server (adds CRLF)

        /// </summary>

        /// <param name="command"></param>

        protected void SendCommand(string command) {

          byte[] commandBytes = System.Text.Encoding.ASCII.GetBytes((command + CRLF).ToCharArray());

          CallTrace("Tx '{0}'", command);

          try {

            pop3Stream.Write(commandBytes, 0, commandBytes.Length);

          } catch (IOException ex) {

            //Unable to write data to the transport connection:

            if (ex.InnerException!=null && ex.InnerException is SocketException) {

              //SocketException

              SocketException innerEx = (SocketException)ex.InnerException;

              if (innerEx.ErrorCode==10053) {

                //probably timeout: An established connection was aborted by the software in your host machine.

                CallWarning("SendCommand", "", "probably timeout occured");

                if (isAutoReconnect) {

                  //try to reconnect and send one more time

                  isTimeoutReconnect = true;

                  try {

                    CallTrace("   try to auto reconnect");

                    Connect();

 

                    CallTrace("   reconnect successful, try to resend command");

                    CallTrace("Tx '{0}'", command);

                    pop3Stream.Write(commandBytes, 0, commandBytes.Length);

                  } finally {

                    isTimeoutReconnect = false;

                  }

                  return;

                }

              }

            }

            throw;

          }

          pop3Stream.Flush();

        }

     */

 

 

    /// <summary>

    /// read single line response from POP3 server.

    /// <example>Example server response: +OK asdfkjahsf</example>

    /// </summary>

    /// <param name="response">response from POP3 server</param>

    /// <returns>true: positive response</returns>

    protected bool readSingleLine(out string response) {

      response = null;

      try {

        response = pop3StreamReader.ReadLine();

      } catch (Exception ex) {

        string s = ex.Message;

      }

      if (response==null) {

        throw new Pop3Exception("Server "+ popServer + " has not responded, timeout has occured.");

      }

      CallTrace("Rx '{0}'", response);

      return (response.Length>0 && response[0]=='+');

    }

 

 

    /// <summary>

    /// read one line in multiline mode from the POP3 server.

    /// </summary>

    /// <param name="response">line received</param>

    /// <returns>false: end of message</returns>

    /// <returns></returns>

    protected bool readMultiLine(out string response) {

      response = null;

      response = pop3StreamReader.ReadLine();

      if (response==null) {

        throw new Pop3Exception("Server "+ popServer + " has not responded, probably timeout has occured.");

      }

      if (isTraceRawEmail) {

        //collect all responses as received

        RawEmailSB.Append(response + CRLF);

      }

      //check for byte stuffing, i.e. if a line starts with a '.', another '.' is added, unless

      //it is the last line

      if (response.Length>0 && response[0]=='.') {

        if (response==".") {

          //closing line found

          return false;

        }

        //remove the first '.'

        response = response.Substring(1, response.Length-1);

      }

      return true;

    }

 

  }

}