Exchange ActiveSync Building Blocks – Autodiscover

Index for the series:
Exchange ActiveSync Building Blocks – Intro

Background reading (not a pre-requisite):
EAS MD 1.4 – Autodiscover Support
blogs.msdn.com – Autodiscover for ActiveSync Developers
[MS-ASCMD] -ActiveSync Command Reference – Autodiscover

So, enough with the excuses. Give us something we can compile, that we can use in an actual ActiveSync client, that is relevant – just something already.

Where to start with a topic like the Exchange ActiveSync protocol? I decided that although it’s kind of in reverse order if you use my EAS MD utility as the yardstick, I wanted to cover the last feature I implemented first in this series Smile (Hey, that means I might still have the code fresh in mind, right?)

There is a reason I do it in this order. When building my tool Autodiscover wasn’t initially on top of the list of issues I had when testing and verifying Exchange Server ActiveSync setups. (I think figuring out the provisioning process was on top of my list actually.)

But at the same time – when you configure an ActiveSync client one of the first things you have to be aware is the address to your Exchange Server. While this is not a big problem to solve by manually typing in the address it does feel smoother just typing your email address accompanied by your password and have the device figure out the rest of it. And while most IT pros will be able to figure out the address to the Exchange Server, (unless your Exchange admin is refusing to let you in on it), many end-users have no idea what Exchange even means. They usually have a clue as to what their email address might be though, so that might be a good starting point on their quest to get mail and “stuff” on their devices.

As it turns out it’s also one of the easier parts of the protocol to get started with, as you don’t have to concern yourself with WBXML and such. I’ve previously explained how Autodiscover works in general, so read up on that if you’re a bit unsure as to what we’re talking about here. (Link on top of this post.)

So, what do we need to test this out. I’d say we need a small app for this purpose.

image

I’m using .NET 4 here, but it should work in .NET 3.5 as well. (Long story short I initially thought I need 4, but had to revise that further on in the process. More details will follow further down the page.)

So, I threw a couple of UI elements together, and you’ll no doubt find it incredibly snazzy.

image

The minimum we require of input from the user is their email address and their password. If everything is good on the Exchange Server we don’t need anything else. (We’ll assume it’s a perfect little world in our lab for now.) We have already agreed, (rather I have described it before), that there are four tests to perform when testing Autodiscover and as such I just created four buttons for triggering the different tests. We will have to agree what the different test numbers do, so I decided this would be a nice order:

Test 1 – https://FQDN/autodiscover/autodiscover.xml
Test 2 – https://autodiscover.FQDN/autodiscover/autodiscover.xml
Test 3 – http://autodiscover.FQDN/autodiscover/autodiscover.xml (HTTP GET)
Test 4 – SRV query _autodiscover._tcp.FQDN

We have to programmatically figure out the FQDN based on the email address. For this scenario we will assume that an email address is always in the form username@domain.com. (I hear that according to RFCs user@company@domain.com could be valid, but let’s get real – how often do we see that occurring?) This means we use @ as the separator. We’ll use the following code for figuring this out (included in all tests):

string[] strEmailAddressSplit;
string strEmailAddress = txtEmailAddress.Text;
string strUsername = null;
string strFqdn = null;
string strPassword = txtPassword.Text;
string strEncCredentials = null;

