Пример #1
0
		///<summary>Gets the default email address for the clinic/practice. Takes a clinic num. If clinic num is 0 or there is no default for that clinic, it will get practice default. May return a new blank object.</summary>
		public static EmailAddress GetByClinic(long clinicNum) {
			EmailAddress emailAddress=null;
			Clinic clinic=Clinics.GetClinic(clinicNum);
			if(PrefC.GetBool(PrefName.EasyNoClinics) || clinic==null) {//No clinic, get practice default
				emailAddress=GetOne(PrefC.GetLong(PrefName.EmailDefaultAddressNum));
			}
			else {
				emailAddress=GetOne(clinic.EmailAddressNum);
				if(emailAddress==null) {//clinic.EmailAddressNum 0. Use default.
					emailAddress=GetOne(PrefC.GetLong(PrefName.EmailDefaultAddressNum));
				}
			}
			if(emailAddress==null) {
				List<EmailAddress> listEmailAddresses=GetListt();
				if(listEmailAddresses.Count>0) {//user didn't set a default
					emailAddress=listEmailAddresses[0];
				}
				else {
					emailAddress=new EmailAddress();//To avoid null checks.
					emailAddress.EmailPassword="";
					emailAddress.EmailUsername="";
					emailAddress.Pop3ServerIncoming="";
					emailAddress.SenderAddress="";
					emailAddress.SMTPserver="";
				}
			}
			return emailAddress;
		}
Пример #2
0
		///<summary>Gets new messages from email inbox, as well as older messages from the db. Also fills the grid.</summary>
		private int GetMessages() {
			AddressInbox=EmailAddresses.GetByClinic(0);//Default for clinic/practice.
			Cursor=Cursors.WaitCursor;
			FillGridEmailMessages();//Show what is in db.
			Cursor=Cursors.Default;
			if(AddressInbox.Pop3ServerIncoming=="") {//Email address not setup.
				Text="Email Inbox";
				AddressInbox=null;
				//todo: Message Box is too instrusive, move this to a status label.
				//MsgBox.Show(this,"Default email address has not been setup completely.");
				return 0;
			}
			Text="Email Inbox for "+AddressInbox.EmailUsername;
			Application.DoEvents();//So that something is showing while the page is loading.
			if(!CodeBase.ODEnvironment.IdIsThisComputer(PrefC.GetString(PrefName.EmailInboxComputerName))) {//This is not the computer to get new messages from.
				return 0;
			}
			if(PrefC.GetString(PrefName.EmailInboxComputerName)=="") {
				MsgBox.Show(this,"Computer name to fetch new email from has not been setup.");
				return 0;
			}
			Cursor=Cursors.WaitCursor;
			int emailMessagesTotalCount=0;
			Text="Email Inbox for "+AddressInbox.EmailUsername+" - Fetching new email...";
			try {
				bool hasMoreEmail=true;
				while(hasMoreEmail) {
					List<EmailMessage> emailMessages=EmailMessages.ReceiveFromInbox(1,AddressInbox);
					emailMessagesTotalCount+=emailMessages.Count;
					if(emailMessages.Count==0) {
						hasMoreEmail=false;
					}
					else { //Show messages as they are downloaded, to indicate to the user that the program is still processing.
						FillGridEmailMessages();
						Application.DoEvents();
					}
				}
			}
			catch(Exception ex) {
				MessageBox.Show(Lan.g(this,"Error retrieving email messages")+": "+ex.Message);
			}
			finally {
				Text="Email Inbox for "+AddressInbox.EmailUsername;
			}
			Text="Email Inbox for "+AddressInbox.EmailUsername+" - Resending any acknowledgments which previously failed...";
			EmailMessages.SendOldestUnsentAck(AddressInbox);
			Text="Email Inbox for "+AddressInbox.EmailUsername;
			Cursor=Cursors.Default;
			return emailMessagesTotalCount;
		}
Пример #3
0
		///<summary>Gets the default email address for the clinic/practice. Takes a clinic num. If clinic num is 0 or there is no default for that clinic, it will get practice default. May return a new blank object.</summary>
		public static EmailAddress GetByClinic(long clinicNum) {
			EmailAddress emailAddress=null;
			Clinic clinic=Clinics.GetClinic(clinicNum);
			if(PrefC.GetBool(PrefName.EasyNoClinics) || clinic==null) {//No clinic, get practice default
				emailAddress=GetOne(PrefC.GetLong(PrefName.EmailDefaultAddressNum));
			}
			else {
				emailAddress=GetOne(clinic.EmailAddressNum);
				if(emailAddress==null) {//clinic.EmailAddressNum 0. Use default.
					emailAddress=GetOne(PrefC.GetLong(PrefName.EmailDefaultAddressNum));
				}
			}
			if(emailAddress==null) {
				if(listt.Count>0) {//user didn't set a default
					emailAddress=listt[0];
				}
				else {
					emailAddress=new EmailAddress();//To avoid null checks.
				}
			}
			return emailAddress;
		}
Пример #4
0
		///<summary>Fetches up to fetchCount number of messages from a POP3 server.  Set fetchCount=0 for all messages.  Typically, fetchCount is 0 or 1.
		///Example host name, pop3.live.com. Port is Normally 110 for plain POP3, 995 for SSL POP3.</summary>
		private static List<EmailMessage> ReceiveFromInboxThreadSafe(int receiveCount,EmailAddress emailAddressInbox) {
			//No need to check RemotingRole; no call to db.
			List<EmailMessage> retVal=new List<EmailMessage>();
			//This code is modified from the example at: http://hpop.sourceforge.net/exampleFetchAllMessages.php
			using(OpenPop.Pop3.Pop3Client client=new OpenPop.Pop3.Pop3Client()) {//The client disconnects from the server when being disposed.
				client.Connect(emailAddressInbox.Pop3ServerIncoming,emailAddressInbox.ServerPortIncoming,emailAddressInbox.UseSSL,180000,180000,null);//3 minute timeout, just as for sending emails.
				client.Authenticate(emailAddressInbox.EmailUsername.Trim(),emailAddressInbox.EmailPassword,OpenPop.Pop3.AuthenticationMethod.UsernameAndPassword);
				List <string> listMsgUids=client.GetMessageUids();//Get all unique identifiers for each email in the inbox.
				List<EmailMessageUid> listDownloadedMsgUids=EmailMessageUids.GetForRecipientAddress(emailAddressInbox.EmailUsername.Trim());
				int msgDownloadedCount=0;
				for(int i=0;i<listMsgUids.Count;i++) {
					int msgIndex=i+1;//The message indicies are 1-based.
					string strMsgUid=listMsgUids[i];
					if(strMsgUid.Length==0) {
						//Message Uids are commonly used, but are optional according to the RFC822 email standard.
						//Uids are assgined by the sending client application, so they could be anything, but are supposed to be unique.
						//Additionally, most email servers are probably smart enough to create a Uid for any message where the Uid is missing.
						//In the worst case scenario, we create a Uid for the message based off of the message header information, which takes a little extra time, 
						//but is better than downloading old messages again, especially if some of those messages contain large attachments.
						OpenPop.Mime.Header.MessageHeader messageHeader=client.GetMessageHeaders(msgIndex);//Takes 1-2 seconds to get this information from the server.  The message, minus body and minus attachments.
						strMsgUid=messageHeader.DateSent.ToString("yyyyMMddHHmmss")+emailAddressInbox.EmailUsername.Trim()+messageHeader.From.Address+messageHeader.Subject;
					}
					else if(strMsgUid.Length>4000) {//The EmailMessageUid.MsgId field is only 4000 characters in size.
						strMsgUid=strMsgUid.Substring(0,4000);
					}
					//Skip any email messages matching Uids which have been previously downloaded.
					bool isDownloaded=false;
					for(int j=0;j<listDownloadedMsgUids.Count;j++) {
						if(listDownloadedMsgUids[j].MsgId==strMsgUid) {
							isDownloaded=true;
							break;
						}
					}
					if(isDownloaded) {
						continue;
					}
					//At this point, we know that the email is one which we have not downloaded yet.
					try {
						OpenPop.Mime.Message openPopMsg=client.GetMessage(msgIndex);//This is where the entire raw email is downloaded.
						string strRawEmail=openPopMsg.MessagePart.BodyEncoding.GetString(openPopMsg.RawMessage);
						EmailMessage emailMessage=ProcessRawEmailMessage(strRawEmail,0,emailAddressInbox);//Inserts to db.
						EmailMessageUid emailMessageUid=new EmailMessageUid();
						emailMessageUid.RecipientAddress=emailMessage.RecipientAddress.Trim();
						emailMessageUid.MsgId=strMsgUid;
						EmailMessageUids.Insert(emailMessageUid);//Remember Uid was downloaded, to avoid email duplication the next time the inbox is refreshed.
						retVal.Add(emailMessage);
						msgDownloadedCount++;
					}
					catch(ThreadAbortException) {
						//This can happen if the application is exiting. We need to leave right away so the program does not lock up.
						//Otherwise, this loop could continue for a while if there are a lot of messages to download.
						throw;
					}
					catch {
						//If one particular email fails to download, then skip it for now and move on to the next email.
					}
					if(receiveCount>0 && msgDownloadedCount>=receiveCount) {
						break;
					}
				}
			}
			//Since this function is fired automatically based on the inbox check interval, we also try to send the oldest unsent Ack.
			//The goal is to keep trying to send the Acks at a reasonable interval until they are successfully delivered.
			SendOldestUnsentAck(emailAddressInbox);
			return retVal;
		}
