How to Process NDR emails from Exchange Server in C#? - c#

Currently my application has ability to process incoming emails and bounce then back if it does not match with given criteria in code. However, I want to add up another type of email to Process which are NDR "Non-Delivery Reports" from Microsoft Exchange Server. So my application do not responsd/Bounce back NDR to exchange server which cause a loop between my Mailbox and Exchange Server.
Following method triggers when Invalid doesn't have a specific
private static void ProcessInvidMsgWithoutNo(string sMsgFrom, string sFromEmail, EmailMsg sMsgReceived, EmailMessage message)
{
EmailMsg.MoveToInvalid(message);
sMsgReceived.IsValid = false;
SaveMsgReceived(sMsgReceived, 0, string.Empty);
if (!sFromEmail.Equals(string.Empty))
{
ResponseForInvidMsg(sFromEmail);
}
else
{
curLog.WriteLog(string.Format(CultureInfo.CurrentCulture, MsgEMChecker27, sMsgFrom));
}
}
Following Method triggers to respond incoming Invalid message as stated above.
private static void ResponseForInvidMsg(string sFromEmail)
{
string tErrSubjectMsg = String.Format(CultureInfo.CurrentCulture, "{0}\\Resource\\MsgErrorSubjectAck.html", Entity.GetSetting("DocRootDir"));
StringBuilder htmlText = new StringBuilder();
FileStream fsFile = new FileStream(tErrSubjectMsg, FileMode.Open);
if (fsFile != null)
{
StreamReader reader = new StreamReader(fsFile, Encoding.Default);
string text;
do
{
text = reader.ReadLine();
if ((text != null) && (text != ""))
htmlText.Append(text + "\n");
} while (text != null);
reader.Close();
fsFile.Close();
fsFile = null;
}
else
htmlText.Append("hello");
string tToCustomerSubject = ReplyForInvalid;
string tMessage = htmlText.ToString();
EmailMsg emTo = new EmailMsg(string.Empty, sFromEmail, tToCustomerSubject, tMessage);
emTo.MsgType = EmailMsg.TypeSentCustomer;
emTo.Send(false); //Not save but CC to generic email box
}
Please, help me to find a way where I can stop my code to respond Exchange Server NDR. Thanks

The place to start would be to check the ItemClass of the message see https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-asemail/51d84da6-a2da-41e9-8ca7-eb6c4e72c28d . NDR's, delivery report etc should have a prefix of Report eg
REPORT.IPM.NOTE.NDR Non-delivery report for a standard message.
REPORT.IPM.NOTE.DR Delivery receipt for a standard message.

Related

StreamReader doesn't retrieve everything from NetworkStream (TCP and C#)

I'm working on a project, where you have to retrieve data from a server and display it in the UI. It's a newsgroups server, that contains about 250 groups in total.
The output from the server SHOULD as far as I understand, be stored in a NetworkStream object and is read from a StreamReader which saves each line into a string.
This works, but unfortunately it seems like it doesn't finish reading everything before finishing the method call.
Next time I call another command and read it from the StreamReader it then returns the rest of the output from the previous command.
I've been struggling with this for hours now, and can't see how to fix this.
This is my code:
public ObservableCollection<Newsgroup> GetNewsGroups()
{
ObservableCollection<Newsgroup> newsgroups = new ObservableCollection<Newsgroup>();
if(connectionStatus.Equals(ConnectionStatus.CONNECTED) && loginStatus.Equals(LoginStatus.LOGGED_IN))
{
byte[] sendMessage = Encoding.UTF8.GetBytes("LIST\n");
// Write to the server
ns.Write(sendMessage, 0, sendMessage.Length);
Console.WriteLine("Sent {0} bytes to server...", sendMessage.Length);
ns.Flush();
// b) Read from the server
reader = new StreamReader(ns, Encoding.UTF8);
// We want to ignore the first line, as it just contains information about the data
string test = reader.ReadLine();
Console.WriteLine(test);
string recieveMessage = "";
if (ns.CanRead)
{
while (reader.Peek() >= 0)
{
recieveMessage = reader.ReadLine();
Console.WriteLine("Got this message {0} back from the server", recieveMessage);
// This part will simply remove the annoying numbers after the newsgroup name
int firstSpaceIndex = recieveMessage.IndexOf(" ");
string refactoredGroupName = recieveMessage.Substring(0, firstSpaceIndex);
newsgroups.Add(new Newsgroup { GroupName = refactoredGroupName });
}
}
}
return newsgroups;
}
I'd be interested to see what information about the data you are throwing away on the first line (what's in "test" variable). If it tells you how many bytes are coming your way, you should use that information to retrieve the correct amount of data instead of Peek.
If the last line contains a single period, change your while loop to look like this instead:
recieveMessage = reader.ReadLine();
while (recieveMessage != ".")
{
Console.WriteLine("Got this message {0} back from the server", recieveMessage); // This part will simply remove the annoying numbers after the newsgroup name int
firstSpaceIndex = recieveMessage.IndexOf(" ");
string refactoredGroupName = recieveMessage.Substring(0, firstSpaceIndex);
newsgroups.Add(new Newsgroup { GroupName = refactoredGroupName });
recieveMessage = reader.ReadLine();
}

