diff --git a/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs b/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs
new file mode 100644
index 0000000..a1599ee
--- /dev/null
+++ b/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs
@@ -0,0 +1,51 @@
+using MailServer.Settings;
+
+namespace MailServer.IMAP.Server
+{
+ partial class AuthUser_EventArgs
+ {
+ ///
+ /// Gibt die IMAP Session zurück
+ ///
+ public IMAP_Session Session
+ {
+ get { return m_pSession; }
+ }
+ ///
+ /// Gibt den Benutzernamen zurück
+ ///
+ public string UserName
+ {
+ get { return m_UserName; }
+ }
+ ///
+ /// Gibt das Passwort zurück
+ ///
+ public string PasswData
+ {
+ get { return m_PasswData; }
+ }
+ ///
+ /// Gibt die AuthData zurück
+ ///
+ public string AuthData
+ {
+ get { return m_Data; }
+ }
+ ///
+ /// Gibt den Authtyp zurück
+ ///
+ public AuthType AuthType
+ {
+ get { return m_AuthType; }
+ }
+ ///
+ /// ist der Benutzer erlaubt
+ ///
+ public bool Validated
+ {
+ get { return m_Validated; }
+ set { m_Validated = value; }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/AuthUser_EventArgs.cs b/MailServer/IMAP/Server/AuthUser_EventArgs.cs
new file mode 100644
index 0000000..d57b921
--- /dev/null
+++ b/MailServer/IMAP/Server/AuthUser_EventArgs.cs
@@ -0,0 +1,33 @@
+using MailServer.Settings;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// Stellt daten für das AuthUser Event zur verfügung
+ ///
+ public class AuthUser_EventArgs
+ {
+ private IMAP_Session m_pSession = null;
+ private string m_UserName = "";
+ private string m_PasswData = "";
+ private string m_Data = "";
+ private AuthType m_AuthType;
+ private bool m_Validated = false;
+ ///
+ /// AuthUser_EventArgs Constructor
+ ///
+ /// Referenz zur IMAP Session
+ /// Benutzername
+ /// Passwort
+ /// Hängt vom Authtyp ab
+ /// Authtyp
+ public AuthUser_EventArgs(IMAP_Session session, string userName, string passwData, string data, AuthType authType)
+ {
+ m_pSession = session;
+ m_UserName = userName;
+ m_PasswData = passwData;
+ m_Data = data;
+ m_AuthType = authType;
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_Session.cs b/MailServer/IMAP/Server/IMAP_Session.cs
new file mode 100644
index 0000000..0aee03f
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_Session.cs
@@ -0,0 +1,3383 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP session.
+ ///
+ public class IMAP_Session
+ {
+ private Socket m_pClientSocket = null; // Referance to client Socket.
+ private IMAP_Server m_pIMAP_Server = null; // Referance to SMTP server.
+ private string m_SessionID = ""; // Holds session ID.
+ private string m_UserName = ""; // Holds loggedIn UserName.
+ private string m_SelectedMailbox = "";
+ private IMAP_Messages m_Messages = null;
+ private string m_ConnectedIp = ""; // Holds connected computer's IP.
+ private bool m_Authenticated = false; // Holds authentication flag.
+ private int m_BadCmdCount = 0; // Holds number of bad commands.
+ private DateTime m_SessionStartTime;
+ private _LogWriter m_pLogWriter = null;
+ private object m_Tag = null;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Referance to socket.
+ /// Referance to IMAP server.
+ /// Session ID which is assigned to this session.
+ /// Log writer.
+ internal IMAP_Session(Socket clientSocket, IMAP_Server server, string sessionID, _LogWriter logWriter)
+ {
+ m_pClientSocket = clientSocket;
+ m_pIMAP_Server = server;
+ m_SessionID = sessionID;
+ m_pLogWriter = logWriter;
+ m_SessionStartTime = DateTime.Now;
+ }
+
+
+ #region function StartProcessing
+
+ ///
+ /// Starts session processing.
+ ///
+ public void StartProcessing()
+ {
+ try
+ {
+ // Store client ip and host name.
+ m_ConnectedIp = Core.ParseIP_from_EndPoint(m_pClientSocket.RemoteEndPoint.ToString());
+
+ // Check if ip is allowed to connect this computer
+ if (m_pIMAP_Server.OnValidate_IpAddress(this.RemoteEndPoint))
+ {
+
+ // Notify that server is ready
+ SendData("* OK " + System.Net.Dns.GetHostName() + " IMAP Service ready\r\n");
+
+ //------ Create command loop --------------------------------//
+ // Loop while QUIT cmd or Session TimeOut.
+ long lastCmdTime = DateTime.Now.Ticks;
+ string lastCmd = "";
+ while (true)
+ {
+ // If there is any data available, begin command reading.
+ if (m_pClientSocket.Available > 0)
+ {
+ try
+ {
+ lastCmd = ReadLine();
+ if (SwitchCommand(lastCmd))
+ {
+ break;
+ }
+ }
+ catch (ReadException rX)
+ {
+ //---- Check that maximum bad commands count isn't exceeded ---------------//
+ if (m_BadCmdCount > m_pIMAP_Server.MaxBadCommands - 1)
+ {
+ SendData("* BAD Too many bad commands, closing transmission channel\r\n");
+ break;
+ }
+ m_BadCmdCount++;
+ //-------------------------------------------------------------------------//
+
+ switch (rX.ReadReplyCode)
+ {
+ case ReadReplyCode.LengthExceeded:
+ SendData("* BAD Line too long.\r\n");
+ break;
+
+ case ReadReplyCode.TimeOut:
+ SendData("* BAD Command timeout.\r\n");
+ break;
+
+ case ReadReplyCode.UnKnownError:
+ SendData("* BAD UnKnown Error.\r\n");
+
+ m_pIMAP_Server.OnSysError(new Exception(rX.Message), new System.Diagnostics.StackTrace());
+ break;
+ }
+ }
+ catch (Exception x)
+ {
+ // Connection lost
+ if (!m_pClientSocket.Connected)
+ {
+ break;
+ }
+
+ SendData("* BAD Unkown temp error\r\n");
+ m_pIMAP_Server.OnSysError(x, new System.Diagnostics.StackTrace());
+ }
+
+ // reset last command time
+ lastCmdTime = DateTime.Now.Ticks;
+ }
+ else
+ {
+ //----- Session timeout stuff ------------------------------------------------//
+ if (DateTime.Now.Ticks > lastCmdTime + ((long)(m_pIMAP_Server.SessionIdleTimeOut)) * 10000)
+ {
+ // Notify for closing
+ SendData("* BAD Session timeout, closing transmission channel\r\n");
+ break;
+ }
+
+ // Wait 100ms to save CPU, otherwise while loop may take 100% CPU.
+ Thread.Sleep(100);
+ //---------------------------------------------------------------------------//
+ }
+ }
+ }
+ }
+ catch (ThreadInterruptedException e)
+ {
+ string dummy = e.Message;
+ }
+ catch (Exception x)
+ {
+ /* RFC 2821 3.9
+ NOTE:
+ An SMTP server which is forcibly shut down via external means SHOULD
+ attempt to send a line containing a 421 response code to the SMTP
+ client before exiting. The SMTP client will normally read the 421
+ response code after sending its next command.
+ */
+ if (m_pClientSocket.Connected)
+ {
+ SendData("* BAD Service not available, closing transmission channel\r\n");
+
+ m_pIMAP_Server.OnSysError(x, new System.Diagnostics.StackTrace());
+ }
+ else
+ {
+ m_pLogWriter.AddEntry("Connection is aborted by client machine", this.SessionID, m_ConnectedIp, "x");
+ }
+ }
+ finally
+ {
+ m_pIMAP_Server.RemoveSession(this.SessionID, m_pLogWriter);
+
+ // Write logs to log file, if needed
+ if (m_pIMAP_Server.LogCommands)
+ {
+ m_pLogWriter.Flush();
+ }
+
+ if (m_pClientSocket.Connected)
+ {
+ m_pClientSocket.Close();
+ }
+ }
+ }
+
+ #endregion
+
+ #region function SwitchCommand
+
+ ///
+ /// Executes IMAP command.
+ ///
+ /// Original command text.
+ /// Returns true if must end session(command loop).
+ private bool SwitchCommand(string IMAP_commandTxt)
+ {
+ // Parse commandTag + comand + args
+ // eg. C:a100 SELECT INBOX
+ // a100 - commandTag
+ // SELECT - command
+ // INBOX - arg
+
+ //---- Parse command --------------------------------------------------//
+ string[] cmdParts = IMAP_commandTxt.TrimStart().Split(new char[] { ' ' });
+ // For bad command, just return empty cmdTag and command name
+ if (cmdParts.Length < 2)
+ {
+ cmdParts = new string[] { "", "" };
+ }
+ string commandTag = cmdParts[0].Trim().Trim();
+ string command = cmdParts[1].ToUpper().Trim();
+ string argsText = Core.GetArgsText(IMAP_commandTxt, cmdParts[0] + " " + cmdParts[1]);
+ //---------------------------------------------------------------------//
+
+ switch (command)
+ {
+ //--- Non-Authenticated State
+ case "AUTHENTICATE":
+ Authenticate(commandTag, argsText);
+ break;
+
+ case "LOGIN":
+ LogIn(commandTag, argsText);
+ break;
+ //--- End of non-Authenticated
+
+ //--- Authenticated State
+ case "SELECT":
+ Select(commandTag, argsText);
+ break;
+
+ case "EXAMINE":
+ Examine(commandTag, argsText);
+ break;
+
+ case "CREATE":
+ Create(commandTag, argsText);
+ break;
+
+ case "DELETE":
+ Delete(commandTag, argsText);
+ break;
+
+ case "RENAME":
+ Rename(commandTag, argsText);
+ break;
+
+ case "SUBSCRIBE":
+ Suscribe(commandTag, argsText);
+ break;
+
+ case "UNSUBSCRIBE":
+ UnSuscribe(commandTag, argsText);
+ break;
+
+ case "LIST":
+ List(commandTag, argsText);
+ break;
+
+ case "LSUB":
+ LSub(commandTag, argsText);
+ break;
+
+ case "STATUS":
+ Status(commandTag, argsText);
+ break;
+
+ case "APPEND":
+ Append(commandTag, argsText);
+ break;
+ //--- End of Authenticated
+
+ //--- Selected State
+ case "CHECK":
+ Check(commandTag);
+ break;
+
+ case "CLOSE":
+ Close(commandTag);
+ break;
+
+ case "EXPUNGE":
+ Expunge(commandTag);
+ break;
+
+ case "SEARCH":
+ Search(commandTag, argsText, false);
+ break;
+
+ case "FETCH":
+ Fetch(commandTag, argsText, false);
+ break;
+
+ case "STORE":
+ Store(commandTag, argsText, false);
+ break;
+
+ case "COPY":
+ Copy(commandTag, argsText, false);
+ break;
+
+ case "UID":
+ Uid(commandTag, argsText);
+ break;
+ //--- End of Selected
+
+ //--- Any State
+ case "CAPABILITY":
+ Capability(commandTag);
+ break;
+
+ case "NOOP":
+ Noop(commandTag);
+ break;
+
+ case "LOGOUT":
+ LogOut(commandTag);
+ return true;
+ //--- End of Any
+
+ default:
+ SendData(commandTag + " BAD command unrecognized\r\n");
+
+ //---- Check that maximum bad commands count isn't exceeded ---------------//
+ if (m_BadCmdCount > m_pIMAP_Server.MaxBadCommands - 1)
+ {
+ SendData("* BAD Too many bad commands, closing transmission channel\r\n");
+ return true;
+ }
+ m_BadCmdCount++;
+ //-------------------------------------------------------------------------//
+ break;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+
+ //--- Non-Authenticated State ------
+
+ #region function Authenticate
+
+ private void Authenticate(string cmdTag, string argsText)
+ {
+ /* Rfc 3501 6.2.2. AUTHENTICATE Command
+
+ Arguments: authentication mechanism name
+
+ Responses: continuation data can be requested
+
+ Result: OK - authenticate completed, now in authenticated state
+ NO - authenticate failure: unsupported authentication
+ mechanism, credentials rejected
+ BAD - command unknown or arguments invalid,
+ authentication exchange cancelled
+ */
+ if (m_Authenticated)
+ {
+ SendData(cmdTag + " NO AUTH you are already logged in\r\n");
+ return;
+ }
+
+ string userName = "";
+ // string password = "";
+
+ switch (argsText.ToUpper())
+ {
+ case "CRAM-MD5":
+
+ #region CRAM-MDD5 authentication
+
+ /* Cram-M5
+ C: A0001 AUTH CRAM-MD5
+ S: +
+ C: base64(decoded:username password_hash)
+ S: A0001 OK CRAM authentication successful
+ */
+
+ string md5Hash = "<" + Guid.NewGuid().ToString().ToLower() + ">";
+ SendData("+ " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash)) + "\r\n");
+
+ string reply = ReadLine();
+ reply = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply));
+ string[] replyArgs = reply.Split(' ');
+ userName = replyArgs[0];
+
+ if (m_pIMAP_Server.OnAuthUser(this, userName, replyArgs[1], md5Hash, AuthType.CRAM_MD5))
+ {
+ SendData(cmdTag + " OK Authentication successful.\r\n");
+ m_Authenticated = true;
+ m_UserName = userName;
+ }
+ else
+ {
+ SendData(cmdTag + " NO Authentication failed\r\n");
+ }
+
+ #endregion
+
+ break;
+
+ default:
+ SendData(cmdTag + " NO unsupported authentication mechanism\r\n");
+ break;
+ }
+
+ }
+
+ #endregion
+
+ #region function LogIn
+
+ private void LogIn(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.2.3 LOGIN Command
+
+ Arguments: user name
+ password
+
+ Responses: no specific responses for this command
+
+ Result: OK - login completed, now in authenticated state
+ NO - login failure: user name or password rejected
+ BAD - command unknown or arguments invalid
+
+ The LOGIN command identifies the client to the server and carries
+ the plaintext password authenticating this user.
+
+ Example: C: a001 LOGIN SMITH SESAME
+ S: a001 OK LOGIN completed
+
+ */
+ if (m_Authenticated)
+ {
+ SendData(cmdTag + " NO LOGIN you are already logged in\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD Invalid arguments\r\n");
+ return;
+ }
+
+ string userName = args[0];
+ string password = args[1];
+
+ if (m_pIMAP_Server.OnAuthUser(this, userName, password, "", AuthType.Plain))
+ {
+ SendData(cmdTag + " OK LOGIN completed\r\n");
+ m_UserName = userName;
+ m_Authenticated = true;
+ }
+ else
+ {
+ SendData(cmdTag + " NO LOGIN failed\r\n");
+ }
+ }
+
+ #endregion
+
+ //--- End of non-Authenticated State
+
+
+ //--- Authenticated State ------
+
+ #region function Select
+
+ private void Select(string cmdTag, string argsText)
+ {
+ /* Rfc 3501 6.3.1 SELECT Command
+
+ Arguments: mailbox name
+
+ Responses: REQUIRED untagged responses: FLAGS, EXISTS, RECENT
+ REQUIRED OK untagged responses: UNSEEN, PERMANENTFLAGS,
+ UIDNEXT, UIDVALIDITY
+
+ Result: OK - select completed, now in selected state
+ NO - select failure, now in authenticated state: no
+ such mailbox, can't access mailbox
+ BAD - command unknown or arguments invalid
+
+ The SELECT command selects a mailbox so that messages in the
+ mailbox can be accessed. Before returning an OK to the client,
+ the server MUST send the following untagged data to the client.
+ Note that earlier versions of this protocol only required the
+ FLAGS, EXISTS, and RECENT untagged data; consequently, client
+ implementations SHOULD implement default behavior for missing data
+ as discussed with the individual item.
+
+ FLAGS Defined flags in the mailbox. See the description
+ of the FLAGS response for more detail.
+
+ EXISTS The number of messages in the mailbox. See the
+ description of the EXISTS response for more detail.
+
+ RECENT The number of messages with the \Recent flag set.
+ See the description of the RECENT response for more
+ detail.
+
+ OK [UNSEEN ]
+ The message sequence number of the first unseen
+ message in the mailbox. If this is missing, the
+ client can not make any assumptions about the first
+ unseen message in the mailbox, and needs to issue a
+ SEARCH command if it wants to find it.
+
+ OK [PERMANENTFLAGS ()]
+ A list of message flags that the client can change
+ permanently. If this is missing, the client should
+ assume that all flags can be changed permanently.
+
+ OK [UIDNEXT ]
+ The next unique identifier value. Refer to section
+ 2.3.1.1 for more information. If this is missing,
+ the client can not make any assumptions about the
+ next unique identifier value.
+
+ OK [UIDVALIDITY ]
+ The unique identifier validity value. Refer to
+ section 2.3.1.1 for more information. If this is
+ missing, the server does not support unique
+ identifiers.
+
+ Only one mailbox can be selected at a time in a connection;
+ simultaneous access to multiple mailboxes requires multiple
+ connections. The SELECT command automatically deselects any
+ currently selected mailbox before attempting the new selection.
+ Consequently, if a mailbox is selected and a SELECT command that
+ fails is attempted, no mailbox is selected.
+
+ If the client is permitted to modify the mailbox, the server
+ SHOULD prefix the text of the tagged OK response with the
+ "[READ-WRITE]" response code.
+
+ If the client is not permitted to modify the mailbox but is
+ permitted read access, the mailbox is selected as read-only, and
+ the server MUST prefix the text of the tagged OK response to
+ SELECT with the "[READ-ONLY]" response code. Read-only access
+ through SELECT differs from the EXAMINE command in that certain
+ read-only mailboxes MAY permit the change of permanent state on a
+ per-user (as opposed to global) basis. Netnews messages marked in
+ a server-based .newsrc file are an example of such per-user
+ permanent state that can be modified with read-only mailboxes.
+
+ Example: C: A142 SELECT INBOX
+ S: * 172 EXISTS
+ S: * 1 RECENT
+ S: * OK [UNSEEN 12] Message 12 is first unseen
+ S: * OK [UIDVALIDITY 3857529045] UIDs valid
+ S: * OK [UIDNEXT 4392] Predicted next UID
+ S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
+ S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
+ S: A142 OK [READ-WRITE] SELECT completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD SELECT invalid arguments\r\n");
+ return;
+ }
+
+ IMAP_Messages messages = m_pIMAP_Server.OnGetMessagesInfo(this, args[0]);
+ if (messages.ErrorText == null)
+ {
+ m_Messages = messages;
+ m_SelectedMailbox = args[0];
+
+ SendData("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n");
+ SendData("* " + m_Messages.Count + " EXISTS\r\n");
+ SendData("* " + m_Messages.RecentCount + " RECENT\r\n");
+ SendData("* OK [UNSEEN " + m_Messages.FirstUnseen + "] Message " + m_Messages.FirstUnseen + " is first unseen\r\n");
+ SendData("* OK [UIDVALIDITY " + m_Messages.MailboxUID + "] UIDs valid\r\n");
+ SendData("* OK [UIDNEXT " + m_Messages.UID_Next + "] Predicted next UID\r\n");
+ SendData("* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent falgs\r\n");
+ SendData(cmdTag + " OK [" + (m_Messages.ReadOnly ? "READ-ONLY" : "READ-WRITE") + "] SELECT Completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + messages.ErrorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Examine
+
+ private void Examine(string cmdTag, string argsText)
+ {
+ /* Rfc 3501 6.3.2 EXAMINE Command
+
+ Arguments: mailbox name
+
+ Responses: REQUIRED untagged responses: FLAGS, EXISTS, RECENT
+ REQUIRED OK untagged responses: UNSEEN, PERMANENTFLAGS,
+ UIDNEXT, UIDVALIDITY
+
+ Result: OK - examine completed, now in selected state
+ NO - examine failure, now in authenticated state: no
+ such mailbox, can't access mailbox
+ BAD - command unknown or arguments invalid
+
+ The EXAMINE command is identical to SELECT and returns the same
+ output; however, the selected mailbox is identified as read-only.
+ No changes to the permanent state of the mailbox, including
+ per-user state, are permitted; in particular, EXAMINE MUST NOT
+ cause messages to lose the \Recent flag.
+
+ The text of the tagged OK response to the EXAMINE command MUST
+ begin with the "[READ-ONLY]" response code.
+
+ Example: C: A932 EXAMINE blurdybloop
+ S: * 17 EXISTS
+ S: * 2 RECENT
+ S: * OK [UNSEEN 8] Message 8 is first unseen
+ S: * OK [UIDVALIDITY 3857529045] UIDs valid
+ S: * OK [UIDNEXT 4392] Predicted next UID
+ S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
+ S: * OK [PERMANENTFLAGS ()] No permanent flags permitted
+ S: A932 OK [READ-ONLY] EXAMINE completed
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD EXAMINE invalid arguments\r\n");
+ return;
+ }
+
+ IMAP_Messages messages = m_pIMAP_Server.OnGetMessagesInfo(this, args[0]);
+ if (messages.ErrorText == null)
+ {
+ messages.ReadOnly = true;
+ m_Messages = messages;
+ m_SelectedMailbox = argsText;
+
+ SendData("* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n");
+ SendData("* " + m_Messages.Count + " EXISTS\r\n");
+ SendData("* " + m_Messages.RecentCount + " RECENT\r\n");
+ SendData("* OK [UNSEEN " + m_Messages.FirstUnseen + "] Message " + m_Messages.FirstUnseen + " is first unseen\r\n");
+ SendData("* OK [UIDVALIDITY " + m_Messages.MailboxUID + "] UIDs valid\r\n");
+ SendData("* OK [UIDNEXT " + m_Messages.UID_Next + "] Predicted next UID\r\n");
+ SendData("* OK [PERMANENTFLAGS ()] No permanent flags permitted\r\n");
+ SendData(cmdTag + " OK [READ-ONLY] EXAMINE Completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + messages.ErrorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Create
+
+ private void Create(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.3
+
+ Arguments: mailbox name
+
+ Responses: no specific responses for this command
+
+ Result: OK - create completed
+ NO - create failure: can't create mailbox with that name
+ BAD - command unknown or arguments invalid
+
+ The CREATE command creates a mailbox with the given name. An OK
+ response is returned only if a new mailbox with that name has been
+ created. It is an error to attempt to create INBOX or a mailbox
+ with a name that refers to an extant mailbox. Any error in
+ creation will return a tagged NO response.
+
+ If the mailbox name is suffixed with the server's hierarchy
+ separator character (as returned from the server by a LIST
+ command), this is a declaration that the client intends to create
+ mailbox names under this name in the hierarchy. Server
+ implementations that do not require this declaration MUST ignore
+ it.
+
+ If the server's hierarchy separator character appears elsewhere in
+ the name, the server SHOULD create any superior hierarchical names
+ that are needed for the CREATE command to complete successfully.
+ In other words, an attempt to create "foo/bar/zap" on a server in
+ which "/" is the hierarchy separator character SHOULD create foo/
+ and foo/bar/ if they do not already exist.
+
+ If a new mailbox is created with the same name as a mailbox which
+ was deleted, its unique identifiers MUST be greater than any
+ unique identifiers used in the previous incarnation of the mailbox
+ UNLESS the new incarnation has a different unique identifier
+ validity value. See the description of the UID command for more
+ detail.
+
+ Example: C: A003 CREATE owatagusiam/
+ S: A003 OK CREATE completed
+ C: A004 CREATE owatagusiam/blurdybloop
+ S: A004 OK CREATE completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD CREATE invalid arguments\r\n");
+ return;
+ }
+
+ string errorText = m_pIMAP_Server.OnCreateMailbox(this, args[0]);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK CREATE completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Delete
+
+ private void Delete(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.4 DELETE Command
+
+ Arguments: mailbox name
+
+ Responses: no specific responses for this command
+
+ Result: OK - create completed
+ NO - create failure: can't create mailbox with that name
+ BAD - command unknown or arguments invalid
+
+ The DELETE command permanently removes the mailbox with the given
+ name. A tagged OK response is returned only if the mailbox has
+ been deleted. It is an error to attempt to delete INBOX or a
+ mailbox name that does not exist.
+
+ The DELETE command MUST NOT remove inferior hierarchical names.
+ For example, if a mailbox "foo" has an inferior "foo.bar"
+ (assuming "." is the hierarchy delimiter character), removing
+ "foo" MUST NOT remove "foo.bar". It is an error to attempt to
+ delete a name that has inferior hierarchical names and also has
+ the \Noselect mailbox name attribute (see the description of the
+ LIST response for more details).
+
+ It is permitted to delete a name that has inferior hierarchical
+ names and does not have the \Noselect mailbox name attribute. In
+ this case, all messages in that mailbox are removed, and the name
+ will acquire the \Noselect mailbox name attribute.
+
+ The value of the highest-used unique identifier of the deleted
+ mailbox MUST be preserved so that a new mailbox created with the
+ same name will not reuse the identifiers of the former
+ incarnation, UNLESS the new incarnation has a different unique
+ identifier validity value. See the description of the UID command
+ for more detail.
+
+ Examples: C: A682 LIST "" *
+ S: * LIST () "/" blurdybloop
+ S: * LIST (\Noselect) "/" foo
+ S: * LIST () "/" foo/bar
+ S: A682 OK LIST completed
+ C: A683 DELETE blurdybloop
+ S: A683 OK DELETE completed
+ C: A684 DELETE foo
+ S: A684 NO Name "foo" has inferior hierarchical names
+ C: A685 DELETE foo/bar
+ S: A685 OK DELETE Completed
+ C: A686 LIST "" *
+ S: * LIST (\Noselect) "/" foo
+ S: A686 OK LIST completed
+ C: A687 DELETE foo
+ S: A687 OK DELETE Completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD DELETE invalid arguments\r\n");
+ return;
+ }
+
+ string errorText = m_pIMAP_Server.OnDeleteMailbox(this, args[0]);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK DELETE Completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Rename
+
+ private void Rename(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.5 RENAME Command
+
+ Arguments: existing mailbox name
+ new mailbox name
+
+ Responses: no specific responses for this command
+
+ Result: OK - rename completed
+ NO - rename failure: can't rename mailbox with that name,
+ can't rename to mailbox with that name
+ BAD - command unknown or arguments invalid
+
+ The RENAME command changes the name of a mailbox. A tagged OK
+ response is returned only if the mailbox has been renamed. It is
+ an error to attempt to rename from a mailbox name that does not
+ exist or to a mailbox name that already exists. Any error in
+ renaming will return a tagged NO response.
+
+ If the name has inferior hierarchical names, then the inferior
+ hierarchical names MUST also be renamed. For example, a rename of
+ "foo" to "zap" will rename "foo/bar" (assuming "/" is the
+ hierarchy delimiter character) to "zap/bar".
+
+ The value of the highest-used unique identifier of the old mailbox
+ name MUST be preserved so that a new mailbox created with the same
+ name will not reuse the identifiers of the former incarnation,
+ UNLESS the new incarnation has a different unique identifier
+ validity value. See the description of the UID command for more
+ detail.
+
+ Renaming INBOX is permitted, and has special behavior. It moves
+ all messages in INBOX to a new mailbox with the given name,
+ leaving INBOX empty. If the server implementation supports
+ inferior hierarchical names of INBOX, these are unaffected by a
+ rename of INBOX.
+
+ Examples: C: A682 LIST "" *
+ S: * LIST () "/" blurdybloop
+ S: * LIST (\Noselect) "/" foo
+ S: * LIST () "/" foo/bar
+ S: A682 OK LIST completed
+ C: A683 RENAME blurdybloop sarasoop
+ S: A683 OK RENAME completed
+ C: A684 RENAME foo zowie
+ S: A684 OK RENAME Completed
+ C: A685 LIST "" *
+ S: * LIST () "/" sarasoop
+ S: * LIST (\Noselect) "/" zowie
+ S: * LIST () "/" zowie/bar
+ S: A685 OK LIST completed
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD RENAME invalid arguments\r\n");
+ return;
+ }
+
+ string mailbox = args[0];
+ string newMailbox = args[1];
+
+ string errorText = m_pIMAP_Server.OnRenameMailbox(this, mailbox, newMailbox);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK RENAME Completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Suscribe
+
+ private void Suscribe(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.6 SUBSCRIBE Commmand
+
+ Arguments: mailbox
+
+ Responses: no specific responses for this command
+
+ Result: OK - subscribe completed
+ NO - subscribe failure: can't subscribe to that name
+ BAD - command unknown or arguments invalid
+
+ The SUBSCRIBE command adds the specified mailbox name to the
+ server's set of "active" or "subscribed" mailboxes as returned by
+ the LSUB command. This command returns a tagged OK response only
+ if the subscription is successful.
+
+ A server MAY validate the mailbox argument to SUBSCRIBE to verify
+ that it exists. However, it MUST NOT unilaterally remove an
+ existing mailbox name from the subscription list even if a mailbox
+ by that name no longer exists.
+
+ Note: this requirement is because some server sites may routinely
+ remove a mailbox with a well-known name (e.g. "system-alerts")
+ after its contents expire, with the intention of recreating it
+ when new contents are appropriate.
+
+ Example: C: A002 SUBSCRIBE #news.comp.mail.mime
+ S: A002 OK SUBSCRIBE completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD SUBSCRIBE invalid arguments\r\n");
+ return;
+ }
+
+ string errorText = m_pIMAP_Server.OnSubscribeMailbox(this, args[0]);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK SUBSCRIBE completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function UnSuscribe
+
+ private void UnSuscribe(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.7 UNSUBSCRIBE Command
+
+ Arguments: mailbox
+
+ Responses: no specific responses for this command
+
+ Result: OK - subscribe completed
+ NO - subscribe failure: can't subscribe to that name
+ BAD - command unknown or arguments invalid
+
+ The UNSUBSCRIBE command removes the specified mailbox name from
+ the server's set of "active" or "subscribed" mailboxes as returned
+ by the LSUB command. This command returns a tagged OK response
+ only if the unsubscription is successful.
+
+ Example: C: A002 UNSUBSCRIBE #news.comp.mail.mime
+ S: A002 OK UNSUBSCRIBE completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 1)
+ {
+ SendData(cmdTag + " BAD UNSUBSCRIBE invalid arguments\r\n");
+ return;
+ }
+
+ string errorText = m_pIMAP_Server.OnUnSubscribeMailbox(this, args[0]);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK UNSUBSCRIBE completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function List
+
+ private void List(string cmdTag, string argsText)
+ {
+ /* Rc 3501 6.3.8 LIST Command
+
+ Arguments: reference name
+ mailbox name with possible wildcards
+
+ Responses: untagged responses: LIST
+
+ Result: OK - list completed
+ NO - list failure: can't list that reference or name
+ BAD - command unknown or arguments invalid
+
+ The LIST command returns a subset of names from the complete set
+ of all names available to the client. Zero or more untagged LIST
+ replies are returned, containing the name attributes, hierarchy
+ delimiter, and name; see the description of the LIST reply for
+ more detail.
+
+ An empty ("" string) reference name argument indicates that the
+ mailbox name is interpreted as by SELECT. The returned mailbox
+ names MUST match the supplied mailbox name pattern. A non-empty
+ reference name argument is the name of a mailbox or a level of
+ mailbox hierarchy, and indicates a context in which the mailbox
+ name is interpreted in an implementation-defined manner.
+
+ An empty ("" string) mailbox name argument is a special request to
+ return the hierarchy delimiter and the root name of the name given
+ in the reference. The value returned as the root MAY be null if
+ the reference is non-rooted or is null. In all cases, the
+ hierarchy delimiter is returned. This permits a client to get the
+ hierarchy delimiter even when no mailboxes by that name currently
+ exist.
+
+ The character "*" is a wildcard, and matches zero or more
+ characters at this position. The character "%" is similar to "*",
+ but it does not match a hierarchy delimiter. If the "%" wildcard
+ is the last character of a mailbox name argument, matching levels
+ of hierarchy are also returned.
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD LIST invalid arguments\r\n");
+ return;
+ }
+
+ string refName = args[0];
+ string mailbox = args[1];
+
+ // Domain separator wanted
+ if (mailbox.Length == 0)
+ {
+ SendData("* LIST (\\Noselect) \"/\" \"\"\r\n");
+ }
+ // List mailboxes
+ else
+ {
+ IMAP_Folders mailboxes = m_pIMAP_Server.OnGetMailboxes(this, refName, mailbox);
+ foreach (IMAP_Folder mBox in mailboxes.Folders)
+ {
+ SendData("* LIST () \"/\" \"" + mBox.Folder + "\" \r\n");
+ }
+ }
+
+ SendData(cmdTag + " OK LIST Completed\r\n");
+ }
+
+ #endregion
+
+ #region function LSub
+
+ private void LSub(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.9 LSUB Command
+
+ Arguments: reference name
+ mailbox name with possible wildcards
+
+ Responses: untagged responses: LSUB
+
+ Result: OK - lsub completed
+ NO - lsub failure: can't list that reference or name
+ BAD - command unknown or arguments invalid
+
+ The LSUB command returns a subset of names from the set of names
+ that the user has declared as being "active" or "subscribed".
+ Zero or more untagged LSUB replies are returned. The arguments to
+ LSUB are in the same form as those for LIST.
+
+ The returned untagged LSUB response MAY contain different mailbox
+ flags from a LIST untagged response. If this should happen, the
+ flags in the untagged LIST are considered more authoritative.
+
+ A special situation occurs when using LSUB with the % wildcard.
+ Consider what happens if "foo/bar" (with a hierarchy delimiter of
+ "/") is subscribed but "foo" is not. A "%" wildcard to LSUB must
+ return foo, not foo/bar, in the LSUB response, and it MUST be
+ flagged with the \Noselect attribute.
+
+ The server MUST NOT unilaterally remove an existing mailbox name
+ from the subscription list even if a mailbox by that name no
+ longer exists.
+
+ Example: C: A002 LSUB "#news." "comp.mail.*"
+ S: * LSUB () "." #news.comp.mail.mime
+ S: * LSUB () "." #news.comp.mail.misc
+ S: A002 OK LSUB completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD LSUB invalid arguments\r\n");
+ return;
+ }
+
+ string refName = args[0];
+ string mailbox = args[1];
+
+ IMAP_Folders mailboxes = m_pIMAP_Server.OnGetSubscribedMailboxes(this, refName, mailbox);
+ foreach (IMAP_Folder mBox in mailboxes.Folders)
+ {
+ SendData("* LSUB () \"/\" \"" + mBox.Folder + "\" \r\n");
+ }
+
+ SendData(cmdTag + " OK LSUB Completed\r\n");
+ }
+
+ #endregion
+
+ #region function Status
+
+ private void Status(string cmdTag, string argsText)
+ {
+ /* RFC 3501 6.3.10 STATUS Command
+
+ Arguments: mailbox name
+ status data item names
+
+ Responses: untagged responses: STATUS
+
+ Result: OK - status completed
+ NO - status failure: no status for that name
+ BAD - command unknown or arguments invalid
+
+ The STATUS command requests the status of the indicated mailbox.
+ It does not change the currently selected mailbox, nor does it
+ affect the state of any messages in the queried mailbox (in
+ particular, STATUS MUST NOT cause messages to lose the \Recent
+ flag).
+
+ The STATUS command provides an alternative to opening a second
+ IMAP4rev1 connection and doing an EXAMINE command on a mailbox to
+ query that mailbox's status without deselecting the current
+ mailbox in the first IMAP4rev1 connection.
+
+ Unlike the LIST command, the STATUS command is not guaranteed to
+ be fast in its response. In some implementations, the server is
+ obliged to open the mailbox read-only internally to obtain certain
+ status information. Also unlike the LIST command, the STATUS
+ command does not accept wildcards.
+
+ The currently defined status data items that can be requested are:
+
+ MESSAGES
+ The number of messages in the mailbox.
+
+ RECENT
+ The number of messages with the \Recent flag set.
+
+ UIDNEXT
+ The next unique identifier value of the mailbox. Refer to
+ section 2.3.1.1 for more information.
+
+ UIDVALIDITY
+ The unique identifier validity value of the mailbox. Refer to
+ section 2.3.1.1 for more information.
+
+ UNSEEN
+ The number of messages which do not have the \Seen flag set.
+
+
+ Example: C: A042 STATUS blurdybloop (UIDNEXT MESSAGES)
+ S: * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)
+ S: A042 OK STATUS completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD STATUS invalid arguments\r\n");
+ return;
+ }
+
+ string mailbox = args[0];
+ string wantedItems = args[1].ToUpper();
+
+ // See wanted items are valid.
+ if (wantedItems.Replace("MESSAGES", "").Replace("RECENT", "").Replace("UIDNEXT", "").Replace("UIDVALIDITY", "").Replace("UNSEEN", "").Trim().Length > 0)
+ {
+ SendData(cmdTag + " BAD STATUS invalid arguments\r\n");
+ return;
+ }
+
+ IMAP_Messages messages = m_pIMAP_Server.OnGetMessagesInfo(this, mailbox);
+ if (messages.ErrorText == null)
+ {
+ string itemsReply = "";
+ if (wantedItems.IndexOf("MESSAGES") > -1)
+ {
+ itemsReply += " MESSAGES " + messages.Count;
+ }
+ if (wantedItems.IndexOf("RECENT") > -1)
+ {
+ itemsReply += " RECENT " + messages.RecentCount;
+ }
+ if (wantedItems.IndexOf("UNSEEN") > -1)
+ {
+ itemsReply += " UNSEEN " + messages.UnSeenCount;
+ }
+ if (wantedItems.IndexOf("UIDVALIDITY") > -1)
+ {
+ itemsReply += " UIDVALIDITY " + messages.MailboxUID;
+ }
+ if (wantedItems.IndexOf("UIDNEXT") > -1)
+ {
+ itemsReply += " UIDNEXT " + messages.UID_Next;
+ }
+ itemsReply = itemsReply.Trim();
+
+ SendData("* STATUS " + messages.Mailbox + " (" + itemsReply + ")\r\n");
+ SendData(cmdTag + " OK STATUS completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + messages.ErrorText + "\r\n");
+ }
+ }
+
+ #endregion
+
+ #region function Append
+
+ private void Append(string cmdTag, string argsText)
+ {
+ /* Rfc 3501 6.3.11 APPEND Command
+
+ Arguments: mailbox name
+ OPTIONAL flag parenthesized list
+ OPTIONAL date/time string
+ message literal
+
+ Responses: no specific responses for this command
+
+ Result: OK - append completed
+ NO - append error: can't append to that mailbox, error
+ in flags or date/time or message text
+ BAD - command unknown or arguments invalid
+
+ The APPEND command appends the literal argument as a new message
+ to the end of the specified destination mailbox. This argument
+ SHOULD be in the format of an [RFC-2822] message. 8-bit
+ characters are permitted in the message. A server implementation
+ that is unable to preserve 8-bit data properly MUST be able to
+ reversibly convert 8-bit APPEND data to 7-bit using a [MIME-IMB]
+ content transfer encoding.
+
+ If a flag parenthesized list is specified, the flags SHOULD be set
+ in the resulting message; otherwise, the flag list of the
+ resulting message is set to empty by default. In either case, the
+ Recent flag is also set.
+
+ If a date-time is specified, the internal date SHOULD be set in
+ the resulting message; otherwise, the internal date of the
+ resulting message is set to the current date and time by default.
+
+ If the append is unsuccessful for any reason, the mailbox MUST be
+ restored to its state before the APPEND attempt; no partial
+ appending is permitted.
+
+ If the destination mailbox does not exist, a server MUST return an
+ error, and MUST NOT automatically create the mailbox. Unless it
+ is certain that the destination mailbox can not be created, the
+ server MUST send the response code "[TRYCREATE]" as the prefix of
+ the text of the tagged NO response. This gives a hint to the
+ client that it can attempt a CREATE command and retry the APPEND
+ if the CREATE is successful.
+
+ Example: C: A003 APPEND saved-messages (\Seen) {310}
+ S: + Ready for literal data
+ C: Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)
+ C: From: Fred Foobar
+ C: Subject: afternoon meeting
+ C: To: mooch@owatagu.siam.edu
+ C: Message-Id:
+ C: MIME-Version: 1.0
+ C: Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+ C:
+ C: Hello Joe, do you think we can meet at 3:30 tomorrow?
+ C:
+ S: A003 OK APPEND completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length < 2 || args.Length > 4)
+ {
+ SendData(cmdTag + " BAD APPEND Invalid arguments\r\n");
+ return;
+ }
+
+ string mailbox = args[0];
+ IMAP_MessageFlags mFlags = 0;
+ DateTime date = DateTime.Now;
+ long msgLen = Convert.ToInt64(args[args.Length - 1].Replace("{", "").Replace("}", ""));
+
+ if (args.Length == 4)
+ {
+ //--- Parse flags, see if valid ----------------
+ string flags = args[1].ToUpper();
+ if (flags.Replace("\\ANSWERED", "").Replace("\\FLAGGED", "").Replace("\\DELETED", "").Replace("\\SEEN", "").Replace("\\DRAFT", "").Trim().Length > 0)
+ {
+ SendData(cmdTag + " BAD arguments invalid\r\n");
+ return;
+ }
+ if (flags.IndexOf("\\ANSWERED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Answered;
+ }
+ if (flags.IndexOf("\\FLAGGED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Flagged;
+ }
+ if (flags.IndexOf("\\DELETED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Deleted;
+ }
+ if (flags.IndexOf("\\SEEN") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Seen;
+ }
+ if (flags.IndexOf("\\DRAFT") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Draft;
+ }
+ //---------------------------------------------
+
+ date = LumiSoft.Net.Mime.MimeParser.ParseDateS(args[2]);
+ }
+ else if (args.Length == 3)
+ {
+ // See if date or flags specified, try date first
+ try
+ {
+ date = LumiSoft.Net.Mime.MimeParser.ParseDateS(args[2]);
+ }
+ catch
+ {
+ //--- Parse flags, see if valid ----------------
+ string flags = args[1].ToUpper();
+ if (flags.Replace("\\ANSWERED", "").Replace("\\FLAGGED", "").Replace("\\DELETED", "").Replace("\\SEEN", "").Replace("\\DRAFT", "").Trim().Length > 0)
+ {
+ SendData(cmdTag + " BAD arguments invalid\r\n");
+ return;
+ }
+ if (flags.IndexOf("\\ANSWERED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Answered;
+ }
+ if (flags.IndexOf("\\FLAGGED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Flagged;
+ }
+ if (flags.IndexOf("\\DELETED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Deleted;
+ }
+ if (flags.IndexOf("\\SEEN") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Seen;
+ }
+ if (flags.IndexOf("\\DRAFT") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Draft;
+ }
+ //---------------------------------------------
+ }
+ }
+
+ // Request data
+ SendData("+ Ready for literal data\r\n");
+
+ using (MemoryStream storeStrm = new MemoryStream())
+ {
+ // Why needed msgLen+2 ???
+ LumiSoft.Net.ReadReplyCode rCode = LumiSoft.Net.Core.ReadData(m_pClientSocket, msgLen + 2, storeStrm, true, 10000);
+ if (rCode == LumiSoft.Net.ReadReplyCode.Ok)
+ {
+ IMAP_Message msg = new IMAP_Message(null, "", 0, mFlags, 0, date);
+ string errotText = m_pIMAP_Server.OnStoreMessage(this, mailbox, msg, storeStrm.ToArray());
+ if (errotText == null)
+ {
+ SendData(cmdTag + " OK APPEND completed, recieved " + storeStrm.Length + " bytes\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errotText + "\r\n");
+ }
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + rCode.ToString() + "\r\n");
+ }
+ }
+ }
+
+ #endregion
+
+ //--- End of Authenticated State
+
+
+ //--- Selected State ------
+
+ #region function Check
+
+ private void Check(string cmdTag)
+ {
+ /* RFC 3501 6.4.1 CHECK Command
+
+ Arguments: none
+
+ Responses: no specific responses for this command
+
+ Result: OK - check completed
+ BAD - command unknown or arguments invalid
+
+ The CHECK command requests a checkpoint of the currently selected
+ mailbox. A checkpoint refers to any implementation-dependent
+ housekeeping associated with the mailbox (e.g. resolving the
+ server's in-memory state of the mailbox with the state on its
+ disk) that is not normally executed as part of each command. A
+ checkpoint MAY take a non-instantaneous amount of real time to
+ complete. If a server implementation has no such housekeeping
+ considerations, CHECK is equivalent to NOOP.
+
+ There is no guarantee that an EXISTS untagged response will happen
+ as a result of CHECK. NOOP, not CHECK, SHOULD be used for new
+ mail polling.
+
+ Example: C: FXXZ CHECK
+ S: FXXZ OK CHECK Completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ SendData(cmdTag + " OK CHECK completed\r\n");
+ }
+
+ #endregion
+
+ #region function Close
+
+ private void Close(string cmdTag)
+ {
+ /* RFC 3501 6.4.2 CLOSE Command
+
+ Arguments: none
+
+ Responses: no specific responses for this command
+
+ Result: OK - close completed, now in authenticated state
+ BAD - command unknown or arguments invalid
+
+ The CLOSE command permanently removes from the currently selected
+ mailbox all messages that have the \Deleted flag set, and returns
+ to authenticated state from selected state. No untagged EXPUNGE
+ responses are sent.
+
+ No messages are removed, and no error is given, if the mailbox is
+ selected by an EXAMINE command or is otherwise selected read-only.
+
+ Even if a mailbox is selected, a SELECT, EXAMINE, or LOGOUT
+ command MAY be issued without previously issuing a CLOSE command.
+ The SELECT, EXAMINE, and LOGOUT commands implicitly close the
+ currently selected mailbox without doing an expunge. However,
+ when many messages are deleted, a CLOSE-LOGOUT or CLOSE-SELECT
+ sequence is considerably faster than an EXPUNGE-LOGOUT or
+ EXPUNGE-SELECT because no untagged EXPUNGE responses (which the
+ client would probably ignore) are sent.
+
+ Example: C: A341 CLOSE
+ S: A341 OK CLOSE completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ if (!m_Messages.ReadOnly)
+ {
+ IMAP_Message[] messages = m_Messages.GetDeleteMessages();
+ foreach (IMAP_Message msg in messages)
+ {
+ m_pIMAP_Server.OnDeleteMessage(this, msg);
+ }
+ }
+
+ m_SelectedMailbox = "";
+ m_Messages = null;
+
+ SendData(cmdTag + " OK CLOSE completed\r\n");
+ }
+
+ #endregion
+
+ #region function Expunge
+
+ private void Expunge(string cmdTag)
+ {
+ /* RFC 3501 6.4.3 EXPUNGE Command
+
+ Arguments: none
+
+ Responses: untagged responses: EXPUNGE
+
+ Result: OK - expunge completed
+ NO - expunge failure: can't expunge (e.g., permission
+ denied)
+ BAD - command unknown or arguments invalid
+
+ The EXPUNGE command permanently removes all messages that have the
+ \Deleted flag set from the currently selected mailbox. Before
+ returning an OK to the client, an untagged EXPUNGE response is
+ sent for each message that is removed.
+
+ Example: C: A202 EXPUNGE
+ S: * 3 EXPUNGE
+ S: * 3 EXPUNGE
+ S: * 5 EXPUNGE
+ S: * 8 EXPUNGE
+ S: A202 OK EXPUNGE completed
+
+ Note: In this example, messages 3, 4, 7, and 11 had the
+ \Deleted flag set. See the description of the EXPUNGE
+ response for further explanation.
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+
+ IMAP_Message[] messages = m_Messages.GetDeleteMessages();
+ foreach (IMAP_Message msg in messages)
+ {
+ string errorText = m_pIMAP_Server.OnDeleteMessage(this, msg);
+ if (errorText == null)
+ {
+ SendData("* " + msg.MessageNo + " EXPUNGE\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ return;
+ }
+ }
+
+ // Refresh m_Messages to reflect deletion
+ m_Messages = m_pIMAP_Server.OnGetMessagesInfo(this, this.SelectedMailbox);
+
+ SendData(cmdTag + " OK EXPUNGE completed\r\n");
+ }
+
+ #endregion
+ //
+ #region function Search
+
+ private void Search(string cmdTag, string argsText, bool uidSearch)
+ {
+ /* Rfc 3501 6.4.4 SEARCH Command
+
+ Arguments: OPTIONAL [CHARSET] specification
+ searching criteria (one or more)
+
+ Responses: REQUIRED untagged response: SEARCH
+
+ Result: OK - search completed
+ NO - search error: can't search that [CHARSET] or
+ criteria
+ BAD - command unknown or arguments invalid
+
+ The SEARCH command searches the mailbox for messages that match
+ the given searching criteria. Searching criteria consist of one
+ or more search keys. The untagged SEARCH response from the server
+ contains a listing of message sequence numbers corresponding to
+ those messages that match the searching criteria.
+
+ When multiple keys are specified, the result is the intersection
+ (AND function) of all the messages that match those keys. For
+ example, the criteria DELETED FROM "SMITH" SINCE 1-Feb-1994 refers
+ to all deleted messages from Smith that were placed in the mailbox
+ since February 1, 1994. A search key can also be a parenthesized
+ list of one or more search keys (e.g., for use with the OR and NOT
+ keys).
+
+ Server implementations MAY exclude [MIME-IMB] body parts with
+ terminal content media types other than TEXT and MESSAGE from
+ consideration in SEARCH matching.
+
+ The OPTIONAL [CHARSET] specification consists of the word
+ "CHARSET" followed by a registered [CHARSET]. It indicates the
+ [CHARSET] of the strings that appear in the search criteria.
+ [MIME-IMB] content transfer encodings, and [MIME-HDRS] strings in
+ [RFC-2822]/[MIME-IMB] headers, MUST be decoded before comparing
+ text in a [CHARSET] other than US-ASCII. US-ASCII MUST be
+ supported; other [CHARSET]s MAY be supported.
+
+ If the server does not support the specified [CHARSET], it MUST
+ return a tagged NO response (not a BAD). This response SHOULD
+ contain the BADCHARSET response code, which MAY list the
+ [CHARSET]s supported by the server.
+
+ In all search keys that use strings, a message matches the key if
+ the string is a substring of the field. The matching is
+ case-insensitive.
+
+ The defined search keys are as follows. Refer to the Formal
+ Syntax section for the precise syntactic definitions of the
+ arguments.
+
+
+ Messages with message sequence numbers corresponding to the
+ specified message sequence number set.
+
+ ALL
+ All messages in the mailbox; the default initial key for
+ ANDing.
+
+ ANSWERED
+ Messages with the \Answered flag set.
+
+ BCC
+ Messages that contain the specified string in the envelope
+ structure's BCC field.
+
+ BEFORE
+ Messages whose internal date (disregarding time and timezone)
+ is earlier than the specified date.
+
+ BODY
+ Messages that contain the specified string in the body of the
+ message.
+
+ CC
+ Messages that contain the specified string in the envelope
+ structure's CC field.
+
+ DELETED
+ Messages with the \Deleted flag set.
+
+ DRAFT
+ Messages with the \Draft flag set.
+
+ FLAGGED
+ Messages with the \Flagged flag set.
+
+ FROM
+ Messages that contain the specified string in the envelope
+ structure's FROM field.
+
+ HEADER
+ Messages that have a header with the specified field-name (as
+ defined in [RFC-2822]) and that contains the specified string
+ in the text of the header (what comes after the colon). If the
+ string to search is zero-length, this matches all messages that
+ have a header line with the specified field-name regardless of
+ the contents.
+
+ KEYWORD
+ Messages with the specified keyword flag set.
+
+ LARGER
+ Messages with an [RFC-2822] size larger than the specified
+ number of octets.
+
+ NEW
+ Messages that have the \Recent flag set but not the \Seen flag.
+ This is functionally equivalent to "(RECENT UNSEEN)".
+
+ NOT
+ Messages that do not match the specified search key.
+
+ OLD
+ Messages that do not have the \Recent flag set. This is
+ functionally equivalent to "NOT RECENT" (as opposed to "NOT
+ NEW").
+
+ ON
+ Messages whose internal date (disregarding time and timezone)
+ is within the specified date.
+
+ OR
+ Messages that match either search key.
+
+ RECENT
+ Messages that have the \Recent flag set.
+
+ SEEN
+ Messages that have the \Seen flag set.
+
+ SENTBEFORE
+ Messages whose [RFC-2822] Date: header (disregarding time and
+ timezone) is earlier than the specified date.
+
+ SENTON
+ Messages whose [RFC-2822] Date: header (disregarding time and
+ timezone) is within the specified date.
+
+ SENTSINCE
+ Messages whose [RFC-2822] Date: header (disregarding time and
+ timezone) is within or later than the specified date.
+
+ SINCE
+ Messages whose internal date (disregarding time and timezone)
+ is within or later than the specified date.
+
+ SMALLER
+ Messages with an [RFC-2822] size smaller than the specified
+ number of octets.
+
+ SUBJECT
+ Messages that contain the specified string in the envelope
+ structure's SUBJECT field.
+
+ TEXT
+ Messages that contain the specified string in the header or
+ body of the message.
+
+ TO
+ Messages that contain the specified string in the envelope
+ structure's TO field.
+
+ UID
+ Messages with unique identifiers corresponding to the specified
+ unique identifier set. Sequence set ranges are permitted.
+
+ UNANSWERED
+ Messages that do not have the \Answered flag set.
+
+ UNDELETED
+ Messages that do not have the \Deleted flag set.
+
+ UNDRAFT
+ Messages that do not have the \Draft flag set.
+
+ UNFLAGGED
+ Messages that do not have the \Flagged flag set.
+
+ UNKEYWORD
+ Messages that do not have the specified keyword flag set.
+
+ UNSEEN
+ Messages that do not have the \Seen flag set.
+
+ Example: C: A282 SEARCH FLAGGED SINCE 1-Feb-1994 NOT FROM "Smith"
+ S: * SEARCH 2 84 882
+ S: A282 OK SEARCH completed
+ C: A283 SEARCH TEXT "string not in mailbox"
+ S: * SEARCH
+ S: A283 OK SEARCH completed
+ C: A284 SEARCH CHARSET UTF-8 TEXT {6}
+ C: XXXXXX
+ S: * SEARCH 43
+ S: A284 OK SEARCH completed
+
+ Note: Since this document is restricted to 7-bit ASCII
+ text, it is not possible to show actual UTF-8 data. The
+ "XXXXXX" is a placeholder for what would be 6 octets of
+ 8-bit data in an actual transaction.
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ // ToDo: just loop messages headers or full messages (depends on search type)
+ //
+
+ SendData(cmdTag + " BAD command not implemented\r\n");
+ }
+
+ #endregion
+ //
+ #region function Fetch
+
+ private void Fetch(string cmdTag, string argsText, bool uidFetch)
+ {
+ /* Rfc 3501 6.4.5 FETCH Command
+
+ Arguments: message set
+ message data item names
+
+ Responses: untagged responses: FETCH
+
+ Result: OK - fetch completed
+ NO - fetch error: can't fetch that data
+ BAD - command unknown or arguments invalid
+
+ The FETCH command retrieves data associated with a message in the
+ mailbox. The data items to be fetched can be either a single atom
+ or a parenthesized list.
+
+ Most data items, identified in the formal syntax under the
+ msg-att-static rule, are static and MUST NOT change for any
+ particular message. Other data items, identified in the formal
+ syntax under the msg-att-dynamic rule, MAY change, either as a
+ result of a STORE command or due to external events.
+
+ For example, if a client receives an ENVELOPE for a
+ message when it already knows the envelope, it can
+ safely ignore the newly transmitted envelope.
+
+ There are three macros which specify commonly-used sets of data
+ items, and can be used instead of data items. A macro must be
+ used by itself, and not in conjunction with other macros or data
+ items.
+
+ ALL
+ Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)
+
+ FAST
+ Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE)
+
+ FULL
+ Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE
+ BODY)
+
+ The currently defined data items that can be fetched are:
+
+ BODY
+ Non-extensible form of BODYSTRUCTURE.
+
+ BODY[]<>
+ The text of a particular body section. The section
+ specification is a set of zero or more part specifiers
+ delimited by periods. A part specifier is either a part number
+ or one of the following: HEADER, HEADER.FIELDS,
+ HEADER.FIELDS.NOT, MIME, and TEXT. An empty section
+ specification refers to the entire message, including the
+ header.
+
+ Every message has at least one part number. Non-[MIME-IMB]
+ messages, and non-multipart [MIME-IMB] messages with no
+ encapsulated message, only have a part 1.
+
+ Multipart messages are assigned consecutive part numbers, as
+ they occur in the message. If a particular part is of type
+ message or multipart, its parts MUST be indicated by a period
+ followed by the part number within that nested multipart part.
+
+ A part of type MESSAGE/RFC822 also has nested part numbers,
+ referring to parts of the MESSAGE part's body.
+
+ The HEADER, HEADER.FIELDS, HEADER.FIELDS.NOT, and TEXT part
+ specifiers can be the sole part specifier or can be prefixed by
+ one or more numeric part specifiers, provided that the numeric
+ part specifier refers to a part of type MESSAGE/RFC822. The
+ MIME part specifier MUST be prefixed by one or more numeric
+ part specifiers.
+
+ The HEADER, HEADER.FIELDS, and HEADER.FIELDS.NOT part
+ specifiers refer to the [RFC-2822] header of the message or of
+ an encapsulated [MIME-IMT] MESSAGE/RFC822 message.
+ HEADER.FIELDS and HEADER.FIELDS.NOT are followed by a list of
+ field-name (as defined in [RFC-2822]) names, and return a
+ subset of the header. The subset returned by HEADER.FIELDS
+ contains only those header fields with a field-name that
+ matches one of the names in the list; similarly, the subset
+ returned by HEADER.FIELDS.NOT contains only the header fields
+ with a non-matching field-name. The field-matching is
+ case-insensitive but otherwise exact. Subsetting does not
+ exclude the [RFC-2822] delimiting blank line between the header
+ and the body; the blank line is included in all header fetches,
+ except in the case of a message which has no body and no blank
+ line.
+
+ The MIME part specifier refers to the [MIME-IMB] header for
+ this part.
+
+ The TEXT part specifier refers to the text body of the message,
+ omitting the [RFC-2822] header.
+
+ Here is an example of a complex message with some of its
+ part specifiers:
+
+ HEADER ([RFC-2822] header of the message)
+ TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
+ 1 TEXT/PLAIN
+ 2 APPLICATION/OCTET-STREAM
+ 3 MESSAGE/RFC822
+ 3.HEADER ([RFC-2822] header of the message)
+ 3.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
+ 3.1 TEXT/PLAIN
+ 3.2 APPLICATION/OCTET-STREAM
+ 4 MULTIPART/MIXED
+ 4.1 IMAGE/GIF
+ 4.1.MIME ([MIME-IMB] header for the IMAGE/GIF)
+ 4.2 MESSAGE/RFC822
+ 4.2.HEADER ([RFC-2822] header of the message)
+ 4.2.TEXT ([RFC-2822] text body of the message) MULTIPART/MIXED
+ 4.2.1 TEXT/PLAIN
+ 4.2.2 MULTIPART/ALTERNATIVE
+ 4.2.2.1 TEXT/PLAIN
+ 4.2.2.2 TEXT/RICHTEXT
+
+
+ It is possible to fetch a substring of the designated text.
+ This is done by appending an open angle bracket ("<"), the
+ octet position of the first desired octet, a period, the
+ maximum number of octets desired, and a close angle bracket
+ (">") to the part specifier. If the starting octet is beyond
+ the end of the text, an empty string is returned.
+
+ Any partial fetch that attempts to read beyond the end of the
+ text is truncated as appropriate. A partial fetch that starts
+ at octet 0 is returned as a partial fetch, even if this
+ truncation happened.
+
+ Note: This means that BODY[]<0.2048> of a 1500-octet message
+ will return BODY[]<0> with a literal of size 1500, not
+ BODY[].
+
+ Note: A substring fetch of a HEADER.FIELDS or
+ HEADER.FIELDS.NOT part specifier is calculated after
+ subsetting the header.
+
+ The \Seen flag is implicitly set; if this causes the flags to
+ change, they SHOULD be included as part of the FETCH responses.
+
+ BODY.PEEK[]<>
+ An alternate form of BODY[] that does not implicitly
+ set the \Seen flag.
+
+ BODYSTRUCTURE
+ The [MIME-IMB] body structure of the message. This is computed
+ by the server by parsing the [MIME-IMB] header fields in the
+ [RFC-2822] header and [MIME-IMB] headers.
+
+ ENVELOPE
+ The envelope structure of the message. This is computed by the
+ server by parsing the [RFC-2822] header into the component
+ parts, defaulting various fields as necessary.
+
+ FLAGS
+ The flags that are set for this message.
+
+ INTERNALDATE
+ The internal date of the message.
+
+ RFC822
+ Functionally equivalent to BODY[], differing in the syntax of
+ the resulting untagged FETCH data (RFC822 is returned).
+
+ RFC822.HEADER
+ Functionally equivalent to BODY.PEEK[HEADER], differing in the
+ syntax of the resulting untagged FETCH data (RFC822.HEADER is
+ returned).
+
+ RFC822.SIZE
+ The [RFC-2822] size of the message.
+
+ RFC822.TEXT
+ Functionally equivalent to BODY[TEXT], differing in the syntax
+ of the resulting untagged FETCH data (RFC822.TEXT is returned).
+
+ UID
+ The unique identifier for the message.
+
+
+ Example: C: A654 FETCH 2:4 (FLAGS BODY[HEADER.FIELDS (DATE FROM)])
+ S: * 2 FETCH ....
+ S: * 3 FETCH ....
+ S: * 4 FETCH ....
+ S: A654 OK FETCH completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD Invalid arguments\r\n");
+ return;
+ }
+
+ // If contains '*' , replace it with total messages number
+ string[] msgSet = args[0].Trim().Replace("*", m_Messages.Count.ToString()).Split(':');
+ int nStartMsg = Convert.ToInt32(msgSet[0]);
+ int nEndMsg = Convert.ToInt32(msgSet[0]);
+ // Message set specified
+ if (msgSet.Length == 2)
+ {
+ nEndMsg = Convert.ToInt32(msgSet[1]);
+ }
+
+ // For UID FETCH msg numbers are UID's not index numbers.
+ // Replace message UID with message index number,
+ // we can do this because UID are ASC order
+ if (uidFetch)
+ {
+ nStartMsg = m_Messages.IndexFromUID(nStartMsg);
+
+ // If end message *, we don't need change it, it ok already
+ if (args[0].IndexOf("*") == -1)
+ {
+ nEndMsg = m_Messages.IndexFromUID(nEndMsg);
+ }
+ }
+
+ // Check that endmessage isn't > than messages available, if so use count instead.
+ if (nEndMsg > m_Messages.Count)
+ {
+ nEndMsg = m_Messages.Count;
+ }
+
+ // Replace macros
+ string fetchItems = args[1].ToUpper();
+ fetchItems = fetchItems.Replace("ALL", "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE");
+ fetchItems = fetchItems.Replace("FAST", "FLAGS INTERNALDATE RFC822.SIZE");
+ fetchItems = fetchItems.Replace("FULL", "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY");
+
+ // If UID FETCH and no UID, we must implicity add it, it's required
+ if (uidFetch && fetchItems.ToUpper().IndexOf("UID") == -1)
+ {
+ fetchItems += " UID";
+ }
+
+ // ToDo: ??? strat parm parsing from left to end in while loop while params parsed or bad param found
+
+ bool headersNeeded = false;
+ bool fullMsgNeeded = false;
+
+ // Parse,validate requested fetch items
+ Hashtable items = new Hashtable();
+ if (fetchItems.IndexOf("UID") > -1)
+ {
+ items.Add("UID", "");
+ fetchItems = fetchItems.Replace("UID", "");
+ }
+ if (fetchItems.IndexOf("RFC822.TEXT") > -1)
+ {
+ fullMsgNeeded = true;
+ items.Add("RFC822.TEXT", "");
+ fetchItems = fetchItems.Replace("RFC822.TEXT", "");
+ }
+ if (fetchItems.IndexOf("RFC822.SIZE") > -1)
+ {
+ items.Add("RFC822.SIZE", "");
+ fetchItems = fetchItems.Replace("RFC822.SIZE", "");
+ }
+ if (fetchItems.IndexOf("RFC822.HEADER") > -1)
+ {
+ headersNeeded = true;
+ items.Add("RFC822.HEADER", "");
+ fetchItems = fetchItems.Replace("RFC822.HEADER", "");
+ }
+ if (fetchItems.IndexOf("RFC822") > -1)
+ {
+ fullMsgNeeded = true;
+ items.Add("RFC822", "");
+ fetchItems = fetchItems.Replace("RFC822", "");
+ }
+ if (fetchItems.IndexOf("INTERNALDATE") > -1)
+ {
+ items.Add("INTERNALDATE", "");
+ fetchItems = fetchItems.Replace("INTERNALDATE", "");
+ }
+ if (fetchItems.IndexOf("FLAGS") > -1)
+ {
+ items.Add("FLAGS", "");
+ fetchItems = fetchItems.Replace("FLAGS", "");
+ }
+ if (fetchItems.IndexOf("ENVELOPE") > -1)
+ {
+ headersNeeded = true;
+ items.Add("ENVELOPE", "");
+ fetchItems = fetchItems.Replace("ENVELOPE", "");
+ }
+ if (fetchItems.IndexOf("BODYSTRUCTURE") > -1)
+ {
+ fullMsgNeeded = true;
+ items.Add("BODYSTRUCTURE", "");
+ fetchItems = fetchItems.Replace("BODYSTRUCTURE", "");
+ }
+ if (fetchItems.IndexOf("BODY.PEEK[") > -1)
+ {
+ int start = fetchItems.IndexOf("BODY.PEEK[") + 10;
+ string val = fetchItems.Substring(start, fetchItems.IndexOf("]", start) - start).ToUpper().Trim();
+
+ // We must support only:
+ // "" - full message
+ // TEXT - message text
+ // HEADER - message header
+ // HEADER.FIELDS - requested message header fields
+ // HEADER.FIELDS.NOT - message header fields except requested
+ // number of mime entry
+
+ if (val.Length > 0)
+ {
+ string[] fArgs = ParseParams(val);
+
+ // Specified number of mime entry requested
+ if (fArgs.Length == 1 && Core.IsNumber(fArgs[0]))
+ {
+ fullMsgNeeded = true;
+ items.Add("BODY.PEEK[NUMBER]", Convert.ToInt32(fArgs[0]));
+ }
+ else
+ {
+ switch (fArgs[0].ToUpper())
+ {
+ case "TEXT":
+ fullMsgNeeded = true;
+ items.Add("BODY.PEEK[TEXT]", "");
+ break;
+
+ case "HEADER":
+ headersNeeded = true;
+ items.Add("BODY.PEEK[HEADER]", "");
+ break;
+
+ case "HEADER.FIELDS":
+ if (fArgs.Length == 2)
+ {
+ headersNeeded = true;
+ items.Add("BODY.PEEK[HEADER.FIELDS]", fArgs[1]);
+ }
+
+ break;
+
+ case "HEADER.FIELDS.NOT":
+ if (fArgs.Length == 2)
+ {
+ headersNeeded = true;
+ items.Add("BODY.PEEK[HEADER.FIELDS.NOT]", fArgs[1]);
+ }
+ break;
+
+ default:
+ SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
+ return;
+ }
+ }
+ }
+ else
+ {
+ fullMsgNeeded = true;
+ items.Add("BODY.PEEK[]", "");
+ }
+
+ fetchItems = fetchItems.Replace(fetchItems.Substring(fetchItems.IndexOf("BODY.PEEK["), fetchItems.IndexOf("]", fetchItems.IndexOf("BODY.PEEK[")) - fetchItems.IndexOf("BODY.PEEK[") + 1), "");
+ }
+ if (fetchItems.IndexOf("BODY[") > -1)
+ {
+ int start = fetchItems.IndexOf("BODY[") + 5;
+ string val = fetchItems.Substring(start, fetchItems.IndexOf("]", start) - start).ToUpper().Trim();
+
+ // We must support only:
+ // "" - full message
+ // TEXT - message text
+ // HEADER - message header
+ // HEADER.FIELDS - requested message header fields
+ // HEADER.FIELDS.NOT - message header fields except requested
+ // number of mime entry
+
+ if (val.Length > 0)
+ {
+ string[] fArgs = ParseParams(val);
+
+ // Specified number of mime entry requested
+ if (fArgs.Length == 1 && Core.IsNumber(fArgs[0]))
+ {
+ fullMsgNeeded = true;
+ items.Add("BODY[NUMBER]", Convert.ToInt32(fArgs[0]));
+ }
+ else
+ {
+ switch (fArgs[0].ToUpper())
+ {
+ case "TEXT":
+ fullMsgNeeded = true;
+ items.Add("BODY[TEXT]", "");
+ break;
+
+ case "HEADER":
+ headersNeeded = true;
+ items.Add("BODY[HEADER]", "");
+ break;
+
+ case "HEADER.FIELDS":
+ if (fArgs.Length == 2)
+ {
+ headersNeeded = true;
+ items.Add("BODY[HEADER.FIELDS]", fArgs[1]);
+ }
+
+ break;
+
+ case "HEADER.FIELDS.NOT":
+ if (fArgs.Length == 2)
+ {
+ headersNeeded = true;
+ items.Add("BODY[HEADER.FIELDS.NOT]", fArgs[1]);
+ }
+ break;
+
+ default:
+ SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
+ return;
+ }
+ }
+ }
+ else
+ {
+ fullMsgNeeded = true;
+ items.Add("BODY[]", "");
+ }
+
+ fetchItems = fetchItems.Replace(fetchItems.Substring(fetchItems.IndexOf("BODY["), fetchItems.IndexOf("]", fetchItems.IndexOf("BODY[")) - fetchItems.IndexOf("BODY[") + 1), "");
+ }
+ if (fetchItems.IndexOf("BODY") > -1)
+ {
+ fullMsgNeeded = true;
+ items.Add("BODY", "");
+ fetchItems = fetchItems.Replace("BODY", "");
+ }
+
+ // If length != 0, then contains invalid fetch items
+ if (fetchItems.Trim().Length > 0)
+ {
+ SendData(cmdTag + " BAD Invalid fetch-items argument\r\n");
+ return;
+ }
+
+
+ // Call fetch for all requsted messages
+ for (int i = nStartMsg - 1; i < nEndMsg; i++)
+ {
+ if (i < 0)
+ {
+ break;
+ }
+
+ IMAP_Message msg = m_Messages[i];
+
+ byte[] buf = null;
+ MemoryStream reply = new MemoryStream();
+ // Write fetch start data "* msgNo FETCH ("
+ buf = Encoding.ASCII.GetBytes("* " + msg.MessageNo + " FETCH (");
+ reply.Write(buf, 0, buf.Length);
+
+ /*
+ bool headersOnly = true;
+ // Check if just to get message or only header, other parsing do here
+ if(items.ContainsKey("RFC822.TEXT") || items.ContainsKey("RFC822") || items.ContainsKey("BODY.PEEK[]") || items.ContainsKey("BODY.PEEK[TEXT]")|| items.ContainsKey("BODY[]") || items.ContainsKey("BODY[TEXT]") || items.ContainsKey("BODY[NUMBER]") || items.ContainsKey("BODY.PEEK[NUMBER]")){
+ headersOnly = false;
+ }
+
+ Message_EventArgs eArgs = m_pIMAP_Server.OnGetMessage(this,msg,headersOnly);
+ byte[] headerData = FetchHelper.GetHeader(eArgs.MessageData);
+ */
+ byte[] msgData = null;
+ // Check if header or data is neccessary. Eg. if only UID wanted, don't get message at all.
+ if (fullMsgNeeded || headersNeeded)
+ {
+ Message_EventArgs eArgs = m_pIMAP_Server.OnGetMessage(this, msg, !fullMsgNeeded);
+ msgData = eArgs.MessageData;
+ }
+
+ IMAP_MessageFlags msgFlagsOr = msg.Flags;
+ // Construct reply here, based on requested fetch items
+ int nCount = 0;
+ foreach (string fetchItem in items.Keys)
+ {
+ switch (fetchItem)
+ {
+ case "UID":
+ buf = Encoding.ASCII.GetBytes("UID " + msg.MessageUID);
+ reply.Write(buf, 0, buf.Length);
+ break;
+
+ case "RFC822.TEXT":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // RFC822.TEXT {size}
+ // msg text
+ byte[] f11Data = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseText(msgData));
+
+ buf = Encoding.ASCII.GetBytes("RFC822.TEXT {" + f11Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(f11Data, 0, f11Data.Length);
+ break;
+
+ case "RFC822.SIZE":
+ // "RFC822.SIZE size
+ buf = Encoding.ASCII.GetBytes("RFC822.SIZE " + msg.Size);
+ reply.Write(buf, 0, buf.Length);
+ break;
+
+ case "RFC822.HEADER":
+ // RFC822.HEADER {size}
+ // msg header data
+ buf = Encoding.ASCII.GetBytes("RFC822.HEADER {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "RFC822":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // RFC822 {size}
+ // msg data
+ buf = Encoding.ASCII.GetBytes("RFC822 {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "INTERNALDATE":
+ // INTERNALDATE "date"
+ buf = Encoding.ASCII.GetBytes("INTERNALDATE \"" + msg.Date.ToString("r", System.Globalization.DateTimeFormatInfo.InvariantInfo) + "\"");
+ reply.Write(buf, 0, buf.Length);
+ break;
+
+ case "FLAGS":
+ buf = Encoding.ASCII.GetBytes("FLAGS (" + msg.FlagsToString() + ")");
+ reply.Write(buf, 0, buf.Length);
+ break;
+
+ case "ENVELOPE":
+ buf = Encoding.ASCII.GetBytes(FetchHelper.ParseEnvelope(msgData));
+ reply.Write(buf, 0, buf.Length);
+ break;
+
+ case "BODYSTRUCTURE":
+ // ToDo:
+
+ break;
+
+ case "BODY.PEEK[]":
+ // BODY[] {size}
+ // msg header data
+ buf = Encoding.ASCII.GetBytes("BODY[] {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "BODY.PEEK[HEADER]":
+ // BODY[HEADER] {size}
+ // msg header data
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER] {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "BODY.PEEK[HEADER.FIELDS]":
+ // BODY[HEADER.FIELDS ()] {size}
+ // msg header data
+ byte[] fData = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(items[fetchItem].ToString(), msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + items[fetchItem].ToString() + ")] {" + fData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(fData, 0, fData.Length);
+ break;
+
+ case "BODY.PEEK[HEADER.FIELDS.NOT]":
+ // BODY[HEADER.FIELDS.NOT ()] {size}
+ // msg header data
+ byte[] f1Data = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(items[fetchItem].ToString(), msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + items[fetchItem].ToString() + ")] {" + f1Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(f1Data, 0, f1Data.Length);
+ break;
+
+ case "BODY.PEEK[TEXT]":
+ // BODY[TEXT] {size}
+ // msg text
+ byte[] f111Data = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseText(msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + f111Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(f111Data, 0, f111Data.Length);
+ break;
+
+ case "BODY.PEEK[NUMBER]":
+ // BODY[no.] {size}
+ // mime part
+ byte[] b1113Data = FetchHelper.ParseMimeEntry(msgData, (int)items[fetchItem]);
+
+ if (b1113Data != null)
+ {
+ buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] {" + b1113Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(b1113Data, 0, b1113Data.Length);
+ }
+ else
+ {
+ // ToDo: What report here
+ // throw new Exception(System.Text.Encoding.ASCII.GetString(eArgs.MessageData));
+ }
+ break;
+
+ case "BODY[]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[] {size}
+ // msg header data
+ buf = Encoding.ASCII.GetBytes("BODY[] {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "BODY[HEADER]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[HEADER] {size}
+ // msg header data
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER] {" + msgData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+ reply.Write(msgData, 0, msgData.Length);
+ break;
+
+ case "BODY[HEADER.FIELDS]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[HEADER.FIELDS ()] {size}
+ // msg header data
+ byte[] bData = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFields(items[fetchItem].ToString(), msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS (" + items[fetchItem].ToString() + ")] {" + bData.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(bData, 0, bData.Length);
+ break;
+
+ case "BODY[HEADER.FIELDS.NOT]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[HEADER.FIELDS.NOT ()] {size}
+ // msg header data
+ byte[] f2Data = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseHeaderFieldsNot(items[fetchItem].ToString(), msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[HEADER.FIELDS.NOT (" + items[fetchItem].ToString() + ")] {" + f2Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(f2Data, 0, f2Data.Length);
+ break;
+
+ case "BODY[TEXT]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[TEXT] {size}
+ // msg text
+ byte[] f1111Data = System.Text.Encoding.ASCII.GetBytes(FetchHelper.ParseText(msgData));
+
+ buf = Encoding.ASCII.GetBytes("BODY[TEXT] {" + f1111Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(f1111Data, 0, f1111Data.Length);
+ break;
+
+ case "BODY[NUMBER]":
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY[no.] {size}
+ // mime part
+ byte[] b113Data = FetchHelper.ParseMimeEntry(msgData, (int)items[fetchItem]);
+
+ if (b113Data != null)
+ {
+ buf = Encoding.ASCII.GetBytes("BODY[" + items[fetchItem].ToString() + "] {" + b113Data.Length + "}\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ reply.Write(b113Data, 0, b113Data.Length);
+ }
+ else
+ {
+ // ToDo: What report here
+ // throw new Exception(System.Text.Encoding.ASCII.GetString(eArgs.MessageData));
+ }
+ break;
+
+ case "BODY":
+ // Sets \seen flag
+
+ // ToDo:
+
+ break;
+ }
+
+ nCount++;
+
+ // Write fetch item separator data " "
+ // We don't write it for last item
+ if (nCount < items.Count)
+ {
+ buf = Encoding.ASCII.GetBytes(" ");
+ reply.Write(buf, 0, buf.Length);
+ }
+ }
+
+ // Write fetch end data ")"
+ buf = Encoding.ASCII.GetBytes(")\r\n");
+ reply.Write(buf, 0, buf.Length);
+
+ // Send fetch reply to client
+ reply.Position = 0;
+ SendData(reply);
+
+
+ // Set message flags here if required or changed
+ if (((int)IMAP_MessageFlags.Recent & (int)msg.Flags) != 0 || msgFlagsOr != msg.Flags)
+ {
+ msg.SetFlags(msg.Flags & ~IMAP_MessageFlags.Recent);
+
+ m_pIMAP_Server.OnStoreMessageFlags(this, msg);
+
+ // ToDo: ??? report * msgNr FLAGS () here
+ }
+
+ }
+
+ SendData(cmdTag + " OK FETCH completed\r\n");
+ }
+
+ #endregion
+
+ #region function Store
+
+ private void Store(string cmdTag, string argsText, bool uidStore)
+ {
+ /* Rfc 3501 6.4.6 STORE Command
+
+ Arguments: message set
+ message data item name
+ value for message data item
+
+ Responses: untagged responses: FETCH
+
+ Result: OK - store completed
+ NO - store error: can't store that data
+ BAD - command unknown or arguments invalid
+
+ The STORE command alters data associated with a message in the
+ mailbox. Normally, STORE will return the updated value of the
+ data with an untagged FETCH response. A suffix of ".SILENT" in
+ the data item name prevents the untagged FETCH, and the server
+ SHOULD assume that the client has determined the updated value
+ itself or does not care about the updated value.
+
+ Note: regardless of whether or not the ".SILENT" suffix was
+ used, the server SHOULD send an untagged FETCH response if a
+ change to a message's flags from an external source is
+ observed. The intent is that the status of the flags is
+ determinate without a race condition.
+
+ The currently defined data items that can be stored are:
+
+ FLAGS
+ Replace the flags for the message (other than \Recent) with the
+ argument. The new value of the flags is returned as if a FETCH
+ of those flags was done.
+
+ FLAGS.SILENT
+ Equivalent to FLAGS, but without returning a new value.
+
+ +FLAGS
+ Add the argument to the flags for the message. The new value
+ of the flags is returned as if a FETCH of those flags was done.
+
+ +FLAGS.SILENT
+ Equivalent to +FLAGS, but without returning a new value.
+
+ -FLAGS
+ Remove the argument from the flags for the message. The new
+ value of the flags is returned as if a FETCH of those flags was
+ done.
+
+ -FLAGS.SILENT
+ Equivalent to -FLAGS, but without returning a new value.
+
+
+ Example: C: A003 STORE 2:4 +FLAGS (\Deleted)
+ S: * 2 FETCH FLAGS (\Deleted \Seen)
+ S: * 3 FETCH FLAGS (\Deleted)
+ S: * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
+ S: A003 OK STORE completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+ if (m_Messages.ReadOnly)
+ {
+ SendData(cmdTag + " NO Mailbox is read-only\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 3)
+ {
+ SendData(cmdTag + " BAD STORE invalid arguments\r\n");
+ return;
+ }
+
+ // If contains '*' , replace it with total messages number
+ string[] msgSet = args[0].Trim().Replace("*", m_Messages.Count.ToString()).Split(':');
+ int nStartMsg = Convert.ToInt32(msgSet[0]);
+ int nEndMsg = Convert.ToInt32(msgSet[0]);
+ // Message set specified
+ if (msgSet.Length == 2)
+ {
+ nEndMsg = Convert.ToInt32(msgSet[1]);
+ }
+
+ // For UID FETCH msg numbers are UID's not index numbers.
+ // Replace message UID with message index number,
+ // we can do this because UID are ASC order
+ if (uidStore)
+ {
+ nStartMsg = m_Messages.IndexFromUID(nStartMsg);
+
+ // If end message *, we don't need change it, it ok already
+ if (args[0].IndexOf("*") == -1)
+ {
+ nEndMsg = m_Messages.IndexFromUID(nEndMsg);
+ }
+ }
+
+ // Check that endmessage isn't > than messages available, if so use count instead.
+ if (nEndMsg > m_Messages.Count)
+ {
+ nEndMsg = m_Messages.Count;
+ }
+
+ //--- Parse Flags behaviour ---------------//
+ string flagsAction = "";
+ bool silent = false;
+ string flagsType = args[1].ToUpper();
+ switch (flagsType)
+ {
+ case "FLAGS":
+ flagsAction = "REPLACE";
+ break;
+
+ case "FLAGS.SILENT":
+ flagsAction = "REPLACE";
+ silent = true;
+ break;
+
+ case "+FLAGS":
+ flagsAction = "ADD";
+ break;
+
+ case "+FLAGS.SILENT":
+ flagsAction = "ADD";
+ silent = true;
+ break;
+
+ case "-FLAGS":
+ flagsAction = "REMOVE";
+ break;
+
+ case "-FLAGS.SILENT":
+ flagsAction = "REMOVE";
+ silent = true;
+ break;
+
+ default:
+ SendData(cmdTag + " BAD arguments invalid\r\n");
+ return;
+ }
+ //-------------------------------------------//
+
+ //--- Parse flags, see if valid ----------------
+ string flags = args[2].ToUpper();
+ if (flags.Replace("\\ANSWERED", "").Replace("\\FLAGGED", "").Replace("\\DELETED", "").Replace("\\SEEN", "").Replace("\\DRAFT", "").Trim().Length > 0)
+ {
+ SendData(cmdTag + " BAD arguments invalid\r\n");
+ return;
+ }
+ IMAP_MessageFlags mFlags = 0;
+ if (flags.IndexOf("\\ANSWERED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Answered;
+ }
+ if (flags.IndexOf("\\FLAGGED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Flagged;
+ }
+ if (flags.IndexOf("\\DELETED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Deleted;
+ }
+ if (flags.IndexOf("\\SEEN") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Seen;
+ }
+ if (flags.IndexOf("\\DRAFT") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Draft;
+ }
+ //---------------------------------------------
+
+ // Call OnStoreMessageFlags for each message in message set
+ // Calulate new flags(using old message flags + new flags) for message
+ // and request to store all flags to message, don't specify if add, remove or replace falgs.
+
+ for (int i = nStartMsg - 1; i < nEndMsg; i++)
+ {
+ IMAP_Message msg = m_Messages[i];
+
+ // Calculate new flags and set to msg
+ switch (flagsAction)
+ {
+ case "REPLACE":
+ msg.SetFlags(mFlags);
+ break;
+
+ case "ADD":
+ msg.SetFlags(msg.Flags | mFlags);
+ break;
+
+ case "REMOVE":
+ msg.SetFlags(msg.Flags & ~mFlags);
+ break;
+ }
+
+ string errorText = m_pIMAP_Server.OnStoreMessageFlags(this, msg);
+ if (errorText == null)
+ {
+ if (!silent)
+ { // Silent doesn't reply untagged lines
+ if (!uidStore)
+ {
+ SendData("* " + (i + 1) + " FETCH FLAGS (" + msg.FlagsToString() + ")\r\n");
+ }
+ // Called from UID command, need to add UID response
+ else
+ {
+ SendData("* " + (i + 1) + " FETCH (FLAGS (" + msg.FlagsToString() + " UID " + msg.MessageUID + "))\r\n");
+ }
+ }
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ return;
+ }
+ }
+
+ SendData(cmdTag + " OK STORE completed\r\n");
+ }
+
+ #endregion
+
+ #region function Copy
+
+ private void Copy(string cmdTag, string argsText, bool uidCopy)
+ {
+ /* RFC 3501 6.4.7 COPY Command
+
+ Arguments: message set
+ mailbox name
+
+ Responses: no specific responses for this command
+
+ Result: OK - copy completed
+ NO - copy error: can't copy those messages or to that
+ name
+ BAD - command unknown or arguments invalid
+
+ The COPY command copies the specified message(s) to the end of the
+ specified destination mailbox. The flags and internal date of the
+ message(s) SHOULD be preserved in the copy.
+
+ If the destination mailbox does not exist, a server SHOULD return
+ an error. It SHOULD NOT automatically create the mailbox. Unless
+ it is certain that the destination mailbox can not be created, the
+ server MUST send the response code "[TRYCREATE]" as the prefix of
+ the text of the tagged NO response. This gives a hint to the
+ client that it can attempt a CREATE command and retry the COPY if
+
+ If the COPY command is unsuccessful for any reason, server
+ implementations MUST restore the destination mailbox to its state
+ before the COPY attempt.
+
+ Example: C: A003 COPY 2:4 MEETING
+ S: A003 OK COPY completed
+
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ SendData(cmdTag + " BAD Invalid arguments\r\n");
+ return;
+ }
+
+ // If contains '*' , replace it with total messages number
+ string[] msgSet = args[0].Trim().Replace("*", m_Messages.Count.ToString()).Split(':');
+ int nStartMsg = Convert.ToInt32(msgSet[0]);
+ int nEndMsg = Convert.ToInt32(msgSet[0]);
+ // Message set specified
+ if (msgSet.Length == 2)
+ {
+ nEndMsg = Convert.ToInt32(msgSet[1]);
+ }
+
+ // For UID FETCH msg numbers are UID's not index numbers.
+ // Replace message UID with message index number,
+ // we can do this because UID are ASC order
+ if (uidCopy)
+ {
+ nStartMsg = m_Messages.IndexFromUID(nStartMsg);
+
+ // If end message *, we don't need change it, it ok already
+ if (args[0].IndexOf("*") == -1)
+ {
+ nEndMsg = m_Messages.IndexFromUID(nEndMsg);
+ }
+ }
+
+ // Check that endmessage isn't > than messages available, if so use count instead.
+ if (nEndMsg > m_Messages.Count)
+ {
+ nEndMsg = m_Messages.Count;
+ }
+
+ // Call copy for requested each message
+ for (int i = nStartMsg - 1; i < nEndMsg; i++)
+ {
+ IMAP_Message msg = m_Messages[i];
+
+ string errorText = m_pIMAP_Server.OnCopyMessage(this, msg, args[1]);
+ if (errorText == null)
+ {
+ SendData(cmdTag + " OK COPY completed\r\n");
+ }
+ else
+ {
+ SendData(cmdTag + " NO " + errorText + "\r\n");
+ return;
+ }
+ }
+
+ }
+
+ #endregion
+
+ #region function Uid
+
+ private void Uid(string cmdTag, string argsText)
+ {
+ /* Rfc 3501 6.4.8 UID Command
+
+ Arguments: command name
+ command arguments
+
+ Responses: untagged responses: FETCH, SEARCH
+
+ Result: OK - UID command completed
+ NO - UID command error
+ BAD - command unknown or arguments invalid
+
+ The UID command has two forms. In the first form, it takes as its
+ arguments a COPY, FETCH, or STORE command with arguments
+ appropriate for the associated command. However, the numbers in
+ the message set argument are unique identifiers instead of message
+ sequence numbers.
+
+ In the second form, the UID command takes a SEARCH command with
+ SEARCH command arguments. The interpretation of the arguments is
+ the same as with SEARCH; however, the numbers returned in a SEARCH
+ response for a UID SEARCH command are unique identifiers instead
+ of message sequence numbers. For example, the command UID SEARCH
+ 1:100 UID 443:557 returns the unique identifiers corresponding to
+ the intersection of the message sequence number set 1:100 and the
+ UID set 443:557.
+
+ Message set ranges are permitted; however, there is no guarantee
+ that unique identifiers be contiguous. A non-existent unique
+ identifier within a message set range is ignored without any error
+ message generated.
+
+ The number after the "*" in an untagged FETCH response is always a
+ message sequence number, not a unique identifier, even for a UID
+ command response. However, server implementations MUST implicitly
+ include the UID message data item as part of any FETCH response
+ caused by a UID command, regardless of whether a UID was specified
+ as a message data item to the FETCH.
+
+ Example: C: A999 UID FETCH 4827313:4828442 FLAGS
+ S: * 23 FETCH (FLAGS (\Seen) UID 4827313)
+ S: * 24 FETCH (FLAGS (\Seen) UID 4827943)
+ S: * 25 FETCH (FLAGS (\Seen) UID 4828442)
+ S: A999 UID FETCH completed
+ */
+ if (!m_Authenticated)
+ {
+ SendData(cmdTag + " NO Authenticate first !\r\n");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ SendData(cmdTag + " NO Select mailbox first !\r\n");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length < 2)
+ { // We must have at least command and message-set or cmd args
+ SendData(cmdTag + " BAD Invalid arguments\r\n");
+ return;
+ }
+
+ // See if valid command specified with UID command
+ switch (args[0].ToUpper())
+ {
+ case "COPY":
+ break;
+ case "FETCH":
+ break;
+ case "STORE":
+ break;
+ case "SEARCH":
+ break;
+
+ default:
+ SendData(cmdTag + " BAD Invalid arguments\r\n");
+ return;
+ }
+
+ // Get commands args text, we just remove COMMAND
+ string cmdArgs = Core.GetArgsText(argsText, args[0]);
+
+ // Do command
+ switch (args[0].ToUpper())
+ {
+ case "COPY":
+ Copy(cmdTag, cmdArgs, true);
+ break;
+
+ case "FETCH":
+ Fetch(cmdTag, cmdArgs, true);
+ break;
+
+ case "STORE":
+ Store(cmdTag, cmdArgs, true);
+ break;
+
+ case "SEARCH":
+ Search(cmdTag, cmdArgs, true);
+ break;
+ }
+ }
+
+ #endregion
+
+ //--- End of Selected State
+
+
+ //--- Any State ------
+
+ #region function Capability
+
+ private void Capability(string cmdTag)
+ {
+ /* RFC 3501 6.1.1
+
+ Arguments: none
+
+ Responses: REQUIRED untagged response: CAPABILITY
+
+ Result: OK - capability completed
+ BAD - command unknown or arguments invalid
+
+ The CAPABILITY command requests a listing of capabilities that the
+ server supports. The server MUST send a single untagged
+ CAPABILITY response with "IMAP4rev1" as one of the listed
+ capabilities before the (tagged) OK response.
+
+ A capability name which begins with "AUTH=" indicates that the
+ server supports that particular authentication mechanism.
+
+ Example: C: abcd CAPABILITY
+ S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI
+ LOGINDISABLED
+ S: abcd OK CAPABILITY completed
+ C: efgh STARTTLS
+ S: efgh OK STARTLS completed
+
+ C: ijkl CAPABILITY
+ S: * CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN
+ S: ijkl OK CAPABILITY completed
+ */
+
+ SendData("* CAPABILITY IMAP4rev1 AUTH=CRAM-MD5\r\n");
+ SendData(cmdTag + " OK CAPABILITY completed\r\n");
+ }
+
+ #endregion
+
+ #region function Noop
+
+ private void Noop(string cmdTag)
+ {
+ /* RFC 3501 6.1.2 NOOP Command
+
+ Arguments: none
+
+ Responses: no specific responses for this command (but see below)
+
+ Result: OK - noop completed
+ BAD - command unknown or arguments invalid
+
+ The NOOP command always succeeds. It does nothing.
+ Since any command can return a status update as untagged data, the
+ NOOP command can be used as a periodic poll for new messages or
+ message status updates during a period of inactivity. The NOOP
+ command can also be used to reset any inactivity autologout timer
+ on the server.
+
+ Example: C: a002 NOOP
+ S: a002 OK NOOP completed
+ */
+
+ SendData(cmdTag + " OK NOOP completed\r\n");
+ }
+
+ #endregion
+
+ #region function LogOut
+
+ private void LogOut(string cmdTag)
+ {
+ /* RFC 3501 6.1.3
+
+ Arguments: none
+
+ Responses: REQUIRED untagged response: BYE
+
+ Result: OK - logout completed
+ BAD - command unknown or arguments invalid
+
+ The LOGOUT command informs the server that the client is done with
+ the connection. The server MUST send a BYE untagged response
+ before the (tagged) OK response, and then close the network
+ connection.
+
+ Example: C: A023 LOGOUT
+ S: * BYE IMAP4rev1 Server logging out
+ S: A023 OK LOGOUT completed
+ (Server and client then close the connection)
+ */
+
+ SendData("* BYE IMAP4rev1 Server logging out\r\n");
+ SendData(cmdTag + " OK LOGOUT completed\r\n");
+ }
+
+ #endregion
+
+ //--- End of Any State
+
+
+ #region function SendData
+
+ ///
+ /// Sends data to socket.
+ ///
+ /// String data which to send.
+ private void SendData(string data)
+ {
+ byte[] byte_data = System.Text.Encoding.ASCII.GetBytes(data);
+
+ int nCount = m_pClientSocket.Send(byte_data, byte_data.Length, 0);
+
+ if (m_pIMAP_Server.LogCommands)
+ {
+ data = data.Replace("\r\n", "");
+ m_pLogWriter.AddEntry(data, this.SessionID, m_ConnectedIp, "S");
+ }
+ }
+
+ ///
+ /// Send stream data to socket.
+ ///
+ ///
+ private void SendData(MemoryStream strm)
+ {
+ //---- split message to blocks -------------------------------//
+ long totalSent = 0;
+ while (strm.Position < strm.Length)
+ {
+ int blockSize = 4024;
+ byte[] dataBuf = new byte[blockSize];
+ int nCount = strm.Read(dataBuf, 0, blockSize);
+ int countSended = m_pClientSocket.Send(dataBuf, nCount, SocketFlags.None);
+
+ totalSent += countSended;
+
+ if (countSended != nCount)
+ {
+ strm.Position = totalSent;
+ }
+ }
+ //-------------------------------------------------------------//
+
+ if (m_pIMAP_Server.LogCommands)
+ {
+ m_pLogWriter.AddEntry("binary " + strm.Length.ToString() + " bytes", this.SessionID, m_ConnectedIp, "S");
+ }
+ }
+
+ #endregion
+
+ #region function ReadLine
+
+ ///
+ /// Reads line from socket.
+ ///
+ ///
+ private string ReadLine()
+ {
+ string line = Core.ReadLine(m_pClientSocket, 500, m_pIMAP_Server.CommandIdleTimeOut);
+
+ if (m_pIMAP_Server.LogCommands)
+ {
+ m_pLogWriter.AddEntry(line + "", this.SessionID, m_ConnectedIp, "C");
+ }
+
+ return line;
+ }
+
+ #endregion
+
+
+ #region function ParseParams
+
+ private string[] ParseParams(string argsText)
+ {
+ ArrayList p = new ArrayList();
+
+ try
+ {
+ while (argsText.Length > 0)
+ {
+ // Parameter is between ""
+ if (argsText.StartsWith("\""))
+ {
+ p.Add(argsText.Substring(1, argsText.IndexOf("\"", 1) - 1));
+ // Remove parsed param
+ argsText = argsText.Substring(argsText.IndexOf("\"", 1) + 1).Trim();
+ }
+ else
+ {
+ // Parameter is between ()
+ if (argsText.StartsWith("("))
+ {
+ p.Add(argsText.Substring(1, argsText.LastIndexOf(")") - 1));
+ // Remove parsed param
+ argsText = argsText.Substring(argsText.LastIndexOf(")") + 1).Trim();
+ }
+ else
+ {
+ // Read parameter till " ", probably there is more params
+ if (argsText.IndexOf(" ") > -1)
+ {
+ p.Add(argsText.Substring(0, argsText.IndexOf(" ")));
+ // Remove parsed param
+ argsText = argsText.Substring(argsText.IndexOf(" ") + 1).Trim();
+ }
+ // This is last param
+ else
+ {
+ p.Add(argsText);
+ argsText = "";
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ string[] retVal = new string[p.Count];
+ p.CopyTo(retVal);
+
+ return retVal;
+ }
+
+ #endregion
+
+
+ #region Properties Implementation
+
+ ///
+ /// Gets session ID.
+ ///
+ public string SessionID
+ {
+ get { return m_SessionID; }
+ }
+
+ ///
+ /// Gets if session authenticated.
+ ///
+ public bool Authenticated
+ {
+ get { return m_Authenticated; }
+ }
+
+ ///
+ /// Gets loggded in user name (session owner).
+ ///
+ public string UserName
+ {
+ get { return m_UserName; }
+ }
+
+ ///
+ /// Gets selected mailbox.
+ ///
+ public string SelectedMailbox
+ {
+ get { return m_SelectedMailbox; }
+ }
+
+ ///
+ /// Gets connected Host(client) EndPoint.
+ ///
+ public EndPoint RemoteEndPoint
+ {
+ get { return m_pClientSocket.RemoteEndPoint; }
+ }
+
+ ///
+ /// Gets local EndPoint which accepted client(connected host).
+ ///
+ public EndPoint LocalEndPoint
+ {
+ get { return m_pClientSocket.LocalEndPoint; }
+ }
+
+ ///
+ /// Gets session start time.
+ ///
+ public DateTime SessionStartTime
+ {
+ get { return m_SessionStartTime; }
+ }
+
+ ///
+ /// Gets or sets custom user data.
+ ///
+ public object Tag
+ {
+ get { return m_Tag; }
+
+ set { m_Tag = value; }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.Designer.cs b/MailServer/IMAP/Server/Server.Designer.cs
new file mode 100644
index 0000000..4c3c24c
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.Designer.cs
@@ -0,0 +1,38 @@
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Erforderliche Designervariable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Verwendete Ressourcen bereinigen.
+ ///
+ /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ Stop();
+ }
+
+ #region Vom Komponenten-Designer generierter Code
+
+ ///
+ /// Erforderliche Methode für die Designerunterstützung.
+ /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ this.ServiceName = "IMAP Server";
+ }
+
+ #endregion
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.EventDelegates.cs b/MailServer/IMAP/Server/Server.EventDelegates.cs
new file mode 100644
index 0000000..2bf0d8b
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.EventDelegates.cs
@@ -0,0 +1,16 @@
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Representiert die Methode die das AuthUser Event vom SMTP_Server handelt
+ ///
+ /// Die Quelle des Events
+ /// Ein AuthUser_EventArgs welches das event beinhaltet
+ public delegate void AuthUserEventHandler(object sender, AuthUser_EventArgs e);
+ public delegate void FolderEventHandler(object sender, Mailbox_EventArgs e);
+ public delegate void FoldersEventHandler(object sender, IMAP_Folders e);
+ public delegate void MessagesEventHandler(object sender, IMAP_Messages e);
+ public delegate void MessageEventHandler(object sender, Message_EventArgs e);
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.Events.cs b/MailServer/IMAP/Server/Server.Events.cs
new file mode 100644
index 0000000..f552e7f
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.Events.cs
@@ -0,0 +1,267 @@
+using System.Net;
+using System;
+using System.Diagnostics;
+
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Erzeugt event ValidateIpAdress
+ ///
+ /// Verbundener Host endpoint
+ /// Gibt true zurück wenn die Verbindung erlaubt ist
+ internal bool OnValidate_IpAddress(EndPoint enpoint)
+ {
+ ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(enpoint);
+ if (this.ValidateIPAddress != null)
+ {
+ this.ValidateIPAddress(this, oArg);
+ }
+ return oArg.Validated;
+ }
+ ///
+ /// Erzeugt event AuthUser
+ ///
+ /// referenz zur Aktuellen IMAP Session
+ /// Benutzername
+ /// Passwort
+ /// Hängt vom AuthTyp ab
+ /// Authtyp
+ /// Gibt True zurück, wenn user erlaubt
+ internal bool OnAuthUser(IMAP_Session session, string userName, string passwordData, string data, AuthType authType)
+ {
+ AuthUser_EventArgs oArgs = new AuthUser_EventArgs(session, userName, passwordData, data, authType);
+ if (this.AuthUser != null)
+ {
+ this.AuthUser(this, oArgs);
+ }
+ return oArgs.Validated;
+ }
+ ///
+ /// Erzeugt event SuscribeMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Name der Mailbox welche aboniert werden soll
+ ///
+ internal string OnSubscribeMailbox(IMAP_Session session, string mailbox)
+ {
+ if (this.SubscribeFolder != null)
+ {
+ Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
+ this.SubscribeFolder(session, eArgs);
+
+ return eArgs.ErrorText;
+ }
+ return null;
+ }
+ ///
+ /// Erzeugt event UnSuscribeMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Name der Mailbox welche abbestellt werden soll
+ ///
+ internal string OnUnSubscribeMailbox(IMAP_Session session, string mailbox)
+ {
+ if (this.UnSubscribeFolder != null)
+ {
+ Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
+ this.UnSubscribeFolder(session, eArgs);
+
+ return eArgs.ErrorText;
+ }
+ return null;
+ }
+ ///
+ /// Erzeugt event GetSuscribedMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Mailbox referenz
+ /// Mailbox name oder pattern
+ /// gibt die Mailbox zurück
+ internal IMAP_Folders OnGetSubscribedMailboxes(IMAP_Session session, string referenceName, string mailBox)
+ {
+ IMAP_Folders retVal = new IMAP_Folders(referenceName, mailBox);
+ if (this.GetSubscribedFolders != null)
+ {
+ this.GetSubscribedFolders(session, retVal);
+ }
+ return retVal;
+ }
+ ///
+ /// Erzeugt event GetMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Mailbox referenz
+ /// Mailbox name oder pattern
+ ///
+ internal IMAP_Folders OnGetMailboxes(IMAP_Session session, string referenceName, string mailBox)
+ {
+ IMAP_Folders retVal = new IMAP_Folders(referenceName, mailBox);
+ if (this.GetFolders != null)
+ {
+ this.GetFolders(session, retVal);
+ }
+ return retVal;
+ }
+ ///
+ /// Erzeugt event CreateMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Mailbox name
+ ///
+ internal string OnCreateMailbox(IMAP_Session session, string mailbox)
+ {
+ if (this.CreateFolder != null)
+ {
+ Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
+ this.CreateFolder(session, eArgs);
+
+ return eArgs.ErrorText;
+ }
+ return null;
+ }
+ ///
+ /// Erzeugt event DeleteMailbox
+ ///
+ /// Referent zur IMAP Session
+ /// Mailbox name
+ ///
+ internal string OnDeleteMailbox(IMAP_Session session, string mailbox)
+ {
+ if (this.DeleteFolder != null)
+ {
+ Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox);
+ this.DeleteFolder(session, eArgs);
+
+ return eArgs.ErrorText;
+ }
+ return null;
+ }
+ ///
+ /// Erzeugt Event RenameMailbox
+ ///
+ /// Referenz zur IMAP Session
+ /// Mailbox name
+ /// neuer Mailbox name
+ ///
+ internal string OnRenameMailbox(IMAP_Session session, string mailbox, string newMailboxName)
+ {
+ if (this.RenameFolder != null)
+ {
+ Mailbox_EventArgs eArgs = new Mailbox_EventArgs(mailbox, newMailboxName);
+ this.RenameFolder(session, eArgs);
+
+ return eArgs.ErrorText;
+ }
+ return null;
+ }
+ ///
+ /// Erzeugt event MailboxInfo
+ ///
+ /// Referenz zur IMAP Session
+ /// Mailbox name
+ ///
+ internal IMAP_Messages OnGetMessagesInfo(IMAP_Session session, string mailbox)
+ {
+ IMAP_Messages messages = new IMAP_Messages(mailbox);
+ if (this.GetMessagesInfo != null)
+ {
+ this.GetMessagesInfo(session, messages);
+ }
+ return messages;
+ }
+ ///
+ /// Erzeugt event GetMessage
+ ///
+ /// Referenz zur IMAP Session
+ /// Nachricht
+ /// nur Kopfzeilen
+ ///
+ internal Message_EventArgs OnGetMessage(IMAP_Session session, IMAP_Message msg, bool headersOnly)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg, headersOnly);
+ if (this.GetMessage != null)
+ {
+ this.GetMessage(session, eArgs);
+ }
+ return eArgs;
+ }
+ ///
+ /// Erzeuge Event DeleteMessage
+ ///
+ /// Referenz zur IMAP Session
+ /// Nachricht
+ ///
+ internal string OnDeleteMessage(IMAP_Session session, IMAP_Message message)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, message);
+ if (this.DeleteMessage != null)
+ {
+ this.DeleteMessage(session, eArgs);
+ }
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Erzeuge Event CopyMesage
+ ///
+ /// Referenz zur IMAP Session
+ /// Nachricht
+ /// Neue Position
+ ///
+ internal string OnCopyMessage(IMAP_Session session, IMAP_Message msg, string location)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg, location);
+ if (this.CopyMessage != null)
+ {
+ this.CopyMessage(session, eArgs);
+ }
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Erzeuge Event StoreMessage
+ ///
+ /// Referenz zur IMAP Session
+ /// Ordner
+ /// Nachricht
+ /// Nachrichteninhalt
+ ///
+ internal string OnStoreMessage(IMAP_Session session, string folder, IMAP_Message msg, byte[] messageData)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(folder, msg);
+ eArgs.MessageData = messageData;
+ if (this.StoreMessage != null)
+ {
+ this.StoreMessage(session, eArgs);
+ }
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Erzeuge Event StoreMessageFlags
+ ///
+ /// Referenz zur IMAP Session
+ /// Nachricht
+ ///
+ internal string OnStoreMessageFlags(IMAP_Session session, IMAP_Message msg)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(session.SelectedMailbox, msg);
+ if (this.StoreMessageFlags != null)
+ {
+ this.StoreMessageFlags(session, eArgs);
+ }
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Erzeuge Event SysError
+ ///
+ /// Ausnahme
+ /// Stacktrace
+ internal void OnSysError(Exception x, StackTrace stackTrace)
+ {
+ if (this.SysError != null)
+ {
+ this.SysError(this, new Error_EventArgs(x, stackTrace));
+ }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.EventsDeclarations.cs b/MailServer/IMAP/Server/Server.EventsDeclarations.cs
new file mode 100644
index 0000000..e821fc7
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.EventsDeclarations.cs
@@ -0,0 +1,74 @@
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Tritt ein wenn ein Computer zum IMAP Server verbindet
+ ///
+ public event ValidateIPHandler ValidateIPAddress = null;
+ ///
+ /// Tritt ein wenn ein User sich zu authentifizieren versucht
+ ///
+ public event AuthUserEventHandler AuthUser = null;
+ ///
+ /// Tritt ein wenn der Server versucht einen Ordner zu beziehen
+ ///
+ public event FolderEventHandler SubscribeFolder = null;
+ ///
+ /// Tritt ein wenn der Server versucht einen Ordner abzubestellen
+ ///
+ public event FolderEventHandler UnSubscribeFolder = null;
+ ///
+ /// Tritt ein wenn der Server versucht alle Ordner zu lesen
+ ///
+ public event FoldersEventHandler GetFolders = null;
+ ///
+ /// Tritt ein wenn der Server versucht die bestellen Ordner zu lesen
+ ///
+ public event FoldersEventHandler GetSubscribedFolders = null;
+ ///
+ /// Tritt ein wenn der Server versucht einen Ordner zu erstellen
+ ///
+ public event FolderEventHandler CreateFolder = null;
+ ///
+ /// Tritt ein wenn der Server versucht einen Ordner zu löschen
+ ///
+ public event FolderEventHandler DeleteFolder = null;
+ ///
+ /// Tritt ein wenn der Server versucht einen Ordner umzubenennen
+ ///
+ public event FolderEventHandler RenameFolder = null;
+ ///
+ /// Tritt ein wenn der Server Nachrichten Infos zu Ordner zu lesen
+ ///
+ public event MessagesEventHandler GetMessagesInfo = null;
+ ///
+ /// Tritt ein wenn der Server versucht eine Nachricht zu löschen
+ ///
+ public event MessageEventHandler DeleteMessage = null;
+ ///
+ /// Tritt ein wenn der Server versucht eine Nachricht zu speichern
+ ///
+ public event MessageEventHandler StoreMessage = null;
+ ///
+ /// Tritt ein wenn der Server versucht Nachrichten Flags zu Setzen
+ ///
+ public event MessageEventHandler StoreMessageFlags = null;
+ ///
+ /// Tritt ein wenn der Server versucht eine Nachricht zu kopieren
+ ///
+ public event MessageEventHandler CopyMessage = null;
+ ///
+ /// Tritt ein wenn der Server versucht eine Nachricht zu lesen
+ ///
+ public event MessageEventHandler GetMessage = null;
+ ///
+ /// Tritt ein wenn der Server einen Fehler hat
+ ///
+ public event ErrorEventHandler SysError = null;
+ ///
+ /// Tritt ein Wenn die Sitzung zu ende ist und der Server einen Log hat
+ ///
+ public event LogEventHandler SessionLog = null;
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.Properties.cs b/MailServer/IMAP/Server/Server.Properties.cs
new file mode 100644
index 0000000..07a1ce7
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.Properties.cs
@@ -0,0 +1,97 @@
+using System.ComponentModel;
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// IP Adresse an der der Server hört
+ ///
+ [Description("IP Address to Listen IMAP requests"), DefaultValue("ALL")]
+ public string IpAddress
+ {
+ get { return m_IPAddress; }
+ set { m_IPAddress = value; }
+ }
+ ///
+ /// Port an dem der Server hört
+ ///
+ [Description("Port to use for IMAP"),DefaultValue(143)]
+ public int Port
+ {
+ get { return m_port; }
+ set { m_port = value; }
+ }
+ ///
+ /// Maximale Sessions
+ ///
+ [Description("Maximum Allowed threads"),DefaultValue(50)]
+ public int Threads
+ {
+ get { return m_MaxThreads; }
+ set { m_MaxThreads = value; }
+ }
+ ///
+ /// Run and Stops the Server
+ ///
+ [Description("Use this property to run and stop SMTP Server"),DefaultValue(false)]
+ public bool Enabled
+ {
+ get { return m_enabled; }
+ set
+ {
+ if (value != m_enabled)
+ {
+ if (value)
+ {
+ Start();
+ }
+ else
+ {
+ Stop();
+ }
+ m_enabled = value;
+ }
+ }
+ }
+ ///
+ /// Logging
+ ///
+ public bool LogCommands
+ {
+ get { return m_LogCmds; }
+ set { m_LogCmds = value; }
+ }
+ ///
+ /// Session ilde timeout [ms]
+ ///
+ public int SessionIdleTimeOut
+ {
+ get { return m_SessionIdleTimeOut; }
+ set { m_SessionIdleTimeOut = value; }
+ }
+ ///
+ /// Command ilde timeout [ms]
+ ///
+ public int CommandIdleTimeOut
+ {
+ get { return m_CommandIdleTimeOut; }
+ set { m_CommandIdleTimeOut = value; }
+ }
+ ///
+ /// Max message Size [b]
+ ///
+ public int MaxMessageSize
+ {
+ get { return m_MaxMessageSize; }
+ set { m_MaxMessageSize = value; }
+ }
+ ///
+ /// Max bad commands bevore kick
+ ///
+ public int MaxBadCommands
+ {
+ get { return m_MaxBadCommands; }
+ set { m_MaxBadCommands = value; }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.Session.cs b/MailServer/IMAP/Server/Server.Session.cs
new file mode 100644
index 0000000..5727990
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.Session.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Fügt eine Session hinzu
+ ///
+ /// Session ID
+ /// Session Object
+ /// Logwriter
+ internal void AddSession(string sessionID, IMAP_Session session, _LogWriter logWriter)
+ {
+ lock (m_SessionTable)
+ {
+ m_SessionTable.Add(sessionID, session);
+
+ if (m_LogCmds)
+ {
+ logWriter.AddEntry("//----- Sys: 'Session:'" + sessionID + " added " + DateTime.Now);
+ }
+ }
+ }
+ ///
+ /// Löscht eine Session
+ ///
+ /// Session ID
+ /// Logwriter
+ internal void RemoveSession(string sessionID, _LogWriter logWriter)
+ {
+ lock (m_SessionTable)
+ {
+ if (!m_SessionTable.Contains(sessionID))
+ {
+ OnSysError(new Exception("Session '" + sessionID + "' doesn't exist."), new System.Diagnostics.StackTrace());
+ return;
+ }
+ m_SessionTable.Remove(sessionID);
+ }
+
+ if (m_LogCmds)
+ {
+ logWriter.AddEntry("//----- Sys: 'Session:'" + sessionID + " removed " + DateTime.Now);
+ }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.StartStopRun.cs b/MailServer/IMAP/Server/Server.StartStopRun.cs
new file mode 100644
index 0000000..d8d2201
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.StartStopRun.cs
@@ -0,0 +1,111 @@
+using System.Collections;
+using System.Threading;
+using System;
+using System.Net.Sockets;
+using System.Net;
+
+namespace MailServer.IMAP.Server
+{
+ partial class Server
+ {
+ ///
+ /// Startet den IMAP Server
+ ///
+ private void Start()
+ {
+ try
+ {
+ // if(!m_enabled && !this.DesignMode){
+ m_SessionTable = new Hashtable();
+
+ Thread startServer = new Thread(new ThreadStart(Run));
+ startServer.Start();
+ // }
+ }
+ catch (Exception x)
+ {
+ OnSysError(x, new System.Diagnostics.StackTrace());
+ }
+ }
+ ///
+ /// Stoppt den IMAP Server
+ ///
+ private void Stop()
+ {
+ try
+ {
+ if (IMAP_Listener != null)
+ {
+ IMAP_Listener.Stop();
+ }
+ }
+ catch (Exception x)
+ {
+ OnSysError(x, new System.Diagnostics.StackTrace());
+ }
+ }
+ ///
+ /// Server Hauptschleife
+ ///
+ private void Run()
+ {
+ try
+ {
+ // check which ip's to listen (all or assigned)
+ if (m_IPAddress.ToLower().IndexOf("all") > -1)
+ {
+ IMAP_Listener = new TcpListener(IPAddress.Any, m_port);
+ }
+ else
+ {
+ IMAP_Listener = new TcpListener(IPAddress.Parse(m_IPAddress), m_port);
+ }
+ // Start listening
+ IMAP_Listener.Start();
+
+
+ //-------- Main Server message loop --------------------------------//
+ while (true)
+ {
+ // Check if maximum allowed thread count isn't exceeded
+ if (m_SessionTable.Count <= m_MaxThreads)
+ {
+
+ // Thread is sleeping, until a client connects
+ Socket clientSocket = IMAP_Listener.AcceptSocket();
+
+ string sessionID = clientSocket.GetHashCode().ToString();
+
+ //****
+ _LogWriter logWriter = new _LogWriter(this.SessionLog);
+ IMAP_Session session = new IMAP_Session(clientSocket, this, sessionID, logWriter);
+
+ Thread clientThread = new Thread(new ThreadStart(session.StartProcessing));
+
+ // Add session to session list
+ AddSession(sessionID, session, logWriter);
+
+ // Start proccessing
+ clientThread.Start();
+ }
+ else
+ {
+ Thread.Sleep(100);
+ }
+ }
+ }
+ catch (ThreadInterruptedException e)
+ {
+ string dummy = e.Message; // Neede for to remove compile warning
+ Thread.CurrentThread.Abort();
+ }
+ catch (Exception x)
+ {
+ if (x.Message != "A blocking operation was interrupted by a call to WSACancelBlockingCall")
+ {
+ OnSysError(x, new System.Diagnostics.StackTrace());
+ }
+ }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.cs b/MailServer/IMAP/Server/Server.cs
new file mode 100644
index 0000000..0d597f5
--- /dev/null
+++ b/MailServer/IMAP/Server/Server.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ServiceProcess;
+using System.Collections;
+using System.Diagnostics;
+using System.Net.Sockets;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP Server Komponente
+ ///
+ public partial class Server : ServiceBase
+ {
+ private TcpListener IMAP_Listener = null;
+ private Hashtable m_SessionTable = null;
+
+ private string m_IPAddress = "ALL"; // Holds IP Address, which to listen incoming calls.
+ private int m_port = 143; // Holds port number, which to listen incoming calls.
+ private int m_MaxThreads = 20; // Holds maximum allowed Worker Threads.
+ private bool m_enabled = false; // If true listens incoming calls.
+ private bool m_LogCmds = false; // If true, writes commands to log file.
+ private int m_SessionIdleTimeOut = 80000; // Holds session idle timeout.
+ private int m_CommandIdleTimeOut = 6000000; // Holds command ilde timeout.
+ private int m_MaxMessageSize = 1000000; // Hold maximum message size.
+ private int m_MaxBadCommands = 8; // Holds maximum bad commands allowed to session.
+
+ ///
+ /// Server laden und anhängen
+ ///
+ /// anhängen an
+ public Server(System.ComponentModel.IContainer container)
+ {
+ container.Add(this);
+ InitializeComponent();
+ }
+ ///
+ /// Server laden
+ ///
+ public Server()
+ {
+ InitializeComponent();
+ }
+ ///
+ /// Starte Server über Dienst
+ ///
+ ///
+ protected override void OnStart(string[] args)
+ {
+ Start();
+ }
+ ///
+ /// Stoppe Server über Dienst
+ ///
+ protected override void OnStop()
+ {
+ Stop();
+ }
+ }
+}
diff --git a/MailServer/MailServer.csproj b/MailServer/MailServer.csproj
index 3e5fb18..e3dc955 100644
--- a/MailServer/MailServer.csproj
+++ b/MailServer/MailServer.csproj
@@ -44,6 +44,42 @@
+
+
+
+
+ AuthUser_EventArgs.cs
+
+
+ Component
+
+
+ Server.cs
+
+
+ Server.cs
+ Component
+
+
+ Server.cs
+ Component
+
+
+ Server.cs
+ Component
+
+
+ Server.cs
+ Component
+
+
+ Server.cs
+ Component
+
+
+ Server.cs
+ Component
+
Component
diff --git a/MailServer/Program.cs b/MailServer/Program.cs
index 2b33199..fa1130d 100644
--- a/MailServer/Program.cs
+++ b/MailServer/Program.cs
@@ -8,6 +8,12 @@ namespace MailServer
{
static class Program
{
+ static void auth_user_arsch(object sender, IMAP.Server.AuthUser_EventArgs e)
+ {
+ e.Validated = false;
+ if(e.UserName == "blubb" && e.PasswData == "asd")
+ e.Validated = true;
+ }
///
/// Der Haupteinstiegspunkt für die Anwendung.
///
@@ -19,6 +25,9 @@ namespace MailServer
new Service1()
};
ServiceBase.Run(ServicesToRun);
+ IMAP.Server.Server asd = new IMAP.Server.Server();
+ asd.AuthUser += new IMAP.Server.Server.AuthUserEventHandler(auth_user_arsch);
+ asd.GetMessage += new IMAP.Server.Server.MessageEventHandler(asd_GetMessage);
}
}
}
diff --git a/MailServer/Settings/Enum.cs b/MailServer/Settings/Enum.cs
new file mode 100644
index 0000000..0793025
--- /dev/null
+++ b/MailServer/Settings/Enum.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.Settings
+{
+ ///
+ /// Autentifizierungstyp
+ ///
+ public enum AuthType
+ {
+ ///
+ /// Plain username/password authentication.
+ ///
+ Plain = 0,
+ ///
+ /// APOP
+ ///
+ APOP = 1,
+ ///
+ /// Not implemented.
+ ///
+ LOGIN = 2,
+ ///
+ /// Cram-md5 authentication.
+ ///
+ CRAM_MD5 = 3,
+ }
+}