Пример #5
0
		/// <summary>This is used from wherever unencrypted email needs to be sent throughout the program.  If a message must be encrypted, then encrypt it before calling this function.
		///Surround with a try catch.</summary>
		public static void SendEmailUnsecure(EmailMessage emailMessage,EmailAddress emailAddress) {
			//No need to check RemotingRole; no call to db.
			SendEmailUnsecure(emailMessage,emailAddress,null);
		}
Пример #6
0
		///<summary>Fetches up to fetchCount number of messages from a POP3 server.  Set fetchCount=0 for all messages.  Typically, fetchCount is 0 or 1.
		///Example host name, pop3.live.com. Port is Normally 110 for plain POP3, 995 for SSL POP3.</summary>
		public static List<EmailMessage> ReceiveFromInbox(int receiveCount,EmailAddress emailAddressInbox) {
			List<EmailMessage> retVal=new List<EmailMessage>();
			if(_isReceivingEmail) {
				return retVal;//Already in the process of receving email. This can happen if the user clicks the refresh button at the same time the main polling thread is receiving.
			}
			_isReceivingEmail=true;
			try {
				lock(_lockEmailReceive) {
					retVal=ReceiveFromInboxThreadSafe(receiveCount,emailAddressInbox);
				}
			}
			catch(Exception) {
				throw;
			}
			finally {
				_isReceivingEmail=false;
			}
			return retVal;
		}
Пример #7
0
		///<summary>Gets the oldest Direct Ack (MDN) from the db which has not been sent yet and attempts to send it.
		///If the Ack fails to send, then it remains in the database with status AckDirectNotSent, so that another attempt will be made when this function is called again.
		///This function throttles the Ack responses to prevent the email host from flagging the emailAddressFrom as a spam account.  The throttle speed is one Ack per 60 seconds (to mimic human behavior).
		///Throws exceptions.</summary>
		public static void SendOldestUnsentAck(EmailAddress emailAddressFrom) {
			if(RemotingClient.RemotingRole==RemotingRole.ClientWeb) {
				Meth.GetVoid(MethodBase.GetCurrentMethod());
				return;
			}
			string command;
			//Get the time that the last Direct Ack was sent for the From address.
			command=DbHelper.LimitOrderBy(
				"SELECT MsgDateTime FROM emailmessage "
					+"WHERE FromAddress='"+POut.String(emailAddressFrom.EmailUsername.Trim())+"' AND SentOrReceived="+POut.Long((int)EmailSentOrReceived.AckDirectProcessed)+" "
					+"ORDER BY MsgDateTime DESC",
				1);
			DateTime dateTimeLastAck=PIn.DateT(Db.GetScalar(command));//dateTimeLastAck will be 0001-01-01 if there is not yet any sent Acks.
			if((DateTime.Now-dateTimeLastAck).TotalSeconds<60) {
				//Our last Ack sent was less than 15 seconds ago.  Abort sending Acks right now.
				return;
			}
			//Get the oldest Ack for the From address which has not been sent yet.
			command=DbHelper.LimitOrderBy(
				"SELECT * FROM emailmessage "
					+"WHERE FromAddress='"+POut.String(emailAddressFrom.EmailUsername.Trim())+"' AND SentOrReceived="+POut.Long((int)EmailSentOrReceived.AckDirectNotSent)+" "
					+"ORDER BY EmailMessageNum",//The oldest Ack is the one that was recorded first.  EmailMessageNum is better than using MsgDateTime, because MsgDateTime is only accurate down to the second.
				1);
			List <EmailMessage> listEmailMessageUnsentAcks=Crud.EmailMessageCrud.SelectMany(command);
			if(listEmailMessageUnsentAcks.Count<1) {
				return;//No Acks to send.
			}
			EmailMessage emailMessageAck=listEmailMessageUnsentAcks[0];
			string strRawEmailAck=emailMessageAck.BodyText;//Not really body text.  The entire raw Ack is saved here, and we use it to reconstruct the Ack email completely.
			Health.Direct.Agent.MessageEnvelope messageEnvelopeMdn=new Health.Direct.Agent.MessageEnvelope(strRawEmailAck);
			Health.Direct.Agent.OutgoingMessage outMsgDirect=new Health.Direct.Agent.OutgoingMessage(messageEnvelopeMdn);
			try {
				string strErrors=SendEmailDirect(outMsgDirect,emailAddressFrom);//Encryption is performed in this step. Throws an exception if unable to send (i.e. when internet down).
				if(strErrors=="") {
					emailMessageAck.SentOrReceived=EmailSentOrReceived.AckDirectProcessed;
					emailMessageAck.MsgDateTime=DateTime.Now;//Update the time, otherwise the throttle will not work properly.
					Update(emailMessageAck);
				}
			}
			catch {
			}
		}
Пример #8
0
		/// <summary>This is used from wherever email needs to be sent throughout the program.  If a message must be encrypted, then encrypt it before calling this function.  nameValueCollectionHeaders can be null.</summary>
		private static void SendEmailUnsecure(EmailMessage emailMessage,EmailAddress emailAddress,NameValueCollection nameValueCollectionHeaders,params AlternateView[] arrayAlternateViews) {
			//No need to check RemotingRole; no call to db.
			if(emailAddress.ServerPort==465) {//implicit
				//uses System.Web.Mail, which is marked as deprecated, but still supports implicit
				System.Web.Mail.MailMessage message=new System.Web.Mail.MailMessage();
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver",emailAddress.SMTPserver);
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport","465");
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing","2");//sendusing: cdoSendUsingPort, value 2, for sending the message using the network.
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate","1");//0=anonymous,1=clear text auth,2=context
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername",emailAddress.EmailUsername.Trim());
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword",emailAddress.EmailPassword);
				//if(PrefC.GetBool(PrefName.EmailUseSSL)) {
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl","true");//false was also tested and does not work
				message.From=emailMessage.FromAddress.Trim();
				message.To=emailMessage.ToAddress.Trim();
				message.Subject=Tidy(emailMessage.Subject);
				message.Body=Tidy(emailMessage.BodyText);
				//message.Cc=;
				//message.Bcc=;
				//message.UrlContentBase=;
				//message.UrlContentLocation=;
				message.BodyEncoding=System.Text.Encoding.UTF8;
				message.BodyFormat=System.Web.Mail.MailFormat.Text;//or .Html
				if(nameValueCollectionHeaders!=null) {
					string[] arrayHeaderKeys=nameValueCollectionHeaders.AllKeys;
					for(int i=0;i<arrayHeaderKeys.Length;i++) {//Needed for Direct Acks to work.
						message.Headers.Add(arrayHeaderKeys[i],nameValueCollectionHeaders[arrayHeaderKeys[i]]);
					}
				}
				//TODO: We need to add some kind of alternatve view or similar replacement for outgoing Direct messages to work with SSL. Write the body to a temporary file and attach with the correct mime type and name?
				string attachPath=EmailMessages.GetEmailAttachPath();
				System.Web.Mail.MailAttachment attach;
				//foreach (string sSubstr in sAttach.Split(delim)){
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					attach=new System.Web.Mail.MailAttachment(ODFileUtils.CombinePaths(attachPath,emailMessage.Attachments[i].ActualFileName));
					//no way to set displayed filename
					message.Attachments.Add(attach);
				}
				System.Web.Mail.SmtpMail.SmtpServer=emailAddress.SMTPserver+":465";//"smtp.gmail.com:465";
				System.Web.Mail.SmtpMail.Send(message);
			}
			else {//explicit default port 587 
				SmtpClient client=new SmtpClient(emailAddress.SMTPserver,emailAddress.ServerPort);
				//The default credentials are not used by default, according to: 
				//http://msdn2.microsoft.com/en-us/library/system.net.mail.smtpclient.usedefaultcredentials.aspx
				client.Credentials=new NetworkCredential(emailAddress.EmailUsername.Trim(),emailAddress.EmailPassword);
				client.DeliveryMethod=SmtpDeliveryMethod.Network;
				client.EnableSsl=emailAddress.UseSSL;
				client.Timeout=180000;//3 minutes
				MailMessage message=new MailMessage();
				message.From=new MailAddress(emailMessage.FromAddress.Trim());
				message.To.Add(emailMessage.ToAddress.Trim());
				message.Subject=Tidy(emailMessage.Subject);
				message.Body=Tidy(emailMessage.BodyText);
				message.IsBodyHtml=false;
				if(nameValueCollectionHeaders!=null) {
					message.Headers.Add(nameValueCollectionHeaders);//Needed for Direct Acks to work.
				}
				for(int i=0;i<arrayAlternateViews.Length;i++) {//Needed for Direct messages to be interpreted encrypted on the receiver's end.
					message.AlternateViews.Add(arrayAlternateViews[i]);
				}
				string attachPath=EmailMessages.GetEmailAttachPath();
				Attachment attach;
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					attach=new Attachment(ODFileUtils.CombinePaths(attachPath,emailMessage.Attachments[i].ActualFileName));
					//@"C:\OpenDentalData\EmailAttachments\1");
					attach.Name=emailMessage.Attachments[i].DisplayedFileName;
					//"canadian.gif";
					message.Attachments.Add(attach);
				}
				client.Send(message);
			}
		}
