diff --git a/MailServer/IMAP/IMAP_ACL_Flags.cs b/MailServer/IMAP/IMAP_ACL_Flags.cs
new file mode 100644
index 0000000..d554eb3
--- /dev/null
+++ b/MailServer/IMAP/IMAP_ACL_Flags.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP
+{
+ ///
+ /// IMAP ACL(access control list) rights.
+ ///
+ public enum IMAP_ACL_Flags
+ {
+ ///
+ /// No permissions at all.
+ ///
+ None = 0,
+ ///
+ /// Lookup (mailbox is visible to LIST/LSUB commands).
+ ///
+ l = 1,
+ ///
+ /// Read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL,SEARCH, COPY from mailbox).
+ ///
+ r = 2,
+ ///
+ /// Keep seen/unseen information across sessions (STORE SEEN flag).
+ ///
+ s = 4,
+ ///
+ /// Write (STORE flags other than SEEN and DELETED).
+ ///
+ w = 8,
+ ///
+ /// Insert (perform APPEND, COPY into mailbox).
+ ///
+ i = 16,
+ ///
+ /// Post (send mail to submission address for mailbox,not enforced by IMAP4 itself).
+ ///
+ p = 32,
+ ///
+ /// Create (CREATE new sub-mailboxes in any implementation-defined hierarchy).
+ ///
+ c = 64,
+ ///
+ /// Delete (STORE DELETED flag, perform EXPUNGE).
+ ///
+ d = 128,
+ ///
+ /// Administer (perform SETACL).
+ ///
+ a = 256,
+ ///
+ /// All permissions
+ ///
+ All = 0xFFFF,
+ }
+}
diff --git a/MailServer/IMAP/IMAP_Utils.cs b/MailServer/IMAP/IMAP_Utils.cs
new file mode 100644
index 0000000..1620197
--- /dev/null
+++ b/MailServer/IMAP/IMAP_Utils.cs
@@ -0,0 +1,610 @@
+using System;
+using System.IO;
+using System.Text;
+using MailServer.IMAP.Server;
+
+namespace MailServer.IMAP
+{
+ ///
+ /// Provides utility methods for IMAP.
+ ///
+ public class IMAP_Utils
+ {
+ #region method ParseMessageFlags
+
+ ///
+ /// Parses message flags from string.
+ ///
+ /// Message flags string.
+ ///
+ public static IMAP_MessageFlags ParseMessageFlags(string flagsString)
+ {
+ IMAP_MessageFlags mFlags = 0;
+
+ flagsString = flagsString.ToUpper();
+
+ if (flagsString.IndexOf("ANSWERED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Answered;
+ }
+ if (flagsString.IndexOf("FLAGGED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Flagged;
+ }
+ if (flagsString.IndexOf("DELETED") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Deleted;
+ }
+ if (flagsString.IndexOf("SEEN") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Seen;
+ }
+ if (flagsString.IndexOf("DRAFT") > -1)
+ {
+ mFlags |= IMAP_MessageFlags.Draft;
+ }
+
+ return mFlags;
+ }
+
+ #endregion
+
+ #region method MessageFlagsToString
+
+ ///
+ /// Converts message flags to string. Eg. \SEEN \DELETED .
+ ///
+ ///
+ public static string MessageFlagsToString(IMAP_MessageFlags msgFlags)
+ {
+ string retVal = "";
+ if (((int)IMAP_MessageFlags.Answered & (int)msgFlags) != 0)
+ {
+ retVal += " \\ANSWERED";
+ }
+ if (((int)IMAP_MessageFlags.Flagged & (int)msgFlags) != 0)
+ {
+ retVal += " \\FLAGGED";
+ }
+ if (((int)IMAP_MessageFlags.Deleted & (int)msgFlags) != 0)
+ {
+ retVal += " \\DELETED";
+ }
+ if (((int)IMAP_MessageFlags.Seen & (int)msgFlags) != 0)
+ {
+ retVal += " \\SEEN";
+ }
+ if (((int)IMAP_MessageFlags.Draft & (int)msgFlags) != 0)
+ {
+ retVal += " \\DRAFT";
+ }
+
+ return retVal.Trim();
+ }
+
+ #endregion
+
+
+ #region method ACL_to_String
+
+ ///
+ /// Converts IMAP_ACL_Flags to string.
+ ///
+ /// Flags to convert.
+ ///
+ public static string ACL_to_String(IMAP_ACL_Flags flags)
+ {
+ string retVal = "";
+ if ((flags & IMAP_ACL_Flags.l) != 0)
+ {
+ retVal += "l";
+ }
+ if ((flags & IMAP_ACL_Flags.r) != 0)
+ {
+ retVal += "r";
+ }
+ if ((flags & IMAP_ACL_Flags.s) != 0)
+ {
+ retVal += "s";
+ }
+ if ((flags & IMAP_ACL_Flags.w) != 0)
+ {
+ retVal += "w";
+ }
+ if ((flags & IMAP_ACL_Flags.i) != 0)
+ {
+ retVal += "i";
+ }
+ if ((flags & IMAP_ACL_Flags.p) != 0)
+ {
+ retVal += "p";
+ }
+ if ((flags & IMAP_ACL_Flags.c) != 0)
+ {
+ retVal += "c";
+ }
+ if ((flags & IMAP_ACL_Flags.d) != 0)
+ {
+ retVal += "d";
+ }
+ if ((flags & IMAP_ACL_Flags.a) != 0)
+ {
+ retVal += "a";
+ }
+
+ return retVal;
+ }
+
+ #endregion
+
+ #region method ACL_From_String
+
+ ///
+ /// Parses IMAP_ACL_Flags from string.
+ ///
+ /// String from where to convert
+ ///
+ public static IMAP_ACL_Flags ACL_From_String(string aclString)
+ {
+ IMAP_ACL_Flags retVal = IMAP_ACL_Flags.None;
+ aclString = aclString.ToLower();
+ if (aclString.IndexOf('l') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.l;
+ }
+ if (aclString.IndexOf('r') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.r;
+ }
+ if (aclString.IndexOf('s') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.s;
+ }
+ if (aclString.IndexOf('w') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.w;
+ }
+ if (aclString.IndexOf('i') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.i;
+ }
+ if (aclString.IndexOf('p') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.p;
+ }
+ if (aclString.IndexOf('c') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.c;
+ }
+ if (aclString.IndexOf('d') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.d;
+ }
+ if (aclString.IndexOf('a') > -1)
+ {
+ retVal |= IMAP_ACL_Flags.a;
+ }
+
+ return retVal;
+ }
+
+ #endregion
+
+
+ #region method ParseDate
+
+ ///
+ /// Parses IMAP date time from string.
+ ///
+ /// DateTime string.
+ /// Returns parsed date-time value.
+ /// Is raised when date is null reference.
+ public static DateTime ParseDate(string date)
+ {
+ if (date == null)
+ {
+ throw new ArgumentNullException("date");
+ }
+
+ /* RFC 3501. IMAP date format.
+ date-time = DQUOTE date-day-fixed "-" date-month "-" date-year SP time SP zone DQUOTE
+ date = day-month-year
+ time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+ */
+ if (date.IndexOf('-') > -1)
+ {
+ try
+ {
+ return DateTime.ParseExact(date.Trim(), new string[] { "d-MMM-yyyy", "d-MMM-yyyy HH:mm:ss zzz" }, System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.None);
+ }
+ catch
+ {
+ throw new ArgumentException("Argument 'date' value '" + date + "' is not valid IMAP date.");
+ }
+ }
+ else
+ {
+ return MailServer.Misc.MIME.MIME_Utils.ParseRfc2822DateTime(date);
+ }
+ }
+
+ #endregion
+
+ #region static DateTimeToString
+
+ ///
+ /// Converts date time to IMAP date time string.
+ ///
+ /// DateTime to convert.
+ ///
+ public static string DateTimeToString(DateTime date)
+ {
+ string retVal = "";
+ retVal += date.ToString("dd-MMM-yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
+ retVal += " " + date.ToString("zzz", System.Globalization.CultureInfo.InvariantCulture).Replace(":", "");
+
+ return retVal;
+ }
+
+ #endregion
+
+
+ #region static method Encode_IMAP_UTF7_String
+
+ ///
+ /// Encodes specified data with IMAP modified UTF7 encoding. Defined in RFC 3501 5.1.3. Mailbox International Naming Convention.
+ /// Example: öö is encoded to &APYA9g-.
+ ///
+ /// Text to encode.
+ ///
+ public static string Encode_IMAP_UTF7_String(string text)
+ {
+ /* RFC 3501 5.1.3. Mailbox International Naming Convention
+ In modified UTF-7, printable US-ASCII characters, except for "&",
+ represent themselves; that is, characters with octet values 0x20-0x25
+ and 0x27-0x7e. The character "&" (0x26) is represented by the
+ two-octet sequence "&-".
+
+ All other characters (octet values 0x00-0x1f and 0x7f-0xff) are
+ represented in modified BASE64, with a further modification from
+ [UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be
+ used to represent any printing US-ASCII character which can represent
+ itself.
+
+ "&" is used to shift to modified BASE64 and "-" to shift back to
+ US-ASCII. There is no implicit shift from BASE64 to US-ASCII, and
+ null shifts ("-&" while in BASE64; note that "&-" while in US-ASCII
+ means "&") are not permitted. However, all names start in US-ASCII,
+ and MUST end in US-ASCII; that is, a name that ends with a non-ASCII
+ ISO-10646 character MUST end with a "-").
+ */
+
+ // Base64 chars, except '/' is replaced with ','
+ char[] base64Chars = new char[]{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9','+',','
+ };
+
+ MemoryStream retVal = new MemoryStream();
+ for (int i = 0; i < text.Length; i++)
+ {
+ char c = text[i];
+
+ // The character "&" (0x26) is represented by the two-octet sequence "&-".
+ if (c == '&')
+ {
+ retVal.Write(new byte[] { (byte)'&', (byte)'-' }, 0, 2);
+ }
+ // It is allowed char, don't need to encode
+ else if (c >= 0x20 && c <= 0x25 || c >= 0x27 && c <= 0x7E)
+ {
+ retVal.WriteByte((byte)c);
+ }
+ // Not allowed char, encode it
+ else
+ {
+ // Superfluous shifts are not allowed.
+ // For example: öö may not encoded as &APY-&APY-, but must be &APYA9g-.
+
+ // Get all continuous chars that need encoding and encode them as one block
+ MemoryStream encodeBlock = new MemoryStream();
+ for (int ic = i; ic < text.Length; ic++)
+ {
+ char cC = text[ic];
+
+ // Allowed char
+ if (cC >= 0x20 && cC <= 0x25 || cC >= 0x27 && cC <= 0x7E)
+ {
+ break;
+ }
+ else
+ {
+ encodeBlock.WriteByte((byte)((cC & 0xFF00) >> 8));
+ encodeBlock.WriteByte((byte)(cC & 0xFF));
+ i = ic;
+ }
+ }
+
+ // Ecode block
+ byte[] encodedData = Core.Base64EncodeEx(encodeBlock.ToArray(), base64Chars, false);
+ retVal.WriteByte((byte)'&');
+ retVal.Write(encodedData, 0, encodedData.Length);
+ retVal.WriteByte((byte)'-');
+ }
+ }
+
+ return System.Text.Encoding.Default.GetString(retVal.ToArray());
+ }
+
+ #endregion
+
+ #region static method Decode_IMAP_UTF7_String
+
+ ///
+ /// Decodes IMAP modified UTF7 encoded data. Defined in RFC 3501 5.1.3. Mailbox International Naming Convention.
+ /// Example: &APYA9g- is decoded to öö.
+ ///
+ /// Text to encode.
+ ///
+ public static string Decode_IMAP_UTF7_String(string text)
+ {
+ /* RFC 3501 5.1.3. Mailbox International Naming Convention
+ In modified UTF-7, printable US-ASCII characters, except for "&",
+ represent themselves; that is, characters with octet values 0x20-0x25
+ and 0x27-0x7e. The character "&" (0x26) is represented by the
+ two-octet sequence "&-".
+
+ All other characters (octet values 0x00-0x1f and 0x7f-0xff) are
+ represented in modified BASE64, with a further modification from
+ [UTF-7] that "," is used instead of "/". Modified BASE64 MUST NOT be
+ used to represent any printing US-ASCII character which can represent
+ itself.
+
+ "&" is used to shift to modified BASE64 and "-" to shift back to
+ US-ASCII. There is no implicit shift from BASE64 to US-ASCII, and
+ null shifts ("-&" while in BASE64; note that "&-" while in US-ASCII
+ means "&") are not permitted. However, all names start in US-ASCII,
+ and MUST end in US-ASCII; that is, a name that ends with a non-ASCII
+ ISO-10646 character MUST end with a "-").
+ */
+
+ // Base64 chars, except '/' is replaced with ','
+ char[] base64Chars = new char[]{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9','+',','
+ };
+
+ StringBuilder retVal = new StringBuilder();
+ for (int i = 0; i < text.Length; i++)
+ {
+ char c = text[i];
+
+ // Encoded block or escaped &
+ if (c == '&')
+ {
+ int endingPos = -1;
+ // Read encoded block
+ for (int b = i + 1; b < text.Length; b++)
+ {
+ // - marks block end
+ if (text[b] == '-')
+ {
+ endingPos = b;
+ break;
+ }
+ // Invalid & sequence, just treat it as '&' char and not like shift.
+ // &....&, but must be &....-
+ else if (text[b] == '&')
+ {
+ break;
+ }
+ }
+
+ // If no ending -, invalid encoded block. Treat it like it is
+ if (endingPos == -1)
+ {
+ // Just let main for to handle other chars after &
+ retVal.Append(c);
+ }
+ // If empty block, then escaped &
+ else if (endingPos - i == 1)
+ {
+ retVal.Append(c);
+ // Move i over '-'
+ i++;
+ }
+ // Decode block
+ else
+ {
+ // Get encoded block
+ byte[] encodedBlock = System.Text.Encoding.Default.GetBytes(text.Substring(i + 1, endingPos - i - 1));
+
+ // Convert to UTF-16 char
+ byte[] decodedData = Core.Base64DecodeEx(encodedBlock, base64Chars);
+ char[] decodedChars = new char[decodedData.Length / 2];
+ for (int iC = 0; iC < decodedChars.Length; iC++)
+ {
+ decodedChars[iC] = (char)(decodedData[iC * 2] << 8 | decodedData[(iC * 2) + 1]);
+ }
+
+ // Decode data
+ retVal.Append(decodedChars);
+
+ // Move i over '-'
+ i += encodedBlock.Length + 1;
+ }
+ }
+ // Normal byte
+ else
+ {
+ retVal.Append(c);
+ }
+ }
+
+ return retVal.ToString();
+ }
+
+ #endregion
+
+
+ #region method NormalizeFolder
+
+ ///
+ /// Normalizes folder path. Example: /Inbox/SubFolder/ will be Inbox/SubFolder.
+ ///
+ /// Folder path to normalize.
+ /// Returns normalized folder path.
+ public static string NormalizeFolder(string folder)
+ {
+ folder = folder.Replace("\\", "/");
+ if (folder.StartsWith("/"))
+ {
+ folder = folder.Substring(1);
+ }
+ if (folder.EndsWith("/"))
+ {
+ folder = folder.Substring(0, folder.Length - 1);
+ }
+
+ return folder;
+ }
+
+ #endregion
+
+ #region static method IsValidFolderName
+
+ ///
+ /// Gets if the specified folder name is valid folder name.
+ ///
+ /// Folder name.
+ /// Returns true if specified folde name is valid.
+ public static bool IsValidFolderName(string folder)
+ {
+ // TODO: Path ?
+
+ return true;
+ }
+
+ #endregion
+
+
+ #region method ParseQuotedParam
+
+ ///
+ /// Parses [quoted] parameter from args text. Parameter may be not quoted, then parameter is
+ /// terminated by SP. Example: argsText="string gdkga agkgs";argsText=stringValue 10.
+ ///
+ /// This method also removes parsed parameter from argsText.
+ ///
+ /// Arguments line from where to parse param.
+ ///
+ public static string ParseQuotedParam(ref string argsText)
+ {
+ string paramValue = "";
+
+ // Get value, it is between ""
+ if (argsText.StartsWith("\""))
+ {
+ // Find next " not escaped "
+ char lastChar = ' ';
+ int qIndex = -1;
+ for (int i = 1; i < argsText.Length; i++)
+ {
+ if (argsText[i] == '\"' && lastChar != '\\')
+ {
+ qIndex = i;
+ break;
+ }
+ lastChar = argsText[i];
+ }
+
+ if (qIndex == -1)
+ {
+ throw new Exception("qouted-string doesn't have enclosing quote(\")");
+ }
+
+ paramValue = argsText.Substring(1, qIndex - 1).Replace("\\\"", "\"");
+
+ // Remove value from argsText
+ argsText = argsText.Substring(qIndex + 1).Trim();
+ }
+ else
+ {
+ paramValue = argsText.Split(' ')[0];
+
+ // Remove value from argsText
+ argsText = argsText.Substring(paramValue.Length).Trim();
+ }
+
+ return paramValue;
+ }
+
+ #endregion
+
+ #region method ParseBracketParam
+
+ ///
+ /// Parses bracket parameter from args text. Parameter may be not between (), then
+ /// then args text is considered as value. Example: (test test);test test.
+ ///
+ /// This method also removes parsed parameter from argsText.
+ ///
+ ///
+ ///
+ public static string ParseBracketParam(ref string argsText)
+ {
+ string paramValue = "";
+ if (argsText.StartsWith("("))
+ {
+ // Find matching )
+ char lastChar = ' ';
+ int bIndex = -1;
+ int nestedBracketCount = 0;
+ for (int i = 1; i < argsText.Length; i++)
+ {
+ // There is nested ()
+ if (argsText[i] == '(')
+ {
+ nestedBracketCount++;
+ }
+ else if (argsText[i] == ')')
+ {
+ if (nestedBracketCount == 0)
+ {
+ bIndex = i;
+ break;
+ }
+ // This was nested bracket )
+ else
+ {
+ nestedBracketCount--;
+ }
+ }
+ lastChar = argsText[i];
+ }
+
+ if (bIndex == -1)
+ {
+ throw new Exception("bracket doesn't have enclosing bracket ')'");
+ }
+
+ paramValue = argsText.Substring(1, bIndex - 1);
+
+ // Remove value from argsText
+ argsText = argsText.Substring(bIndex + 1).Trim();
+ }
+ else
+ {
+ paramValue = argsText;
+
+ argsText = "";
+ }
+
+ return paramValue;
+ }
+
+ #endregion
+ }
+}
diff --git a/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs b/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs
deleted file mode 100644
index a1599ee..0000000
--- a/MailServer/IMAP/Server/AuthUser_EventArgs.Properties.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-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
deleted file mode 100644
index d57b921..0000000
--- a/MailServer/IMAP/Server/AuthUser_EventArgs.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-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/AuthUser_EventArgs/AuthUser_EventArgs.cs b/MailServer/IMAP/Server/AuthUser_EventArgs/AuthUser_EventArgs.cs
new file mode 100644
index 0000000..210ed0f
--- /dev/null
+++ b/MailServer/IMAP/Server/AuthUser_EventArgs/AuthUser_EventArgs.cs
@@ -0,0 +1,111 @@
+using System;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// Provides data for the AuthUser event for IMAP_Server.
+ ///
+ 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;
+ private string m_ReturnData = "";
+ private string m_ErrorText = null;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Reference to IMAP session.
+ /// Username.
+ /// Password data.
+ /// Authentication specific data(as tag).
+ /// Authentication type.
+ 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;
+ }
+
+ #region Properties Implementation
+
+ ///
+ /// Gets reference to smtp session.
+ ///
+ public IMAP_Session Session
+ {
+ get { return m_pSession; }
+ }
+
+ ///
+ /// User name.
+ ///
+ public string UserName
+ {
+ get { return m_UserName; }
+ }
+
+ ///
+ /// Password data. eg. for AUTH=PLAIN it's password and for AUTH=APOP it's md5HexHash.
+ ///
+ public string PasswData
+ {
+ get { return m_PasswData; }
+ }
+
+ ///
+ /// Authentication specific data(as tag).
+ ///
+ public string AuthData
+ {
+ get { return m_Data; }
+ }
+
+ ///
+ /// Authentication type.
+ ///
+ public AuthType AuthType
+ {
+ get { return m_AuthType; }
+ }
+
+ ///
+ /// Gets or sets if user is valid.
+ ///
+ public bool Validated
+ {
+ get { return m_Validated; }
+
+ set { m_Validated = value; }
+ }
+
+ ///
+ /// Gets or sets authentication data what must be returned for connected client.
+ ///
+ public string ReturnData
+ {
+ get { return m_ReturnData; }
+
+ set { m_ReturnData = value; }
+ }
+
+ ///
+ /// Gets or sets error text returned to connected client.
+ ///
+ public string ErrorText
+ {
+ get { return m_ErrorText; }
+
+ set { m_ErrorText = value; }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_Message/IMAP_Message.cs b/MailServer/IMAP/Server/IMAP_Message/IMAP_Message.cs
new file mode 100644
index 0000000..f4a8cc6
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_Message/IMAP_Message.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP message info.
+ ///
+ public class IMAP_Message
+ {
+ private IMAP_MessageCollection m_pOwner = null;
+ private string m_ID = "";
+ private long m_UID = 0;
+ private DateTime m_InternalDate = DateTime.Now;
+ private long m_Size = 0;
+ private IMAP_MessageFlags m_Flags = IMAP_MessageFlags.None;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Owner collection.
+ /// Message ID.
+ /// Message IMAP UID value.
+ /// Message store date.
+ /// Message size in bytes.
+ /// Message flags.
+ internal IMAP_Message(IMAP_MessageCollection onwer, string id, long uid, DateTime internalDate, long size, IMAP_MessageFlags flags)
+ {
+ m_pOwner = onwer;
+ m_ID = id;
+ m_UID = uid;
+ m_InternalDate = internalDate;
+ m_Size = size;
+ m_Flags = flags;
+ }
+
+
+ #region method SetFlags
+
+ ///
+ /// Sets message flags.
+ ///
+ /// Message flags.
+ internal void SetFlags(IMAP_MessageFlags flags)
+ {
+ m_Flags = flags;
+ }
+
+ #endregion
+
+
+ #region Properties Implementation
+
+ ///
+ /// Gets message 1 based sequence number in the collection. This property is slow, use with care, never use in big for loops !
+ ///
+ public int SequenceNo
+ {
+ get { return m_pOwner.IndexOf(this) + 1; }
+ }
+
+ ///
+ /// Gets message ID.
+ ///
+ public string ID
+ {
+ get { return m_ID; }
+ }
+
+ ///
+ /// Gets message IMAP UID value.
+ ///
+ public long UID
+ {
+ get { return m_UID; }
+ }
+
+ ///
+ /// Gets message store date.
+ ///
+ public DateTime InternalDate
+ {
+ get { return m_InternalDate; }
+ }
+
+ ///
+ /// Gets message size in bytes.
+ ///
+ public long Size
+ {
+ get { return m_Size; }
+ }
+
+ ///
+ /// Gets message flags.
+ ///
+ public IMAP_MessageFlags Flags
+ {
+ get { return m_Flags; }
+ }
+
+ ///
+ /// Gets message flags string. For example: "\DELETES \SEEN".
+ ///
+ public string FlagsString
+ {
+ get { return IMAP_Utils.MessageFlagsToString(m_Flags); }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_MessageCollection/IMAP_MessageCollection.cs b/MailServer/IMAP/Server/IMAP_MessageCollection/IMAP_MessageCollection.cs
new file mode 100644
index 0000000..990dd57
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_MessageCollection/IMAP_MessageCollection.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP messages info collection.
+ ///
+ public class IMAP_MessageCollection : IEnumerable
+ {
+ private SortedList m_pMessages = null;
+
+ ///
+ /// Default constructor.
+ ///
+ public IMAP_MessageCollection()
+ {
+ m_pMessages = new SortedList();
+ }
+
+
+ #region method Add
+
+ ///
+ /// Adds new message info to the collection.
+ ///
+ /// Message ID.
+ /// Message IMAP UID value.
+ /// Message store date.
+ /// Message size in bytes.
+ /// Message flags.
+ /// Returns added IMAp message info.
+ public IMAP_Message Add(string id, long uid, DateTime internalDate, long size, IMAP_MessageFlags flags)
+ {
+ if (uid < 1)
+ {
+ throw new ArgumentException("Message UID value must be > 0 !");
+ }
+
+ IMAP_Message message = new IMAP_Message(this, id, uid, internalDate, size, flags);
+ m_pMessages.Add(uid, message);
+
+ return message;
+ }
+
+ #endregion
+
+ #region method Remove
+
+ ///
+ /// Removes specified IMAP message from the collection.
+ ///
+ /// IMAP message to remove.
+ public void Remove(IMAP_Message message)
+ {
+ m_pMessages.Remove(message.UID);
+ }
+
+ #endregion
+
+ #region method ContainsUID
+
+ ///
+ /// Gets collection contains specified message with specified UID.
+ ///
+ /// Message UID.
+ ///
+ public bool ContainsUID(long uid)
+ {
+ return m_pMessages.ContainsKey(uid);
+ }
+
+ #endregion
+
+ #region method IndexOf
+
+ ///
+ /// Gets index of specified message in the collection.
+ ///
+ /// Message indesx to get.
+ /// Returns index of specified message in the collection or -1 if message doesn't belong to this collection.
+ public int IndexOf(IMAP_Message message)
+ {
+ return m_pMessages.IndexOfKey(message.UID);
+ }
+
+ #endregion
+
+ #region method Clear
+
+ ///
+ /// Removes all messages from the collection.
+ ///
+ public void Clear()
+ {
+ m_pMessages.Clear();
+ }
+
+ #endregion
+
+ #region method GetWithFlags
+
+ ///
+ /// Gets messages which has specified flags set.
+ ///
+ /// Flags to match.
+ ///
+ public IMAP_Message[] GetWithFlags(IMAP_MessageFlags flags)
+ {
+ List retVal = new List();
+ foreach (IMAP_Message message in m_pMessages.Values)
+ {
+ if ((message.Flags & flags) != 0)
+ {
+ retVal.Add(message);
+ }
+ }
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+
+
+ #region Interface IEnumerator
+
+ ///
+ /// Gets enumerator.
+ ///
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return m_pMessages.Values.GetEnumerator();
+ }
+
+ #endregion
+
+ #region Properties Implementation
+
+ ///
+ /// Gets number of messages in the collection.
+ ///
+ public int Count
+ {
+ get { return m_pMessages.Count; }
+ }
+
+ ///
+ /// Gets a IMAP_Message object in the collection by index number.
+ ///
+ /// An Int32 value that specifies the position of the IMAP_Message object in the IMAP_MessageCollection collection.
+ public IMAP_Message this[int index]
+ {
+ get { return m_pMessages.Values[index]; }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_MessageFlags.cs b/MailServer/IMAP/Server/IMAP_MessageFlags.cs
new file mode 100644
index 0000000..e1a2fc3
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_MessageFlags.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP message flags.
+ ///
+ public enum IMAP_MessageFlags
+ {
+ ///
+ /// No flags defined.
+ ///
+ None = 0,
+
+ ///
+ /// Message has been read.
+ ///
+ Seen = 2,
+
+ ///
+ /// Message has been answered.
+ ///
+ Answered = 4,
+
+ ///
+ /// Message is "flagged" for urgent/special attention.
+ ///
+ Flagged = 8,
+
+ ///
+ /// Message is "deleted" for removal by later EXPUNGE.
+ ///
+ Deleted = 16,
+
+ ///
+ /// Message has not completed composition.
+ ///
+ Draft = 32,
+
+ ///
+ /// Message is "recently" arrived in this mailbox.
+ ///
+ Recent = 64,
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_SelectedFolder/IMAP_SelectedFolder.cs b/MailServer/IMAP/Server/IMAP_SelectedFolder/IMAP_SelectedFolder.cs
new file mode 100644
index 0000000..686308d
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_SelectedFolder/IMAP_SelectedFolder.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// Holds IMAP selected folder info.
+ ///
+ public class IMAP_SelectedFolder
+ {
+ private string m_Folder = "";
+ private long m_FolderUID = 0;
+ private bool m_ReadOnly = false;
+ private IMAP_MessageCollection m_pMessages = null;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Folder name.
+ internal IMAP_SelectedFolder(string folder)
+ {
+ m_Folder = folder;
+ m_pMessages = new IMAP_MessageCollection();
+ }
+
+
+ #region method Update
+
+ ///
+ /// Updates current folder messages info with new messages info.
+ ///
+ ///
+ ///
+ internal string Update(IMAP_SelectedFolder folder)
+ {
+ StringBuilder retVal = new StringBuilder();
+ long maxUID = this.MessageUidNext - 1;
+
+ long countExists = this.Messages.Count;
+ long countRecent = this.RecentCount;
+
+ // Add new messages
+ for (int i = folder.Messages.Count - 1; i > 0; i--)
+ {
+ IMAP_Message message = folder.Messages[i];
+ // New message
+ if (message.UID > maxUID)
+ {
+ m_pMessages.Add(
+ message.ID,
+ message.UID,
+ message.InternalDate,
+ message.Size,
+ message.Flags
+ );
+ }
+ // New messages ended
+ else
+ {
+ break;
+ }
+ }
+
+ // Remove deleted messages
+ for (int i = 0; i < m_pMessages.Count - 1; i++)
+ {
+ IMAP_Message message = m_pMessages[i];
+
+ if (!folder.m_pMessages.ContainsUID(message.UID))
+ {
+ retVal.Append("* " + message.SequenceNo + " EXPUNGE\r\n");
+ m_pMessages.Remove(message);
+ i--;
+ }
+ }
+
+ if (countExists != this.Messages.Count)
+ {
+ retVal.Append("* " + this.Messages.Count + " EXISTS\r\n");
+ }
+ if (countRecent != this.RecentCount)
+ {
+ retVal.Append("* " + this.RecentCount + " RECENT\r\n");
+ }
+
+ return retVal.ToString();
+ }
+
+ #endregion
+
+
+ #region Properties Implementation
+
+ ///
+ /// Gets selected folder name.
+ ///
+ public string Folder
+ {
+ get { return m_Folder; }
+ }
+
+ ///
+ /// Gets folder UID(UIDVADILITY) value.
+ ///
+ public long FolderUID
+ {
+ get { return m_FolderUID; }
+
+ set { m_FolderUID = value; }
+ }
+
+ ///
+ /// Gets or sets if folder is read only.
+ ///
+ public bool ReadOnly
+ {
+ get { return m_ReadOnly; }
+
+ set { m_ReadOnly = value; }
+ }
+
+ ///
+ /// Gets selected folder messages info.
+ ///
+ public IMAP_MessageCollection Messages
+ {
+ get { return m_pMessages; }
+ }
+
+ ///
+ /// Gets number of messages with \UNSEEN flags in the collection.
+ ///
+ public int UnSeenCount
+ {
+ get
+ {
+ int count = 0;
+ foreach (IMAP_Message message in m_pMessages)
+ {
+ if ((message.Flags & IMAP_MessageFlags.Seen) == 0)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+ }
+
+ ///
+ /// Gets number of messages with \RECENT flags in the collection.
+ ///
+ public int RecentCount
+ {
+ get
+ {
+ int count = 0;
+ foreach (IMAP_Message message in m_pMessages)
+ {
+ if ((message.Flags & IMAP_MessageFlags.Recent) != 0)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+ }
+
+ ///
+ /// Gets number of messages with \DELETED flags in the collection.
+ ///
+ public int DeletedCount
+ {
+ get
+ {
+ int count = 0;
+ foreach (IMAP_Message message in m_pMessages)
+ {
+ if ((message.Flags & IMAP_MessageFlags.Deleted) != 0)
+ {
+ count++;
+ }
+ }
+ return count;
+ }
+ }
+
+ ///
+ /// Gets first message index in the collection which has not \SEEN flag set.
+ ///
+ public int FirstUnseen
+ {
+ get
+ {
+ int index = 1;
+ foreach (IMAP_Message message in m_pMessages)
+ {
+ if ((message.Flags & IMAP_MessageFlags.Seen) == 0)
+ {
+ return index;
+ }
+ index++;
+ }
+
+ return 0;
+ }
+ }
+
+ ///
+ /// Gets next new message predicted UID.
+ ///
+ public long MessageUidNext
+ {
+ get
+ {
+ if (m_pMessages.Count > 0)
+ {
+ return m_pMessages[m_pMessages.Count - 1].UID + 1;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/IMAP_Session.cs b/MailServer/IMAP/Server/IMAP_Session.cs
deleted file mode 100644
index 0aee03f..0000000
--- a/MailServer/IMAP/Server/IMAP_Session.cs
+++ /dev/null
@@ -1,3383 +0,0 @@
-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/IMAP_Session/IMAP_Session.cs b/MailServer/IMAP/Server/IMAP_Session/IMAP_Session.cs
new file mode 100644
index 0000000..2b4b518
--- /dev/null
+++ b/MailServer/IMAP/Server/IMAP_Session/IMAP_Session.cs
@@ -0,0 +1,4846 @@
+using System;
+using MailServer.Misc.SocketServer;
+using MailServer.Misc;
+using System.Text;
+using System.IO;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP session.
+ ///
+ public class IMAP_Session : SocketServerSession
+ {
+ #region class Command_IDLE
+
+ ///
+ /// This class implements IDLE command. Defined in RFC 2177.
+ ///
+ private class Command_IDLE
+ {
+ private bool m_IsDisposed = false;
+ private IMAP_Session m_pSession = null;
+ private string m_CmdTag = "";
+ private TimerEx m_pTimer = null;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Owner IMAP session.
+ /// IDLE command command-tag.
+ /// Is raised when session or cmdTag is null reference.
+ /// Is raised when any of the arguments has invalid value.
+ public Command_IDLE(IMAP_Session session, string cmdTag)
+ {
+ if (session == null)
+ {
+ throw new ArgumentNullException("session");
+ }
+ if (cmdTag == null)
+ {
+ throw new ArgumentNullException("cmdTag");
+ }
+ if (cmdTag == string.Empty)
+ {
+ throw new ArgumentException("Argument 'cmdTag' value must be specified.", "cmdTag");
+ }
+
+ m_pSession = session;
+ m_CmdTag = cmdTag;
+
+ Start();
+ }
+
+ #region method Dispose
+
+ ///
+ /// Cleans up any resources being used.
+ ///
+ public void Dispose()
+ {
+ if (m_IsDisposed)
+ {
+ return;
+ }
+ m_IsDisposed = true;
+
+ m_pSession.m_pIDLE = null;
+ m_pSession = null;
+ m_pTimer.Dispose();
+ m_pTimer = null;
+ }
+
+ #endregion
+
+
+ #region Events handling
+
+ #region method m_pTimer_Elapsed
+
+ private void m_pTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+ {
+ // If mailbox changes, report them to connected client.
+ m_pSession.ProcessMailboxChanges();
+ }
+
+ #endregion
+
+ #region method ReadLineCompleted
+
+ private void ReadLineCompleted(SocketCallBackResult result, long count, Exception x, object tag)
+ {
+ // Accoridng RFC, we should get only "DONE" here.
+
+ if (result == SocketCallBackResult.Ok)
+ {
+ if (Encoding.Default.GetString(((MemoryStream)tag).ToArray()).ToUpperInvariant() == "DONE")
+ {
+ // Send "cmd-tag OK IDLE terminated" to connected client.
+ m_pSession.Socket.WriteLine(m_CmdTag + " OK IDLE terminated");
+
+ m_pSession.BeginRecieveCmd();
+
+ Dispose();
+ }
+ // Connected client send illegal stuff us, end session.
+ else
+ {
+ m_pSession.EndSession();
+
+ Dispose();
+ }
+ }
+ // Receive errors, probably TCP connection broken.
+ else
+ {
+ m_pSession.EndSession();
+
+ Dispose();
+ }
+ }
+
+ #endregion
+
+ #endregion
+
+
+ #region method Start
+
+ ///
+ /// Starts IDLE command processing.
+ ///
+ private void Start()
+ {
+ /* RFC 2177 IDLE command example.
+ C: A004 IDLE
+ S: * 2 EXPUNGE
+ S: * 3 EXISTS
+ S: + idling
+ ...time passes; another client expunges message 3...
+ S: * 3 EXPUNGE
+ S: * 2 EXISTS
+ ...time passes; new mail arrives...
+ S: * 3 EXISTS
+ C: DONE
+ S: A004 OK IDLE terminated
+ */
+
+ // Send status reponse to connected client if any.
+ m_pSession.ProcessMailboxChanges();
+
+ // Send "+ idling" to connected client.
+ m_pSession.Socket.WriteLine("+ idling");
+
+ // Start timer to poll mailbox changes.
+ m_pTimer = new TimerEx(60000, true);
+ m_pTimer.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimer_Elapsed);
+ m_pTimer.Enabled = true;
+
+ // Start waiting DONE command from connected client.
+ MemoryStream ms = new MemoryStream();
+ m_pSession.Socket.BeginReadLine(ms, 1024, ms, new SocketCallBack(this.ReadLineCompleted));
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ private bool m_Disposed = false;
+ private Server m_pServer = null;
+ private string m_SelectedMailbox = "";
+ private IMAP_SelectedFolder m_pSelectedFolder = null;
+ private int m_BadCmdCount = 0;
+ private Command_IDLE m_pIDLE = null;
+
+ ///
+ /// Default constructor.
+ ///
+ /// Session ID.
+ /// Server connected socket.
+ /// BindInfo what accepted socket.
+ /// Reference to server.
+ internal IMAP_Session(string sessionID, SocketEx socket, IPBindInfo bindInfo, Server server)
+ : base(sessionID, socket, bindInfo, server)
+ {
+ m_pServer = server;
+
+ // Start session proccessing
+ StartSession();
+ }
+
+
+ #region method StartSession
+
+ ///
+ /// Starts session.
+ ///
+ private void StartSession()
+ {
+ // Add session to session list
+ m_pServer.AddSession(this);
+
+ try
+ {
+ // Check if ip is allowed to connect this computer
+ if (m_pServer.OnValidate_IpAddress(this.LocalEndPoint, this.RemoteEndPoint))
+ {
+ //--- Dedicated SSL connection, switch to SSL -----------------------------------//
+ if (this.BindInfo.SslMode == SslMode.SSL)
+ {
+ try
+ {
+ this.Socket.SwitchToSSL(this.BindInfo.Certificate);
+
+ if (this.Socket.Logger != null)
+ {
+ this.Socket.Logger.AddTextEntry("SSL negotiation completed successfully.");
+ }
+ }
+ catch (Exception x)
+ {
+ if (this.Socket.Logger != null)
+ {
+ this.Socket.Logger.AddTextEntry("SSL handshake failed ! " + x.Message);
+
+ EndSession();
+ return;
+ }
+ }
+ }
+ //-------------------------------------------------------------------------------//
+
+ // Notify that server is ready
+ if (m_pServer.GreetingText == "")
+ {
+ this.Socket.WriteLine("* OK " + Net_Utils.GetLocalHostName(this.BindInfo.HostName) + " IMAP Server ready");
+ }
+ else
+ {
+ this.Socket.WriteLine("* OK " + m_pServer.GreetingText);
+ }
+
+ BeginRecieveCmd();
+ }
+ else
+ {
+ EndSession();
+ }
+ }
+ catch (Exception x)
+ {
+ OnError(x);
+ }
+ }
+
+ #endregion
+
+ #region method EndSession
+
+ ///
+ /// Ends session, closes socket.
+ ///
+ private void EndSession()
+ {
+ lock (this)
+ {
+ // We have ended this sessions already, probably some async opration ended and called this, skip it.
+ if (m_Disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ m_Disposed = true;
+
+ // Write logs to log file, if needed
+ if (m_pServer.LogCommands)
+ {
+ this.Socket.Logger.Flush();
+ }
+
+ if (this.Socket != null)
+ {
+ this.Socket.Shutdown(SocketShutdown.Both);
+ this.Socket.Disconnect();
+ }
+ }
+ catch
+ { // We don't need to check errors here, because they only may be Socket closing errors.
+ }
+ finally
+ {
+ m_pServer.RemoveSession(this);
+ }
+ }
+ }
+
+ #endregion
+
+
+ #region method Kill
+
+ ///
+ /// Kill this session.
+ ///
+ public override void Kill()
+ {
+ EndSession();
+ }
+
+ #endregion
+
+ #region method OnSessionTimeout
+
+ ///
+ /// Is called by server when session has timed out.
+ ///
+ internal protected override void OnSessionTimeout()
+ {
+ try
+ {
+ this.Socket.WriteLine("* BYE Session timeout, closing transmission channel");
+ }
+ catch
+ {
+ }
+
+ EndSession();
+ }
+
+ #endregion
+
+ #region method OnError
+
+ ///
+ /// Is called when error occures.
+ ///
+ ///
+ private void OnError(Exception x)
+ {
+ // Session disposed, so we don't care about terminating errors any more.
+ if (m_Disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ // We must see InnerException too, SocketException may be as inner exception.
+ SocketException socketException = null;
+ if (x is SocketException)
+ {
+ socketException = (SocketException)x;
+ }
+ else if (x.InnerException != null && x.InnerException is SocketException)
+ {
+ socketException = (SocketException)x.InnerException;
+ }
+
+ if (socketException != null)
+ {
+ // Client disconnected without shutting down
+ if (socketException.ErrorCode == 10054 || socketException.ErrorCode == 10053)
+ {
+ if (m_pServer.LogCommands)
+ {
+ this.Socket.Logger.AddTextEntry("Client aborted/disconnected");
+ }
+
+ EndSession();
+
+ // Exception handled, return
+ return;
+ }
+ // Read/Write timed-out
+ else if (socketException.ErrorCode == 10060)
+ {
+ if (m_pServer.LogCommands)
+ {
+ this.Socket.Logger.AddTextEntry("Read or write to destination host timed-out !");
+ }
+
+ // Exception handled, return
+ return;
+ }
+ }
+
+ m_pServer.OnSysError("", x);
+ }
+ catch (Exception ex)
+ {
+ m_pServer.OnSysError("", ex);
+ }
+ }
+
+ #endregion
+
+
+ #region method BeginRecieveCmd
+
+ ///
+ /// Starts recieveing command.
+ ///
+ private void BeginRecieveCmd()
+ {
+ MemoryStream strm = new MemoryStream();
+ this.Socket.BeginReadLine(strm, 1024, strm, new SocketCallBack(this.EndRecieveCmd));
+ }
+
+ #endregion
+
+ #region method EndRecieveCmd
+
+ ///
+ /// Is called if command is recieved.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void EndRecieveCmd(SocketCallBackResult result, long count, Exception exception, object tag)
+ {
+ try
+ {
+ switch (result)
+ {
+ case SocketCallBackResult.Ok:
+ MemoryStream strm = (MemoryStream)tag;
+
+ string cmdLine = System.Text.Encoding.Default.GetString(strm.ToArray());
+
+ // Exceute command
+ if (SwitchCommand(cmdLine))
+ {
+ // Session end, close session
+ EndSession();
+ }
+ break;
+
+ case SocketCallBackResult.LengthExceeded:
+ this.Socket.WriteLine("* BAD Line too long.");
+
+ BeginRecieveCmd();
+ break;
+
+ case SocketCallBackResult.SocketClosed:
+ EndSession();
+ break;
+
+ case SocketCallBackResult.Exception:
+ OnError(exception);
+ break;
+ }
+ }
+ catch (Exception x)
+ {
+ OnError(x);
+ }
+ }
+
+ #endregion
+
+
+ #region method 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 = IMAP_commandTxt.Substring((cmdParts[0] + " " + cmdParts[1]).Length).Trim();
+ //---------------------------------------------------------------------//
+
+ bool getNextCmd = true;
+
+ switch (command)
+ {
+ //--- Non-Authenticated State
+ case "STARTTLS":
+ STARTTLS(commandTag, argsText);
+ break;
+
+ 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":
+ getNextCmd = BeginAppendCmd(commandTag, argsText);
+ break;
+
+ case "NAMESPACE":
+ Namespace(commandTag, argsText);
+ break;
+
+ case "GETACL":
+ GETACL(commandTag, argsText);
+ break;
+
+ case "SETACL":
+ SETACL(commandTag, argsText);
+ break;
+
+ case "DELETEACL":
+ DELETEACL(commandTag, argsText);
+ break;
+
+ case "LISTRIGHTS":
+ LISTRIGHTS(commandTag, argsText);
+ break;
+
+ case "MYRIGHTS":
+ MYRIGHTS(commandTag, argsText);
+ break;
+
+ case "GETQUOTA":
+ GETQUOTA(commandTag, argsText);
+ break;
+
+ case "GETQUOTAROOT":
+ GETQUOTAROOT(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;
+
+ case "IDLE":
+ getNextCmd = !Idle(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:
+ this.Socket.WriteLine(commandTag + " BAD command unrecognized");
+
+ //---- Check that maximum bad commands count isn't exceeded ---------------//
+ if (m_BadCmdCount > m_pServer.MaxBadCommands - 1)
+ {
+ this.Socket.WriteLine("* BAD Too many bad commands, closing transmission channel");
+ return true;
+ }
+ m_BadCmdCount++;
+ //-------------------------------------------------------------------------//
+ break;
+ }
+
+ if (getNextCmd)
+ {
+ BeginRecieveCmd();
+ }
+
+ return false;
+ }
+
+ #endregion
+
+
+ //--- Non-Authenticated State ------
+
+ #region method STARTTLS
+
+ private void STARTTLS(string cmdTag, string argsText)
+ {
+ /* RFC 2595 3. IMAP STARTTLS extension.
+ 3. IMAP STARTTLS extension
+
+ When the TLS extension is present in IMAP, "STARTTLS" is listed as a
+ capability in response to the CAPABILITY command. This extension
+ adds a single command, "STARTTLS" to the IMAP protocol which is used
+ to begin a TLS negotiation.
+
+ 3.1. STARTTLS Command
+
+ Arguments: none
+
+ Responses: no specific responses for this command
+
+ Result: OK - begin TLS negotiation
+ BAD - command unknown or arguments invalid
+
+ A TLS negotiation begins immediately after the CRLF at the end of
+ the tagged OK response from the server. Once a client issues a
+ STARTTLS command, it MUST NOT issue further commands until a
+ server response is seen and the TLS negotiation is complete.
+
+ The STARTTLS command is only valid in non-authenticated state.
+ The server remains in non-authenticated state, even if client
+ credentials are supplied during the TLS negotiation. The SASL
+ [SASL] EXTERNAL mechanism MAY be used to authenticate once TLS
+ client credentials are successfully exchanged, but servers
+ supporting the STARTTLS command are not required to support the
+ EXTERNAL mechanism.
+
+ Once TLS has been started, the client MUST discard cached
+ information about server capabilities and SHOULD re-issue the
+ CAPABILITY command. This is necessary to protect against
+ man-in-the-middle attacks which alter the capabilities list prior
+ to STARTTLS. The server MAY advertise different capabilities
+ after STARTTLS.
+
+ The formal syntax for IMAP is amended as follows:
+
+ command_any =/ "STARTTLS"
+
+ Example: C: a001 CAPABILITY
+ S: * CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED
+ S: a001 OK CAPABILITY completed
+ C: a002 STARTTLS
+ S: a002 OK Begin TLS negotiation now
+
+ C: a003 CAPABILITY
+ S: * CAPABILITY IMAP4rev1 AUTH=EXTERNAL
+ S: a003 OK CAPABILITY completed
+ C: a004 LOGIN joe password
+ S: a004 OK LOGIN completed
+ */
+
+ if (this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO The STARTTLS command is only valid in non-authenticated state !");
+ return;
+ }
+ if (this.Socket.SSL)
+ {
+ this.Socket.WriteLine(cmdTag + " NO The STARTTLS already started !");
+ return;
+ }
+ if (this.BindInfo.Certificate == null)
+ {
+ this.Socket.WriteLine(cmdTag + " NO TLS not available, SSL certificate isn't specified !");
+ return;
+ }
+
+
+ this.Socket.WriteLine(cmdTag + " OK Ready to start TLS");
+
+ try
+ {
+ this.Socket.SwitchToSSL(this.BindInfo.Certificate);
+
+ if (m_pServer.LogCommands)
+ {
+ this.Socket.Logger.AddTextEntry("TLS negotiation completed successfully.");
+ }
+ }
+ catch (Exception x)
+ {
+ this.Socket.WriteLine(cmdTag + " NO TLS handshake failed ! " + x.Message);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO AUTH you are already logged in");
+ return;
+ }
+
+ string userName = "";
+ // string password = "";
+ AuthUser_EventArgs aArgs = null;
+
+ 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() + ">";
+ this.Socket.WriteLine("+ " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(md5Hash)));
+
+ string reply = this.Socket.ReadLine();
+ reply = System.Text.Encoding.Default.GetString(Convert.FromBase64String(reply));
+ string[] replyArgs = reply.Split(' ');
+ userName = replyArgs[0];
+
+ aArgs = m_pServer.OnAuthUser(this, userName, replyArgs[1], md5Hash, AuthType.CRAM_MD5);
+
+ // There is custom error, return it
+ if (aArgs.ErrorText != null)
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
+ return;
+ }
+
+ if (aArgs.Validated)
+ {
+ this.Socket.WriteLine(cmdTag + " OK Authentication successful.");
+
+ this.SetUserName(userName);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authentication failed");
+ }
+
+ #endregion
+
+ break;
+
+ case "DIGEST-MD5":
+
+ #region DIGEST-MD5 authentication
+
+ /* RFC 2831 AUTH DIGEST-MD5
+ *
+ * Example:
+ *
+ * C: AUTH DIGEST-MD5
+ * S: + base64(realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",algorithm=md5-sess)
+ * C: base64(username="chris",realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",
+ * nc=00000001,cnonce="OA6MHXh6VqTrRk",digest-uri="imap/elwood.innosoft.com",
+ * response=d388dad90d4bbd760a152321f2143af7,qop=auth)
+ * S: + base64(rspauth=ea40f60335c427b5527b84dbabcdfffd)
+ * C:
+ * S: A0001 OK Authentication successful.
+ */
+
+ string realm = this.BindInfo.HostName;
+ string nonce = AuthHelper.GenerateNonce();
+
+ this.Socket.WriteLine("+ " + AuthHelper.Base64en(AuthHelper.Create_Digest_Md5_ServerResponse(realm, nonce)));
+
+ string clientResponse = AuthHelper.Base64de(this.Socket.ReadLine());
+ // Check that realm and nonce in client response are same as we specified
+ if (clientResponse.IndexOf("realm=\"" + realm + "\"") > -1 && clientResponse.IndexOf("nonce=\"" + nonce + "\"") > -1)
+ {
+ // Parse user name and password compare value
+ // string userName = "";
+ string passwData = "";
+ string cnonce = "";
+ foreach (string clntRespParam in clientResponse.Split(','))
+ {
+ if (clntRespParam.StartsWith("username="))
+ {
+ userName = clntRespParam.Split(new char[] { '=' }, 2)[1].Replace("\"", "");
+ }
+ else if (clntRespParam.StartsWith("response="))
+ {
+ passwData = clntRespParam.Split(new char[] { '=' }, 2)[1];
+ }
+ else if (clntRespParam.StartsWith("cnonce="))
+ {
+ cnonce = clntRespParam.Split(new char[] { '=' }, 2)[1].Replace("\"", "");
+ }
+ }
+
+ aArgs = m_pServer.OnAuthUser(this, userName, passwData, clientResponse, AuthType.DIGEST_MD5);
+
+ // There is custom error, return it
+ if (aArgs.ErrorText != null)
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
+ return;
+ }
+
+ if (aArgs.Validated)
+ {
+ // Send server computed password hash
+ this.Socket.WriteLine("+ " + AuthHelper.Base64en("rspauth=" + aArgs.ReturnData));
+
+ // We must got empty line here
+ clientResponse = this.Socket.ReadLine();
+ if (clientResponse == "")
+ {
+ this.Socket.WriteLine(cmdTag + " OK Authentication successful.");
+
+ this.SetUserName(userName);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authentication failed");
+ }
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authentication failed");
+ }
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authentication failed");
+ }
+
+ #endregion
+
+ break;
+
+ default:
+ this.Socket.WriteLine(cmdTag + " NO unsupported authentication mechanism");
+ break;
+ }
+
+ }
+
+ #endregion
+
+ #region method 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
+
+ //----
+ C: a001 LOGIN "SMITH" "SESAME"
+ S: a001 OK LOGIN completed
+
+ */
+ if (this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Reauthentication error, you are already logged in");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid arguments, syntax: { LOGIN \"\" \"\"}");
+ return;
+ }
+
+ string userName = args[0];
+ string password = args[1];
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ AuthUser_EventArgs aArgs = m_pServer.OnAuthUser(this, userName, password, "", AuthType.Plain);
+ // There is custom error, return it
+ if (aArgs.ErrorText != null)
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + aArgs.ErrorText);
+ return;
+ }
+
+ if (aArgs.Validated)
+ {
+ this.Socket.WriteLine(cmdTag + " OK LOGIN Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
+
+ this.SetUserName(userName);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO LOGIN failed");
+ }
+ }
+
+ #endregion
+
+ //--- End of non-Authenticated State
+
+
+ //--- Authenticated State ------
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD SELECT invalid arguments. Syntax: { SELECT \"mailboxName\"}");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this, selectedFolder);
+ if (eArgs.ErrorText == null)
+ {
+ m_pSelectedFolder = selectedFolder;
+ m_SelectedMailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[0]);
+
+ string response = "";
+ response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n";
+ response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n";
+ response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n";
+ response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n";
+ response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] Folder UID\r\n";
+ response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next message UID\r\n";
+ response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent flags\r\n";
+ response += cmdTag + " OK [" + (m_pSelectedFolder.ReadOnly ? "READ-ONLY" : "READ-WRITE") + "] SELECT Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
+
+ this.Socket.Write(response);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD EXAMINE invalid arguments. Syntax: { EXAMINE \"mailboxName\"}");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this, selectedFolder);
+ if (eArgs.ErrorText == null)
+ {
+ m_pSelectedFolder = selectedFolder;
+ m_pSelectedFolder.ReadOnly = true;
+ m_SelectedMailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[0]);
+
+ string response = "";
+ response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n";
+ response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n";
+ response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n";
+ response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n";
+ response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] UIDs valid\r\n";
+ response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next UID\r\n";
+ response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent falgs\r\n";
+ response += cmdTag + " OK [READ-ONLY] EXAMINE Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
+
+ this.Socket.Write(response);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD CREATE invalid arguments. Syntax: { CREATE \"mailboxName\"}");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ string errorText = m_pServer.OnCreateMailbox(this, IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK CREATE Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD DELETE invalid arguments. Syntax: { DELETE \"mailboxName\"}");
+ return;
+ }
+
+ string errorText = m_pServer.OnDeleteMailbox(this, IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK DELETE Completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD RENAME invalid arguments. Syntax: { RENAME \"mailboxName\" \"newMailboxName\"}");
+ return;
+ }
+
+ string mailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[0]);
+ string newMailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[1]);
+
+ string errorText = m_pServer.OnRenameMailbox(this, mailbox, newMailbox);
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK RENAME Completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD SUBSCRIBE invalid arguments. Syntax: { SUBSCRIBE \"mailboxName\"}");
+ return;
+ }
+
+ string errorText = m_pServer.OnSubscribeMailbox(this, IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK SUBSCRIBE completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region methd 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD UNSUBSCRIBE invalid arguments. Syntax: { UNSUBSCRIBE \"mailboxName\"}");
+ return;
+ }
+
+ string errorText = m_pServer.OnUnSubscribeMailbox(this, IMAP_Utils.Decode_IMAP_UTF7_String(args[0]));
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK UNSUBSCRIBE completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid LIST arguments. Syntax: { LIST \"\" \"\"}");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ string refName = IMAP_Utils.Decode_IMAP_UTF7_String(args[0]);
+ string mailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[1]);
+
+ string reply = "";
+
+ // Folder separator wanted
+ if (mailbox.Length == 0)
+ {
+ reply += "* LIST (\\Noselect) \"/\" \"\"\r\n";
+ }
+ // List mailboxes
+ else
+ {
+ IMAP_Folders mailboxes = m_pServer.OnGetMailboxes(this, refName, mailbox);
+ foreach (IMAP_Folder mBox in mailboxes.Folders)
+ {
+ if (mBox.Selectable)
+ {
+ reply += "* LIST () \"/\" \"" + IMAP_Utils.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
+ }
+ else
+ {
+ reply += "* LIST (\\Noselect) \"/\" \"" + IMAP_Utils.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
+ }
+ }
+ }
+
+ reply += cmdTag + " OK LIST Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD LSUB invalid arguments");
+ return;
+ }
+
+ string refName = IMAP_Utils.Decode_IMAP_UTF7_String(args[0]);
+ string mailbox = IMAP_Utils.Decode_IMAP_UTF7_String(args[1]);
+
+ string reply = "";
+
+ IMAP_Folders mailboxes = m_pServer.OnGetSubscribedMailboxes(this, refName, mailbox);
+ foreach (IMAP_Folder mBox in mailboxes.Folders)
+ {
+ reply += "* LSUB () \"/\" \"" + IMAP_Utils.Encode_IMAP_UTF7_String(mBox.Folder) + "\" \r\n";
+ }
+
+ reply += cmdTag + " OK LSUB Completed\r\n";
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid STATUS arguments. Syntax: { STATUS \"\" \"(status-data-items)\"}");
+ return;
+ }
+
+ string folder = IMAP_Utils.Decode_IMAP_UTF7_String(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)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD STATUS invalid arguments");
+ return;
+ }
+
+ IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(folder);
+ IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this, selectedFolder);
+ if (eArgs.ErrorText == null)
+ {
+ string itemsReply = "";
+ if (wantedItems.IndexOf("MESSAGES") > -1)
+ {
+ itemsReply += " MESSAGES " + selectedFolder.Messages.Count;
+ }
+ if (wantedItems.IndexOf("RECENT") > -1)
+ {
+ itemsReply += " RECENT " + selectedFolder.RecentCount;
+ }
+ if (wantedItems.IndexOf("UNSEEN") > -1)
+ {
+ itemsReply += " UNSEEN " + selectedFolder.UnSeenCount;
+ }
+ if (wantedItems.IndexOf("UIDVALIDITY") > -1)
+ {
+ itemsReply += " UIDVALIDITY " + selectedFolder.FolderUID;
+ }
+ if (wantedItems.IndexOf("UIDNEXT") > -1)
+ {
+ itemsReply += " UIDNEXT " + selectedFolder.MessageUidNext;
+ }
+ itemsReply = itemsReply.Trim();
+
+ this.Socket.WriteLine("* STATUS " + args[0] + " (" + itemsReply + ")");
+ this.Socket.WriteLine(cmdTag + " OK STATUS completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText);
+ }
+ }
+
+ #endregion
+
+ #region command Append
+
+ #region method BeginAppendCmd
+
+ ///
+ /// Returns true if command ended syncronously.
+ ///
+ private bool BeginAppendCmd(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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return true;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length < 2 || args.Length > 4)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD APPEND Invalid arguments");
+ return true;
+ }
+
+ string mailbox = IMAP_Utils.Decode_IMAP_UTF7_String(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)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
+ return false;
+ }
+
+ mFlags = IMAP_Utils.ParseMessageFlags(flags);
+ date = IMAP_Utils.ParseDate(args[2]);
+ }
+ else if (args.Length == 3)
+ {
+ // See if date or flags specified, try date first
+ try
+ {
+ date = IMAP_Utils.ParseDate(args[1]);
+ }
+ 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)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
+ return false;
+ }
+
+ mFlags = IMAP_Utils.ParseMessageFlags(flags);
+ }
+ }
+
+ // Request data
+ this.Socket.WriteLine("+ Ready for literal data");
+
+ MemoryStream strm = new MemoryStream();
+ Hashtable param = new Hashtable();
+ param.Add("cmdTag", cmdTag);
+ param.Add("mailbox", mailbox);
+ param.Add("mFlags", mFlags);
+ param.Add("date", date);
+ param.Add("strm", strm);
+
+ // Begin recieving data Why needed msgLen+2 ???
+ this.Socket.BeginReadSpecifiedLength(strm, (int)msgLen + 2, param, new SocketCallBack(this.EndAppendCmd));
+
+ return false;
+ }
+
+ #endregion
+
+ #region method EndAppendCmd
+
+ ///
+ /// Is called when DATA command is finnished.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void EndAppendCmd(SocketCallBackResult result, long count, Exception exception, object tag)
+ {
+ try
+ {
+ switch (result)
+ {
+ case SocketCallBackResult.Ok:
+ Hashtable param = (Hashtable)tag;
+ string cmdTag = (string)param["cmdTag"];
+ string mailbox = (string)param["mailbox"];
+ IMAP_MessageFlags mFlags = (IMAP_MessageFlags)param["mFlags"];
+ DateTime date = (DateTime)param["date"];
+ MemoryStream strm = (MemoryStream)param["strm"];
+
+ IMAP_Message msg = new IMAP_Message(null, "", 0, date, 0, mFlags);
+ string errotText = m_pServer.OnStoreMessage(this, mailbox, msg, strm.ToArray());
+ if (errotText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK APPEND completed, recieved " + strm.Length + " bytes");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errotText);
+ }
+ break;
+
+ case SocketCallBackResult.LengthExceeded:
+ // SendData("552 Requested mail action aborted: exceeded storage allocation\r\n");
+
+ // BeginRecieveCmd();
+ break;
+
+ case SocketCallBackResult.SocketClosed:
+ EndSession();
+ return;
+
+ case SocketCallBackResult.Exception:
+ OnError(exception);
+ return;
+ }
+
+ // Command completed ok, get next command
+ BeginRecieveCmd();
+ }
+ catch (Exception x)
+ {
+ OnError(x);
+ }
+ }
+
+ #endregion
+
+ #endregion
+
+ #region method Namespace
+
+ private void Namespace(string cmdTag, string argsText)
+ {
+ /* Rfc 2342 5. NAMESPACE Command.
+
+ Arguments: none
+
+ Response: an untagged NAMESPACE response that contains the prefix
+ and hierarchy delimiter to the server's Personal
+ Namespace(s), Other Users' Namespace(s), and Shared
+ Namespace(s) that the server wishes to expose. The
+ response will contain a NIL for any namespace class
+ that is not available. Namespace_Response_Extensions
+ MAY be included in the response.
+ Namespace_Response_Extensions which are not on the IETF
+ standards track, MUST be prefixed with an "X-".
+
+ Result: OK - Command completed
+ NO - Error: Can't complete command
+ BAD - argument invalid
+
+ Example: < A server that contains a Personal Namespace and a single Shared
+ Namespace. >
+
+ C: A001 NAMESPACE
+ S: * NAMESPACE (("" "/")) NIL (("Public Folders/" "/"))
+ S: A001 OK NAMESPACE command completed
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ SharedRootFolders_EventArgs eArgs = m_pServer.OnGetSharedRootFolders(this);
+ string publicRootFolders = "NIL";
+ if (eArgs.PublicRootFolders != null && eArgs.PublicRootFolders.Length > 0)
+ {
+ publicRootFolders = "(";
+ foreach (string publicRootFolder in eArgs.PublicRootFolders)
+ {
+ publicRootFolders += "(\"" + publicRootFolder + "/\" \"/\")";
+ }
+ publicRootFolders += ")";
+ }
+ string sharedRootFolders = "NIL";
+ if (eArgs.SharedRootFolders != null && eArgs.SharedRootFolders.Length > 0)
+ {
+ sharedRootFolders = "(";
+ foreach (string sharedRootFolder in eArgs.SharedRootFolders)
+ {
+ sharedRootFolders += "(\"" + sharedRootFolder + "/\" \"/\")";
+ }
+ sharedRootFolders += ")";
+ }
+
+ string response = "* NAMESPACE ((\"\" \"/\")) " + sharedRootFolders + " " + publicRootFolders + "\r\n";
+ response += cmdTag + " OK NAMESPACE completed";
+
+ this.Socket.WriteLine(response);
+ }
+
+ #endregion
+
+
+ #region method GETACL
+
+ private void GETACL(string cmdTag, string argsText)
+ {
+ /* RFC 2086 4.3. GETACL
+ Arguments: mailbox name
+
+ Data: untagged responses: ACL
+
+ Result: OK - getacl completed
+ NO - getacl failure: can't get acl
+ BAD - command unknown or arguments invalid
+
+ The GETACL command returns the access control list for mailbox in
+ an untagged ACL reply.
+
+ Example: C: A002 GETACL INBOX
+ S: * ACL INBOX Fred rwipslda
+ S: A002 OK Getacl complete
+
+ .... Multiple users
+ S: * ACL INBOX Fred rwipslda test rwipslda
+
+ .... No acl flags for Fred
+ S: * ACL INBOX Fred "" test rwipslda
+
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: GETACLFolderName");
+ return;
+ }
+
+ IMAP_GETACL_eArgs eArgs = m_pServer.OnGetFolderACL(this, IMAP_Utils.Decode_IMAP_UTF7_String(IMAP_Utils.NormalizeFolder(args[0])));
+ if (eArgs.ErrorText.Length > 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO GETACL " + eArgs.ErrorText);
+ }
+ else
+ {
+ string response = "";
+ if (eArgs.ACL.Count > 0)
+ {
+ response += "* ACL \"" + args[0] + "\"";
+ foreach (DictionaryEntry ent in eArgs.ACL)
+ {
+ string aclFalgs = IMAP_Utils.ACL_to_String((IMAP_ACL_Flags)ent.Value);
+ if (aclFalgs.Length == 0)
+ {
+ aclFalgs = "\"\"";
+ }
+ response += " \"" + ent.Key + "\" " + aclFalgs;
+ }
+ response += "\r\n";
+ }
+ response += cmdTag + " OK Getacl complete\r\n";
+
+ this.Socket.Write(response);
+ }
+ }
+
+ #endregion
+
+ #region method SETACL
+
+ private void SETACL(string cmdTag, string argsText)
+ {
+ /* RFC 2086 4.1. SETACL
+ Arguments: mailbox name
+ authentication identifier
+ access right modification
+
+ Data: no specific data for this command
+
+ Result: OK - setacl completed
+ NO - setacl failure: can't set acl
+ BAD - command unknown or arguments invalid
+
+ The SETACL command changes the access control list on the
+ specified mailbox so that the specified identifier is granted
+ permissions as specified in the third argument.
+
+ The third argument is a string containing an optional plus ("+")
+ or minus ("-") prefix, followed by zero or more rights characters.
+ If the string starts with a plus, the following rights are added
+ to any existing rights for the identifier. If the string starts
+ with a minus, the following rights are removed from any existing
+ rights for the identifier. If the string does not start with a
+ plus or minus, the rights replace any existing rights for the
+ identifier.
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 3)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: SETACLFolderNameUserNameACL_Flags");
+ return;
+ }
+
+ string aclFlags = args[2];
+ IMAP_Flags_SetType setType = IMAP_Flags_SetType.Replace;
+ if (aclFlags.StartsWith("+"))
+ {
+ setType = IMAP_Flags_SetType.Add;
+ }
+ else if (aclFlags.StartsWith("-"))
+ {
+ setType = IMAP_Flags_SetType.Remove;
+ }
+
+ IMAP_SETACL_eArgs eArgs = m_pServer.OnSetFolderACL(this, IMAP_Utils.NormalizeFolder(args[0]), args[1], setType, IMAP_Utils.ACL_From_String(aclFlags));
+ if (eArgs.ErrorText.Length > 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO SETACL " + eArgs.ErrorText);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " OK SETACL completed");
+ }
+ }
+
+ #endregion
+
+ #region method DELETEACL
+
+ private void DELETEACL(string cmdTag, string argsText)
+ {
+ /* RFC 2086 4.2. DELETEACL
+ Arguments: mailbox name
+ authentication identifier
+
+ Data: no specific data for this command
+
+ Result: OK - deleteacl completed
+ NO - deleteacl failure: can't delete acl
+ BAD - command unknown or arguments invalid
+
+ The DELETEACL command removes any pair for the
+ specified identifier from the access control list for the specified
+ mailbox.
+
+ Example: C: A002 DELETEACL INBOX test
+ S: A002 OK DELETEACL completed
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: DELETEACLFolderNameUserName");
+ return;
+ }
+
+ IMAP_DELETEACL_eArgs eArgs = m_pServer.OnDeleteFolderACL(this, IMAP_Utils.NormalizeFolder(args[0]), args[1]);
+ if (eArgs.ErrorText.Length > 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO DELETEACL " + eArgs.ErrorText);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " OK DELETEACL completed");
+ }
+ }
+
+ #endregion
+
+ #region method LISTRIGHTS
+
+ private void LISTRIGHTS(string cmdTag, string argsText)
+ {
+ /* RFC 2086 4.4. LISTRIGHTS
+ Arguments: mailbox name
+ authentication identifier
+
+ Data: untagged responses: LISTRIGHTS
+
+ Result: OK - listrights completed
+ NO - listrights failure: can't get rights list
+ BAD - command unknown or arguments invalid
+
+ The LISTRIGHTS command takes a mailbox name and an identifier and
+ returns information about what rights may be granted to the identifier
+ in the ACL for the mailbox.
+
+ Example: C: a001 LISTRIGHTS ~/Mail/saved smith
+ S: * LISTRIGHTS ~/Mail/saved "smith" la r swicd
+ S: a001 OK Listrights completed
+
+
+ C: a005 LISTRIGHTS archive.imap anyone
+ S: * LISTRIGHTS archive.imap "anyone" "" l r s w i p c d a
+ 0 1 2 3 4 5 6 7 8 9
+
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: LISTRIGHTSFolderNameUserName");
+ return;
+ }
+
+ string response = "* LISTRIGHTS " + "\"" + args[0] + "\" \"" + args[1] + "\" l r s w i p c d a\r\n";
+ response += cmdTag + " OK MYRIGHTS Completed\r\n";
+
+ this.Socket.Write(response);
+ }
+
+ #endregion
+
+ #region method MYRIGHTS
+
+ private void MYRIGHTS(string cmdTag, string argsText)
+ {
+ /* RFC 2086 4.5. MYRIGHTS
+ Arguments: mailbox name
+
+ Data: untagged responses: MYRIGHTS
+
+ Result: OK - myrights completed
+ NO - myrights failure: can't get rights
+ BAD - command unknown or arguments invalid
+
+ The MYRIGHTS command returns the set of rights that the user has
+ to mailbox in an untagged MYRIGHTS reply.
+
+ Example: C: A003 MYRIGHTS INBOX
+ S: * MYRIGHTS INBOX rwipslda
+ S: A003 OK Myrights complete
+
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETACL invalid arguments. Syntax: MYRIGHTSFolderName");
+ return;
+ }
+
+ IMAP_GetUserACL_eArgs eArgs = m_pServer.OnGetUserACL(this, IMAP_Utils.NormalizeFolder(args[0]), this.UserName);
+ if (eArgs.ErrorText.Length > 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO MYRIGHTS " + eArgs.ErrorText);
+ }
+ else
+ {
+ string aclFlags = IMAP_Utils.ACL_to_String(eArgs.ACL);
+ if (aclFlags.Length == 0)
+ {
+ aclFlags = "\"\"";
+ }
+ string response = "* MYRIGHTS " + "\"" + args[0] + "\" " + aclFlags + "\r\n";
+ response += cmdTag + " OK MYRIGHTS Completed\r\n";
+
+ this.Socket.Write(response);
+ }
+ }
+
+ #endregion
+
+
+ #region method GETQUOTA
+
+ private void GETQUOTA(string cmdTag, string argsText)
+ {
+ /* RFC 2087 4.2. GETQUOTA
+ Arguments: quota root
+
+ Data: untagged responses: QUOTA
+
+ Result: OK - getquota completed
+ NO - getquota error: no such quota root, permission denied
+ BAD - command unknown or arguments invalid
+
+ The GETQUOTA command takes the name of a quota root and returns the
+ quota root's resource usage and limits in an untagged QUOTA response.
+
+ Example: C: A003 GETQUOTA ""
+ S: * QUOTA "" (STORAGE 10 512)
+ S: A003 OK Getquota completed
+
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETQUOTA invalid arguments. Syntax: GETQUOTA \"quota_root\"");
+ return;
+ }
+
+ IMAP_eArgs_GetQuota eArgs = m_pServer.OnGetUserQuota(this);
+ string reply = "* QUOTA \"\" (STORAGE " + eArgs.MailboxSize + " " + eArgs.MaxMailboxSize + ")\r\n";
+ reply += cmdTag + " OK GETQUOTA completed\r\n";
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ #region method GETQUOTAROOT
+
+ private void GETQUOTAROOT(string cmdTag, string argsText)
+ {
+ /* RFC 2087 4.3. GETQUOTAROOT
+ Arguments: mailbox name
+
+ Data: untagged responses: QUOTAROOT, QUOTA
+
+ Result: OK - getquota completed
+ NO - getquota error: no such mailbox, permission denied
+ BAD - command unknown or arguments invalid
+
+ The GETQUOTAROOT command takes the name of a mailbox and returns the
+ list of quota roots for the mailbox in an untagged QUOTAROOT
+ response. For each listed quota root, it also returns the quota
+ root's resource usage and limits in an untagged QUOTA response.
+
+ Example: C: A003 GETQUOTAROOT INBOX
+ S: * QUOTAROOT INBOX ""
+ S: * QUOTA "" (STORAGE 10 512)
+ S: A003 OK Getquota completed
+
+ */
+
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+
+ string[] args = TextUtils.SplitQuotedString(argsText, ' ', true);
+ if (args.Length != 1)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD GETQUOTAROOT invalid arguments. Syntax: GETQUOTAROOT \"folder\"");
+ return;
+ }
+
+ IMAP_eArgs_GetQuota eArgs = m_pServer.OnGetUserQuota(this);
+ string reply = "* QUOTAROOT \"" + args[0] + "\" \"\"\r\n";
+ reply += "* QUOTA \"\" (STORAGE " + eArgs.MailboxSize + " " + eArgs.MaxMailboxSize + ")\r\n";
+ reply += cmdTag + " OK GETQUOTAROOT completed\r\n";
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ //--- End of Authenticated State
+
+
+ //--- Selected State ------
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ this.Socket.WriteLine(cmdTag + " OK CHECK completed");
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ if (!m_pSelectedFolder.ReadOnly)
+ {
+ IMAP_Message[] messages = m_pSelectedFolder.Messages.GetWithFlags(IMAP_MessageFlags.Deleted);
+ foreach (IMAP_Message msg in messages)
+ {
+ m_pServer.OnDeleteMessage(this, msg);
+ }
+ }
+
+ m_SelectedMailbox = "";
+ m_pSelectedFolder = null;
+
+ this.Socket.WriteLine(cmdTag + " OK CLOSE completed");
+ }
+
+ #endregion
+
+ #region method 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.
+
+ The EXPUNGE response reports that the specified message sequence
+ number has been permanently removed from the mailbox. The message
+ sequence number for each successive message in the mailbox is
+ IMMEDIATELY DECREMENTED BY 1, and this decrement is reflected in
+ message sequence numbers in subsequent responses (including other
+ untagged EXPUNGE responses).
+
+
+ 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ IMAP_Message[] messages = m_pSelectedFolder.Messages.GetWithFlags(IMAP_MessageFlags.Deleted);
+ for (int i = 0; i < messages.Length; i++)
+ {
+ IMAP_Message msg = messages[i];
+
+ string errorText = m_pServer.OnDeleteMessage(this, msg);
+ if (errorText == null)
+ {
+ this.Socket.WriteLine("* " + msg.SequenceNo + " EXPUNGE");
+ m_pSelectedFolder.Messages.Remove(msg);
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ return;
+ }
+ }
+
+ this.Socket.WriteLine(cmdTag + " OK EXPUNGE completed");
+ }
+
+ #endregion
+
+ #region method 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}
+ S: + Continue ### THIS IS UNDOCUMENTED !!!
+ C: XXXXXX[arg conitnue]
+ 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ //--- Get Optional charset, if specified -----------------------------------------------------------------//
+ string charset = "ASCII";
+ // CHARSET charset
+ if (argsText.ToUpper().StartsWith("CHARSET"))
+ {
+ // Remove CHARSET from argsText
+ argsText = argsText.Substring(7).Trim();
+
+ string charsetValueString = IMAP_Utils.ParseQuotedParam(ref argsText);
+
+ try
+ {
+ System.Text.Encoding.GetEncoding(charsetValueString);
+
+ charset = charsetValueString;
+ }
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + " NO [BADCHARSET UTF-8] " + charsetValueString + " is not supported");
+ return;
+ }
+ }
+ //---------------------------------------------------------------------------------------------------------//
+
+ /* If multiline command, read all lines
+ C: A284 SEARCH CHARSET UTF-8 TEXT {6}
+ S: + Continue ### THIS IS UNDOCUMENTED !!!
+ C: XXXXXX[arg conitnue]
+ */
+ argsText = argsText.Trim();
+ while (argsText.EndsWith("}") && argsText.IndexOf("{") > -1)
+ {
+ long dataLength = 0;
+ try
+ {
+ // Get data length from {xxx}
+ dataLength = Convert.ToInt64(argsText.Substring(argsText.LastIndexOf("{") + 1, argsText.Length - argsText.LastIndexOf("{") - 2));
+ }
+ // There is no valid numeric value between {}, just skip and allow SearchGroup parser to handle this value
+ catch
+ {
+ break;
+ }
+
+ MemoryStream dataStream = new MemoryStream();
+
+ this.Socket.WriteLine("+ Continue");
+ this.Socket.ReadSpecifiedLength((int)dataLength, dataStream);
+ string argsContinueLine = this.Socket.ReadLine().TrimEnd();
+
+ // Append readed data + [args conitnue] line
+ argsText += System.Text.Encoding.GetEncoding(charset).GetString(dataStream.ToArray()) + argsContinueLine;
+
+ // There is no more argumets, stop getting.
+ // We must check this because if length = 0 and no args returned, last line ends with {0},
+ // leaves this into endless loop.
+ if (argsContinueLine == "")
+ {
+ break;
+ }
+ }
+
+ //--- Parse search criteria ------------------------//
+ SearchGroup searchCriteria = new SearchGroup();
+ try
+ {
+ searchCriteria.Parse(new StringReader(argsText));
+ }
+ catch (Exception x)
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + x.Message);
+ return;
+ }
+ //--------------------------------------------------//
+ /*
+ string searchResponse = "* SEARCH";
+
+ // No header and body text needed, can do search on internal messages info data
+ if(!searchCriteria.IsHeaderNeeded() && !searchCriteria.IsBodyTextNeeded()){
+ // Loop internal messages info, see what messages match
+ for(int i=0;i -1){
+ searchResponse += " " + no.ToString();
+ }
+ }
+ }
+ }
+
+ searchResponse += "\r\n";
+ searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
+ */
+ ProcessMailboxChanges();
+
+ // Just loop messages headers or full messages (depends on search type)
+ // string searchResponse = "* SEARCH";
+ this.Socket.Write("* SEARCH");
+ string searchResponse = "";
+
+ IMAP_MessageItems_enum messageItems = IMAP_MessageItems_enum.None;
+ if (searchCriteria.IsBodyTextNeeded())
+ {
+ messageItems |= IMAP_MessageItems_enum.Message;
+ }
+ else if (searchCriteria.IsHeaderNeeded())
+ {
+ messageItems |= IMAP_MessageItems_enum.Header;
+ }
+ for (int i = 0; i < m_pSelectedFolder.Messages.Count; i++)
+ {
+ IMAP_Message msg = m_pSelectedFolder.Messages[i];
+
+ //-- Get message only if matching needs it ------------------------//
+ Mail_Message message = null;
+ if ((messageItems & IMAP_MessageItems_enum.Message) != 0 || (messageItems & IMAP_MessageItems_enum.Header) != 0)
+ {
+ // Raise event GetMessageItems, get requested message items.
+ IMAP_eArgs_MessageItems eArgs = m_pServer.OnGetMessageItems(this, msg, messageItems);
+
+ // Message data is null if no such message available, just skip that message
+ if (!eArgs.MessageExists)
+ {
+ continue;
+ }
+ try
+ {
+ // Ensure that all requested items were provided.
+ eArgs.Validate();
+ }
+ catch (Exception x)
+ {
+ m_pServer.OnSysError(x.Message, x);
+ this.Socket.WriteLine(cmdTag + " NO Internal IMAP server component error: " + x.Message);
+ return;
+ }
+
+ try
+ {
+ if (eArgs.MessageStream != null)
+ {
+ message = Mail_Message.ParseFromStream(eArgs.MessageStream);
+ }
+ else
+ {
+ message = Mail_Message.ParseFromStream(new MemoryStream(eArgs.Header));
+ }
+ }
+ // Message parsing failed, bad message. Just make new warning message.
+ catch
+ {
+ message = new Mail_Message();
+ message.MimeVersion = "1.0";
+ message.MessageID = MIME_Utils.CreateMessageID();
+ message.Date = DateTime.Now;
+ message.From = new Mail_t_MailboxList();
+ message.From.Add(new Mail_t_Mailbox("system", "system"));
+ message.To = new Mail_t_AddressList();
+ message.To.Add(new Mail_t_Mailbox("system", "system"));
+ message.Subject = "[BAD MESSAGE] Bad message, message parsing failed !";
+
+ //--- multipart/mixed -------------------------------------------------------------------------------------------------
+ MIME_h_ContentType contentType_multipartMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
+ contentType_multipartMixed.Param_Boundary = Guid.NewGuid().ToString().Replace('-', '.');
+ MIME_b_MultipartMixed multipartMixed = new MIME_b_MultipartMixed(contentType_multipartMixed);
+ message.Body = multipartMixed;
+
+ //--- text/plain ---------------------------------------------------------------------------------------------------
+ MIME_Entity entity_text_plain = new MIME_Entity();
+ MIME_b_Text text_plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
+ entity_text_plain.Body = text_plain;
+ text_plain.SetText(MIME_TransferEncodings.QuotedPrintable, Encoding.UTF8, "NOTE: Bad message, message parsing failed.\r\n\r\n");
+ multipartMixed.BodyParts.Add(entity_text_plain);
+ }
+ }
+ //-----------------------------------------------------------------//
+
+ string bodyText = "";
+ if (searchCriteria.IsBodyTextNeeded())
+ {
+ bodyText = message.BodyText;
+ }
+
+ // See if message matches to search criteria
+ if (searchCriteria.Match(i + 1, msg.UID, (int)msg.Size, msg.InternalDate, msg.Flags, message, bodyText))
+ {
+ if (uidSearch)
+ {
+ this.Socket.Write(" " + msg.UID.ToString());
+ }
+ else
+ {
+ this.Socket.Write(" " + (i + 1).ToString());
+ }
+ }
+ }
+
+ searchResponse += "\r\n";
+ searchResponse += cmdTag + " OK SEARCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n";
+
+ // Send search server response
+ this.Socket.Write(searchResponse);
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ IMAP_MessageItems_enum messageItems = IMAP_MessageItems_enum.None;
+
+ #region Parse parameters
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
+ return;
+ }
+
+ IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
+ // Just try if it can be parsed as sequence-set
+ try
+ {
+ if (uidFetch)
+ {
+ if (m_pSelectedFolder.Messages.Count > 0)
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
+ }
+ }
+ else
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages.Count);
+ }
+ }
+ // This isn't valid sequnce-set value
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid value '" + args[0] + "' Syntax: { FETCH ()}!");
+ return;
+ }
+
+ // 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";
+ }
+
+ // Start parm parsing from left to end in while loop while params parsed or bad param found
+ ArrayList fetchFlags = new ArrayList();
+ StringReader argsReader = new StringReader(fetchItems.Trim());
+ while (argsReader.Available > 0)
+ {
+ argsReader.ReadToFirstChar();
+
+ #region BODYSTRUCTURE
+
+ // BODYSTRUCTURE
+ if (argsReader.StartsWith("BODYSTRUCTURE"))
+ {
+ argsReader.ReadSpecifiedLength("BODYSTRUCTURE".Length);
+
+ fetchFlags.Add(new object[] { "BODYSTRUCTURE" });
+ messageItems |= IMAP_MessageItems_enum.BodyStructure;
+ }
+
+ #endregion
+
+ #region BODY, BODY[]<>, BODY.PEEK[]<>
+
+ // BODY, BODY[]<>, BODY.PEEK[]<>
+ else if (argsReader.StartsWith("BODY"))
+ {
+ // Remove BODY
+ argsReader.ReadSpecifiedLength("BODY".Length);
+
+ bool peek = false;
+ // BODY.PEEK
+ if (argsReader.StartsWith(".PEEK"))
+ {
+ // Remove .PEEK
+ argsReader.ReadSpecifiedLength(".PEEK".Length);
+
+ peek = true;
+ }
+
+ // []<>
+ if (argsReader.StartsWith("["))
+ {
+ // Read value between []
+ string section = "";
+ try
+ {
+ section = argsReader.ReadParenthesized();
+ }
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[], closing ] parenthesize is missing !");
+ return;
+ }
+
+ string originalSectionValue = section;
+ string mimePartsSpecifier = "";
+ string sectionType = "";
+ string sectionArgs = "";
+ /* Validate
+ Section can be:
+ "" - entire message
+ [MimePartsSepcifier.]HEADER - message header
+ [MimePartsSepcifier.]HEADER.FIELDS (headerFields) - message header fields
+ [MimePartsSepcifier.]HEADER.FIELDS.NOT (headerFields) - message header fields except requested
+ [MimePartsSepcifier.]TEXT - message text
+ [MimePartsSepcifier.]MIME - same as header, different response
+ */
+ if (section.Length > 0)
+ {
+ string[] section_args = section.Split(new char[] { ' ' }, 2);
+ section = section_args[0];
+ if (section_args.Length == 2)
+ {
+ sectionArgs = section_args[1];
+ }
+
+ if (section.EndsWith("HEADER"))
+ {
+ // Remove HEADER from end
+ section = section.Substring(0, section.Length - "HEADER".Length);
+
+ sectionType = "HEADER";
+ messageItems |= IMAP_MessageItems_enum.Header;
+ }
+ else if (section.EndsWith("HEADER.FIELDS"))
+ {
+ // Remove HEADER.FIELDS from end
+ section = section.Substring(0, section.Length - "HEADER.FIELDS".Length);
+
+ sectionType = "HEADER.FIELDS";
+ messageItems |= IMAP_MessageItems_enum.Header;
+ }
+ else if (section.EndsWith("HEADER.FIELDS.NOT"))
+ {
+ // Remove HEADER.FIELDS.NOT from end
+ section = section.Substring(0, section.Length - "HEADER.FIELDS.NOT".Length);
+
+ sectionType = "HEADER.FIELDS.NOT";
+ messageItems |= IMAP_MessageItems_enum.Header;
+ }
+ else if (section.EndsWith("TEXT"))
+ {
+ // Remove TEXT from end
+ section = section.Substring(0, section.Length - "TEXT".Length);
+
+ sectionType = "TEXT";
+ messageItems |= IMAP_MessageItems_enum.Message;
+ }
+ else if (section.EndsWith("MIME"))
+ {
+ // Remove MIME from end
+ section = section.Substring(0, section.Length - "MIME".Length);
+
+ sectionType = "MIME";
+ messageItems = IMAP_MessageItems_enum.Header;
+ }
+
+ // Remove last ., if there is any
+ if (section.EndsWith("."))
+ {
+ section = section.Substring(0, section.Length - 1);
+ }
+
+ // MimePartsSepcifier is specified, validate it. It can contain numbers only.
+ if (section.Length > 0)
+ {
+ // Now we certainly need full message, because nested mime parts wanted
+ messageItems |= IMAP_MessageItems_enum.Message;
+
+ string[] sectionParts = section.Split('.');
+ foreach (string sectionPart in sectionParts)
+ {
+ if (!Net_Utils.IsInteger(sectionPart))
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[] argument. Invalid : " + section);
+ return;
+ }
+ }
+
+ mimePartsSpecifier = section;
+ }
+ }
+ else
+ {
+ messageItems |= IMAP_MessageItems_enum.Message;
+ }
+
+ long startPosition = -1;
+ long length = -1;
+ // See if partial fetch
+ if (argsReader.StartsWith("<"))
+ {
+ /* syntax:
+ startPosition[.endPosition]
+ */
+
+ // Read partial value between <>
+ string partial = "";
+ try
+ {
+ partial = argsReader.ReadParenthesized();
+ }
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[], closing > parenthesize is missing !");
+ return;
+ }
+
+ string[] start_length = partial.Split('.');
+
+ // Validate
+ if (start_length.Length == 0 || start_length.Length > 2 || !Net_Utils.IsInteger(start_length[0]) || (start_length.Length == 2 && !Net_Utils.IsInteger(start_length[1])))
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid BODY[] argument. Invalid : " + partial);
+ return;
+ }
+
+ startPosition = Convert.ToInt64(start_length[0]);
+ if (start_length.Length == 2)
+ {
+ length = Convert.ToInt64(start_length[1]);
+ }
+ }
+
+ // object[] structure for BODY[]
+ // fetchFlagName
+ // isPeek
+ // mimePartsSpecifier
+ // originalSectionValue
+ // sectionType
+ // sectionArgs
+ // startPosition
+ // length
+ fetchFlags.Add(new object[] { "BODY[]", peek, mimePartsSpecifier, originalSectionValue, sectionType, sectionArgs, startPosition, length });
+ }
+ // BODY
+ else
+ {
+ fetchFlags.Add(new object[] { "BODY" });
+ messageItems |= IMAP_MessageItems_enum.BodyStructure;
+ }
+ }
+
+ #endregion
+
+ #region ENVELOPE
+
+ // ENVELOPE
+ else if (argsReader.StartsWith("ENVELOPE"))
+ {
+ argsReader.ReadSpecifiedLength("ENVELOPE".Length);
+
+ fetchFlags.Add(new object[] { "ENVELOPE" });
+ messageItems |= IMAP_MessageItems_enum.Envelope;
+ }
+
+ #endregion
+
+ #region FLAGS
+
+ // FLAGS
+ // The flags that are set for this message.
+ else if (argsReader.StartsWith("FLAGS"))
+ {
+ argsReader.ReadSpecifiedLength("FLAGS".Length);
+
+ fetchFlags.Add(new object[] { "FLAGS" });
+ }
+
+ #endregion
+
+ #region INTERNALDATE
+
+ // INTERNALDATE
+ else if (argsReader.StartsWith("INTERNALDATE"))
+ {
+ argsReader.ReadSpecifiedLength("INTERNALDATE".Length);
+
+ fetchFlags.Add(new object[] { "INTERNALDATE" });
+ }
+
+ #endregion
+
+ #region RFC822.HEADER
+
+ // RFC822.HEADER
+ else if (argsReader.StartsWith("RFC822.HEADER"))
+ {
+ argsReader.ReadSpecifiedLength("RFC822.HEADER".Length);
+
+ fetchFlags.Add(new object[] { "RFC822.HEADER" });
+ messageItems |= IMAP_MessageItems_enum.Header;
+ }
+
+ #endregion
+
+ #region RFC822.SIZE
+
+ // RFC822.SIZE
+ // The [RFC-2822] size of the message.
+ else if (argsReader.StartsWith("RFC822.SIZE"))
+ {
+ argsReader.ReadSpecifiedLength("RFC822.SIZE".Length);
+
+ fetchFlags.Add(new object[] { "RFC822.SIZE" });
+ }
+
+ #endregion
+
+ #region RFC822.TEXT
+
+ // RFC822.TEXT
+ else if (argsReader.StartsWith("RFC822.TEXT"))
+ {
+ argsReader.ReadSpecifiedLength("RFC822.TEXT".Length);
+
+ fetchFlags.Add(new object[] { "RFC822.TEXT" });
+ messageItems |= IMAP_MessageItems_enum.Message;
+ }
+
+ #endregion
+
+ #region RFC822
+
+ // RFC822 NOTE: RFC822 must be below RFC822.xxx or is parsed wrong !
+ else if (argsReader.StartsWith("RFC822"))
+ {
+ argsReader.ReadSpecifiedLength("RFC822".Length);
+
+ fetchFlags.Add(new object[] { "RFC822" });
+ messageItems |= IMAP_MessageItems_enum.Message;
+ }
+
+ #endregion
+
+ #region UID
+
+ // UID
+ // The unique identifier for the message.
+ else if (argsReader.StartsWith("UID"))
+ {
+ argsReader.ReadSpecifiedLength("UID".Length);
+
+ fetchFlags.Add(new object[] { "UID" });
+ }
+
+ #endregion
+
+ // This must be unknown fetch flag
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid fetch-items argument. Unkown part starts from: " + argsReader.SourceString);
+ return;
+ }
+ }
+
+ #endregion
+
+ // ToDo: ??? But non of the servers do it ?
+ // The server should respond with a tagged BAD response to a command that uses a message
+ // sequence number greater than the number of messages in the selected mailbox. This
+ // includes "*" if the selected mailbox is empty.
+ // if(m_Messages.Count == 0 || ){
+ // SendData(cmdTag + " BAD Sequence number greater than the number of messages in the selected mailbox !\r\n");
+ // return;
+ // }
+
+ // Create buffered writer, so we make less network calls.
+ SocketBufferedWriter bufferedWriter = new SocketBufferedWriter(this.Socket);
+
+ for (int i = 0; i < m_pSelectedFolder.Messages.Count; i++)
+ {
+ IMAP_Message msg = m_pSelectedFolder.Messages[i];
+
+ // For UID FETCH we must compare UIDs and for normal FETCH message numbers.
+ bool sequenceSetContains = false;
+ if (uidFetch)
+ {
+ sequenceSetContains = sequenceSet.Contains(msg.UID);
+ }
+ else
+ {
+ sequenceSetContains = sequenceSet.Contains(i + 1);
+ }
+
+ if (sequenceSetContains)
+ {
+ IMAP_eArgs_MessageItems eArgs = null;
+ // Get message items only if they are needed.
+ if (messageItems != IMAP_MessageItems_enum.None)
+ {
+ // Raise event GetMessageItems to get all neccesary message itmes
+ eArgs = m_pServer.OnGetMessageItems(this, msg, messageItems);
+
+ // Message doesn't exist any more, notify email client.
+ if (!eArgs.MessageExists)
+ {
+ bufferedWriter.Write("* " + msg.SequenceNo + " EXPUNGE");
+ m_pSelectedFolder.Messages.Remove(msg);
+ i--;
+ continue;
+ }
+ try
+ {
+ // Ensure that all requested items were provided.
+ eArgs.Validate();
+ }
+ catch (Exception x)
+ {
+ m_pServer.OnSysError(x.Message, x);
+ this.Socket.WriteLine(cmdTag + " NO Internal IMAP server component error: " + x.Message);
+ return;
+ }
+ }
+
+ // Write fetch start data "* msgNo FETCH ("
+ bufferedWriter.Write("* " + (i + 1) + " FETCH (");
+
+ IMAP_MessageFlags msgFlagsOr = msg.Flags;
+ // Construct reply here, based on requested fetch items
+ int nCount = 0;
+ foreach (object[] fetchFlag in fetchFlags)
+ {
+ string fetchFlagName = (string)fetchFlag[0];
+
+ #region BODY
+
+ // BODY
+ if (fetchFlagName == "BODY")
+ {
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // BODY ()
+ bufferedWriter.Write("BODY " + eArgs.BodyStructure);
+ }
+
+ #endregion
+
+ #region BODY[], BODY.PEEK[]
+
+ // BODY[]<>, BODY.PEEK[]<>
+ else if (fetchFlagName == "BODY[]")
+ {
+ // Force to write all buffered data.
+ bufferedWriter.Flush();
+
+ // object[] structure for BODY[]
+ // fetchFlagName
+ // isPeek
+ // mimePartsSpecifier
+ // originalSectionValue
+ // sectionType
+ // sectionArgs
+ // startPosition
+ // length
+ bool isPeek = (bool)fetchFlag[1];
+ string mimePartsSpecifier = (string)fetchFlag[2];
+ string originalSectionValue = (string)fetchFlag[3];
+ string sectionType = (string)fetchFlag[4];
+ string sectionArgs = (string)fetchFlag[5];
+ long startPosition = (long)fetchFlag[6];
+ long length = (long)fetchFlag[7];
+
+ // Difference between BODY[] and BODY.PEEK[] is that .PEEK won't set seen flag
+ if (!isPeek)
+ {
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+ }
+
+ /* Section value:
+ "" - entire message
+ HEADER - message header
+ HEADER.FIELDS - message header fields
+ HEADER.FIELDS.NOT - message header fields except requested
+ TEXT - message text
+ MIME - same as header, different response
+ */
+ Stream dataStream = null;
+ if (sectionType == "" && mimePartsSpecifier == "")
+ {
+ dataStream = eArgs.MessageStream;
+ }
+ else
+ {
+ Mail_Message message = null;
+ try
+ {
+ if (eArgs.MessageStream == null)
+ {
+ message = Mail_Message.ParseFromStream(new MemoryStream(eArgs.Header));
+ }
+ else
+ {
+ message = Mail_Message.ParseFromStream(eArgs.MessageStream);
+ }
+ }
+ // Invalid message, parsing failed
+ catch
+ {
+ message = new Mail_Message();
+ message.MimeVersion = "1.0";
+ message.MessageID = MIME_Utils.CreateMessageID();
+ message.Date = DateTime.Now;
+ message.From = new Mail_t_MailboxList();
+ message.From.Add(new Mail_t_Mailbox("system", "system"));
+ message.To = new Mail_t_AddressList();
+ message.To.Add(new Mail_t_Mailbox("system", "system"));
+ message.Subject = "[BAD MESSAGE] Bad message, message parsing failed !";
+
+ //--- multipart/mixed -------------------------------------------------------------------------------------------------
+ MIME_h_ContentType contentType_multipartMixed = new MIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
+ contentType_multipartMixed.Param_Boundary = Guid.NewGuid().ToString().Replace('-', '.');
+ MIME_b_MultipartMixed multipartMixed = new MIME_b_MultipartMixed(contentType_multipartMixed);
+ message.Body = multipartMixed;
+
+ //--- text/plain ---------------------------------------------------------------------------------------------------
+ MIME_Entity entity_text_plain = new MIME_Entity();
+ MIME_b_Text text_plain = new MIME_b_Text(MIME_MediaTypes.Text.plain);
+ entity_text_plain.Body = text_plain;
+ text_plain.SetText(MIME_TransferEncodings.QuotedPrintable, Encoding.UTF8, "NOTE: Bad message, message parsing failed.\r\n\r\n");
+ multipartMixed.BodyParts.Add(entity_text_plain);
+ }
+
+ MIME_Entity currentEntity = message;
+ // Specific mime entity requested, get it
+ if (mimePartsSpecifier != "")
+ {
+ currentEntity = FetchHelper.GetMimeEntity(message, mimePartsSpecifier);
+ }
+
+ if (currentEntity != null)
+ {
+ if (sectionType == "HEADER")
+ {
+ dataStream = new MemoryStream(FetchHelper.GetMimeEntityHeader(currentEntity));
+ }
+ else if (sectionType == "HEADER.FIELDS")
+ {
+ dataStream = new MemoryStream(FetchHelper.ParseHeaderFields(sectionArgs, currentEntity));
+ }
+ else if (sectionType == "HEADER.FIELDS.NOT")
+ {
+ dataStream = new MemoryStream(FetchHelper.ParseHeaderFieldsNot(sectionArgs, currentEntity));
+ }
+ else if (sectionType == "TEXT")
+ {
+ if (currentEntity.Body is MIME_b_Text)
+ {
+ dataStream = new MemoryStream(((MIME_b_Text)currentEntity.Body).EncodedData);
+ }
+ }
+ else if (sectionType == "MIME")
+ {
+ dataStream = new MemoryStream(FetchHelper.GetMimeEntityHeader(currentEntity));
+ }
+ else if (sectionType == "")
+ {
+ if (currentEntity.Body is MIME_b_SinglepartBase)
+ {
+ dataStream = new MemoryStream(((MIME_b_SinglepartBase)currentEntity.Body).EncodedData);
+ }
+ }
+ }
+ }
+
+ // Partial fetch. Reports in fetch reply.
+ if (startPosition > -1)
+ {
+ if (dataStream == null)
+ {
+ this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> \"\"\r\n");
+ }
+ else
+ {
+ long lengthToSend = length;
+ if (lengthToSend == -1)
+ {
+ lengthToSend = (dataStream.Length - dataStream.Position) - startPosition;
+ }
+ if ((lengthToSend + startPosition) > (dataStream.Length - dataStream.Position))
+ {
+ lengthToSend = (dataStream.Length - dataStream.Position) - startPosition;
+ }
+
+ if (startPosition >= (dataStream.Length - dataStream.Position))
+ {
+ this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> \"\"\r\n");
+ }
+ else
+ {
+ this.Socket.Write("BODY[" + originalSectionValue + "]<" + startPosition.ToString() + "> {" + lengthToSend + "}\r\n");
+ dataStream.Position += startPosition;
+ this.Socket.Write(dataStream, lengthToSend);
+ }
+ }
+ }
+ // Normal fetch
+ else
+ {
+ if (dataStream == null)
+ {
+ this.Socket.Write("BODY[" + originalSectionValue + "] \"\"\r\n");
+ }
+ else
+ {
+ this.Socket.Write("BODY[" + originalSectionValue + "] {" + (dataStream.Length - dataStream.Position) + "}\r\n");
+ this.Socket.Write(dataStream);
+ }
+ }
+ }
+
+ #endregion
+
+ #region BODYSTRUCTURE
+
+ // BODYSTRUCTURE
+ else if (fetchFlagName == "BODYSTRUCTURE")
+ {
+ bufferedWriter.Write("BODYSTRUCTURE " + eArgs.BodyStructure);
+ }
+
+ #endregion
+
+ #region ENVELOPE
+
+ // ENVELOPE
+ else if (fetchFlagName == "ENVELOPE")
+ {
+ bufferedWriter.Write("ENVELOPE " + eArgs.Envelope);
+ }
+
+ #endregion
+
+ #region FLAGS
+
+ // FLAGS
+ else if (fetchFlagName == "FLAGS")
+ {
+ bufferedWriter.Write("FLAGS (" + msg.FlagsString + ")");
+ }
+
+ #endregion
+
+ #region INTERNALDATE
+
+ // INTERNALDATE
+ else if (fetchFlagName == "INTERNALDATE")
+ {
+ // INTERNALDATE "date"
+ bufferedWriter.Write("INTERNALDATE \"" + IMAP_Utils.DateTimeToString(msg.InternalDate) + "\"");
+ }
+
+ #endregion
+
+ #region RFC822
+
+ // RFC822
+ else if (fetchFlagName == "RFC822")
+ {
+ // Force to write all buffered data.
+ bufferedWriter.Flush();
+
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ // RFC822 {size}
+ // msg data
+ this.Socket.Write("RFC822 {" + eArgs.MessageSize.ToString() + "}\r\n");
+ this.Socket.Write(eArgs.MessageStream);
+ }
+
+ #endregion
+
+ #region RFC822.HEADER
+
+ // RFC822.HEADER
+ else if (fetchFlagName == "RFC822.HEADER")
+ {
+ // Force to write all buffered data.
+ bufferedWriter.Flush();
+
+ // RFC822.HEADER {size}
+ // msg header data
+ this.Socket.Write("RFC822.HEADER {" + eArgs.Header.Length + "}\r\n");
+ this.Socket.Write(eArgs.Header);
+ }
+
+ #endregion
+
+ #region RFC822.SIZE
+
+ // RFC822.SIZE
+ else if (fetchFlagName == "RFC822.SIZE")
+ {
+ // RFC822.SIZE size
+ bufferedWriter.Write("RFC822.SIZE " + msg.Size);
+ }
+
+ #endregion
+
+ #region RFC822.TEXT
+
+ // RFC822.TEXT
+ else if (fetchFlagName == "RFC822.TEXT")
+ {
+ // Force to write all buffered data.
+ bufferedWriter.Flush();
+
+ // Sets \seen flag
+ msg.SetFlags(msg.Flags | IMAP_MessageFlags.Seen);
+
+ //--- Find body text entity ------------------------------------//
+ Mail_Message message = Mail_Message.ParseFromStream(eArgs.MessageStream);
+ MIME_Entity bodyTextEntity = null;
+ if (message.ContentType == null)
+ {
+ bodyTextEntity = message;
+ }
+ else
+ {
+ foreach (MIME_Entity entity in message.AllEntities)
+ {
+ if (string.Equals(entity.ContentType.TypeWithSubype, MIME_MediaTypes.Text.plain, StringComparison.InvariantCultureIgnoreCase))
+ {
+ bodyTextEntity = entity;
+ break;
+ }
+ }
+ }
+ //----------------------------------------------------------------//
+
+ // RFC822.TEXT {size}
+ // msg text
+ byte[] data = System.Text.Encoding.ASCII.GetBytes("");
+ if (bodyTextEntity != null)
+ {
+ if (bodyTextEntity.Body is MIME_b_Text)
+ {
+ data = ((MIME_b_Text)bodyTextEntity.Body).EncodedData;
+ }
+ }
+
+ this.Socket.Write("RFC822.TEXT {" + data.Length + "}\r\n");
+ this.Socket.Write(data);
+ }
+
+ #endregion
+
+ #region UID
+
+ // UID
+ else if (fetchFlagName == "UID")
+ {
+ bufferedWriter.Write("UID " + msg.UID);
+ }
+
+ #endregion
+
+ nCount++;
+
+ // Write fetch item separator data " "
+ // We don't write it for last item
+ if (nCount < fetchFlags.Count)
+ {
+ bufferedWriter.Write(" ");
+ }
+ }
+
+ // Write fetch end data ")"
+ bufferedWriter.Write(")\r\n");
+
+ // Free event args, close message stream, ... .
+ if (eArgs != null)
+ {
+ eArgs.Dispose();
+ }
+
+ // 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_pServer.OnStoreMessageFlags(this, msg);
+ }
+ }
+ }
+
+ // Force to write all buffered data.
+ bufferedWriter.Flush();
+
+ this.Socket.WriteLine(cmdTag + " OK FETCH completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+ if (m_pSelectedFolder.ReadOnly)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Mailbox is read-only");
+ return;
+ }
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 3)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD STORE invalid arguments. Syntax: { STORE ()}");
+ return;
+ }
+
+ IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
+ // Just try if it can be parsed as sequence-set
+ try
+ {
+ if (uidStore)
+ {
+ if (m_pSelectedFolder.Messages.Count > 0)
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
+ }
+ }
+ else
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages.Count);
+ }
+ }
+ // This isn't vaild sequnce-set value
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + "BAD Invalid value '" + args[0] + "' Syntax: { STORE ()}!");
+ return;
+ }
+
+ //--- 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:
+ this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
+ 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)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD arguments invalid");
+ return;
+ }
+
+ IMAP_MessageFlags mFlags = IMAP_Utils.ParseMessageFlags(flags);
+
+ // Call OnStoreMessageFlags for each message in sequence 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 = 0; i < m_pSelectedFolder.Messages.Count; i++)
+ {
+ IMAP_Message msg = m_pSelectedFolder.Messages[i];
+
+ // For UID STORE we must compare UIDs and for normal STORE message numbers.
+ bool sequenceSetContains = false;
+ if (uidStore)
+ {
+ sequenceSetContains = sequenceSet.Contains(msg.UID);
+ }
+ else
+ {
+ sequenceSetContains = sequenceSet.Contains(i + 1);
+ }
+
+ if (sequenceSetContains)
+ {
+ // 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;
+ }
+
+ // ToDo: see if flags changed, if not don't call OnStoreMessageFlags
+
+ string errorText = m_pServer.OnStoreMessageFlags(this, msg);
+ if (errorText == null)
+ {
+ if (!silent)
+ { // Silent doesn't reply untagged lines
+ if (!uidStore)
+ {
+ this.Socket.WriteLine("* " + (i + 1) + " FETCH FLAGS (" + msg.FlagsString + ")");
+ }
+ // Called from UID command, need to add UID response
+ else
+ {
+ this.Socket.WriteLine("* " + (i + 1) + " FETCH (FLAGS (" + msg.FlagsString + ") UID " + msg.UID + "))");
+ }
+ }
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ return;
+ }
+ }
+ }
+
+ this.Socket.WriteLine(cmdTag + " OK STORE completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds");
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length != 2)
+ {
+ this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
+ return;
+ }
+
+ IMAP_SequenceSet sequenceSet = new IMAP_SequenceSet();
+ // Just try if it can be parsed as sequence-set
+ try
+ {
+ if (uidCopy)
+ {
+ if (m_pSelectedFolder.Messages.Count > 0)
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages[m_pSelectedFolder.Messages.Count - 1].UID);
+ }
+ }
+ else
+ {
+ sequenceSet.Parse(args[0], m_pSelectedFolder.Messages.Count);
+ }
+ }
+ // This isn't vaild sequnce-set value
+ catch
+ {
+ this.Socket.WriteLine(cmdTag + "BAD Invalid value '" + args[0] + "' Syntax: { COPY \"\"}!");
+ return;
+ }
+
+ string errorText = "";
+ for (int i = 0; i < m_pSelectedFolder.Messages.Count; i++)
+ {
+ IMAP_Message msg = m_pSelectedFolder.Messages[i];
+
+ // For UID COPY we must compare UIDs and for normal COPY message numbers.
+ bool sequenceSetContains = false;
+ if (uidCopy)
+ {
+ sequenceSetContains = sequenceSet.Contains(msg.UID);
+ }
+ else
+ {
+ sequenceSetContains = sequenceSet.Contains(i + 1);
+ }
+
+ if (sequenceSetContains)
+ {
+ errorText = m_pServer.OnCopyMessage(this, msg, IMAP_Utils.Decode_IMAP_UTF7_String(args[1]));
+ if (errorText != null)
+ {
+ break; // Errors return error text, don't try to copy other messages
+ }
+ }
+ }
+
+ if (errorText == null)
+ {
+ this.Socket.WriteLine(cmdTag + " OK COPY completed");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " NO " + errorText);
+ }
+ }
+
+ #endregion
+
+ #region method 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 (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return;
+ }
+
+ string[] args = ParseParams(argsText);
+ if (args.Length < 2)
+ { // We must have at least command and message-set or cmd args
+ this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
+ return;
+ }
+
+ // Get commands args text, we just remove COMMAND
+ string cmdArgs = argsText.Substring(args[0].Length).Trim();
+
+ // See if valid command specified with UID 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;
+
+ default:
+ this.Socket.WriteLine(cmdTag + " BAD Invalid arguments");
+ return;
+ }
+ }
+
+ #endregion
+
+ #region method Idle
+
+ ///
+ /// Processes IDLE command.
+ ///
+ /// Command tag.
+ /// Command arguments text.
+ /// Returns true if IDLE comand accepted, otherwise false.
+ private bool Idle(string cmdTag, string argsText)
+ {
+ if (!this.Authenticated)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Authenticate first !");
+ return false;
+ }
+ if (m_SelectedMailbox.Length == 0)
+ {
+ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !");
+ return false;
+ }
+
+ m_pIDLE = new Command_IDLE(this, cmdTag);
+
+ return true;
+ }
+
+ #endregion
+
+ //--- End of Selected State
+
+
+ //--- Any State ------
+
+ #region method 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
+ */
+
+ string reply = "* CAPABILITY IMAP4rev1";
+ if ((m_pServer.SupportedAuthentications & SaslAuthTypes.Digest_md5) != 0)
+ {
+ reply += " AUTH=DIGEST-MD5";
+ }
+ if ((m_pServer.SupportedAuthentications & SaslAuthTypes.Cram_md5) != 0)
+ {
+ reply += " AUTH=CRAM-MD5";
+ }
+ if (!this.Socket.SSL && this.BindInfo.Certificate != null)
+ {
+ reply += " STARTTLS";
+ }
+ reply += " NAMESPACE ACL QUOTA IDLE\r\n";
+ reply += cmdTag + " OK CAPABILITY completed\r\n";
+
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ #region method 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
+ */
+
+ // Store start time
+ long startTime = DateTime.Now.Ticks;
+
+ // If there is selected mailbox, see if messages status has changed
+ if (m_SelectedMailbox.Length > 0)
+ {
+ ProcessMailboxChanges();
+
+ this.Socket.WriteLine(cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n");
+ }
+ else
+ {
+ this.Socket.WriteLine(cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n");
+ }
+ }
+
+ #endregion
+
+ #region method 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)
+ */
+
+ string reply = "* BYE IMAP4rev1 Server logging out\r\n";
+ reply += cmdTag + " OK LOGOUT completed\r\n";
+
+ this.Socket.Write(reply);
+ }
+
+ #endregion
+
+ //--- End of Any State
+
+ #region method ProcessMailboxChanges
+
+ ///
+ /// Processes changes and sends status responses if there are changes in selected mailbox.
+ ///
+ private void ProcessMailboxChanges()
+ {
+ // Get status
+ IMAP_SelectedFolder folderInfo = new IMAP_SelectedFolder(m_SelectedMailbox);
+ IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this, folderInfo);
+
+ // Join new info with exisiting
+ string statusResponse = m_pSelectedFolder.Update(folderInfo);
+ if (!string.IsNullOrEmpty(statusResponse))
+ {
+ this.Socket.WriteLine(statusResponse);
+
+ m_pSelectedFolder = folderInfo;
+ }
+ }
+
+ #endregion
+
+ #region method 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
+ // Note: If there is ({ before SP, cosider that it's last parameter.
+ // For example body[header.fields (from to)]
+ if (argsText.IndexOf(" ") > -1 && argsText.IndexOfAny(new char[] { '(', '[' }, 0, 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 selected mailbox.
+ ///
+ public string SelectedMailbox
+ {
+ get { return m_SelectedMailbox; }
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MailServer/IMAP/Server/Server.Designer.cs b/MailServer/IMAP/Server/Server.Designer.cs
deleted file mode 100644
index 4c3c24c..0000000
--- a/MailServer/IMAP/Server/Server.Designer.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-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
deleted file mode 100644
index 2bf0d8b..0000000
--- a/MailServer/IMAP/Server/Server.EventDelegates.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-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
deleted file mode 100644
index f552e7f..0000000
--- a/MailServer/IMAP/Server/Server.Events.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-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
deleted file mode 100644
index e821fc7..0000000
--- a/MailServer/IMAP/Server/Server.EventsDeclarations.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-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
deleted file mode 100644
index 07a1ce7..0000000
--- a/MailServer/IMAP/Server/Server.Properties.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-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
deleted file mode 100644
index 5727990..0000000
--- a/MailServer/IMAP/Server/Server.Session.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-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
deleted file mode 100644
index d8d2201..0000000
--- a/MailServer/IMAP/Server/Server.StartStopRun.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-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
deleted file mode 100644
index 0d597f5..0000000
--- a/MailServer/IMAP/Server/Server.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-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/IMAP/Server/Server/Server.Delegates.cs b/MailServer/IMAP/Server/Server/Server.Delegates.cs
new file mode 100644
index 0000000..cc1ead7
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.Delegates.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// Represents the method that will handle the AuthUser event for SMTP_Server.
+ ///
+ /// The source of the event.
+ /// A AuthUser_EventArgs that contains the event data.
+ 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_eArgs_GetMessagesInfo e);
+ ///
+ ///
+ ///
+ public delegate void MessagesItemsEventHandler(object sender, IMAP_eArgs_MessageItems e);
+ ///
+ ///
+ ///
+ public delegate void MessageEventHandler(object sender, Message_EventArgs e);
+ /*///
+ ///
+ ///
+ public delegate void SearchEventHandler(object sender,IMAP_eArgs_Search e);*/
+ ///
+ ///
+ ///
+ public delegate void SharedRootFoldersEventHandler(object sender, SharedRootFolders_EventArgs e);
+ ///
+ ///
+ ///
+ public delegate void GetFolderACLEventHandler(object sender, IMAP_GETACL_eArgs e);
+ ///
+ ///
+ ///
+ public delegate void DeleteFolderACLEventHandler(object sender, IMAP_DELETEACL_eArgs e);
+ ///
+ ///
+ ///
+ public delegate void SetFolderACLEventHandler(object sender, IMAP_SETACL_eArgs e);
+ ///
+ ///
+ ///
+ public delegate void GetUserACLEventHandler(object sender, IMAP_GetUserACL_eArgs e);
+ ///
+ ///
+ ///
+ public delegate void GetUserQuotaHandler(object sender, IMAP_eArgs_GetQuota e);
+}
diff --git a/MailServer/IMAP/Server/Server/Server.Events.cs b/MailServer/IMAP/Server/Server/Server.Events.cs
new file mode 100644
index 0000000..f14932c
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.Events.cs
@@ -0,0 +1,341 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ public partial class Server
+ {
+ ///
+ /// Raises event ValidateIP event.
+ ///
+ /// Server IP.
+ /// Connected client IP.
+ /// Returns true if connection allowed.
+ internal bool OnValidate_IpAddress(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
+ {
+ ValidateIP_EventArgs oArg = new ValidateIP_EventArgs(localEndPoint, remoteEndPoint);
+ if (this.ValidateIPAddress != null)
+ {
+ this.ValidateIPAddress(this, oArg);
+ }
+ return oArg.Validated;
+ }
+ ///
+ /// Raises event AuthUser.
+ ///
+ /// Reference to current IMAP session.
+ /// User name.
+ /// Password compare data,it depends of authentication type.
+ /// For md5 eg. md5 calculation hash.It depends of authentication type.
+ /// Authentication type.
+ /// Returns true if user is authenticated ok.
+ internal AuthUser_EventArgs 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;
+ }
+ ///
+ /// Raises event 'SubscribeMailbox'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox which to subscribe.
+ ///
+ 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;
+ }
+ ///
+ /// Raises event 'UnSubscribeMailbox'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox which to unsubscribe.
+ ///
+ 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;
+ }
+ ///
+ /// Raises event 'GetSubscribedMailboxes'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox reference.
+ /// Mailbox search pattern or mailbox.
+ ///
+ internal IMAP_Folders OnGetSubscribedMailboxes(IMAP_Session session, string referenceName, string mailBox)
+ {
+ IMAP_Folders retVal = new IMAP_Folders(session, referenceName, mailBox);
+ if (this.GetSubscribedFolders != null)
+ {
+ this.GetSubscribedFolders(session, retVal);
+ }
+
+ return retVal;
+ }
+ ///
+ /// Raises event 'GetMailboxes'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox reference.
+ /// Mailbox search pattern or mailbox.
+ ///
+ internal IMAP_Folders OnGetMailboxes(IMAP_Session session, string referenceName, string mailBox)
+ {
+ IMAP_Folders retVal = new IMAP_Folders(session, referenceName, mailBox);
+ if (this.GetFolders != null)
+ {
+ this.GetFolders(session, retVal);
+ }
+
+ return retVal;
+ }
+ ///
+ /// Raises event 'CreateMailbox'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox to create.
+ ///
+ 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;
+ }
+ ///
+ /// Raises event 'DeleteMailbox'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox which to delete.
+ ///
+ 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;
+ }
+ ///
+ /// Raises event 'RenameMailbox'.
+ ///
+ /// Reference to IMAP session.
+ /// Mailbox which to rename.
+ /// New 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;
+ }
+ ///
+ /// Raises event 'GetMessagesInfo'.
+ ///
+ /// Reference to IMAP session.
+ /// Folder which messages info to get.
+ ///
+ internal IMAP_eArgs_GetMessagesInfo OnGetMessagesInfo(IMAP_Session session, IMAP_SelectedFolder folder)
+ {
+ IMAP_eArgs_GetMessagesInfo eArgs = new IMAP_eArgs_GetMessagesInfo(session, folder);
+ if (this.GetMessagesInfo != null)
+ {
+ this.GetMessagesInfo(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ ///
+ /// Raises event GetMessageItems.
+ ///
+ /// Reference to IMAP session.
+ /// Message info what message items to get.
+ /// Specifies message items what must be filled.
+ ///
+ internal protected IMAP_eArgs_MessageItems OnGetMessageItems(IMAP_Session session, IMAP_Message messageInfo, IMAP_MessageItems_enum messageItems)
+ {
+ IMAP_eArgs_MessageItems eArgs = new IMAP_eArgs_MessageItems(session, messageInfo, messageItems);
+ if (this.GetMessageItems != null)
+ {
+ this.GetMessageItems(session, eArgs);
+ }
+ return eArgs;
+ }
+ ///
+ /// Raises event 'DeleteMessage'.
+ ///
+ /// Reference to IMAP session.
+ /// Message which to delete.
+ ///
+ internal string OnDeleteMessage(IMAP_Session session, IMAP_Message message)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), message);
+ if (this.DeleteMessage != null)
+ {
+ this.DeleteMessage(session, eArgs);
+ }
+
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Raises event 'CopyMessage'.
+ ///
+ /// Reference to IMAP session.
+ /// Message which to copy.
+ /// New message location.
+ ///
+ internal string OnCopyMessage(IMAP_Session session, IMAP_Message msg, string location)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), msg, location);
+ if (this.CopyMessage != null)
+ {
+ this.CopyMessage(session, eArgs);
+ }
+
+ return eArgs.ErrorText;
+ }
+ ///
+ /// Raises event 'StoreMessage'.
+ ///
+ /// Reference to IMAP session.
+ /// Folder where to store.
+ /// Message which to store.
+ /// Message data which to store.
+ ///
+ 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;
+ }
+ /*///
+ /// Raises event 'Search'.
+ ///
+ /// IMAP session what calls this search.
+ /// Folder what messages to search.
+ /// Matcher what must be used to check if message matches searching criterial.
+ ///
+ internal IMAP_eArgs_Search OnSearch(IMAP_Session session,string folder,IMAP_SearchMatcher matcher)
+ {
+ IMAP_eArgs_Search eArgs = new IMAP_eArgs_Search(session,folder,matcher);
+ if(this.Search != null){
+ this.Search(session,eArgs);
+ }
+
+ return eArgs;
+ }*/
+ ///
+ /// Raises event 'StoreMessageFlags'.
+ ///
+ /// Reference to IMAP session.
+ /// Message which flags to store.
+ ///
+ internal string OnStoreMessageFlags(IMAP_Session session, IMAP_Message msg)
+ {
+ Message_EventArgs eArgs = new Message_EventArgs(IMAP_Utils.Decode_IMAP_UTF7_String(session.SelectedMailbox), msg);
+ if (this.StoreMessageFlags != null)
+ {
+ this.StoreMessageFlags(session, eArgs);
+ }
+
+ return eArgs.ErrorText;
+ }
+ internal SharedRootFolders_EventArgs OnGetSharedRootFolders(IMAP_Session session)
+ {
+ SharedRootFolders_EventArgs eArgs = new SharedRootFolders_EventArgs(session);
+ if (this.GetSharedRootFolders != null)
+ {
+ this.GetSharedRootFolders(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ internal IMAP_GETACL_eArgs OnGetFolderACL(IMAP_Session session, string folderName)
+ {
+ IMAP_GETACL_eArgs eArgs = new IMAP_GETACL_eArgs(session, folderName);
+ if (this.GetFolderACL != null)
+ {
+ this.GetFolderACL(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ internal IMAP_SETACL_eArgs OnSetFolderACL(IMAP_Session session, string folderName, string userName, IMAP_Flags_SetType flagsSetType, IMAP_ACL_Flags aclFlags)
+ {
+ IMAP_SETACL_eArgs eArgs = new IMAP_SETACL_eArgs(session, folderName, userName, flagsSetType, aclFlags);
+ if (this.SetFolderACL != null)
+ {
+ this.SetFolderACL(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ internal IMAP_DELETEACL_eArgs OnDeleteFolderACL(IMAP_Session session, string folderName, string userName)
+ {
+ IMAP_DELETEACL_eArgs eArgs = new IMAP_DELETEACL_eArgs(session, folderName, userName);
+ if (this.DeleteFolderACL != null)
+ {
+ this.DeleteFolderACL(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ internal IMAP_GetUserACL_eArgs OnGetUserACL(IMAP_Session session, string folderName, string userName)
+ {
+ IMAP_GetUserACL_eArgs eArgs = new IMAP_GetUserACL_eArgs(session, folderName, userName);
+ if (this.GetUserACL != null)
+ {
+ this.GetUserACL(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ internal IMAP_eArgs_GetQuota OnGetUserQuota(IMAP_Session session)
+ {
+ IMAP_eArgs_GetQuota eArgs = new IMAP_eArgs_GetQuota(session);
+ if (this.GetUserQuota != null)
+ {
+ this.GetUserQuota(session, eArgs);
+ }
+
+ return eArgs;
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server/Server.EventsDeclarations.cs b/MailServer/IMAP/Server/Server/Server.EventsDeclarations.cs
new file mode 100644
index 0000000..5eaf57d
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.EventsDeclarations.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ public partial class Server
+ {
+ ///
+ /// Occurs when new computer connected to IMAP server.
+ ///
+ public event ValidateIPHandler ValidateIPAddress = null;
+ ///
+ /// Occurs when connected user tryes to authenticate.
+ ///
+ public event AuthUserEventHandler AuthUser = null;
+ ///
+ /// Occurs when server requests to subscribe folder.
+ ///
+ public event FolderEventHandler SubscribeFolder = null;
+ ///
+ /// Occurs when server requests to unsubscribe folder.
+ ///
+ public event FolderEventHandler UnSubscribeFolder = null;
+ ///
+ /// Occurs when server requests all available folders.
+ ///
+ public event FoldersEventHandler GetFolders = null;
+ ///
+ /// Occurs when server requests subscribed folders.
+ ///
+ public event FoldersEventHandler GetSubscribedFolders = null;
+ ///
+ /// Occurs when server requests to create folder.
+ ///
+ public event FolderEventHandler CreateFolder = null;
+ ///
+ /// Occurs when server requests to delete folder.
+ ///
+ public event FolderEventHandler DeleteFolder = null;
+ ///
+ /// Occurs when server requests to rename folder.
+ ///
+ public event FolderEventHandler RenameFolder = null;
+ ///
+ /// Occurs when server requests to folder messages info.
+ ///
+ public event MessagesEventHandler GetMessagesInfo = null;
+ ///
+ /// Occurs when server requests to delete message.
+ ///
+ public event MessageEventHandler DeleteMessage = null;
+ ///
+ /// Occurs when server requests to store message.
+ ///
+ public event MessageEventHandler StoreMessage = null;
+ /*///
+ /// Occurs when server requests to search specified folder messages.
+ ///
+ public event SearchEventHandler Search = null;*/
+ ///
+ /// Occurs when server requests to store message flags.
+ ///
+ public event MessageEventHandler StoreMessageFlags = null;
+ ///
+ /// Occurs when server requests to copy message to new location.
+ ///
+ public event MessageEventHandler CopyMessage = null;
+ ///
+ /// Occurs when server requests to get message items.
+ ///
+ public event MessagesItemsEventHandler GetMessageItems = null;
+ ///
+ /// Occurs when IMAP session has finished and session log is available.
+ ///
+ public event LogEventHandler SessionLog = null;
+ ///
+ /// Occurs when IMAP server requests shared root folders info.
+ ///
+ public event SharedRootFoldersEventHandler GetSharedRootFolders = null;
+ ///
+ /// Occurs when IMAP server requests folder ACL.
+ ///
+ public event GetFolderACLEventHandler GetFolderACL = null;
+ ///
+ /// Occurs when IMAP server requests to delete folder ACL.
+ ///
+ public event DeleteFolderACLEventHandler DeleteFolderACL = null;
+ ///
+ /// Occurs when IMAP server requests to set folder ACL.
+ ///
+ public event SetFolderACLEventHandler SetFolderACL = null;
+ ///
+ /// Occurs when IMAP server requests to get user ACL for specified folder.
+ ///
+ public event GetUserACLEventHandler GetUserACL = null;
+ ///
+ /// Occurs when IMAP server requests to get user quota.
+ ///
+ public event GetUserQuotaHandler GetUserQuota = null;
+ }
+}
diff --git a/MailServer/IMAP/Server/Server/Server.Properties.cs b/MailServer/IMAP/Server/Server/Server.Properties.cs
new file mode 100644
index 0000000..dc0419b
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.Properties.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ public partial class Server
+ {
+ ///
+ /// Gets or sets server supported authentication types.
+ ///
+ public SaslAuthTypes SupportedAuthentications
+ {
+ get { return m_SupportedAuth; }
+ set { m_SupportedAuth = value; }
+ }
+ ///
+ /// Gets or sets server greeting text.
+ ///
+ public string GreetingText
+ {
+ get { return m_GreetingText; }
+ set { m_GreetingText = value; }
+ }
+ ///
+ /// Gets or sets maximum allowed conncurent connections from 1 IP address. Value 0 means unlimited connections.
+ ///
+ public int MaxConnectionsPerIP
+ {
+ get { return m_MaxConnectionsPerIP; }
+ set { m_MaxConnectionsPerIP = value; }
+ }
+ ///
+ /// Maximum message size.
+ ///
+ public int MaxMessageSize
+ {
+ get { return m_MaxMessageSize; }
+ set { m_MaxMessageSize = value; }
+ }
+ ///
+ /// Gets active sessions.
+ ///
+ public new IMAP_Session[] Sessions
+ {
+ get
+ {
+ SocketServerSession[] sessions = base.Sessions;
+ IMAP_Session[] imapSessions = new IMAP_Session[sessions.Length];
+ sessions.CopyTo(imapSessions, 0);
+
+ return imapSessions;
+ }
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server/Server.Session.cs b/MailServer/IMAP/Server/Server/Server.Session.cs
new file mode 100644
index 0000000..e1f5eb4
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.Session.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MailServer.IMAP.Server
+{
+ public partial class Server
+ {
+ ///
+ /// Initialize and start new session here. Session isn't added to session list automatically,
+ /// session must add itself to server session list by calling AddSession().
+ ///
+ /// Connected client socket.
+ /// BindInfo what accepted socket.
+ protected override void InitNewSession(Socket socket, IPBindInfo bindInfo)
+ {
+ // Check maximum conncurent connections from 1 IP.
+ if (m_MaxConnectionsPerIP > 0)
+ {
+ lock (this.Sessions)
+ {
+ int nSessions = 0;
+ foreach (SocketServerSession s in this.Sessions)
+ {
+ IPEndPoint ipEndpoint = s.RemoteEndPoint;
+ if (ipEndpoint != null)
+ {
+ if (ipEndpoint.Address.Equals(((IPEndPoint)socket.RemoteEndPoint).Address))
+ {
+ nSessions++;
+ }
+ }
+
+ // Maimum allowed exceeded
+ if (nSessions >= m_MaxConnectionsPerIP)
+ {
+ socket.Send(System.Text.Encoding.ASCII.GetBytes("* NO Maximum connections from your IP address is exceeded, try again later !\r\n"));
+ socket.Shutdown(SocketShutdown.Both);
+ socket.Close();
+ return;
+ }
+ }
+ }
+ }
+
+ string sessionID = Guid.NewGuid().ToString();
+ SocketEx socketEx = new SocketEx(socket);
+ if (LogCommands)
+ {
+ socketEx.Logger = new SocketLogger(socket, this.SessionLog);
+ socketEx.Logger.SessionID = sessionID;
+ }
+ IMAP_Session session = new IMAP_Session(sessionID, socketEx, bindInfo, this);
+ }
+ }
+}
diff --git a/MailServer/IMAP/Server/Server/Server.cs b/MailServer/IMAP/Server/Server/Server.cs
new file mode 100644
index 0000000..85bae09
--- /dev/null
+++ b/MailServer/IMAP/Server/Server/Server.cs
@@ -0,0 +1,24 @@
+using MailServer.Misc.SocketServer;
+using MailServer.Misc;
+using System.Net;
+
+namespace MailServer.IMAP.Server
+{
+ ///
+ /// IMAP server componet.
+ ///
+ public partial class Server : SocketServer
+ {
+ private int m_MaxConnectionsPerIP = 0;
+ private SaslAuthTypes m_SupportedAuth = SaslAuthTypes.All;
+ private string m_GreetingText = "";
+ private int m_MaxMessageSize = 1000000;
+ ///
+ /// Defalut constructor.
+ ///
+ public Server() : base()
+ {
+ this.BindInfo = new IPBindInfo[]{new IPBindInfo("",IPAddress.Any,143,SslMode.None,null)};
+ }
+ }
+}
diff --git a/MailServer/MailServer.csproj b/MailServer/MailServer.csproj
index e3dc955..c6c9ef3 100644
--- a/MailServer/MailServer.csproj
+++ b/MailServer/MailServer.csproj
@@ -44,40 +44,80 @@
-
-
-
-
- AuthUser_EventArgs.cs
-
-
+
+
+
+
+
+
+
+
+
Component
-
- Server.cs
+
+
+ Code
-
- Server.cs
+
+ Code
+
+
+ Code
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Component
-
- Server.cs
+
Component
-
- Server.cs
+
Component
-
- Server.cs
+
Component
-
- Server.cs
+
Component
-
- Server.cs
+
+ Component
+
+
+ Component
+
+
+
Component
@@ -89,6 +129,10 @@
+
+
+
+