C# Saving Exchange .EML file from within Windows Service

I'm currently writing a Windows Service to log in to a specific Exchange account, get any new emails, parse them, and save the email in the appropriate folder.
Everything is working perfectly, except saving the email.
Relevant code blocks (try / catch blocks and irrelevant stuff removed to keep it short):-
Set up the Service
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials(emailAddress, password);
service.AutodiscoverUrl(emailAddress, RedirectionUrlValidationCallback);
CheckForNewEmails(service);
Getting and checking new emails
private static void CheckForNewEmails(ExchangeService service)
{
int offset = 0;
int pageSize = 50;
bool more = true;
ItemView view = new ItemView(pageSize, offset, OffsetBasePoint.Beginning);
view.PropertySet = PropertySet.IdOnly;
FindItemsResults<Item> findResults;
List<EmailMessage> emails = new List<EmailMessage>();
while (more)
{
findResults = service.FindItems(WellKnownFolderName.Inbox, view);
foreach (var item in findResults.Items)
{
emails.Add((EmailMessage)item);
}
more = findResults.MoreAvailable;
if (more)
{
view.Offset += pageSize;
}
}
if (emails.Count > 0)
{
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
var mailItems = new List<DatabaseService.MailItem>();
var dbService = new DatabaseService();
var defaultUser = dbService.GetDefaultUser(defaultUserID);
foreach (var email in emails)
{
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
}
}
}
Finding who it should be linked to
public void FindLinks(ExchangeService service, ref MailItem mailItem, User defaultUser)
{
string address = mailItem.Sender;
// get file hash
var tempPath = Path.GetTempPath();
var fileName = GetFilenameFromSubject(mailItem);
var fullName = Path.Combine(tempPath, fileName);
SaveAsEML(service, mailItem, fullName);
var sha = new SHA256Managed();
mailItem.Hash = Convert.ToBase64String(sha.ComputeHash(File.OpenRead(fullName)));
File.Delete(fullName);
using (var db = DatabaseHelpers.GetEntityModel())
{
// Do all the linking stuff
}
}
And finally, the problem area: Saving the file to disk (in this case to a temporary folder so I can get the file hash, to check it's not a duplicate (e.g. CC'd etc))
Looking through StackOverflow and various other sources, there seem to be two ways to do this:-
1) Using the MailItem.Load method
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
mailItem.mail.Load(new PropertySet(ItemSchema.MimeContent));
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
Using the above code, the file is created and has the correct content.
However, attempting to access any of the email properties AFTER this point results in a Null Exception crash (big crash too, no Try/Catch or UnhandledException trap will pick it up, just kills the Service)
In the above code, this line crashes the entire service:-
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
Specifically, referencing email.DateTimeSent
I added in some diagnostic code directly before this line:-
if (email == null) { Log it's null; } else { Log NOT null;}
if (email.DateTimeSent == null) { Log it's null; } else { Log NOT null; }
The first line logs NOT null, so email still exists.
However, the second line instantly crashes with a null exception error, without logging anything.
If I comment out the line SaveAsEML(service, mailItem, fullName); from FindLinks then everything works perfectly (except of course the file isn't saved).
2) Binding a Property Set
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
Doing it this way, nothing crashes, it goes through every email just fine, and can reference email.DateTimeSent and all the other properties.
Unfortunately, it creates zero-length files, no content.
Been banging my head against the wall for hours now over this (took me an hour of adding diagnostics everywhere just to track down the crash happening when referencing properties), and it's undoubtedly something trivial I've overlooked, so if some kind soul could point out my stupidity I'd be most grateful!
Edit:
I was able to work around the above problem easily enough by simply saving the value of the properties before using them:-
foreach (var email in emails)
{
var dateTimeSent = email.DateTimeSent;
var sender = email.Sender;
var subject = email.Subject;
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID,
dateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
sender,
subject,
mailItem.Hash ?? "No Hash",
mailItem.LinkString ?? "No Linkstring"
));
}
This allowed me to use the MailItem.Load method which saved the file, and thus do what I was after in this specific case.
However there are other things this Service will need to do, and I really don't want to have to save a copy of every single property I'll need to access.
The reason this would fail
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
Is that you have used Bind to Load the message with the MimeContent in the email variable and then you haven't used that. mailItem.mail won't be linked at all to email variable created as part of this operation (even though they are the same object on the server). Locally these are just two separate variables. EWS is client/server so you make a request and the Managed API will return a local object that represents the result of the operation. But that object is disconnected hence when you do the bind above it just generates another client object to represent the result of that operation. eg so the above should have been
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(email.MimeContent.Content, 0, email .MimeContent.Content.Length);
}
}