Пример #9
0
		///<summary>outMsgDirect must be unencrypted, because this function will encrypt.  Encrypts the message, verifies trust, locates the public encryption key for the To address (if already stored locally), etc.
		///patNum can be zero.  emailSentOrReceived must be either SentDirect or a Direct Ack type such as AckDirectProcessed.
		///Returns an empty string upon success, or an error string if there were errors.  It is possible that the email was sent to some trusted recipients and not sent to untrusted recipients (in which case there would be errors but some recipients would receive successfully).</summary>
		private static string SendEmailDirect(Health.Direct.Agent.OutgoingMessage outMsgUnencrypted,EmailAddress emailAddressFrom) {
			//No need to check RemotingRole; no call to db.
			string strErrors="";
			string strSenderAddress=emailAddressFrom.EmailUsername.Trim();//Cannot be emailAddressFrom.SenderAddress, or else will not find the right encryption certificate.
			Health.Direct.Agent.DirectAgent directAgent=GetDirectAgentForEmailAddress(strSenderAddress);
			//Locate or discover public certificates for each receiver for encryption purposes.
			for(int i=0;i<outMsgUnencrypted.Recipients.Count;i++) {
				if(outMsgUnencrypted.Recipients[i].Certificates!=null) {
					continue;//The certificate(s) for this recipient were already located somehow. Skip.
				}
				try {
					int certNewCount=FindPublicCertForAddress(outMsgUnencrypted.Recipients[i].Address.Trim());
					if(certNewCount!=0) {//If the certificate is already in the local public store or if one was discovered over the internet.
						string strSenderDomain=strSenderAddress.Substring(strSenderAddress.IndexOf("@")+1);//For example, if strSenderAddress is [email protected], then this will be opendental.com
						//Refresh the directAgent class using the updated list of public certs while leaving everything else alone. This must be done, or else the certificate will not be found when encrypting the outgoing email.
						directAgent=new Health.Direct.Agent.DirectAgent(strSenderDomain,directAgent.PrivateCertResolver,Health.Direct.Common.Certificates.SystemX509Store.OpenExternal().CreateResolver(),directAgent.TrustAnchors);
						directAgent.EncryptMessages=true;
						HashDirectAgents[strSenderDomain]=directAgent;
					}
				}
				catch(Exception ex) {
					if(strErrors!="") {
						strErrors+="\r\n";
					}
					strErrors+=ex.Message;
				}
			}
			Health.Direct.Agent.OutgoingMessage outMsgEncrypted=null;
			try {
				outMsgEncrypted=directAgent.ProcessOutgoing(outMsgUnencrypted);//This is where encryption and trust verification occurs.
			}
			catch(Exception ex) {
				if(strErrors!="") {
					strErrors+="\r\n";
				}
				strErrors+=ex.Message;
				return strErrors;//Cannot recover from an encryption error.
			}
			outMsgEncrypted.Message.SubjectValue="Encrypted Message";//Prevents a warning in the transport testing tool (TTT). http://tools.ietf.org/html/rfc5322#section-3.6.5
			EmailMessage emailMessageEncrypted=ConvertMessageToEmailMessage(outMsgEncrypted.Message,false);//No point in saving the encrypted attachment, because nobody can read it and it will bloat the OpenDentImages folder.
			NameValueCollection nameValueCollectionHeaders=new NameValueCollection();
			for(int i=0;i<outMsgEncrypted.Message.Headers.Count;i++) {
				nameValueCollectionHeaders.Add(outMsgEncrypted.Message.Headers[i].Name,outMsgEncrypted.Message.Headers[i].ValueRaw);
			}
			byte[] arrayEncryptedBody=Encoding.UTF8.GetBytes(outMsgEncrypted.Message.Body.Text);//The bytes of the encrypted and base 64 encoded body string.  No need to call Tidy() here because this body text will be in base64.
			MemoryStream ms=new MemoryStream(arrayEncryptedBody);
			ms.Position=0;
			//The memory stream for the alternate view must be mime (not an entire email), based on AlternateView use example http://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage.alternateviews.aspx
			AlternateView alternateView=new AlternateView(ms,outMsgEncrypted.Message.ContentType);//Causes the receiver to recognize this email as an encrypted email.
			alternateView.TransferEncoding=TransferEncoding.SevenBit;
			SendEmailUnsecure(emailMessageEncrypted,emailAddressFrom,nameValueCollectionHeaders,alternateView);//Not really unsecure in this spot, because the message is already encrypted.
			ms.Dispose();
			return strErrors;
		}
Пример #10
0
		///<summary>Used for creating encrypted Message Disposition Notification (MDN) ack messages for Direct.
		///An ack must be sent when a message is received/processed, and other acks are supposed be sent when other events occur (but are not required).
		///For example, when the user reads a decrypted message we must send an ack with notification type of Displayed (not required).</summary>
		private static string SendAckDirect(Health.Direct.Agent.IncomingMessage inMsg,EmailAddress emailAddressFrom,long patNum) {
			//No need to check RemotingRole; no call to db.
			//The CreateAcks() function handles the case where the incoming message is an MDN, in which case we do not reply with anything.
			//The CreateAcks() function also takes care of figuring out where to send the MDN, because the rules are complicated.
			//According to http://wiki.directproject.org/Applicability+Statement+for+Secure+Health+Transport+Working+Version#x3.0%20Message%20Disposition%20Notification,
			//The MDN must be sent to the first available of: Disposition-Notification-To header, MAIL FROM SMTP command, Sender header, From header.
			Health.Direct.Common.Mail.Notifications.MDNStandard.NotificationType notificationType=Health.Direct.Common.Mail.Notifications.MDNStandard.NotificationType.Failed;
			notificationType=Health.Direct.Common.Mail.Notifications.MDNStandard.NotificationType.Processed;
			IEnumerable<Health.Direct.Common.Mail.Notifications.NotificationMessage> notificationMsgs=inMsg.CreateAcks("OpenDental "+Assembly.GetExecutingAssembly().GetName().Version,"",notificationType);
			if(notificationMsgs==null) {
				return "";
			}
			string strErrorsAll="";
			foreach(Health.Direct.Common.Mail.Notifications.NotificationMessage notificationMsg in notificationMsgs) {
				string strErrors="";
				try {
					//According to RFC3798, section 3 - Format of a Message Disposition Notification http://tools.ietf.org/html/rfc3798#page-3
					//A message disposition notification is a MIME message with a top-level
					//content-type of multipart/report (defined in [RFC-REPORT]).  When
					//multipart/report content is used to transmit an MDN:
					//(a)  The report-type parameter of the multipart/report content is "disposition-notification".
					//(b)  The first component of the multipart/report contains a human-readable explanation of the MDN, as described in [RFC-REPORT].
					//(c)  The second component of the multipart/report is of content-type message/disposition-notification, described in section 3.1 of this document.
					//(d)  If the original message or a portion of the message is to be returned to the sender, it appears as the third component of the multipart/report.
					//     The decision of whether or not to return the message or part of the message is up to the MUA generating the MDN.  However, in the case of 
					//     encrypted messages requesting MDNs, encrypted message text MUST be returned, if it is returned at all, only in its original encrypted form.
					Health.Direct.Agent.OutgoingMessage outMsgDirect=new Health.Direct.Agent.OutgoingMessage(notificationMsg);
					if(notificationMsg.ToValue.Trim().ToLower()==notificationMsg.FromValue.Trim().ToLower()) {
						continue;//Do not send an ack to self.
					}
					EmailMessage emailMessage=ConvertMessageToEmailMessage(outMsgDirect.Message,false);
					emailMessage.PatNum=patNum;
					//First save the ack message to the database in case their is a failure sending the email. This way we can remember to try and send it again later, based on SentOrRecieved.
					emailMessage.SentOrReceived=EmailSentOrReceived.AckDirectNotSent;
					MemoryStream ms=new MemoryStream();
					notificationMsg.Save(ms);
					byte[] arrayMdnMessageBytes=ms.ToArray();
					emailMessage.BodyText=Encoding.UTF8.GetString(arrayMdnMessageBytes);
					ms.Dispose();
					Insert(emailMessage);				
				}
				catch(Exception ex) {
					strErrors=ex.Message;
				}
				if(strErrorsAll!="") {
					strErrorsAll+="\r\n";
				}
				strErrorsAll+=strErrors;
			}
			try {
				SendOldestUnsentAck(emailAddressFrom);//Send the ack(s) we created above.
			}
			catch {
				//Not critical to send the acks here, because they will be sent later if they failed now.
			}
			return strErrorsAll;
		}
Пример #11
0
		///<summary></summary>
		public static void Update(EmailAddress emailAddress){
			if(RemotingClient.RemotingRole==RemotingRole.ClientWeb){
				Meth.GetVoid(MethodBase.GetCurrentMethod(),emailAddress);
				return;
			}
			Crud.EmailAddressCrud.Update(emailAddress);
		}
