Skip to main content

ASP.NET and Exchange tips and samples

I’ve been writing some ASP.NET pages for my Exchange boxes at the moment and although there is a lot of good information out there about ASP.Net finding good information about using ASP.Net with Exchange can be a little challenging so I though I share some info and links that I found useful. For this post I’m looking at using NTLM authentication on a remote web server to access Exchange 200x box on another server (if your using Form Based Authentication have a look at this )

Authentication

This was one of the hardest things for me to get my head around from classic asp. With “ASP.NET it provides an out-of-process execution model, which protects the server process from user code” this provides a good primer on the ASP.NET process model


The import part of this is what account the ASP.NET process runs under by default in IIS5 it runs under a local SAM account “ASPNET”. On an IIS6 box (win 2003) the ASP.NET process runs under the default Network Service (system account) account. This becomes important when you start to try and delegate authentication to access another server.

Impersonation

Impersonation is important because it allows you to access a mailbox in your ASP.Net page using the credentials of the user you have authenticated on your webserver. The important thing to remember about impersonation is that by itself it only allows you to access resources on the local server using the credentials you’re impersonating. There is a good KB article on impersonation that gives you the different methods you can use to perform impersonation from using the web.config file to doing it in-code. I like the in-code method to me its a more flexible and secure way to do it.

Delegation

“Impersonation enables ASP.NET to execute code and access resources in the context of an authenticated and authorized user, but only on the server where ASP.NET is running. To access resources located on another computer on behalf of an impersonated user requires authentication delegation (or delegation for short).” Ref this

This is pretty important thing if you want your remote web server to be able to access your mailbox. Another import thing from the “Integrated Windows Authentication” section is “When used in conjunction with Kerberos v5 authentication, IIS can delegate security credentials among computers running Windows 2000 or later that are trusted and configured for delegation.”. Which means to get delegation to work you need to be using Kerberos and you need to go off and read another couple of articles which details how to set this up this msdn article and Q810572. The import part in these articles is if you’re running IIS5 in the default config “the account used to run the server process (the process that performs impersonation) is allowed to delegate client accounts. You must configure the user account under which the server process runs, or if the process runs under the local SYSTEM account.”. On a IIS5 box the default user is the aspnet user which means you will need to change the account being used to one that can be trusted for delegation (eg the system account).

(Note: this was one thing that I found really hard to get good accurate information on what I’ve included in this post is what I found worked for me. Running the asp.net process under the system account does open up certain security issues but hey so does enabling delegation on your web server if someone wishes’s to correct me please send me an email. If you’re using 2003 this conversation is kind of redundant plus you have the ability to do constrained delegation)

SSL and Self signed certificates

Running WebDAV over http remotely is not a great idea because your going to be shipping your data as clear text over the network So its best to use https to do all your conversations with the Exchange server. If you’re down the less financially able spectrum you may not be able to afford to be using registered SSL certificates. In this case your application may have to deal with certificate warning messages. You can do this “by implementing your own CertificatePolicy class (which implements the ICertificatePolicy interface). In this class you will have to write your own CheckValidationResult function that has to return true or false” have a read of http://weblogs.asp.net/jan/archive/2003/12/04/41154.aspx which gives a good description and some code to work around this.

Some Examples

I’ve come up with a couple of samples that demonstrate all the stuff I’ve talked about. Both samples do the same thing they use the System.Directroyservices namespace to build a URL to access the user’s logon information (based on the user that is authenticated on the IIS server). Once the URL is built it then connects to the mailbox and queries all the contacts in a user’s mailbox. The first example uses a XSL file to do a transform of the XML returned from the WebDAV query. They both use SSL and implement a method to override any certificate popup warnings. The second example loads the result of the WebDAV query into an ADO.NET dataset and then displays the result in a datagrid. Both code samples need to be located in a directory that has all other authentication except windows authentication turned off. I’ve posted a downloadable copy of both examples here the second example looks like.

<%@ Page %>
<%@Import namespace="System.Data"%>
<%@Import namespace="System.Net"%>
<%@Assembly name="System.DirectoryServices, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a, Custom=null"%>
<%@Import namespace="System.DirectoryServices"%>
<%@Import namespace="System.Security.Cryptography.X509Certificates"%>
<script language="vb" runat="server">

Public Class AcceptAllCertificatePolicy
Implements ICertificatePolicy
Public Overridable Function CheckValidationResult(ByVal srvPoint As ServicePoint,
ByVal certificate As X509Certificate, ByVal request As WebRequest, ByVal problem
As Integer) As Boolean Implements ICertificatePolicy.CheckValidationResult
Return True 'this accepts all certificates
End Function
End Class