How to programmatically click a .zip link in an email message body

This is probably very simple but I am extremely new to coding anything, sorry in advance.
Currently I have a button4 that will read through my inbox for messages with a certain subject, if condition is met it displays the messages first class properties in a listview but I want it to also download the link found in each email.
It is a .zip link that when the link is clicked from inside the email it will download the zip. I want it to automatically download all links found when button4 is clicked.
I will show my button4 code and then an example of what the email is.
button4 code:
private void button4_Click(object sender, EventArgs e)
{
EmailConnect();
TimeSpan ts = new TimeSpan(0, -2, 0, 0);
DateTime date = DateTime.Now.Add(ts);
SearchFilter.IsGreaterThanOrEqualTo filter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, date);
if (service != null)
{
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, filter, new ItemView(50));
foreach (Item item in findResults)
{
EmailMessage message = EmailMessage.Bind(service, item.Id);
string subject = message.Subject.ToString();
if (subject.Contains("NFIRS File Validation"))
{
ListViewItem listitem = new ListViewItem(new[]
{message.DateTimeReceived.ToString(), message.From.Name.ToString() + "(" + message.From.Address.ToString() + ")", message.Subject, ((message.HasAttachments) ? "Yes" : "No")});
lstMsg.Items.Add(listitem);
}
}
if (findResults.Items.Count <= 0)
{
lstMsg.Items.Add("No Messages found!!");
}
}
}
Example email:
NFIRS File Validation
The NFIRS File Validation service has completed processing your files. Please follow this link to retrieve the zip file containing your results.
https://www.nfirs.fema.gov/biarchive/xxxxxxxxx_xxxxxxxxx.zip
This file will be deleted after 28 days.
If you have any questions, please do not reply to this email. Instead, please contact the NFIRS Support Center.
This is basically a duplicate of the link #DonBoitnott commented the only extra steps I am taking is putting the body of each email into a property list parsing it and making sure it saves as the same filename as the URL had in the original email
private void handleLinks(List<EmailProperties> properties)
{
using (WebClient client = new WebClient())
{
foreach (var prop in properties)
{
string link = searchForLink(prop.Body);
string fileName = MyExtensions.Between(link, "https://www.nfirs.fema.gov/biarchive/", ".zip");
string saveTo = string.Format((#"C:\Users\Foo\Downloads\{0}.zip"), fileName);
prop.Name = fileName;
client.DownloadFile(link, saveTo);
}
}
}
private string searchForLink(string body)
{
return MyExtensions.Between(body, "results.\r\n\r\n", "\r\n\r\nThis file will");
}

C# inserting a jpg file into an Outlook email message

This is my first time posting a question although I have been lurking and learning from all of you for a while now. I have been developing a C# Windows Form application that automates several day to day activities. One of the simplest pieces of this application is really giving me a hard time.
I need to insert two jpg files into an Email that I am responding to. I can accomplish this by pulling the files directly from the drive but would prefer them to be stored as a resource in the executable. This way I can pass the EXE to others and they can use it also. Here is an example of the code that works when it is stored locally. what I would prefer to do however is replace #"H:\ISOIR\PhishMeIcon.jpg" with Resource1.PhishMeIconJPG . I have seen several discussion about streams and converting the file to Byte format but this does not seem to interact well with Outlook.
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Make sure you have the Email reporting the incident Open");
string inc_num;
inc_num = incident_number.Text;
Microsoft.Office.Interop.Outlook.Application aPP = new Microsoft.Office.Interop.Outlook.Application(1);
Microsoft.Office.Interop.Outlook.MailItem mail = (Microsoft.Office.Interop.Outlook.MailItem)aPP.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
Microsoft.Office.Interop.Outlook.Folder f = aPP.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderDrafts) as Microsoft.Office.Interop.Outlook.Folder;
Microsoft.Office.Interop.Outlook.Inspector inSpect = null;
Microsoft.Office.Interop.Outlook.MailItem sMail = null;
Microsoft.Office.Interop.Outlook.MailItem rMail = null;
Microsoft.Office.Interop.Outlook.Attachment phishICO = null;
try
{
inSpect = aPP.ActiveInspector();
sMail = inSpect.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
rMail = sMail.ReplyAll();
phishICO = rMail.Attachments.Add(#"H:\ISOIR\PhishMeIcon.jpg", Microsoft.Office.Interop.Outlook.OlAttachmentType.olEmbeddeditem, null, "name");
string imageCid = "PhishMeIcon.jpg#123";
phishICO.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E", imageCid);
rMail.Subject = ("INC- ") + inc_num;
rMail.HTMLBody = Resource1.SPAM_Response_P1 + String.Format("<body><img src=\"cid:{0}\"></body>", imageCid) + Resource1.SPAM_Response_P2 + rMail.HTMLBody;
rMail.HTMLBody = rMail.HTMLBody.Replace("XXXX", inc_num);
rMail.Save();
MessageBox.Show("Your Email has been saved in your DRAFT Folder for review");
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message,
"An exception is occured in the code of add-in.");
}
finally
{
if (sMail != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(sMail);
if (rMail != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(rMail);
if (inSpect != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(inSpect);
}
}

How to send arbitrary FTP commands in C#

I have implemented the ability to upload, download, delete, etc. using the FtpWebRequest class in C#. That is fairly straight forward.
What I need to do now is support sending arbitrary FTP commands such as
quote SITE LRECL=132 RECFM=FB
or
quote SYST
Here's an example configuration straight from our app.config:
<!-- The following commands will be executed before any uploads occur -->
<extraCommands>
<command>quote SITE LRECL=132 RECFM=FB</command>
</extraCommands>
I'm still researching how to do this using FtpWebRequest. I'll probably try WebClient class next. Anyone can point me in the right direction quicker? Thanks!
UPDATE:
I've come to that same conclusion, as of .NET Framework 3.5 FtpWebRequest doesn't support anything except what's in WebRequestMethods.Ftp.*. I'll try a third party app recommended by some of the other posts. Thanks for the help!
I don't think it can be done with FtpWebRequest... The only way to specify a FTP command is through the Method property, and the documentation states :
Note that the strings defined in the WebRequestMethods.Ftp class are the only supported options for the Method property. Setting the Method property to any other value will result in an ArgumentException exception.
SITE and SYST are not among the predefined options, so I guess you're stuck...
Don't waste time to try the WebClient class, it will give you even less flexibility than FtpWebRequest.
However, there are plenty of third-party FTP implementation, open source or commercial, and I'm pretty sure some of them can handle custom commands...
The FtpWebRequest won't help you as Thomas Levesque has said in his answer. You can use some third party solutions or the following, simplified TcpClient based code which I have refactored from an answer written in Visual Basic:
public static void SendFtpCommand()
{
var serverName = "[FTP_SERVER_NAME]";
var port = 21;
var userName = "[FTP_USER_NAME]";
var password = "[FTP_PASSWORD]"
var command = "SITE CHMOD 755 [FTP_FILE_PATH]";
var tcpClient = new TcpClient();
try
{
tcpClient.Connect(serverName, port);
Flush(tcpClient);
var response = TransmitCommand(tcpClient, "user " + userName);
if (response.IndexOf("331", StringComparison.OrdinalIgnoreCase) < 0)
throw new Exception(string.Format("Error \"{0}\" while sending user name \"{1}\".", response, userName));
response = TransmitCommand(tcpClient, "pass " + password);
if (response.IndexOf("230", StringComparison.OrdinalIgnoreCase) < 0)
throw new Exception(string.Format("Error \"{0}\" while sending password.", response));
response = TransmitCommand(tcpClient, command);
if (response.IndexOf("200", StringComparison.OrdinalIgnoreCase) < 0)
throw new Exception(string.Format("Error \"{0}\" while sending command \"{1}\".", response, command));
}
finally
{
if (tcpClient.Connected)
tcpClient.Close();
}
}
private static string TransmitCommand(TcpClient tcpClient, string cmd)
{
var networkStream = tcpClient.GetStream();
if (!networkStream.CanWrite || !networkStream.CanRead)
return string.Empty;
var sendBytes = Encoding.ASCII.GetBytes(cmd + "\r\n");
networkStream.Write(sendBytes, 0, sendBytes.Length);
var streamReader = new StreamReader(networkStream);
return streamReader.ReadLine();
}
private static string Flush(TcpClient tcpClient)
{
try
{
var networkStream = tcpClient.GetStream();
if (!networkStream.CanWrite || !networkStream.CanRead)
return string.Empty;
var receiveBytes = new byte[tcpClient.ReceiveBufferSize];
networkStream.ReadTimeout = 10000;
networkStream.Read(receiveBytes, 0, tcpClient.ReceiveBufferSize);
return Encoding.ASCII.GetString(receiveBytes);
}
catch
{
// Ignore all irrelevant exceptions
}
return string.Empty;
}
You can expect the following flow while getting through the FTP:
220 (vsFTPd 2.2.2)
user [FTP_USER_NAME]
331 Please specify the password.
pass [FTP_PASSWORD]
230 Login successful.
SITE CHMOD 755 [FTP_FILE_PATH]
200 SITE CHMOD command ok.
You can try our Rebex FTP component:
// create client and connect
Ftp client = new Ftp();
client.Connect("ftp.example.org");
client.Login("username", "password");
// send SITE command
// note that QUOTE and SITE are ommited. QUOTE is command line ftp syntax only.
client.Site("LRECL=132 RECFM=FB");
// send SYST command
client.SendCommand("SYST");
FtpResponse response = client.ReadResponse();
if (response.Group != 2)
; // handle error
// disconnect
client.Disconnect();
Use sendCommand("SITE LRECL=242 BLKSIZE=0 RECFM=FB");

Categories

Resources