Пример #12
0
		///<summary>outMsgDirect must be unencrypted, because this function will encrypt.  Encrypts the message, verifies trust, locates the public encryption key for the To address (if already stored locally), etc.
		///Returns an empty string upon success, or an error string if there were errors.  It is possible that the email was sent to some trusted recipients and not sent to untrusted recipients (in which case there would be errors but some recipients would receive successfully).</summary>
		private static string SendEmailDirect(Health.Direct.Agent.OutgoingMessage outMsgUnencrypted,EmailAddress emailAddressFrom) {
			//No need to check RemotingRole; no call to db.
			string strErrors="";
			string strSenderAddress=emailAddressFrom.EmailUsername.Trim();//Cannot be emailAddressFrom.SenderAddress, or else will not find the right encryption certificate.
			Health.Direct.Agent.DirectAgent directAgent=GetDirectAgentForEmailAddress(strSenderAddress);
			//Locate or discover public certificates for each receiver for encryption purposes.
			for(int i=0;i<outMsgUnencrypted.Recipients.Count;i++) {
				if(outMsgUnencrypted.Recipients[i].Certificates!=null) {
					continue;//The certificate(s) for this recipient were already located somehow. Skip.
				}
				try {
					int certNewCount=FindPublicCertForAddress(outMsgUnencrypted.Recipients[i].Address.Trim());
					if(certNewCount!=0) {//If the certificate is already in the local public store or if one was discovered over the internet.
						RefreshCertStoreExternal(emailAddressFrom);
					}
				}
				catch(Exception ex) {
					if(strErrors!="") {
						strErrors+="\r\n";
					}
					strErrors+=ex.Message;
				}
			}
			Health.Direct.Agent.OutgoingMessage outMsgEncrypted=null;
			try {
				outMsgEncrypted=directAgent.ProcessOutgoing(outMsgUnencrypted);//This is where encryption, signing, and trust verification occurs.
			}
			catch(Exception ex) {
				if(strErrors!="") {
					strErrors+="\r\n";
				}
				strErrors+=ex.Message;
				return strErrors;//Cannot recover from an encryption error.
			}
			outMsgEncrypted.Message.SubjectValue="Encrypted Message";//Prevents a warning in the transport testing tool (TTT). http://tools.ietf.org/html/rfc5322#section-3.6.5
			EmailMessage emailMessageEncrypted=ConvertMessageToEmailMessage(outMsgEncrypted.Message,false,true);//No point in saving the encrypted attachment, because nobody can read it and it will bloat the OpenDentImages folder.
			NameValueCollection nameValueCollectionHeaders=new NameValueCollection();
			for(int i=0;i<outMsgEncrypted.Message.Headers.Count;i++) {
				nameValueCollectionHeaders.Add(outMsgEncrypted.Message.Headers[i].Name,outMsgEncrypted.Message.Headers[i].ValueRaw);
			}
			byte[] arrayEncryptedBody=Encoding.UTF8.GetBytes(outMsgEncrypted.Message.Body.Text);//The bytes of the encrypted and base 64 encoded body string.  No need to call Tidy() here because this body text will be in base64.
			MemoryStream ms=new MemoryStream(arrayEncryptedBody);
			ms.Position=0;
			//The memory stream for the alternate view must be mime (not an entire email), based on AlternateView use example http://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage.alternateviews.aspx
			AlternateView alternateView=new AlternateView(ms,outMsgEncrypted.Message.ContentType);//Causes the receiver to recognize this email as an encrypted email.
			alternateView.TransferEncoding=TransferEncoding.SevenBit;
			if(emailAddressFrom.ServerPort==465) {//Implicit SSL
				//See comments inside SendEmailUnsecure() regarding why this does not work.
				if(strErrors!="") {
					strErrors+="\r\n";
				}
				strErrors+=Lans.g("EmailMessages","Direct messages cannot be sent over implicit SSL.");
			}
			else {
				WireEmailUnsecure(emailMessageEncrypted,emailAddressFrom,nameValueCollectionHeaders,alternateView);//Not really unsecure in this spot, because the message is already encrypted.
			}
			ms.Dispose();
			return strErrors;
		}
		private void VerifyInputs() {
			AllowSendMessages();
			long priProvNum=0;
			long patNumSubj=_patNum;
			string notificationSubject;
			string notificationBodyNoUrl;
			string notificationURL;
			Family family=null;
			Patient patCur=Patients.GetPat(_patNum);
			comboRegardingPatient.Items.Clear();
			if(patCur==null) {
				BlockSendSecureMessage("Patient is invalid.");
				_listPatients=null;
			}
			else {			
				family=Patients.GetFamily(_patNum);				
				textTo.Text=patCur.GetNameFL();
				Provider priProv=Providers.GetProv(patCur.PriProv);
				if(priProv==null) {
					BlockSendSecureMessage("Invalid primary provider for this patient.");
				}
				else {
					priProvNum=priProv.ProvNum;
					Provider userODProv=Providers.GetProv(Security.CurUser.ProvNum);
					if(userODProv==null) {
						BlockSendSecureMessage("Not logged in as valid provider. Login as patient's primary provider to send message.");
					}
					else if(userODProv.ProvNum!=priProv.ProvNum) {
						BlockSendSecureMessage("The patient's primary provider does not match the provider attached to the user currently logged in. Login as patient's primary provider to send message.");
					}
					else {
						textFrom.Text=priProv.GetFormalName();					
					}
				}
				if(patCur.Email=="") {
					BlockSendNotificationMessage("Missing patient email. Setup patient email using Family module.");
				}
				if(patCur.OnlinePassword=="") {
					BlockSendNotificationMessage("Patient has not been given online access. Setup patient online access using Family module.");
				}
			}
			//We are replying to an existing message so verify that the provider linked to this message matches our currently logged in provider.  
			//This is because web mail communications will be visible in the patients Chart Module.
			if(_replyToEmailMessageNum>0) {
				EmailMessage replyToEmailMessage=EmailMessages.GetOne(_replyToEmailMessageNum);
				if(replyToEmailMessage==null) {
					MsgBox.Show(this,"Invalid input email message");
					DialogResult=DialogResult.Abort;  //nothing to show so abort, caller should be waiting for abort to determine if message should be marked read
					return;
				}				
				textSubject.Text=replyToEmailMessage.Subject;
				if(replyToEmailMessage.Subject.IndexOf("RE:")!=0) {
					textSubject.Text="RE: "+textSubject.Text;
				}
				patNumSubj=replyToEmailMessage.PatNumSubj;
				Patient patRegarding=Patients.GetOnePat(family.ListPats,patNumSubj);
				textBody.Text="\r\n\r\n-----"+Lan.g(this,"Original Message")+"-----\r\n"
					+(patRegarding == null ? "" : (Lan.g(this,"Regarding Patient")+": "+patRegarding.GetNameFL()+"\r\n"))
					+Lan.g(this,"From")+": "+replyToEmailMessage.FromAddress+"\r\n"
					+Lan.g(this,"Sent")+": "+replyToEmailMessage.MsgDateTime.ToShortDateString()+" "+replyToEmailMessage.MsgDateTime.ToShortTimeString()+"\r\n"
					+Lan.g(this,"To")+": "+replyToEmailMessage.ToAddress+"\r\n"
					+Lan.g(this,"Subject")+": "+replyToEmailMessage.Subject
					+"\r\n\r\n"+replyToEmailMessage.BodyText;		
				
			}
			if(patCur==null || family==null) {
				BlockSendSecureMessage("Patient's family not setup propertly. Make sure guarantor is valid.");
			}
			else {
				_listPatients=new List<Patient>();
				bool isGuar=patCur.Guarantor==patCur.PatNum;
				for(int i=0;i<family.ListPats.Length;i++) {
					Patient patFamilyMember=family.ListPats[i];
					if(isGuar || patFamilyMember.PatNum==patNumSubj) {
						_listPatients.Add(patFamilyMember);
						comboRegardingPatient.Items.Add(patFamilyMember.GetNameFL());
						if(patFamilyMember.PatNum==patNumSubj) {
							comboRegardingPatient.SelectedIndex=(comboRegardingPatient.Items.Count-1);
						}
					}
				}
			}
			notificationSubject=PrefC.GetString(PrefName.PatientPortalNotifySubject);
			notificationBodyNoUrl=PrefC.GetString(PrefName.PatientPortalNotifyBody);
			notificationURL=PrefC.GetString(PrefName.PatientPortalURL);
			_emailAddressSender=EmailAddresses.GetByClinic(0);//Default for clinic/practice.
			if(_emailAddressSender==null) {
				BlockSendNotificationMessage("Practice email is not setup properly. Setup practice email in setup.");
			}			
			if(notificationSubject=="") {
				BlockSendNotificationMessage("Missing notification email subject. Create a subject in setup.");
			}
			if(notificationBodyNoUrl=="") {
				BlockSendNotificationMessage("Missing notification email body. Create a body in setup.");
			}
			if(_allowSendSecureMessage) {
				_secureMessage=new EmailMessage();
				_secureMessage.FromAddress=textFrom.Text;
				_secureMessage.ToAddress=textTo.Text;
				_secureMessage.PatNum=patCur.PatNum;
				_secureMessage.SentOrReceived=EmailSentOrReceived.WebMailSent;  //this is secure so mark as webmail sent
				_secureMessage.ProvNumWebMail=priProvNum;
			}
			if(_allowSendNotificationMessage) {
				_insecureMessage=new EmailMessage();
				_insecureMessage.FromAddress=_emailAddressSender.SenderAddress;
				_insecureMessage.ToAddress=patCur.Email;
				_insecureMessage.PatNum=patCur.PatNum;
				_insecureMessage.Subject=notificationSubject;
				_insecureMessage.BodyText=notificationBodyNoUrl.Replace("[URL]",notificationURL);
				_insecureMessage.SentOrReceived=EmailSentOrReceived.Sent; //this is not secure so just mark as regular sent
			}
			if(_allowSendSecureMessage && _allowSendNotificationMessage) {
				labelNotification.Text=Lan.g(this,"Notification email will be sent to patient")+": "+patCur.Email;
			}
		}
