C# mailkit shows error when inbox is empty - c#

Below code works well, but the code will be checked every 5000ms, when the inbox folder is empty system shows an error.
We need to check inbox every 5000ms even it be empty we need to read it.
using (var client = new ImapClient())
{
client.Connect(EXT_IMAP_SERVER, EXT_IMAP_PORT, true);
client.Authenticate(EXT_USERNAME, EXT_PASSWORD);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadWrite);
var LAST_MSG = inbox.GetMessage (inbox.Count - 1);
DATA = LAST_MSG.Subject;
if(DATA != null); // this condition didn't solve our issue
{
inbox.AddFlags(inbox.Count - 1, MessageFlags.Deleted, true);
}
client.Disconnect(true);
}
**ERROR:
System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'index')**

Instead of checking if last message was null, check the Count of messages and if it's more than 0, then you do what you need to do.
using (var client = new ImapClient())
{
client.Connect(EXT_IMAP_SERVER, EXT_IMAP_PORT, true);
client.Authenticate(EXT_USERNAME, EXT_PASSWORD);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadWrite);
// THIS
if(inbox.Count > 0) {
var LAST_MSG = inbox.GetMessage (inbox.Count - 1);
// .....
}
client.Disconnect(true);
}

Related

How to process userAccountControl array to know if a user is enabled?

I have to query a domain controller for the enabled state of a specific account. I have to do this with LDAP because the machine making the query is not joined to the domain.
My code is as follows:
public async Task<bool> IsUserEnabled(string samAccountName)
{
var server = "[OMITTED]";
var username = "[OMITTED]";
var password = "[OMITTED]";
var domain = "[OMITTED]";
var query = $"(&(objectCategory=person)(SAMAccountName={samAccountName}))";
var credentials = new NetworkCredential(username, password, domain);
var conn = new LdapConnection(server);
try
{
conn.Credential = credentials;
conn.Timeout = new TimeSpan(0, 5, 0);
conn.Bind();
var domain_parts = domain.Split('.');
if (domain_parts.Length < 2)
{
throw new ArgumentException($"Invalid domain name (name used: \"{domain}\").");
}
var target = $"dc={domain_parts[0]},dc={domain_parts[1]}";
var search_scope = SearchScope.Subtree;
var search_request = new SearchRequest(target, query, search_scope, null);
var temp_response = await Task<DirectoryResponse>.Factory.FromAsync(
conn.BeginSendRequest,
(iar) => conn.EndSendRequest(iar),
search_request,
PartialResultProcessing.NoPartialResultSupport,
null);
var response = temp_response as SearchResponse;
if (response == null)
{
throw new InvalidOperationException("LDAP server answered NULL.");
}
var entries = response.Entries;
var entry = entries[0].Attributes["userAccountControl"];
var values = entry.GetValues(typeof(byte[])).First();
var output = (byte[])values;
var result = false;
return result;
}
catch (LdapException lex)
{
throw new Exception("Error querying LDAP server", lex);
}
catch (Exception ex)
{
throw new Exception("Unknown error", ex);
}
finally
{
conn.Dispose();
}
}
In the lines
var values = entry.GetValues(typeof(byte[])).First();
var output = (byte[])values;
I get a byte array with what I think is the current user's flags, but I don't know how to process this data in order to know if the user is enabled or not.
Most advice I found is that I should convert this to an integer and OR-it with 2 in order to know, but Convert.ToInt32(output) throws an exception (Unable to cast object of type 'System.Byte[]' to type 'System.IConvertible'.) and using BitConverter.ToInt32(output, 0) also throws an exception (Destination array is not long enough to copy all the items in the collection. Check array index and length.)
One thing I noticed is that the array is 3 bytes long, and I don't know if that's right.
Ive queried 2 users: one (we know this one is enabled in the AD) returns a [53, 49, 50] array, and the other one (this one is disabled) returns [53, 52, 54].
Where do I go from here?
AFAIK the userAccountControl attribute is a 4-byte integer value (a bit mask), so why would you try and convert this into a byte array at all?
var entry = entries[0].Attributes["userAccountControl"];
// return True if the UF_ACCOUNT_DISABLE flag (bit no. 1) is not set
// meaning the user is Enabled
return (entry & 2) == 0;
See userAccountControl

MailKit: Mail client is showing message as received at time of move and not retaining original flags