try {
    strEmailAddressSplit = txtEmailAddress.Text.Split('@');
    strUsername = strEmailAddressSplit[0];
    strFqdn = strEmailAddressSplit[1];
}
catch (Exception)
{
    //Do nothing }

You need to send a request to the server in question, and this takes the form of plain old xml:

<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/
              autodiscover/mobilesync/requestschema/2006">
  <Request>
    <EMailAddress>andreas@contoso.com</EMailAddress>
    <AcceptableResponseSchema>
      http://schemas.microsoft.com/exchange/autodiscover/mobilesync/
      responseschema/2006
    </AcceptableResponseSchema>
  </Request>
</Autodiscover>

And as long as you do the escaping properly you can just assign the xml as a string variable. (I do this manually here because it’s a rather short xml document we’re converting.) I’ve also replaced the static email address in the xml with a variable. (I’ve broken the string into more lines than necessary to fit into a readable frame here.)

string autodiscoverRequest =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/" +
"autodiscover/mobilesync/requestschema/2006\">" +
"<Request>" +
"<EMailAddress>" + txtEmailAddress.Text +
"</EMailAddress><AcceptableResponseSchema>" +
"http://schemas.microsoft.com/exchange/autodiscover/mobilesync/" +
"responseschema/2006</AcceptableResponseSchema>" +
"</Request>" +
"</Autodiscover>";

So, let’s get to it – we combine what we’ve got so far and create the actual tests:

Test #1

strEncCredentials = getEncCredentials(strEmailAddress, strPassword);

string uri = "https://" + strFqdn + "/autodiscover/autodiscover.xml";
txtOutput.Text += "Test #1:\r\n\r\n";

try {
   HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
   webRequest.Method = "POST";
   webRequest.UserAgent = "MobilityDojo.net";

   txtOutput.Text +=
   "Testing following Autodiscover address:\r\n" + uri + "\r\n";

   System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
   Byte[] byteRequest = encoding.GetBytes(autodiscoverRequest);  

   webRequest.Headers.Add("Authorization", "Basic " + strEncCredentials);
   webRequest.ContentType = "text/xml";
   webRequest.ContentLength = byteRequest.Length;

   Stream sw = webRequest.GetRequestStream();
   sw.Write(byteRequest, 0, byteRequest.Length);
   sw.Close();

   WebResponse webResponse = webRequest.GetResponse();
   if (webResponse == null)
   {
   }

   StreamReader sr = new StreamReader(webResponse.GetResponseStream());
   txtOutput.Text += sr.ReadToEnd().Trim() + "\r\n\r\n";
}
catch (WebException ex)
{
   txtOutput.Text += "Something went wrong!\r\n";
   txtOutput.Text += ex.Message.ToString() + "\r\n\r\n";
}

Hey, isn’t that exception handling exceptionally lazy? It is. We’re actually just operating very binary – things either work, or they don’t work. We do print out the exception, and maybe we can draw some conclusions based on that, but we’re still ignoring it basically. This will be addressed at a later point in time. For now we assume that there’s no reason it would fail Smile

If this is causing you problems, and you can’t figure it out from the exception, you could run a test with the more feature complete EAS MD utility. I just want to make it simple here for the sake of clarity.

The next test; #2 is very similar. We’re doing the same thing as in the first test, we just use a slightly different uri. Just to be on the safe side – here it is:

Test #2

strEncCredentials = getEncCredentials(strEmailAddress, strPassword);           

string uri = "https://autodiscover." + strFqdn + "/autodiscover/autodiscover.xml";
txtOutput.Text += "Test #2:\r\n\r\n";

try {
   HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
   webRequest.Method = "POST";
   webRequest.UserAgent = "MobilityDojo.net";

   txtOutput.Text +=
   "Testing following Autodiscover address:\r\n" + uri + "\r\n";

   System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
   Byte[] byteRequest = encoding.GetBytes(autodiscoverRequest);

   webRequest.Headers.Add("Authorization", "Basic " + strEncCredentials);
   webRequest.ContentType = "text/xml";
   webRequest.ContentLength = byteRequest.Length;

   Stream sw = webRequest.GetRequestStream();
   sw.Write(byteRequest, 0, byteRequest.Length);
   sw.Close();

   WebResponse webResponse = webRequest.GetResponse();
   if (webResponse == null)
   {
   }

   StreamReader sr = new StreamReader(webResponse.GetResponseStream());
   txtOutput.Text += sr.ReadToEnd().Trim() + "\r\n\r\n";
}
catch (WebException ex)
{
   txtOutput.Text += "Something went wrong!\r\n";
   txtOutput.Text += ex.Message.ToString() + "\r\n\r\n";
}

With test #3 things get slightly different. We use HTTP GET instead of POST, (meaning we can skip most of the request building), and we don’t need authentication either. (Of course that has nothing to with the method being GET, but how Autodiscover works.)

This code turns out a couple of lines shorter:

Test #3

private void btnTest3_Click(object sender, EventArgs e)
{
   try {
       HttpWebRequest webRequest =
       (HttpWebRequest)WebRequest.Create(uri);
       webRequest.Method = "GET";
       webRequest.UserAgent = "MobilityDojo.net";

       txtOutput.Text +=
       "Testing following Autodiscover address:\r\n" + uri + "\r\n";

       //If we allow automatic redirection (HTTP 302) //we end up with HTTP 401 //Need to disable redirection to catch it webRequest.AllowAutoRedirect = false;

       WebResponse webResponse = webRequest.GetResponse();
       if (webResponse == null)
       {
       }

       //If we get a redirect notification we parse out the address. //This is expected/correct behaviour for the HTTP GET test. HttpWebResponse resEx = (HttpWebResponse)webResponse;
       int resCode = (int)resEx.StatusCode;
       if (resCode == 302)
       {

           try {
               string[] resultHeaders =
               webResponse.Headers.GetValues("Location");
               txtOutput.Text += "Autodiscover redirect; " +
               resultHeaders[0].ToString() + "\r\n";
           }
           catch (Exception)
           {
           }
       }
   }
   catch (WebException)
   {
   }
}

The important things to note here are:

– We need to disable automatic redirection, (or it will fail due to missing authentication), since it will ideally redirect to the URL we use in test #2.
– If we don’t parse out the returned address it’s not much point in performing this test.
– This test is supposed to lead to a subsequent test; you’re not going to get an actual Autodiscover response from this test alone.

We’re almost there now. Just the final test left.

Test #4

Process nsLookup = new Process();
ProcessStartInfo nsLookupInfo =
new ProcessStartInfo("nslookup.exe",
"-type=srv _autodiscover._tcp." + strFqdn);
nsLookupInfo.UseShellExecute = false;
nsLookupInfo.RedirectStandardOutput = true;
nsLookupInfo.WindowStyle = ProcessWindowStyle.Hidden;
nsLookupInfo.CreateNoWindow = true;

nsLookup.StartInfo = nsLookupInfo;
nsLookup.Start();

StreamReader sr = nsLookup.StandardOutput;
txtOutput.Text += sr.ReadToEnd().Trim() + "\r\n";

nsLookup.Close();

 

I came under the impression the last time I was looking into this that .NET 4 would bring native support for SRV records. (I think it was very late at night where the focus probably was losing out to sleep.) Boy, was I wrong. And boy I had no idea how painful this could be. No, I didn’t find an easy way to query SRV records. I found third-party DNS libraries, but these are old, and throw a fit if you have IPv6 enabled on your computer. You can write your own wrapper of course, but that would take just a few more code lines than I was ready for now. I ended up doing a slightly hackish approach – most Windows systems these days should have nslookup available. So I just spawn an invisible command line in the background doing a query with nslookup, and paste the results into the output windows. It works, and looks decent enough, but I don’t consider it the perfect solution. The thing to look for in the output window is the “srv hostname” field. This might contain autodiscover.FQDN as used in the previous tests, (in which case you’ll already have a valid response through those tests), or contain a completely different URL (also a valid configuration).

If the URL returned by test #4 is different from the attempts of Test 1-3 you should perform the tests one more time using this new URL. With proper Exchange configuration it should only be necessary to run test #1.

If you’re able to follow me through a silly scenario it would be possible to have a process like this:

– Test 1-3 fails.
– Test 4 returns fake-autodiscover.contoso.com.
– Test 2 fails.
– Test 3 succeeds, and returns real-autodiscover.contoso.com.
– Test 2 succeeds (against real-autodiscover.contoso.com).

I don’t think that’s a normal scenario, but who knows what some individual might find clever if they’re following a security through obscurity train of thought. Please do not configure your Exchange Server this way Smile

If you’re still not getting any valid responses from the server you should consider the option that something may be configured incorrectly on Exchange. (Unless the purpose is to not provide the Autodiscover service as a design decision.)

Which brings up a suitable question – what does a valid response look like? Well, like this perhaps:

<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/
              autodiscover/responseschema/2006">
  <Response xmlns="http://schemas.microsoft.com/exchange/
            autodiscover/mobilesync/responseschema/2006">
    <Culture>en:us</Culture>
    <User>
      <DisplayName>Andreas Helland</DisplayName>
      <EMailAddress>andreas@contoso.com</EMailAddress>
    </User>
    <Action>
      <Settings>
        <Server>
          <Type>MobileSync</Type>
          <Url>https://eas.contoso.com/Microsoft-Server-ActiveSync</Url>
          <Name>https://eas.contoso.com/Microsoft-Server-ActiveSync</Name>
        </Server>
      </Settings>
    </Action>
  </Response>
</Autodiscover>

It can come in different forms though – and you should also check that it’s not indicating you should do another Autodiscover lookup.

At this time you should hopefully have the address for the ActiveSync service sorted, and you can proceed setting up the synchronization partnership. This also concludes this first part of the Exchange ActiveSync Building Blocks series. I hope this is starting to get more interesting by now, and more content is in the pipeline. Let me know if there’s anything that isn’t working out with the current approach.

Downloads:
The C# source – Autodiscover.cs
The Visual Studio Solution – EAS_BB_Part-01_01.zip

One Response to “Exchange ActiveSync Building Blocks – Autodiscover”

  1. Exchange ActiveSync Building Blocks – Intro | MobilityDojo.net
Leave a Reply

*
RSS for Posts RSS for Comments