Пример #14
0
		///<summary>Converts any raw email message (encrypted or not) into an EmailMessage object, and saves any email attachments to the emailattach table in the db.
		///The emailMessageNum will be used to set EmailMessage.EmailMessageNum.  If emailMessageNum is 0, then the EmailMessage will be inserted into the db, otherwise the EmailMessage will be updated in the db.
		///If the raw message is encrypted, then will attempt to decrypt.  If decryption fails, then the EmailMessage SentOrReceived will be ReceivedEncrypted and the EmailMessage body will be set to the entire contents of the raw email.  If decryption succeeds, then EmailMessage SentOrReceived will be set to ReceivedDirect, the EmailMessage body will contain the decrypted body text, and a Direct Ack "processed" message will be sent back to the sender using the email settings from emailAddressReceiver.</summary>
		public static EmailMessage ProcessRawEmailMessage(string strRawEmail,long emailMessageNum,EmailAddress emailAddressReceiver) {
			//No need to check RemotingRole; no call to db.
			Health.Direct.Agent.IncomingMessage inMsg=null;
			try {
				inMsg=new Health.Direct.Agent.IncomingMessage(strRawEmail);//Used to parse all email (encrypted or not).
			}
			catch(Exception ex) {
				throw new ApplicationException("Failed to parse raw email message.\r\n"+ex.Message);
			}
			bool isEncrypted=false;
			if(inMsg.Message.ContentType.ToLower().Contains("application/pkcs7-mime")) {//The email MIME/body is encrypted (known as S/MIME). Treated as a Direct message.
				isEncrypted=true;
			}
			EmailMessage emailMessage=null;
			if(isEncrypted) {
				emailMessage=ConvertMessageToEmailMessage(inMsg.Message,false);//Exclude attachments until we decrypt.
				emailMessage.RawEmailIn=strRawEmail;//This raw email is encrypted.
				emailMessage.EmailMessageNum=emailMessageNum;
				emailMessage.SentOrReceived=EmailSentOrReceived.ReceivedEncrypted;
				//The entire contents of the email are saved in the emailMessage.BodyText field, so that if decryption fails, the email will still be saved to the db for decryption later if possible.
				emailMessage.BodyText=strRawEmail;
				emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
				try {
					Health.Direct.Agent.DirectAgent directAgent=GetDirectAgentForEmailAddress(inMsg.Message.ToValue.Trim());
					//throw new ApplicationException("test decryption failure");
					inMsg=directAgent.ProcessIncoming(inMsg);//Decrypts, valudates trust, etc.
					emailMessage=ConvertMessageToEmailMessage(inMsg.Message,true);//If the message was wrapped, then the To, From, Subject and Date can change after decyption. We also need to create the attachments for the decrypted message.
					emailMessage.RawEmailIn=inMsg.SerializeMessage();//Now that we have decrypted, we must get the raw email contents differently (cannot use strRawEmail). 
					emailMessage.EmailMessageNum=emailMessageNum;
					emailMessage.SentOrReceived=EmailSentOrReceived.ReceivedDirect;
					emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
				}
				catch(Exception ex) {
					//SentOrReceived will be ReceivedEncrypted, indicating to the calling code that decryption failed.
					if(emailMessageNum==0) {
						EmailMessages.Insert(emailMessage);
						return emailMessage;//If the message was just downloaded, then this function was called from the inbox, simply return the inserted email without an exception (it can be decypted later manually by the user).
					}
					//Do not update if emailMessageNum<>0, because nothing changed (was encrypted and still is).
					throw ex;//Throw an exception if trying to decrypt an email that was already in the database, so the user can see the error message in the UI.
				}
			}
			else {//Unencrypted
				emailMessage=ConvertMessageToEmailMessage(inMsg.Message,true);
				emailMessage.RawEmailIn=strRawEmail;
				emailMessage.EmailMessageNum=emailMessageNum;
				emailMessage.SentOrReceived=EmailSentOrReceived.Received;
				emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
			}
			EhrSummaryCcd ehrSummaryCcd=null;
			if(isEncrypted) {
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					if(Path.GetExtension(emailMessage.Attachments[i].ActualFileName).ToLower()!=".xml") {
						continue;
					}
					string strAttachPath=GetEmailAttachPath();
					string strAttachFile=ODFileUtils.CombinePaths(strAttachPath,emailMessage.Attachments[i].ActualFileName);
					string strAttachText=File.ReadAllText(strAttachFile);
					if(EhrCCD.IsCCD(strAttachText)) {
						if(emailMessage.PatNum==0) {
							try {
								XmlDocument xmlDocCcd=new XmlDocument();
								xmlDocCcd.LoadXml(strAttachText);
								emailMessage.PatNum=EhrCCD.GetCCDpat(xmlDocCcd);// A match is not guaranteed, which is why we have a button to allow the user to change the patient.
							}
							catch {
								//Invalid XML.  Cannot match patient.
							}
						}
						ehrSummaryCcd=new EhrSummaryCcd();
						ehrSummaryCcd.ContentSummary=strAttachText;
						ehrSummaryCcd.DateSummary=DateTime.Today;
						ehrSummaryCcd.EmailAttachNum=i;//Temporary value, so we can locate the FK down below.
						ehrSummaryCcd.PatNum=emailMessage.PatNum;
						break;//We can only handle one CCD message per email, because we only have one patnum field per email record and the ehrsummaryccd record requires a patnum.
					}
				}
			}
			if(emailMessageNum==0) {
				EmailMessages.Insert(emailMessage);//Also inserts all of the attachments in emailMessage.Attachments after setting each attachment EmailMessageNum properly.
			}
			else {
				EmailMessages.Update(emailMessage);//Also deletes all previous attachments, then recreates all of the attachments in emailMessage.Attachments after setting each attachment EmailMessageNum properly.
			}
			if(ehrSummaryCcd!=null) {
				ehrSummaryCcd.EmailAttachNum=emailMessage.Attachments[(int)ehrSummaryCcd.EmailAttachNum].EmailAttachNum;
				EhrSummaryCcds.Insert(ehrSummaryCcd);
			}
			if(isEncrypted) {
				//Send a Message Disposition Notification (MDN) message to the sender, as required by the Direct messaging specifications.
				//The MDN will be attached to the same patient as the incoming message.
				SendAckDirect(inMsg,emailAddressReceiver,emailMessage.PatNum);
			}
			return emailMessage;
		}
Пример #15
0
		///<summary>Throws exceptions.  Attempts to physically send the message over the network wire.
		///Perfect for signed or encrypted email, because the MIME Content-Type is strictly defined for these types of emails.
		///Does not work for implicit SSL, but works for all other email settings, including explicit SSL.
		///If a message must be encrypted, then encrypt it before calling this function.
		///The patNum can be 0, but should be included if known, for auditing purposes.</summary>
		private static void WireEmailUnsecure(Health.Direct.Agent.OutgoingMessage msgOut,EmailAddress emailAddress,long patNum) {
			//No need to check RemotingRole; no call to db.
			//When batch email operations are performed, we sometimes do this check further up in the UI.  This check is here to as a catch-all.
			if(!Security.IsAuthorized(Permissions.EmailSend,DateTime.Now,true,Security.CurUser.UserGroupNum)) {//This overload throws an exception if user is not authorized.
				return;
			}
			if(emailAddress.IsImplicitSsl) {
				//The poor Content-Type header treatment by the System.Web.Mail.MailMessage class is the reason why both encrypted messages (Direct) and also signed unencrypted messages do not work though implicit SSL.
				//The System.Web.Mail.MailMessage class only understands plain text and html messages.
				//For a signed unencrypted message, the Content-Type header in the msgOut is "Content-Type: multipart/signed; boundary=PartA; protocol="application/pkcs7-signature"; micalg=sha1"
				//If the Content-Type header is added to the System.Web.Mail.MailMessage.Headers,
				//the Content-Type is modified to the following by C# when sending: "Content-Type: text/plain; boundary=PartA; protocol="application/pkcs7-signature"; micalg=sha1"
				throw new Exception(Lans.g("EmailMessages","Cannot send this type of message over implicit SSL."));
			}
			SmtpClient client=new SmtpClient(emailAddress.SMTPserver,emailAddress.ServerPort);
			//The default credentials are not used by default, according to: 
			//http://msdn2.microsoft.com/en-us/library/system.net.mail.smtpclient.usedefaultcredentials.aspx
			client.Credentials=new NetworkCredential(emailAddress.EmailUsername.Trim(),emailAddress.EmailPassword);
			client.DeliveryMethod=SmtpDeliveryMethod.Network;
			client.EnableSsl=emailAddress.UseSSL;
			client.Timeout=180000;//3 minutes
			MailMessage message=new MailMessage();
			string contentType="text/plain";//This is the default value that C# would use if we did not specify a Content-Type.  However we need to specify the Content-Type for the AlternateView.
			for(int i=0;i<msgOut.Message.Headers.Count;i++) {//This copies all headers, including but not limited to: From/To/Subject/Date/MessageID/etc...
				string name=msgOut.Message.Headers[i].Name;
				string val=msgOut.Message.Headers[i].ValueRaw;
				if(name.ToUpper()=="BCC") {
					message.Bcc.Add(val.Trim());
				}
				else if(name.ToUpper()=="CC") {
					message.CC.Add(val.Trim());
				}
				else if(name.ToUpper()=="CONTENT-TYPE") {
					contentType=val;
				}
				else if(name.ToUpper()=="FROM") {
					message.From=new MailAddress(val.Trim());
				}
				else if(name.ToUpper()=="PRIORITY") {
					message.Priority=MailPriority.Normal;
					if(val.ToLower()=="high") {
						message.Priority=MailPriority.High;
					}
					else if(val.ToLower()=="low") {
						message.Priority=MailPriority.Low;
					}
				}
				else if(name.ToUpper()=="REPLY-TO") {
					message.ReplyTo=new MailAddress(val.Trim());
				}
				else if(name.ToUpper()=="REPLY-TO-LIST") {
					string[] arrayReplyTo=val.Split(',');
					for(int j=0;j<arrayReplyTo.Length;j++) {
						message.ReplyToList.Add(arrayReplyTo[j].Trim());
					}
				}
				else if(name.ToUpper()=="SENDER") {
					message.Sender=new MailAddress(val.Trim());
				}
				else if(name.ToUpper()=="SUBJECT") {
					message.Subject=SubjectTidy(val);
				}
				else if(name.ToUpper()=="TO") {
					message.To.Add(val.Trim());
				}
				else {//Other headers, such as MessageID, which is needed for Direct messaging, but is not part of the standard MailMessage object.
					message.Headers.Add(name,val);//Add to header verbatim.
				}
			}
			//Using an AlternateView is the only way to specify a custom Content-Type.  Both encrypted email and signed email messages have special Content-Types.  Necessary for Direct messaging.
			byte[] arrayContentBytes=Encoding.UTF8.GetBytes(msgOut.Message.Body.Text);//This includes the body and all attachments.  Should have already been formatted properly by the Direct library.
			MemoryStream msEmailContent=new MemoryStream(arrayContentBytes);
			msEmailContent.Position=0;
			AlternateView alternateView=new AlternateView(msEmailContent,contentType);
			alternateView.TransferEncoding=TransferEncoding.SevenBit;//Default is base64, but 7bit is much easier to read/debug.
			message.AlternateViews.Add(alternateView);
			client.Send(message);
			msEmailContent.Dispose();
			SecurityLogs.MakeLogEntry(Permissions.EmailSend,patNum,"Email Sent");
		}