I'm working writing a script to migrate emails from one account to another. I'm having two issues.
The messages are moving over, but the mail client is showing their
received date as the date/time it was moved (in the list of messages
view), not the date/time that's showing in the message header. I'm
guessing a file date isn't being retained?
The message flags aren't being copied over. Such as the message
being read already. I'd like to see all of the flags being passed...basically I need to move the message as it exists on the previous account.
protected void CopyBtn_Click(object sender, EventArgs e)
{
try
{
ImapClient Client = new ImapClient();
ImapClient Client2 = new ImapClient();
Client.Connect(SourceHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client.Authenticate(SourceUsernameBox.Text.Trim(), SourcePasswordBox.Text.Trim());
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Connect(DestinationHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client2.Authenticate(DestinationUsernameBox.Text.Trim(), DestinationPasswordBox.Text.Trim());
Client2.Inbox.Open(FolderAccess.ReadWrite);
var folders = Client.GetFolders(Client.PersonalNamespaces[0]);
//move all messages in folders & create folders if necessary
foreach (var folder in folders)
{
folder.Open(FolderAccess.ReadWrite);
var uids = folder.Search(SearchQuery.All);
foreach (var uid in uids)
{
var folders2 = Client2.GetFolders(Client2.PersonalNamespaces[0]);
var message = folder.GetMessage(uid);
string currentFolder = folder.ToString().Replace("INBOX.", ""); //Remove the 'INBOX.' text that's getting prepended by cPanel/Dovecot
var toplevel = Client2.GetFolder(Client2.PersonalNamespaces[0]);
var folderExists = FindFolder(toplevel, currentFolder);
if (folderExists == null)
toplevel.Create(currentFolder, true);
Client2.GetFolder(currentFolder).Append(message);
}
}
//move inbox messages
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Inbox.Open(FolderAccess.ReadWrite);
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
label1.Text = "Finished Successfully.";
label1.ForeColor = System.Drawing.Color.Green;
}
catch (Exception ex)
{
label1.Text = ex.Message;
label1.ForeColor = System.Drawing.Color.Red;
}
}
You need to use one of the other Append() methods that takes a MessageFlags argument and a DateTimeOffset argument to specify the timestamp for when the message arrived.
But in order to get that information, you will also need to Fetch() that metadata for each of the messages.
Here's how I would change your loop:
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
Fixed:
var uids = Client.Inbox.Search (SearchQuery.All);
var items = Client.Inbox.Fetch (uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
foreach (var item in items)
{
var message = Client.Inbox.GetMessage (item.UniqueId);
Client2.Inbox.Append (message, item.Flags.Value, item.InternalDate.Value);
}
Like the OP, I am having the same issue. I used the solution provided by #jstedfast but the destination account still shows the copied email having the date and time of the copy and not the original date and time. Below is my code. Any suggestions would be greatly appreciated!
Edit (12/21/2021):
I figured out the cause of this issue! I had initially tried copying over a bunch of emails without using the flags that retains the date and time. I didn't know that would be required. So I then implemented the changes necessary to use those flags and before running the program again, I deleted all of the original emails that I had copied over from the previous version of my program, but I never emptied the trash in gmail. So although I was copying over the messages again, it must have just used the original copied messages and just removed the deleted flag. Once I emptied the gmail trash and ran my program again, the emails copied over correctly while retaining the original received date.
private void CopyEmailsAndFolders()
{
try
{
ImapClient yahooEmailAccnt = connectToEmailServerAndAccount(SrcEmailAccount.ImapServer, SrcEmailAccount.LogFileName, SrcEmailAccount.AccountUserName, SrcEmailAccount.AccountPassword);
ImapClient gmailEmailAccnt = connectToEmailServerAndAccount(DestEmailAccount.ImapServer, DestEmailAccount.LogFileName, DestEmailAccount.AccountUserName, DestEmailAccount.AccountPassword);
try
{
List<string> yahooFoldersList = getFoldersList(yahooEmailAccnt);
//loop through each of the folders in the source (Yahoo) email account
foreach (string folderName in yahooFoldersList)
{
if (folderName.ToLower() == "inbox")
{
continue;
}
var gmailFolder = gmailEmailAccnt.GetFolder(folderName);
var yahooFolder = yahooEmailAccnt.GetFolder(folderName);
yahooFolder.Open(FolderAccess.ReadOnly);
//get a list of all email UID's
var uids = yahooFolder.Search(SearchQuery.All);
//now get a list of all the items in the folder to include the critical date time properties we want to preserve
var items = yahooFolder.Fetch(uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
//now loop through all of the emails we found in the current folder and copy them to the destination (gmail) email account
foreach (var item in items)
{
var message = yahooFolder.GetMessage(item.UniqueId);
gmailFolder.Append(message, item.Flags.Value, item.InternalDate.Value);
}
}
}
catch (Exception fEx)
{
MessageBox.Show(fEx.Message);
}
finally
{
yahooEmailAccnt.Disconnect(true);
gmailEmailAccnt.Disconnect(true);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private ImapClient connectToEmailServerAndAccount(string imapServer, string logFile, string userName, string password)
{
ImapClient newEmailClient = new ImapClient();
try
{
newEmailClient = new ImapClient(new ProtocolLogger(logFile));
newEmailClient.Connect(imapServer, 993, SecureSocketOptions.SslOnConnect);
newEmailClient.Authenticate(userName, password);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return newEmailClient;
}
private List<string> getFoldersList(ImapClient emailClient)
{
List<string> foldersList = new List<string>();
try
{
// Get the first personal namespace and list the toplevel folders under it.
var personal = emailClient.GetFolder(emailClient.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders(false))
{
foldersList.Add(folder.Name);
Console.WriteLine("[folder] {0}", folder.Name);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return foldersList;
}

MailKit: How to download all attachments locally from a MimeMessage

I have looked at other examples online, but I am unable to figure out how to download and store ALL the attachments from a MimeMessage object.
I did look into the WriteTo(), but I could not get it to work.
Also wondering whether attachments will be saved according to the original file name, and type inside the email.
Here is what I have so far:
using (var client = new ImapClient())
{
client.Connect(Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove(Constant.GoogleOAuth);
client.Authenticate(Constant.GoogleUserName, Constant.GenericPassword);
if (client.IsConnected == true)
{
FolderAccess inboxAccess = client.Inbox.Open(FolderAccess.ReadWrite);
IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder);
IList<UniqueId> uids = client.Inbox.Search(SearchQuery.All);
if (inboxFolder != null & inboxFolder.Unread > 0)
{
foreach (UniqueId msgId in uids)
{
MimeMessage message = inboxFolder.GetMessage(msgId);
foreach (MimeEntity attachment in message.Attachments)
{
//need to save all the attachments locally
}
}
}
}
}
This is all explained in the FAQ in the "How do I save attachments?" section.
Here is a fixed version of the code you posted in your question:
using (var client = new ImapClient ()) {
client.Connect (Constant.GoogleImapHost, Constant.ImapPort, SecureSocketOptions.SslOnConnect);
client.AuthenticationMechanisms.Remove (Constant.GoogleOAuth);
client.Authenticate (Constant.GoogleUserName, Constant.GenericPassword);
client.Inbox.Open (FolderAccess.ReadWrite);
IList<UniqueId> uids = client.Inbox.Search (SearchQuery.All);
foreach (UniqueId uid in uids) {
MimeMessage message = client.Inbox.GetMessage (uid);
foreach (MimeEntity attachment in message.Attachments) {
var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;
using (var stream = File.Create (fileName)) {
if (attachment is MessagePart) {
var rfc822 = (MessagePart) attachment;
rfc822.Message.WriteTo (stream);
} else {
var part = (MimePart) attachment;
part.Content.DecodeTo (stream);
}
}
}
}
}
A few notes:
There's no need to check if client.IsConnected after authenticating. If it wasn't connected, it would have thrown an exception in the Authenticate() method. It would have thrown an exception in the Connect() method as well if it didn't succeed. There is no need to check the IsConnected state if you literally just called Connect() 2 lines up.
Why are you checking inboxFolder.Unread if you don't even use it anywhere? If you just want to download unread messages, change your search to be SearchQuery.NotSeen which will give you only the message UIDs that have not been read.
I removed your IMailFolder inboxFolder = client.GetFolder(Constant.InboxFolder); logic because you don't need it. If you are going to do the SEARCH using client.Inbox, then don't iterate over the results with a different folder object.

How to get all the messages from the Imapclient folder?

I intended to get all my messages in the Inbox folder and put them into the datagridview component.But the sentence "var message = client.Inbox.GetMessage(uids.Count - i - 1);" throws an exception:The IMAP server did not return the requested message. Is there anything wrong with my code?
//get a imapclient and connect to the server
string loginemail = UserInfo.LoginEmail;
string password = UserInfo.LoginPassword;
var client = new ImapClient();
client.Connect("imap.qq.com", 993, SecureSocketOptions.SslOnConnect);
client.Authenticate(loginemail, password);
client.Inbox.Open(FolderAccess.ReadOnly);
var uids = client.Inbox.Search(SearchQuery.All);
//get all the messages from the specified folder
for (int i = 0; i < uids.Count; i++)
{
dataGridView1.Rows.Add(1);
var message = client.Inbox.GetMessage(uids.Count - i - 1);
dataGridView1.Rows[i].Cells[0].Value = message.From.ToString();
if (message.Subject != null) { dataGridView1.Rows[i].Cells[1].Value = message.Subject.ToString(); }
else { dataGridView1.Rows[i].Cells[1].Value = ""; }
dataGridView1.Rows[i].Cells[2].Value = message.Date.ToString();
}
The only way to figure out the problem is to follow the directions in the MailKit FAQ to get a protocol log and look to see what the server is sending as a reply.

MailSystem.Net Delete Message, IndexOnServer Property = 0

I'm using MailSystem.NET and trying to delete a message from the server. The problem is the IndexOnServer property is 0, and I get the following error:
Command "store 0 +flags.silent (\Deleted)" failed : 121031084812790 BAD Error in IMAP command STORE: Invalid messageset
Here's my code:
Imap4Client client = new Imap4Client();
client.Connect(account.Server, account.Username, account.Password);
var inbox = client.SelectMailbox("Inbox");
MessageCollection messages = inbox.SearchParse("SINCE " + DateTime.Now.AddHours(-hours).ToString("dd-MMM-yyyy"));
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer, true);
}
How do I get the correct index of the message so I can delete it?
Edit:
The problem with the suggestions using the standard 1-based loop is that the counter index won't be in sync with the message index, since in my case I'm searching to retrieve a specific subset of messages only (as I understand it).
Thank you.
You can try deleting by the UID which should be more reliable and unique to each message. This has worked well for me in the past.
Edit: Since deleting the message causes all the indexes to shift down by one, you can use two separate counters. One to keep track of when you have iterated through the entire box (messagesLeft) and the other will keep track of the current message index which will be decreased by 1 if a message is deleted (since it would move up one place in line).
Mailbox box = client.AllMailboxes["inbox"];
Fetch fetch = box.Fetch;
int messagesLeft = box.Count;
int msgIndex = 0;
while (messagesLeft > 0)
{
msgIndex++;
messagesLeft--;
Message email = fetch.MessageObject(msgIndex);
if (criteria)
{
box.UidDeleteMessage(fetch.Uid(msgIndex), true);
msgIndex--;
}
}
In response to your comment, here is a fuller example of how you can use the UID for deletion without being concerned with the numeric position / index.
class Email
{
int UID { get; set; }
DateTime Sent { get; set; }
public string Body { get; set; }
// put whichever properties you will need
}
List<Email> GetEmails(string mailbox);
{
Mailbox box = client.AllMailboxes[mailbox];
Fetch fetch = box.Fetch;
List<Email> list = new List<Email>();
for (int x = 1; x <= box.MessageCount; x++)
{
Message msg = fetch.MessageObject(x);
list.Add(new Email() { } // set properties from the msg object
}
return list;
}
void DeleteEmail(Email email, string mailbox)
{
Mailbox box = client.AllMailboxes[mailbox];
box.UidDeleteMessage(email.Uid, true);
}
static void Main()
{
List<Email> emails = GetEmails("inbox");
emails = emails.Where(email => email.Sent < DateTime.Now.AddHours(-hours))
foreach (Email email in emails)
DeleteEmail(email);
}
This is official example from documantation.
instead of inbox.search("all") change it to your search query, etc.
http://mailsystem.codeplex.com/discussions/269304
//action_id is the Message.MessageId of the email
//action_flag is the Gmail special folder to move to (Trash)
//copying to Trash removes the email from the inbox, but it can't be moved back once done, even from the web interface
public static void move_msg_to(string action_id, string action_flag)
{
Imap4Client imap = new Imap4Client();
imap.ConnectSsl("imap.gmail.com", 993);
imap.Login("heythatsme#gmail.com", "heythatsmypassword");
imap.Command("capability");
Mailbox inbox = imap.SelectMailbox("inbox");
int[] ids = inbox.Search("ALL");
if (ids.Length > 0)
{
Message msg = null;
for (var i = 0; i < ids.Length; i++)
{
msg = inbox.Fetch.MessageObject(ids[i]);
if (msg.MessageId == action_id)
{
imap.Command("copy " + ids[i].ToString() + " [Gmail]/" + action_flag);
break;
}
}
}
}
just add 1 to the message index.
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer + 1, true);
}
Try deleting last message first then second last and then come to delete first message.
IF you delete and expunge have been set then message number changes.

Categories