Sub Page_Load(sender As Object, e As EventArgs)
System.Net.ServicePointManager.CertificatePolicy = New
AcceptAllCertificatePolicy
Dim Request As System.Net.HttpWebRequest
Dim Response As System.Net.HttpWebResponse
Dim strRootURI As String
Dim strQuery As String
Dim bytes() As Byte
Dim workrow As System.Data.DataRow
Dim resrow As System.Data.DataRow
Dim impersonationContext As
System.Security.Principal.WindowsImpersonationContext
Dim currentWindowsIdentity As System.Security.Principal.WindowsIdentity
currentWindowsIdentity = CType(User.Identity,
System.Security.Principal.WindowsIdentity)
impersonationContext = currentWindowsIdentity.Impersonate()
Dim MyCredentialCache As System.Net.CredentialCache
Dim resdataset As New System.Data.DataSet
Dim RequestStream As System.IO.Stream
Dim ResponseStream As System.IO.Stream
Dim ResponseXmlDoc As System.Xml.XmlDocument
Dim DisplayNameNodes As System.Xml.XmlNodeList
Dim objsearch As New System.DirectoryServices.DirectorySearcher
Dim strrootdse As String = objsearch.SearchRoot.Path
Dim objdirentry As New system.DirectoryServices.DirectoryEntry(strrootdse)
Dim objresult As system.DirectoryServices.SearchResult
Dim stremailaddress As String
Dim strhomeserver As String
objsearch.Filter = "(&(&(&(& (mailnickname=*) (|
(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)" _
& "(msExchHomeServerName=*)))
)))(objectCategory=user)(userPrincipalName=*)(mailNickname=" &
System.Environment.UserName & ")))"
objsearch.SearchScope = DirectoryServices.SearchScope.Subtree
objsearch.PropertiesToLoad.Add("mail")
objsearch.PropertiesToLoad.Add("msExchHomeServerName")
objsearch.Sort.Direction = DirectoryServices.SortDirection.Ascending
objsearch.Sort.PropertyName = "mail"
Dim colresults As DirectoryServices.SearchResultCollection = objsearch.FindAll()
For Each objresult In colresults
stremailaddress = objresult.GetDirectoryEntry().Properties("mail").Value
strhomeserver =
objresult.GetDirectoryEntry().Properties("msExchHomeServerName").Value
Next
Dim emailNameNodes As System.Xml.XmlNodeList
strhomeserver = Right(strhomeserver, Len(strhomeserver) - (InStr(strhomeserver,
"cn=Servers/cn=") + 13))
strRootURI = "https://" & strhomeserver & "/exchange/" & stremailaddress &
"/contacts/"
strQuery = "<?xml version=""1.0""?>" & _
"<D:searchrequest xmlns:D = ""DAV:"" >" & _
"<D:sql>SELECT ""urn:schemas:contacts:cn"",
""http://schemas.microsoft.com/mapi/email1emailaddress"" " & _
"FROM """ & strRootURI & """" & _
"WHERE ""DAV:ishidden"" = false AND ""DAV:isfolder"" = false AND
""DAV:contentclass"" = 'urn:content-classes:person'" & _
"</D:sql></D:searchrequest>"
Request = CType(System.Net.WebRequest.Create(strRootURI), _
System.Net.HttpWebRequest)
Request.Credentials = System.Net.CredentialCache.DefaultCredentials
Request.Method = "SEARCH"
bytes = System.Text.Encoding.UTF8.GetBytes(strQuery)
Request.ContentLength = bytes.Length
RequestStream = Request.GetRequestStream()
RequestStream.Write(bytes, 0, bytes.Length)
RequestStream.Close()
Request.ContentType = "text/xml"
Request.Headers.Add("Translate", "F")
Response = CType(Request.GetResponse(), System.Net.HttpWebResponse)
ResponseStream = Response.GetResponseStream()
ResponseXmlDoc = New System.Xml.XmlDocument
ResponseXmlDoc.Load(ResponseStream)
DisplayNameNodes = ResponseXmlDoc.GetElementsByTagName("d:cn")
emailNameNodes = ResponseXmlDoc.GetElementsByTagName("e:email1emailaddress")
Dim resultstable As DataTable
resultstable=new DataTable()
resultstable.Columns.Add("Contact Name")
resultstable.Columns.Add("Email Address")
AddRow(resultstable, "glenscales@yahoo.com", "Tests")
If DisplayNameNodes.Count > 0 Then
Dim i As Integer
For i = 0 To DisplayNameNodes.Count - 1
AddRow(resultstable, emailNameNodes(i).InnerText, DisplayNameNodes(i).InnerText)
Next
Else
Console.WriteLine("No non-folder items found...")
End If
Showcontacts.DataSource=resultstable
Showcontacts.DataBind()
impersonationContext.Undo()
End Sub

Sub AddRow(resultstable As DataTable, Emailaddress As String, Name As String)
Dim row As DataRow
row=resultstable.NewRow()
row("Contact Name")=Name
row("Email Address")=Emailaddress
resultstable.Rows.Add(row)
End Sub
</script>
<HTML>
<body>
<form id="Recipe1416vb" method="post" runat="server">
<asp:DataGrid ID="Showcontacts" Runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundColumn DataField="Contact Name" HeaderText="Contact Name" />
<asp:BoundColumn DataField="Email Address" HeaderText="Email Address" />
</Columns>
</asp:DataGrid>
</form>
</body>
</HTML>

Popular posts from this blog

Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShell

As well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement here ). A while ago I created  this script that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message. Token Acquisition  To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration  https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.