Пример #16
0
		///<summary>Converts any raw email message (encrypted or not) into an EmailMessage object, and saves any email attachments to the emailattach table in the db.
		///The emailMessageNum will be used to set EmailMessage.EmailMessageNum.  If emailMessageNum is 0, then the EmailMessage will be inserted into the db, otherwise the EmailMessage will be updated in the db.
		///If the raw message is encrypted, then will attempt to decrypt.  If decryption fails, then the EmailMessage SentOrReceived will be ReceivedEncrypted and the EmailMessage body will be set to the entire contents of the raw email.
		///If decryption succeeds, then EmailMessage SentOrReceived will be set to ReceivedDirect, the EmailMessage body will contain the decrypted body text, and a Direct Ack "processed" message will be sent back to the sender using the email settings from emailAddressReceiver.
		///Set isAck to true if decrypting a direct message, false otherwise.</summary>
		public static EmailMessage ProcessRawEmailMessageIn(string strRawEmail,long emailMessageNum,EmailAddress emailAddressReceiver,bool isAck) {
			//No need to check RemotingRole; no call to db.
			Health.Direct.Agent.IncomingMessage inMsg=RawEmailToIncomingMessage(strRawEmail);
			bool isEncrypted=IsReceivedMessageEncrypted(inMsg);
			EmailMessage emailMessage=null;
			if(isEncrypted) {
				emailMessage=ConvertMessageToEmailMessage(inMsg.Message,false,false);//Exclude attachments until we decrypt.
				emailMessage.RawEmailIn=strRawEmail;//The raw encrypted email, including the message, the attachments, and the signature.  The body of the encrypted email is just a base64 string until decrypted.
				emailMessage.EmailMessageNum=emailMessageNum;
				emailMessage.SentOrReceived=EmailSentOrReceived.ReceivedEncrypted;
				emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
				//The entire contents of the email are saved in the emailMessage.BodyText field, so that if decryption fails, the email will still be saved to the db for decryption later if possible.
				emailMessage.BodyText=strRawEmail;
				try {
					inMsg=DecryptIncomingMessage(inMsg);
					emailMessage=ConvertMessageToEmailMessage(inMsg.Message,true,false);//If the message was wrapped, then the To, From, Subject and Date can change after decyption. We also need to create the attachments for the decrypted message.
					emailMessage.RawEmailIn=strRawEmail;//The raw encrypted email, including the message, the attachments, and the signature.  The body of the encrypted email is just a base64 string until decrypted.
					emailMessage.EmailMessageNum=emailMessageNum;
					emailMessage.SentOrReceived=EmailSentOrReceived.ReceivedDirect;
					emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
					if(inMsg.HasSenderSignatures) {
						for(int i=0;i<inMsg.SenderSignatures.Count;i++) {
							EmailAttach emailAttach=EmailAttaches.CreateAttach("smime.p7s","",inMsg.SenderSignatures[i].Certificate.GetRawCertData(),false);
							emailMessage.Attachments.Add(emailAttach);
						}
					}
				}
				catch(Exception ex) {
					//SentOrReceived will be ReceivedEncrypted, indicating to the calling code that decryption failed.
					//The decryption step may have failed due to an untrusted sender, in which case the decrypting actually took place and the signature was extracted.
					//We add the signature to the email message so it will show up next to the email message in the inbox and make it easier for the user to add trust for the sender.
					if(inMsg.HasSenderSignatures) {
						for(int i=0;i<inMsg.SenderSignatures.Count;i++) {
							EmailAttach emailAttach=EmailAttaches.CreateAttach("smime.p7s","",inMsg.SenderSignatures[i].Certificate.GetRawCertData(),false);
							emailMessage.Attachments.Add(emailAttach);
						}
					}
					if(emailMessageNum==0) {
						EmailMessages.Insert(emailMessage);
						return emailMessage;//If the message was just downloaded, then this function was called from the inbox, simply return the inserted email without an exception (it can be decypted later manually by the user).
					}
					//Do not update if emailMessageNum<>0, because nothing changed (was encrypted and still is).
					throw ex;//Throw an exception if trying to decrypt an email that was already in the database, so the user can see the error message in the UI.
				}
			}
			else {//Unencrypted
				emailMessage=ConvertMessageToEmailMessage(inMsg.Message,true,false);
				emailMessage.RawEmailIn=strRawEmail;
				emailMessage.EmailMessageNum=emailMessageNum;
				emailMessage.SentOrReceived=EmailSentOrReceived.Received;
				emailMessage.RecipientAddress=emailAddressReceiver.EmailUsername.Trim();
			}
			EhrSummaryCcd ehrSummaryCcd=null;
			if(isEncrypted) {
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					if(Path.GetExtension(emailMessage.Attachments[i].ActualFileName).ToLower()!=".xml") {
						continue;
					}
					string strAttachPath=EmailAttaches.GetAttachPath();
					string strAttachFile=ODFileUtils.CombinePaths(strAttachPath,emailMessage.Attachments[i].ActualFileName);
					string strAttachText=File.ReadAllText(strAttachFile);
					if(EhrCCD.IsCCD(strAttachText)) {
						if(emailMessage.PatNum==0) {
							try {
								XmlDocument xmlDocCcd=new XmlDocument();
								xmlDocCcd.LoadXml(strAttachText);
								emailMessage.PatNum=EhrCCD.GetCCDpat(xmlDocCcd);// A match is not guaranteed, which is why we have a button to allow the user to change the patient.
							}
							catch {
								//Invalid XML.  Cannot match patient.
							}
						}
						ehrSummaryCcd=new EhrSummaryCcd();
						ehrSummaryCcd.ContentSummary=strAttachText;
						ehrSummaryCcd.DateSummary=DateTime.Today;
						ehrSummaryCcd.EmailAttachNum=i;//Temporary value, so we can locate the FK down below.
						ehrSummaryCcd.PatNum=emailMessage.PatNum;
						break;//We can only handle one CCD message per email, because we only have one patnum field per email record and the ehrsummaryccd record requires a patnum.
					}
				}
			}
			if(emailMessageNum==0) {
				EmailMessages.Insert(emailMessage);//Also inserts all of the attachments in emailMessage.Attachments after setting each attachment EmailMessageNum properly.
			}
			else {
				EmailMessages.Update(emailMessage);//Also deletes all previous attachments, then recreates all of the attachments in emailMessage.Attachments after setting each attachment EmailMessageNum properly.
			}
			if(ehrSummaryCcd!=null) {
				ehrSummaryCcd.EmailAttachNum=emailMessage.Attachments[(int)ehrSummaryCcd.EmailAttachNum].EmailAttachNum;
				EhrSummaryCcds.Insert(ehrSummaryCcd);
			}
			if(isEncrypted && isAck) {
				//Send a Message Disposition Notification (MDN) message to the sender, as required by the Direct messaging specifications.
				//The MDN will be attached to the same patient as the incoming message.
				SendAckDirect(inMsg,emailAddressReceiver,emailMessage.PatNum);
			}
			return emailMessage;
		}
Пример #17
0
		///<summary>Fetches up to fetchCount number of messages from a POP3 server.  Set fetchCount=0 for all messages.  Typically, fetchCount is 0 or 1.
		///Example host name, pop3.live.com. Port is Normally 110 for plain POP3, 995 for SSL POP3.</summary>
		private static List<EmailMessage> ReceiveFromInboxThreadSafe(int receiveCount,EmailAddress emailAddressInbox) {
			//No need to check RemotingRole; no call to db.
			List<EmailMessage> retVal=new List<EmailMessage>();
			//This code is modified from the example at: http://hpop.sourceforge.net/exampleFetchAllMessages.php
			using(OpenPop.Pop3.Pop3Client client=new OpenPop.Pop3.Pop3Client()) {//The client disconnects from the server when being disposed.
				client.Connect(emailAddressInbox.Pop3ServerIncoming,emailAddressInbox.ServerPortIncoming,emailAddressInbox.UseSSL,180000,180000,null);//3 minute timeout, just as for sending emails.
				client.Authenticate(emailAddressInbox.EmailUsername.Trim(),emailAddressInbox.EmailPassword,OpenPop.Pop3.AuthenticationMethod.UsernameAndPassword);
				List <string> listMsgUids=client.GetMessageUids();//Get all unique identifiers for each email in the inbox.
				List<EmailMessageUid> listDownloadedMsgUids=EmailMessageUids.GetForRecipientAddress(emailAddressInbox.EmailUsername.Trim());
				List<string> listDownloadedMsgUidStrs=new List<string>();
				for(int i=0;i<listDownloadedMsgUids.Count;i++) {
					listDownloadedMsgUidStrs.Add(listDownloadedMsgUids[i].MsgId);
				}
				int msgDownloadedCount=0;
				for(int i=0;i<listMsgUids.Count;i++) {
					int msgIndex=i+1;//The message indicies are 1-based.
					string strMsgUid=listMsgUids[i];//Example: 1420562540.886638.p3plgemini22-06.prod.phx.2602059520
					OpenPop.Mime.Header.MessageHeader messageHeader=null;
					if(strMsgUid.Length==0) {
						//Message Uids are commonly used, but are optional according to the RFC822 email standard.
						//Uids are assgined by the sending client application, so they could be anything, but are supposed to be unique.
						//Additionally, most email servers are probably smart enough to create a Uid for any message where the Uid is missing.
						//In the worst case scenario, we create a Uid for the message based off of the message header information, which takes a little extra time, 
						//but is better than downloading old messages again, especially if some of those messages contain large attachments.
						messageHeader=client.GetMessageHeaders(msgIndex);//Takes 1-2 seconds to get this information from the server.  The message, minus body and minus attachments.
						strMsgUid=messageHeader.DateSent.ToString("yyyyMMddHHmmss")+emailAddressInbox.EmailUsername.Trim()+messageHeader.From.Address+messageHeader.Subject;
					}
					if(strMsgUid.Length>4000) {//The EmailMessageUid.MsgId field is only 4000 characters in size.
						strMsgUid=strMsgUid.Substring(0,4000);
					}
					if(listDownloadedMsgUidStrs.Contains(strMsgUid)) {
						continue;//Skip emails which have already been downloaded.
					}
					//messageHeader will only be defined if we created our own unique ID manually above.  MessageId is optional, just as the message UIDs are.
					if(messageHeader!=null && messageHeader.MessageId!="") {
						//The MessageId is usually generated by the email server.
						//The message does not have a UID, and the ID that we made up has not been downloaded before.  As a last resort we check the MessageId in 
						//the message header.  MessageId is different than the UID.  We should have used the MessageId as the second option in the past, but now 
						//we are stuck using it as a third option, because using MessageId as a second option would cause old emails to download again.
						strMsgUid=messageHeader.MessageId;//Example: [email protected]
						if(strMsgUid.Length>4000) {//The EmailMessageUid.MsgId field is only 4000 characters in size.
							strMsgUid=strMsgUid.Substring(0,4000);
						}
						if(listDownloadedMsgUidStrs.Contains(strMsgUid)) {
							continue;//Skip emails which have already been downloaded.
						}
					}
					//At this point, we know that the email is one which we have not downloaded yet.
					try {
						OpenPop.Mime.Message openPopMsg=client.GetMessage(msgIndex);//This is where the entire raw email is downloaded.
						bool isEmailFromInbox=true;
						if(openPopMsg.Headers.From.ToString().ToLower().Contains(emailAddressInbox.EmailUsername.Trim().ToLower())) {//The email Recipient and email From addresses are the same.
							//The email Recipient and email To or CC or BCC addresses are the same.  We have verified that a user can send an email to themself using only CC or BCC.
							if(String.Join(",",openPopMsg.Headers.To).ToLower().Contains(emailAddressInbox.EmailUsername.Trim().ToLower()) ||
								String.Join(",",openPopMsg.Headers.Cc).ToLower().Contains(emailAddressInbox.EmailUsername.Trim().ToLower()) ||
								String.Join(",",openPopMsg.Headers.Bcc).ToLower().Contains(emailAddressInbox.EmailUsername.Trim().ToLower()))
							{
								//Download this message because it was clearly sent from the user to theirself.
							}
							else {
								//Gmail will report sent email as if it is part of the Inbox. These emails will have the From address as the Recipient address, but the To address will be a different address.
								isEmailFromInbox=false;
							}
						}
						if(isEmailFromInbox) {
							string strRawEmail=openPopMsg.MessagePart.BodyEncoding.GetString(openPopMsg.RawMessage);
							EmailMessage emailMessage=ProcessRawEmailMessageIn(strRawEmail,0,emailAddressInbox,true);//Inserts to db.
							retVal.Add(emailMessage);
							msgDownloadedCount++;
						}
						EmailMessageUid emailMessageUid=new EmailMessageUid();
						emailMessageUid.RecipientAddress=emailAddressInbox.EmailUsername.Trim();
						emailMessageUid.MsgId=strMsgUid;
						EmailMessageUids.Insert(emailMessageUid);//Remember Uid was downloaded, to avoid email duplication the next time the inbox is refreshed.
					}
					catch(ThreadAbortException) {
						//This can happen if the application is exiting. We need to leave right away so the program does not lock up.
						//Otherwise, this loop could continue for a while if there are a lot of messages to download.
						throw;
					}
					catch {
						//If one particular email fails to download, then skip it for now and move on to the next email.
					}
					if(receiveCount>0 && msgDownloadedCount>=receiveCount) {
						break;
					}
				}
			}
			//Since this function is fired automatically based on the inbox check interval, we also try to send the oldest unsent Ack.
			//The goal is to keep trying to send the Acks at a reasonable interval until they are successfully delivered.
			SendOldestUnsentAck(emailAddressInbox);
			return retVal;
		}
Пример #18
0
		///<summary>Throws exceptions.  Uses the Direct library to sign the message, so that our unencrypted/signed messages are built the same way as our encrypted/signed messages.
		///The provided certificate must contain a private key, or else the signing will fail (exception) when computing the signature digest.</summary>
		public static void SendEmailUnsecureWithSig(EmailMessage emailMessage,EmailAddress emailAddressFrom,X509Certificate2 certPrivate) {
			if(emailAddressFrom.IsImplicitSsl) {
				throw new Exception(Lans.g("EmailMessages","Digitally signed messages cannot be sent over implicit SSL."));//See detailed comments in the private version of SendEmailUnsecure().
			}
			//No need to check RemotingRole; no call to db.
			emailMessage.FromAddress=emailAddressFrom.EmailUsername.Trim();//Cannot be emailAddressFrom.SenderAddress, or else will not find the correct signing certificate.  Used in ConvertEmailMessageToMessage().
			Health.Direct.Common.Mail.Message msg=ConvertEmailMessageToMessage(emailMessage,true);
			Health.Direct.Agent.MessageEnvelope msgEnvelope=new Health.Direct.Agent.MessageEnvelope(msg);
			Health.Direct.Agent.OutgoingMessage msgOut=new Health.Direct.Agent.OutgoingMessage(msgEnvelope);
			Health.Direct.Agent.DirectAgent directAgent=GetDirectAgentForEmailAddress(emailMessage.FromAddress);
			Health.Direct.Common.Cryptography.SignedEntity signedEntity=directAgent.Cryptographer.Sign(msgOut.Message,certPrivate);//Compute the signature digest.  A hash of the certificate against the raw email content.
			msgOut.Message.UpdateBody(signedEntity);//Modify the relevant message headers as well as the entire message body to include the signature digest.
			WireEmailUnsecure(msgOut,emailAddressFrom,emailMessage.PatNum);
		}
Пример #19
0
		///<summary>Throws exceptions.  Attempts to physically send the message over the network wire.
		///This is used from wherever email needs to be sent throughout the program.  If a message must be encrypted, then encrypt it before calling this function.  nameValueCollectionHeaders can be null.</summary>
		private static void WireEmailUnsecure(EmailMessage emailMessage,EmailAddress emailAddress,NameValueCollection nameValueCollectionHeaders,params AlternateView[] arrayAlternateViews) {
			//No need to check RemotingRole; no call to db.
			//When batch email operations are performed, we sometimes do this check further up in the UI.  This check is here to as a catch-all.
			if(!Security.IsAuthorized(Permissions.EmailSend,DateTime.Now,true,Security.CurUser.UserGroupNum)){//This overload throws an exception if user is not authorized.
				return;
			}
			if(emailAddress.ServerPort==465) {//implicit
				//uses System.Web.Mail, which is marked as deprecated, but still supports implicit
				//http://msdn.microsoft.com/en-us/library/ms877952(v=exchg.65).aspx
				System.Web.Mail.MailMessage message=new System.Web.Mail.MailMessage();
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserver",emailAddress.SMTPserver);
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport","465");
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusing","2");//sendusing: 1=pickup, 2=port, 3=using microsoft exchange
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate","1");//0=anonymous,1=clear text auth,2=context (NTLM)
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername",emailAddress.EmailUsername.Trim());
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword",emailAddress.EmailPassword);
				//if(PrefC.GetBool(PrefName.EmailUseSSL)) {
				message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpusessl","true");//false was also tested and does not work
				message.From=emailMessage.FromAddress.Trim();
				message.To=emailMessage.ToAddress.Trim();
				message.Subject=SubjectTidy(emailMessage.Subject);
				message.Body=BodyTidy(emailMessage.BodyText);
				//message.Cc=;
				//message.Bcc=;
				//message.UrlContentBase=;
				//message.UrlContentLocation=;
				message.BodyEncoding=System.Text.Encoding.UTF8;
				message.BodyFormat=System.Web.Mail.MailFormat.Text;//or .Html
				if(nameValueCollectionHeaders!=null) {
					string[] arrayHeaderKeys=nameValueCollectionHeaders.AllKeys;
					for(int i=0;i<arrayHeaderKeys.Length;i++) {//Needed for Direct Acks to work.
						message.Headers.Add(arrayHeaderKeys[i],nameValueCollectionHeaders[arrayHeaderKeys[i]]);
					}
				}
				//TODO: We need to add some kind of alternatve view or similar replacement for outgoing Direct messages to work with SSL. Write the body to a temporary file and attach with the correct mime type and name?
				string attachPath=EmailAttaches.GetAttachPath();
				System.Web.Mail.MailAttachment attach;
				//foreach (string sSubstr in sAttach.Split(delim)){
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					attach=new System.Web.Mail.MailAttachment(ODFileUtils.CombinePaths(attachPath,emailMessage.Attachments[i].ActualFileName));
					//No way to set displayed filename on the MailAttachment object itself.  TODO: Copy the file to the temp directory in order to rename, then the attachment will go out with the correct name.
					message.Attachments.Add(attach);
				}
				System.Web.Mail.SmtpMail.SmtpServer=emailAddress.SMTPserver+":465";//"smtp.gmail.com:465";
				System.Web.Mail.SmtpMail.Send(message);
			}
			else {//explicit default port 587 
				SmtpClient client=new SmtpClient(emailAddress.SMTPserver,emailAddress.ServerPort);
				//The default credentials are not used by default, according to: 
				//http://msdn2.microsoft.com/en-us/library/system.net.mail.smtpclient.usedefaultcredentials.aspx
				client.Credentials=new NetworkCredential(emailAddress.EmailUsername.Trim(),emailAddress.EmailPassword);
				client.DeliveryMethod=SmtpDeliveryMethod.Network;
				client.EnableSsl=emailAddress.UseSSL;
				client.Timeout=180000;//3 minutes
				MailMessage message=new MailMessage();
				message.From=new MailAddress(emailMessage.FromAddress.Trim());
				message.To.Add(emailMessage.ToAddress.Trim());
				message.Subject=SubjectTidy(emailMessage.Subject);
				message.Body=BodyTidy(emailMessage.BodyText);
				message.IsBodyHtml=false;
				if(nameValueCollectionHeaders!=null) {
					message.Headers.Add(nameValueCollectionHeaders);//Needed for Direct Acks to work.
				}
				for(int i=0;i<arrayAlternateViews.Length;i++) {//Needed for Direct messages to be interpreted encrypted on the receiver's end.
					message.AlternateViews.Add(arrayAlternateViews[i]);
				}
				string attachPath=EmailAttaches.GetAttachPath();
				Attachment attach;
				for(int i=0;i<emailMessage.Attachments.Count;i++) {
					attach=new Attachment(ODFileUtils.CombinePaths(attachPath,emailMessage.Attachments[i].ActualFileName));
					//@"C:\OpenDentalData\EmailAttachments\1");
					attach.Name=emailMessage.Attachments[i].DisplayedFileName;
					//"canadian.gif";
					message.Attachments.Add(attach);
				}
				client.Send(message);
				SecurityLogs.MakeLogEntry(Permissions.EmailSend,emailMessage.PatNum,"Email Sent");
			}
		}
Пример #20
0
		///<summary></summary>
		public static long Insert(EmailAddress emailAddress) {
			if(RemotingClient.RemotingRole==RemotingRole.ClientWeb){
				emailAddress.EmailAddressNum=Meth.GetLong(MethodBase.GetCurrentMethod(),emailAddress);
				return emailAddress.EmailAddressNum;
			}
			return Crud.EmailAddressCrud.Insert(emailAddress);
		}
Пример #21
0
		///<summary>Receives one email from the inbox, and returns the contents of the attachment as a string.  Will throw an exception if anything goes wrong, so surround with a try-catch.</summary>
		public static string ReceiveOneForEhrTest() {
			//No need to check RemotingRole; no call to db.
			if(PrefC.GetString(PrefName.EHREmailToAddress)=="") {//this pref is hidden, so no practical way for user to turn this on.
				throw new ApplicationException("This feature cannot be used except in a test environment because email is not secure.");
			}
			if(PrefC.GetString(PrefName.EHREmailPOPserver)=="") {
				throw new ApplicationException("No POP server set up.");
			}
			EmailAddress emailAddress=new EmailAddress();
			emailAddress.Pop3ServerIncoming=PrefC.GetString(PrefName.EHREmailPOPserver);
			emailAddress.ServerPortIncoming=PrefC.GetInt(PrefName.EHREmailPort);
			emailAddress.EmailUsername=PrefC.GetString(PrefName.EHREmailFromAddress);
			emailAddress.EmailPassword=PrefC.GetString(PrefName.EHREmailPassword);
			List<EmailMessage> emailMessages=ReceiveFromInbox(1,emailAddress);
			if(emailMessages.Count==0) {
				throw new Exception("Inbox empty.");
			}
			EmailMessage emailMessage=emailMessages[0];
			if(emailMessage.Attachments==null || emailMessage.Attachments.Count==0) {
				throw new Exception("No attachments");
			}
			string strAttachFile=ODFileUtils.CombinePaths(GetEmailAttachPath(),emailMessage.Attachments[0].ActualFileName);
			return File.ReadAllText(strAttachFile);
		}
Пример #22
0
		///<summary>Encrypts the message, verifies trust, locates the public encryption key for the To address (if already stored locally), etc.  emailMessage.  
		///Use this polymorphism when the attachments have already been saved to the email attachments folder in file form.  patNum can be 0.
		///Returns an empty string upon success, or an error string if there were errors.  It is possible that the email was sent to some trusted recipients and not sent to untrusted recipients (in which case there would be errors but some recipients would receive successfully).
		///Surround with a try catch.</summary>
		public static string SendEmailDirect(EmailMessage emailMessage,EmailAddress emailAddressFrom) {
			//No need to check RemotingRole; no call to db.
			emailMessage.FromAddress=emailAddressFrom.EmailUsername.Trim();//Cannot be emailAddressFrom.SenderAddress, or else will not find the correct encryption certificate.  Used in ConvertEmailMessageToMessage().
			//Start by converting the emailMessage to an unencrypted message using the Direct libraries. The email must be in this form to carry out encryption.
			Health.Direct.Common.Mail.Message msgUnencrypted=ConvertEmailMessageToMessage(emailMessage,true);
			Health.Direct.Agent.MessageEnvelope msgEnvelopeUnencrypted=new Health.Direct.Agent.MessageEnvelope(msgUnencrypted);
			Health.Direct.Agent.OutgoingMessage outMsgUnencrypted=new Health.Direct.Agent.OutgoingMessage(msgEnvelopeUnencrypted);
			string strErrors=SendEmailDirect(outMsgUnencrypted,emailAddressFrom);
			return strErrors;
		}
Пример #23
0
		///<summary>Refreshes our cached copy of the public key certificate store and the anchor certificate store from the Windows certificate store.</summary>
		public static void RefreshCertStoreExternal(EmailAddress emailAddressLocal) {
			string strSenderAddress=emailAddressLocal.EmailUsername.Trim();//Cannot be emailAddressFrom.SenderAddress, or else will not find the right encryption certificate.
			Health.Direct.Agent.DirectAgent directAgent=GetDirectAgentForEmailAddress(strSenderAddress);
			string strSenderDomain=GetDomainForAddress(strSenderAddress);
			//Refresh the directAgent class using the updated list of public certs while leaving everything else alone. This must be done, or else the certificate will not be found when encrypting the outgoing email.
			directAgent=new Health.Direct.Agent.DirectAgent(strSenderDomain,directAgent.PrivateCertResolver,Health.Direct.Common.Certificates.SystemX509Store.OpenExternal().CreateResolver(),
				new Health.Direct.Common.Certificates.TrustAnchorResolver(Health.Direct.Common.Certificates.SystemX509Store.OpenAnchor()));
			directAgent.EncryptMessages=true;
			HashDirectAgents[strSenderDomain]=directAgent;
		}