001/*
002 * The MIT License
003 * Copyright (c) 2012 Microsoft Corporation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a copy
006 * of this software and associated documentation files (the "Software"), to deal
007 * in the Software without restriction, including without limitation the rights
008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 * copies of the Software, and to permit persons to whom the Software is
010 * furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 * THE SOFTWARE.
022 */
023
024package microsoft.exchange.webservices.data.autodiscover;
025
026import microsoft.exchange.webservices.data.autodiscover.configuration.ConfigurationSettingsBase;
027import microsoft.exchange.webservices.data.autodiscover.configuration.outlook.OutlookConfigurationSettings;
028import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverEndpoints;
029import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverErrorCode;
030import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverLocalException;
031import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverRemoteException;
032import microsoft.exchange.webservices.data.autodiscover.request.AutodiscoverRequest;
033import microsoft.exchange.webservices.data.autodiscover.request.GetDomainSettingsRequest;
034import microsoft.exchange.webservices.data.autodiscover.request.GetUserSettingsRequest;
035import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponse;
036import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponseCollection;
037import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponse;
038import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponseCollection;
039import microsoft.exchange.webservices.data.core.EwsUtilities;
040import microsoft.exchange.webservices.data.core.EwsXmlReader;
041import microsoft.exchange.webservices.data.core.ExchangeServiceBase;
042import microsoft.exchange.webservices.data.core.request.HttpClientWebRequest;
043import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
044import microsoft.exchange.webservices.data.credential.WSSecurityBasedCredentials;
045import microsoft.exchange.webservices.data.autodiscover.enumeration.DomainSettingName;
046import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
047import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
048import microsoft.exchange.webservices.data.autodiscover.enumeration.UserSettingName;
049import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
050import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
051import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
052import microsoft.exchange.webservices.data.autodiscover.exception.MaximumRedirectionHopsExceededException;
053import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
054import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
055import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
056import microsoft.exchange.webservices.data.misc.OutParam;
057import microsoft.exchange.webservices.data.security.XmlNodeType;
058
059import javax.xml.stream.XMLStreamException;
060
061import java.io.ByteArrayInputStream;
062import java.io.ByteArrayOutputStream;
063import java.io.IOException;
064import java.io.InputStream;
065import java.io.OutputStream;
066import java.io.PrintWriter;
067import java.net.MalformedURLException;
068import java.net.URI;
069import java.net.URISyntaxException;
070import java.util.ArrayList;
071import java.util.Arrays;
072import java.util.Collection;
073import java.util.EnumSet;
074import java.util.List;
075
076/**
077 * Represents a binding to the Exchange Autodiscover Service.
078 */
079public class AutodiscoverService extends ExchangeServiceBase
080    implements IAutodiscoverRedirectionUrl, IFunctionDelegate {
081
082  // region Private members
083  /**
084   * The domain.
085   */
086  private String domain;
087
088  /**
089   * The is external.
090   */
091  private Boolean isExternal = true;
092
093  /**
094   * The url.
095   */
096  private URI url;
097
098  /**
099   * The redirection url validation callback.
100   */
101  private IAutodiscoverRedirectionUrl
102      redirectionUrlValidationCallback;
103
104  /**
105   * The dns client.
106   */
107  private AutodiscoverDnsClient dnsClient;
108
109  /**
110   * The dns server address.
111   */
112  private String dnsServerAddress;
113
114  /**
115   * The enable scp lookup.
116   */
117  private boolean enableScpLookup = true;
118
119  // Autodiscover legacy path
120  /**
121   * The Constant AutodiscoverLegacyPath.
122   */
123  private static final String AutodiscoverLegacyPath =
124      "/autodiscover/autodiscover.xml";
125
126  // Autodiscover legacy HTTPS Url
127  /**
128   * The Constant AutodiscoverLegacyHttpsUrl.
129   */
130  private static final String AutodiscoverLegacyHttpsUrl = "https://%s" +
131      AutodiscoverLegacyPath;
132  // Autodiscover legacy HTTP Url
133  /**
134   * The Constant AutodiscoverLegacyHttpUrl.
135   */
136  private static final String AutodiscoverLegacyHttpUrl = "http://%s" +
137      AutodiscoverLegacyPath;
138  // Autodiscover SOAP HTTPS Url
139  /**
140   * The Constant AutodiscoverSoapHttpsUrl.
141   */
142  private static final String AutodiscoverSoapHttpsUrl =
143      "https://%s/autodiscover/autodiscover.svc";
144  // Autodiscover SOAP WS-Security HTTPS Url
145  /**
146   * The Constant AutodiscoverSoapWsSecurityHttpsUrl.
147   */
148  private static final String AutodiscoverSoapWsSecurityHttpsUrl =
149      AutodiscoverSoapHttpsUrl +
150          "/wssecurity";
151
152  /**
153   * Autodiscover SOAP WS-Security symmetrickey HTTPS Url
154   */
155  private static final String AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl =
156      AutodiscoverSoapHttpsUrl + "/wssecurity/symmetrickey";
157
158  /**
159   * Autodiscover SOAP WS-Security x509cert HTTPS Url
160   */
161  private static final String AutodiscoverSoapWsSecurityX509CertHttpsUrl =
162      AutodiscoverSoapHttpsUrl + "/wssecurity/x509cert";
163
164
165  // Autodiscover request namespace
166  /**
167   * The Constant AutodiscoverRequestNamespace.
168   */
169  private static final String AutodiscoverRequestNamespace =
170      "http://schemas.microsoft.com/exchange/autodiscover/" +
171          "outlook/requestschema/2006";
172  // Maximum number of Url (or address) redirections that will be followed by
173  // an Autodiscover call
174  /**
175   * The Constant AutodiscoverMaxRedirections.
176   */
177  protected static final int AutodiscoverMaxRedirections = 10;
178  // HTTP header indicating that SOAP Autodiscover service is enabled.
179  /**
180   * The Constant AutodiscoverSoapEnabledHeaderName.
181   */
182  private static final String AutodiscoverSoapEnabledHeaderName =
183      "X-SOAP-Enabled";
184  // HTTP header indicating that WS-Security Autodiscover service is enabled.
185  /**
186   * The Constant AutodiscoverWsSecurityEnabledHeaderName.
187   */
188  private static final String AutodiscoverWsSecurityEnabledHeaderName =
189      "X-WSSecurity-Enabled";
190
191
192  /**
193   * HTTP header indicating that WS-Security/SymmetricKey Autodiscover service is enabled.
194   */
195
196  private static final String AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName =
197      "X-WSSecurity-SymmetricKey-Enabled";
198
199
200  /**
201   * HTTP header indicating that WS-Security/X509Cert Autodiscover service is enabled.
202   */
203
204  private static final String AutodiscoverWsSecurityX509CertEnabledHeaderName =
205      "X-WSSecurity-X509Cert-Enabled";
206
207
208  // Minimum request version for Autodiscover SOAP service.
209  /**
210   * The Constant MinimumRequestVersionForAutoDiscoverSoapService.
211   */
212  private static final ExchangeVersion
213      MinimumRequestVersionForAutoDiscoverSoapService =
214      ExchangeVersion.Exchange2010;
215
216  /**
217   * Default implementation of AutodiscoverRedirectionUrlValidationCallback.
218   * Always returns true indicating that the URL can be used.
219   *
220   * @param redirectionUrl the redirection url
221   * @return Returns true.
222   * @throws AutodiscoverLocalException the autodiscover local exception
223   */
224  private boolean defaultAutodiscoverRedirectionUrlValidationCallback(
225      String redirectionUrl) throws AutodiscoverLocalException {
226    throw new AutodiscoverLocalException(String.format(
227        "Autodiscover blocked a potentially insecure redirection to %s. To allow Autodiscover to follow the "
228        + "redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) "
229        + "overload.", redirectionUrl));
230  }
231
232  // Legacy Autodiscover
233
234  /**
235   * Calls the Autodiscover service to get configuration settings at the
236   * specified URL.
237   *
238   * @param <TSettings>  the generic type
239   * @param cls          the cls
240   * @param emailAddress the email address
241   * @param url          the url
242   * @return The requested configuration settings. (TSettings The type of the
243   * settings to retrieve)
244   * @throws Exception the exception
245   */
246  private <TSettings extends ConfigurationSettingsBase>
247  TSettings getLegacyUserSettingsAtUrl(
248      Class<TSettings> cls, String emailAddress, URI url)
249      throws Exception {
250    this
251        .traceMessage(TraceFlags.AutodiscoverConfiguration,
252                      String.format("Trying to call Autodiscover for %s on %s.", emailAddress, url));
253
254    TSettings settings = cls.newInstance();
255
256    HttpWebRequest request = null;
257    try {
258      request = this.prepareHttpWebRequestForUrl(url);
259
260      this.traceHttpRequestHeaders(
261          TraceFlags.AutodiscoverRequestHttpHeaders,
262          request);
263      // OutputStreamWriter out = new
264      // OutputStreamWriter(request.getOutputStream());
265      OutputStream urlOutStream = request.getOutputStream();
266
267      // If tracing is enabled, we generate the request in-memory so that we
268      // can pass it along to the ITraceListener. Then we copy the stream to
269      // the request stream.
270      if (this.isTraceEnabledFor(TraceFlags.AutodiscoverRequest)) {
271        ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
272
273        PrintWriter writer = new PrintWriter(memoryStream);
274        this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);
275        writer.flush();
276
277        this.traceXml(TraceFlags.AutodiscoverRequest, memoryStream);
278        // out.write(memoryStream.toString());
279        // out.close();
280        memoryStream.writeTo(urlOutStream);
281        urlOutStream.flush();
282        urlOutStream.close();
283        memoryStream.close();
284      } else {
285        PrintWriter writer = new PrintWriter(urlOutStream);
286        this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer);
287
288      /*  Flush Start */
289        writer.flush();
290        urlOutStream.flush();
291        urlOutStream.close();
292      /* Flush End */
293      }
294      request.executeRequest();
295      request.getResponseCode();
296      URI redirectUrl;
297      OutParam<URI> outParam = new OutParam<URI>();
298      if (this.tryGetRedirectionResponse(request, outParam)) {
299        redirectUrl = outParam.getParam();
300        settings.makeRedirectionResponse(redirectUrl);
301        return settings;
302      }
303      InputStream serviceResponseStream = request.getInputStream();
304      // If tracing is enabled, we read the entire response into a
305      // MemoryStream so that we
306      // can pass it along to the ITraceListener. Then we parse the response
307      // from the
308      // MemoryStream.
309      if (this.isTraceEnabledFor(TraceFlags.AutodiscoverResponse)) {
310        ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
311
312        while (true) {
313          int data = serviceResponseStream.read();
314          if (-1 == data) {
315            break;
316          } else {
317            memoryStream.write(data);
318          }
319        }
320        memoryStream.flush();
321
322        this.traceResponse(request, memoryStream);
323        ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream(
324            memoryStream.toByteArray());
325        EwsXmlReader reader = new EwsXmlReader(memoryStreamIn);
326        reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
327        settings.loadFromXml(reader);
328
329      } else {
330        EwsXmlReader reader = new EwsXmlReader(serviceResponseStream);
331        reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
332        settings.loadFromXml(reader);
333      }
334
335      serviceResponseStream.close();
336    } finally {
337      if (request != null) {
338        try {
339          request.close();
340        } catch (Exception e2) {
341          // Ignore exception while closing the request.
342        }
343      }
344    }
345
346    return settings;
347  }
348
349  /**
350   * Writes the autodiscover request.
351   *
352   * @param emailAddress the email address
353   * @param settings     the settings
354   * @param writer       the writer
355   * @throws java.io.IOException Signals that an I/O exception has occurred.
356   */
357  private void writeLegacyAutodiscoverRequest(String emailAddress,
358      ConfigurationSettingsBase settings, PrintWriter writer)
359      throws IOException {
360    writer.write(String.format("<Autodiscover xmlns=\"%s\">", AutodiscoverRequestNamespace));
361    writer.write("<Request>");
362    writer.write(String.format("<EMailAddress>%s</EMailAddress>",
363        emailAddress));
364    writer.write(
365        String.format("<AcceptableResponseSchema>%s</AcceptableResponseSchema>", settings.getNamespace()));
366    writer.write("</Request>");
367    writer.write("</Autodiscover>");
368  }
369
370  /**
371   * Gets a redirection URL to an SSL-enabled Autodiscover service from the
372   * standard non-SSL Autodiscover URL.
373   *
374   * @param domainName the domain name
375   * @return A valid SSL-enabled redirection URL. (May be null)
376   * @throws EWSHttpException the EWS http exception
377   * @throws XMLStreamException the XML stream exception
378   * @throws IOException Signals that an I/O exception has occurred.
379   * @throws ServiceLocalException the service local exception
380   * @throws URISyntaxException the uRI syntax exception
381   */
382  private URI getRedirectUrl(String domainName)
383      throws EWSHttpException, XMLStreamException, IOException, ServiceLocalException, URISyntaxException {
384    String url = String.format(AutodiscoverLegacyHttpUrl, "autodiscover." + domainName);
385
386    traceMessage(TraceFlags.AutodiscoverConfiguration,
387                 String.format("Trying to get Autodiscover redirection URL from %s.", url));
388
389    HttpWebRequest request = null;
390
391    try {
392      request = new HttpClientWebRequest(httpClient, httpContext);
393      request.setProxy(getWebProxy());
394
395      try {
396        request.setUrl(URI.create(url).toURL());
397      } catch (MalformedURLException e) {
398        String strErr = String.format("Incorrect format : %s", url);
399        throw new ServiceLocalException(strErr);
400      }
401
402      request.setRequestMethod("GET");
403      request.setAllowAutoRedirect(false);
404      request.setTimeout(getTimeout());
405
406      // Do NOT allow authentication as this single request will be made over plain HTTP.
407      request.setAllowAuthentication(false);
408
409      prepareCredentials(request);
410
411      request.prepareConnection();
412      try {
413        request.executeRequest();
414      } catch (IOException e) {
415        traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
416        return null;
417      }
418
419      OutParam<URI> outParam = new OutParam<URI>();
420      if (tryGetRedirectionResponse(request, outParam)) {
421        return outParam.getParam();
422      }
423    } finally {
424      if (request != null) {
425        try {
426          request.close();
427        } catch (Exception e) {
428          // Ignore exception when closing the request
429        }
430      }
431    }
432
433    traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
434    return null;
435  }
436
437  /**
438   * Tries the get redirection response.
439   *
440   * @param request     the request
441   * @param redirectUrl the redirect URL
442   * @return true if a valid redirection URL was found
443   * @throws XMLStreamException the XML stream exception
444   * @throws IOException signals that an I/O exception has occurred.
445   * @throws EWSHttpException the EWS http exception
446   */
447  private boolean tryGetRedirectionResponse(HttpWebRequest request,
448      OutParam<URI> redirectUrl) throws XMLStreamException, IOException,
449      EWSHttpException {
450    // redirectUrl = null;
451    if (AutodiscoverRequest.isRedirectionResponse(request)) {
452      // Get the redirect location and verify that it's valid.
453      String location = request.getResponseHeaderField("Location");
454
455      if (!(location == null || location.isEmpty())) {
456        try {
457          redirectUrl.setParam(new URI(location));
458
459          // Check if URL is SSL and that the path matches.
460          if ((redirectUrl.getParam().getScheme().toLowerCase()
461              .equals("https")) &&
462              (redirectUrl.getParam().getPath()
463                  .equalsIgnoreCase(
464                      AutodiscoverLegacyPath))) {
465            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
466                String.format("Redirection URL found: '%s'",
467                    redirectUrl.getParam().toString()));
468
469            return true;
470          }
471        } catch (URISyntaxException ex) {
472          this
473              .traceMessage(
474                  TraceFlags.AutodiscoverConfiguration,
475                  String
476                      .format(
477                          "Invalid redirection URL " +
478                              "was returned: '%s'",
479                          location));
480          return false;
481        }
482      }
483    }
484    return false;
485  }
486
487  /**
488   * Calls the legacy Autodiscover service to retrieve configuration settings.
489   *
490   * @param <TSettings>  the generic type
491   * @param cls          the cls
492   * @param emailAddress The email address to retrieve configuration settings for.
493   * @return The requested configuration settings.
494   * @throws Exception the exception
495   */
496  protected <TSettings extends ConfigurationSettingsBase>
497  TSettings getLegacyUserSettings(
498      Class<TSettings> cls, String emailAddress) throws Exception {
499                /*int currentHop = 1;
500                return this.internalGetConfigurationSettings(cls, emailAddress,
501                                currentHop);*/
502
503    // If Url is specified, call service directly.
504    if (this.url != null) {
505      // this.Uri is intended for Autodiscover SOAP service, convert to Legacy endpoint URL.
506      URI autodiscoverUrl = new URI(this.url.toString() + AutodiscoverLegacyPath);
507      return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);
508    }
509
510    // If Domain is specified, figure out the endpoint Url and call service.
511    else if (!(this.domain == null || this.domain.isEmpty())) {
512      URI autodiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, this.domain));
513      return this.getLegacyUserSettingsAtUrl(cls,
514          emailAddress, autodiscoverUrl);
515    } else {
516      // No Url or Domain specified, need to
517      //figure out which endpoint to use.
518      int currentHop = 1;
519      OutParam<Integer> outParam = new OutParam<Integer>();
520      outParam.setParam(currentHop);
521      List<String> redirectionEmailAddresses = new ArrayList<String>();
522      return this.internalGetLegacyUserSettings(
523          cls,
524          emailAddress,
525          redirectionEmailAddresses,
526          outParam);
527    }
528  }
529
530  /**
531   * Calls the Autodiscover service to retrieve configuration settings.
532   *
533   * @param <TSettings>  the generic type
534   * @param cls          the cls
535   * @param emailAddress The email address to retrieve configuration settings for.
536   * @param currentHop   Current number of redirection urls/addresses attempted so far.
537   * @return The requested configuration settings.
538   * @throws Exception the exception
539   */
540  private <TSettings extends ConfigurationSettingsBase>
541  TSettings internalGetLegacyUserSettings(
542      Class<TSettings> cls,
543      String emailAddress,
544      List<String> redirectionEmailAddresses,
545      OutParam<Integer> currentHop)
546      throws Exception {
547    String domainName = EwsUtilities.domainFromEmailAddress(emailAddress);
548
549    int scpUrlCount;
550    OutParam<Integer> outParamInt = new OutParam<Integer>();
551    List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParamInt);
552    scpUrlCount = outParamInt.getParam();
553    if (urls.size() == 0) {
554      throw new ServiceValidationException(
555          "This Autodiscover request requires that either the Domain or Url be specified.");
556    }
557
558    // Assume caller is not inside the Intranet, regardless of whether SCP
559    // Urls
560    // were returned or not. SCP Urls are only relevent if one of them
561    // returns
562    // valid Autodiscover settings.
563    this.isExternal = true;
564
565    int currentUrlIndex = 0;
566
567    // Used to save exception for later reporting.
568    Exception delayedException = null;
569    TSettings settings;
570
571    do {
572      URI autodiscoverUrl = urls.get(currentUrlIndex);
573      boolean isScpUrl = currentUrlIndex < scpUrlCount;
574
575      try {
576        settings = this.getLegacyUserSettingsAtUrl(cls,
577            emailAddress, autodiscoverUrl);
578
579        switch (settings.getResponseType()) {
580          case Success:
581            // Not external if Autodiscover endpoint found via SCP
582            // returned the settings.
583            if (isScpUrl) {
584              this.isExternal = false;
585            }
586            this.url = autodiscoverUrl;
587            return settings;
588          case RedirectUrl:
589            if (currentHop.getParam() < AutodiscoverMaxRedirections) {
590              currentHop.setParam(currentHop.getParam() + 1);
591
592              this
593                  .traceMessage(
594                      TraceFlags.AutodiscoverResponse,
595                      String
596                          .format(
597                              "Autodiscover " +
598                                  "service " +
599                                  "returned " +
600                                  "redirection URL '%s'.",
601                              settings
602                                  .getRedirectTarget()));
603
604              urls.add(currentUrlIndex, new URI(
605                  settings.getRedirectTarget()));
606
607              break;
608            } else {
609              throw new MaximumRedirectionHopsExceededException();
610            }
611          case RedirectAddress:
612            if (currentHop.getParam() < AutodiscoverMaxRedirections) {
613              currentHop.setParam(currentHop.getParam() + 1);
614
615              this
616                  .traceMessage(
617                      TraceFlags.AutodiscoverResponse,
618                      String
619                          .format(
620                              "Autodiscover " +
621                                  "service " +
622                                  "returned " +
623                                  "redirection email " +
624                                  "address '%s'.",
625                              settings
626                                  .getRedirectTarget()));
627              // Bug E14:255576 If this email address was already tried, we may have a loop
628              // in SCP lookups. Disable consideration of SCP records.
629              this.disableScpLookupIfDuplicateRedirection(
630                  settings.getRedirectTarget(),
631                  redirectionEmailAddresses);
632
633              return this.internalGetLegacyUserSettings(cls,
634                  settings.getRedirectTarget(),
635                  redirectionEmailAddresses,
636                  currentHop);
637            } else {
638              throw new MaximumRedirectionHopsExceededException();
639            }
640          case Error:
641            // Don't treat errors from an SCP-based Autodiscover service
642            // to be conclusive.
643            // We'll try the next one and record the error for later.
644            if (isScpUrl) {
645              this
646                  .traceMessage(
647                      TraceFlags.AutodiscoverConfiguration,
648                      "Error returned by " +
649                          "Autodiscover service " +
650                          "found via SCP, treating " +
651                          "as inconclusive.");
652
653              delayedException = new AutodiscoverRemoteException(
654                  "The Autodiscover service returned an error.", settings.getError());
655              currentUrlIndex++;
656            } else {
657              throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getError());
658            }
659            break;
660          default:
661            EwsUtilities
662                .ewsAssert(false, "Autodiscover.GetConfigurationSettings",
663                           "An unexpected error has occured. This code path should never be reached.");
664            break;
665        }
666      } catch (XMLStreamException ex) {
667        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String
668            .format("%s failed: XML parsing error: %s", url, ex
669                .getMessage()));
670
671        // The content at the URL wasn't a valid response, let's try the
672        // next.
673        currentUrlIndex++;
674      } catch (IOException ex) {
675        this.traceMessage(
676            TraceFlags.AutodiscoverConfiguration,
677            String.format("%s failed: I/O error: %s",
678                url, ex.getMessage()));
679
680        // The content at the URL wasn't a valid response, let's try the next.
681        currentUrlIndex++;
682      } catch (Exception ex) {
683        HttpWebRequest response = null;
684        URI redirectUrl;
685        OutParam<URI> outParam1 = new OutParam<URI>();
686        if ((response != null) &&
687            this.tryGetRedirectionResponse(response, outParam1)) {
688          redirectUrl = outParam1.getParam();
689          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
690              String.format(
691                  "Host returned a redirection to url %s",
692                  redirectUrl.toString()));
693
694          currentHop.setParam(currentHop.getParam() + 1);
695          urls.add(currentUrlIndex, redirectUrl);
696        } else {
697          if (response != null) {
698            this.processHttpErrorResponse(response, ex);
699
700          }
701
702          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
703              String.format("%s failed: %s (%s)", url, ex
704                  .getClass().getName(), ex.getMessage()));
705
706          // The url did not work, let's try the next.
707          currentUrlIndex++;
708        }
709      }
710    } while (currentUrlIndex < urls.size());
711
712    // If we got this far it's because none of the URLs we tried have
713    // worked. As a next-to-last chance, use GetRedirectUrl to
714    // try to get a redirection URL using an HTTP GET on a non-SSL
715    // Autodiscover endpoint. If successful, use this
716    // redirection URL to get the configuration settings for this email
717    // address. (This will be a common scenario for
718    // DataCenter deployments).
719    URI redirectionUrl = this.getRedirectUrl(domainName);
720    OutParam<TSettings> outParam = new OutParam<TSettings>();
721    if ((redirectionUrl != null)
722        && this.tryLastChanceHostRedirection(cls, emailAddress,
723        redirectionUrl, outParam)) {
724      settings = outParam.getParam();
725      return settings;
726    } else {
727      // Getting a redirection URL from an HTTP GET failed too. As a last
728      // chance, try to get an appropriate SRV Record
729      // using DnsQuery. If successful, use this redirection URL to get
730      // the configuration settings for this email address.
731      redirectionUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName);
732      if ((redirectionUrl != null)
733          && this.tryLastChanceHostRedirection(cls, emailAddress,
734          redirectionUrl, outParam)) {
735        return outParam.getParam();
736      }
737
738      // If there was an earlier exception, throw it.
739      if (delayedException != null) {
740        throw delayedException;
741      }
742
743      throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
744    }
745  }
746
747  /**
748   * Get an autodiscover SRV record in DNS and construct autodiscover URL.
749   *
750   * @param domainName Name of the domain.
751   * @return Autodiscover URL (may be null if lookup failed)
752   * @throws Exception the exception
753   */
754  protected URI getRedirectionUrlFromDnsSrvRecord(String domainName)
755      throws Exception {
756
757    this
758        .traceMessage(
759            TraceFlags.AutodiscoverConfiguration,
760            String
761                .format(
762                    "Trying to get Autodiscover host " +
763                        "from DNS SRV record for %s.",
764                    domainName));
765
766    String hostname = this.dnsClient
767        .findAutodiscoverHostFromSrv(domainName);
768    if (!(hostname == null || hostname.isEmpty())) {
769      this
770          .traceMessage(TraceFlags.AutodiscoverConfiguration,
771              String.format(
772                  "Autodiscover host %s was returned.",
773                  hostname));
774
775      return new URI(String.format(AutodiscoverLegacyHttpsUrl,
776          hostname));
777    } else {
778      this.traceMessage(TraceFlags.AutodiscoverConfiguration,
779          "No matching Autodiscover DNS SRV records were found.");
780
781      return null;
782    }
783  }
784
785  /**
786   * Tries to get Autodiscover settings using redirection Url.
787   *
788   * @param <TSettings>    the generic type
789   * @param cls            the cls
790   * @param emailAddress   The email address.
791   * @param redirectionUrl Redirection Url.
792   * @param settings       The settings.
793   * @return boolean The boolean.
794   * @throws AutodiscoverLocalException  the autodiscover local exception
795   * @throws AutodiscoverRemoteException the autodiscover remote exception
796   * @throws Exception                   the exception
797   */
798  private <TSettings extends ConfigurationSettingsBase> boolean
799  tryLastChanceHostRedirection(
800      Class<TSettings> cls, String emailAddress, URI redirectionUrl,
801      OutParam<TSettings> settings) throws AutodiscoverLocalException,
802      AutodiscoverRemoteException, Exception {
803    List<String> redirectionEmailAddresses = new ArrayList<String>();
804
805    // Bug 60274: Performing a non-SSL HTTP GET to retrieve a redirection
806    // URL is potentially unsafe. We allow the caller
807    // to specify delegate to be called to determine whether we are allowed
808    // to use the redirection URL.
809    if (this
810        .callRedirectionUrlValidationCallback(redirectionUrl.toString())) {
811      for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
812        try {
813          settings.setParam(this.getLegacyUserSettingsAtUrl(cls,
814              emailAddress, redirectionUrl));
815
816          switch (settings.getParam().getResponseType()) {
817            case Success:
818              return true;
819            case Error:
820              throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getParam()
821                  .getError());
822            case RedirectAddress:
823              // If this email address was already tried,
824              //we may have a loop
825              // in SCP lookups. Disable consideration of SCP records.
826              this.disableScpLookupIfDuplicateRedirection(settings.getParam().getRedirectTarget(),
827                  redirectionEmailAddresses);
828              OutParam<Integer> outParam = new OutParam<Integer>();
829              outParam.setParam(currentHop);
830              settings.setParam(
831                  this.internalGetLegacyUserSettings(cls,
832                      emailAddress,
833                      redirectionEmailAddresses,
834                      outParam));
835              currentHop = outParam.getParam();
836              return true;
837            case RedirectUrl:
838              try {
839                redirectionUrl = new URI(settings.getParam()
840                    .getRedirectTarget());
841              } catch (URISyntaxException ex) {
842                this
843                    .traceMessage(
844                        TraceFlags.
845                            AutodiscoverConfiguration,
846                        String
847                            .format(
848                                "Service " +
849                                    "returned " +
850                                    "invalid " +
851                                    "redirection " +
852                                    "URL %s",
853                                settings
854                                    .getParam()
855                                    .getRedirectTarget()));
856                return false;
857              }
858              break;
859            default:
860              String failureMessage = String.format(
861                  "Autodiscover call at %s failed with error %s, target %s",
862                  redirectionUrl,
863                  settings.getParam().getResponseType(),
864                  settings.getParam().getRedirectTarget());
865              this.traceMessage(
866                  TraceFlags.AutodiscoverConfiguration, failureMessage);
867
868              return false;
869          }
870        } catch (XMLStreamException ex) {
871          // If the response is malformed, it wasn't a valid
872          // Autodiscover endpoint.
873          this
874              .traceMessage(TraceFlags.AutodiscoverConfiguration,
875                  String.format(
876                      "%s failed: XML parsing error: %s",
877                      redirectionUrl.toString(), ex
878                          .getMessage()));
879          return false;
880        } catch (IOException ex) {
881          this.traceMessage(
882              TraceFlags.AutodiscoverConfiguration,
883              String.format("%s failed: I/O error: %s",
884                  redirectionUrl, ex.getMessage()));
885          return false;
886        } catch (Exception ex) {
887          // TODO: BUG response is always null
888          HttpWebRequest response = null;
889          OutParam<URI> outParam = new OutParam<URI>();
890          if ((response != null)
891              && this.tryGetRedirectionResponse(response,
892              outParam)) {
893            redirectionUrl = outParam.getParam();
894            this
895                .traceMessage(
896                    TraceFlags.AutodiscoverConfiguration,
897                    String
898                        .format(
899                            "Host returned a " +
900                                "redirection" +
901                                " to url %s",
902                            redirectionUrl));
903
904          } else {
905            if (response != null) {
906              this.processHttpErrorResponse(response, ex);
907            }
908
909            this
910                .traceMessage(
911                    TraceFlags.AutodiscoverConfiguration,
912                    String.format("%s failed: %s (%s)",
913                        url, ex.getClass().getName(),
914                        ex.getMessage()));
915            return false;
916          }
917        }
918      }
919    }
920
921    return false;
922  }
923
924  /**
925   * Disables SCP lookup if duplicate email address redirection.
926   *
927   * @param emailAddress              The email address to use.
928   * @param redirectionEmailAddresses The list of prior redirection email addresses.
929   */
930  private void disableScpLookupIfDuplicateRedirection(
931      String emailAddress,
932      List<String> redirectionEmailAddresses) {
933    // SMTP addresses are case-insensitive so entries are converted to lower-case.
934    emailAddress = emailAddress.toLowerCase();
935
936    if (redirectionEmailAddresses.contains(emailAddress)) {
937      this.enableScpLookup = false;
938    } else {
939      redirectionEmailAddresses.add(emailAddress);
940    }
941  }
942
943  /**
944   * Gets user settings from Autodiscover legacy endpoint.
945   *
946   * @param emailAddress      The email address to use.
947   * @param requestedSettings The requested settings.
948   * @return GetUserSettingsResponse
949   * @throws Exception on error
950   */
951  protected GetUserSettingsResponse internalGetLegacyUserSettings(
952      String emailAddress,
953      List<UserSettingName> requestedSettings) throws Exception {
954    // Cannot call legacy Autodiscover service with WindowsLive and other WSSecurity-based credential
955    if ((this.getCredentials() != null) && (this.getCredentials() instanceof WSSecurityBasedCredentials)) {
956      throw new AutodiscoverLocalException(
957          "WindowsLiveCredentials can't be used with this Autodiscover endpoint.");
958    }
959
960    OutlookConfigurationSettings settings = this.getLegacyUserSettings(
961        OutlookConfigurationSettings.class,
962        emailAddress);
963
964
965
966    return settings.convertSettings(emailAddress, requestedSettings);
967  }
968
969  /**
970   * Calls the SOAP Autodiscover service
971   * for user settings for a single SMTP address.
972   *
973   * @param smtpAddress       SMTP address.
974   * @param requestedSettings The requested settings.
975   * @return GetUserSettingsResponse
976   * @throws Exception on error
977   */
978  protected GetUserSettingsResponse internalGetSoapUserSettings(
979      String smtpAddress,
980      List<UserSettingName> requestedSettings) throws Exception {
981    List<String> smtpAddresses = new ArrayList<String>();
982    smtpAddresses.add(smtpAddress);
983
984    List<String> redirectionEmailAddresses = new ArrayList<String>();
985    redirectionEmailAddresses.add(smtpAddress.toLowerCase());
986
987    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
988      GetUserSettingsResponse response = this.getUserSettings(smtpAddresses,
989          requestedSettings).getTResponseAtIndex(0);
990
991      switch (response.getErrorCode()) {
992        case RedirectAddress:
993          this.traceMessage(
994              TraceFlags.AutodiscoverResponse,
995              String.format("Autodiscover service returned redirection email address '%s'.",
996                  response.getRedirectTarget()));
997
998          smtpAddresses.clear();
999          smtpAddresses.add(response.getRedirectTarget().
1000              toLowerCase());
1001          this.url = null;
1002          this.domain = null;
1003
1004          // If this email address was already tried,
1005          //we may have a loop
1006          // in SCP lookups. Disable consideration of SCP records.
1007          this.disableScpLookupIfDuplicateRedirection(response.getRedirectTarget(),
1008              redirectionEmailAddresses);
1009          break;
1010
1011        case RedirectUrl:
1012          this.traceMessage(
1013              TraceFlags.AutodiscoverResponse,
1014              String.format("Autodiscover service returned redirection URL '%s'.",
1015                  response.getRedirectTarget()));
1016
1017          //this.url = new URI(response.getRedirectTarget());
1018          this.url = this.getCredentials().adjustUrl(new URI(response.getRedirectTarget()));
1019          break;
1020
1021        case NoError:
1022        default:
1023          return response;
1024      }
1025    }
1026
1027    throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1028  }
1029
1030  /**
1031   * Gets the user settings using Autodiscover SOAP service.
1032   *
1033   * @param smtpAddresses The SMTP addresses of the users.
1034   * @param settings      The settings.
1035   * @return GetUserSettingsResponseCollection Object.
1036   * @throws Exception the exception
1037   */
1038  protected GetUserSettingsResponseCollection getUserSettings(
1039      final List<String> smtpAddresses, List<UserSettingName> settings)
1040      throws Exception {
1041    EwsUtilities.validateParam(smtpAddresses, "smtpAddresses");
1042    EwsUtilities.validateParam(settings, "settings");
1043
1044    return this.getSettings(
1045        GetUserSettingsResponseCollection.class, UserSettingName.class,
1046        smtpAddresses, settings, null, this,
1047        new IFuncDelegate<String>() {
1048          public String func() throws FormatException {
1049            return EwsUtilities
1050                .domainFromEmailAddress(smtpAddresses.get(0));
1051          }
1052        });
1053  }
1054
1055  /**
1056   * Gets user or domain settings using Autodiscover SOAP service.
1057   *
1058   * @param <TGetSettingsResponseCollection> the generic type
1059   * @param <TSettingName>                   the generic type
1060   * @param cls                              the cls
1061   * @param cls1                             the cls1
1062   * @param identities                       Either the domains or the SMTP addresses of the users.
1063   * @param settings                         The settings.
1064   * @param requestedVersion                 Requested version of the Exchange service.
1065   * @param getSettingsMethod                The method to use.
1066   * @param getDomainMethod                  The method to calculate the domain value.
1067   * @return TGetSettingsResponse Collection.
1068   * @throws Exception the exception
1069   */
1070  private <TGetSettingsResponseCollection, TSettingName>
1071  TGetSettingsResponseCollection getSettings(
1072      Class<TGetSettingsResponseCollection> cls,
1073      Class<TSettingName> cls1,
1074      List<String> identities,
1075      List<TSettingName> settings,
1076      ExchangeVersion requestedVersion,
1077      IFunctionDelegate<List<String>, List<TSettingName>,
1078          TGetSettingsResponseCollection> getSettingsMethod,
1079      IFuncDelegate<String> getDomainMethod) throws Exception {
1080    TGetSettingsResponseCollection response;
1081
1082    // Autodiscover service only exists in E14 or later.
1083    if (this.getRequestedServerVersion().compareTo(
1084        MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1085      throw new ServiceVersionException(String.format(
1086          "The Autodiscover service only supports %s or a later version.",
1087          MinimumRequestVersionForAutoDiscoverSoapService));
1088    }
1089
1090    // If Url is specified, call service directly.
1091    if (this.url != null) {
1092      URI autodiscoverUrl = this.url;
1093      response = getSettingsMethod.func(identities, settings,
1094          requestedVersion, this.url);
1095      this.url = autodiscoverUrl;
1096      return response;
1097    }
1098    // If Domain is specified, determine endpoint Url and call service.
1099    else if (!(this.domain == null || this.domain.isEmpty())) {
1100      URI autodiscoverUrl = this.getAutodiscoverEndpointUrl(this.domain);
1101      response = getSettingsMethod.func(identities, settings,
1102          requestedVersion,
1103          autodiscoverUrl);
1104
1105      // If we got this far, response was successful, set Url.
1106      this.url = autodiscoverUrl;
1107      return response;
1108    }
1109    // No Url or Domain specified, need to figure out which endpoint(s) to
1110    // try.
1111    else {
1112      // Assume caller is not inside the Intranet, regardless of whether
1113      // SCP Urls
1114      // were returned or not. SCP Urls are only relevent if one of them
1115      // returns
1116      // valid Autodiscover settings.
1117      this.isExternal = true;
1118
1119      URI autodiscoverUrl;
1120
1121      String domainName = getDomainMethod.func();
1122      int scpHostCount;
1123      OutParam<Integer> outParam = new OutParam<Integer>();
1124      List<String> hosts = this.getAutodiscoverServiceHosts(domainName,
1125          outParam);
1126      scpHostCount = outParam.getParam();
1127      if (hosts.size() == 0) {
1128        throw new ServiceValidationException(
1129            "This Autodiscover request requires that either the Domain or Url be specified.");
1130      }
1131
1132      for (int currentHostIndex = 0; currentHostIndex < hosts.size(); currentHostIndex++) {
1133        String host = hosts.get(currentHostIndex);
1134        boolean isScpHost = currentHostIndex < scpHostCount;
1135        OutParam<URI> outParams = new OutParam<URI>();
1136        if (this.tryGetAutodiscoverEndpointUrl(host, outParams)) {
1137          autodiscoverUrl = outParams.getParam();
1138          response = getSettingsMethod.func(identities, settings,
1139              requestedVersion,
1140              autodiscoverUrl);
1141
1142          // If we got this far, the response was successful, set Url.
1143          this.url = autodiscoverUrl;
1144
1145          // Not external if Autodiscover endpoint found via SCP
1146          // returned the settings.
1147          if (isScpHost) {
1148            this.isExternal = false;
1149          }
1150
1151          return response;
1152        }
1153      }
1154
1155      // Next-to-last chance: try unauthenticated GET over HTTP to be
1156      // redirected to appropriate service endpoint.
1157      autodiscoverUrl = this.getRedirectUrl(domainName);
1158      OutParam<URI> outParamUrl = new OutParam<URI>();
1159      if ((autodiscoverUrl != null) &&
1160          this
1161              .callRedirectionUrlValidationCallback(
1162                  autodiscoverUrl.toString()) &&
1163          this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1164              .getHost(), outParamUrl)) {
1165        autodiscoverUrl = outParamUrl.getParam();
1166        response = getSettingsMethod.func(identities, settings,
1167            requestedVersion,
1168            autodiscoverUrl);
1169
1170        // If we got this far, the response was successful, set Url.
1171        this.url = autodiscoverUrl;
1172
1173        return response;
1174      }
1175
1176      // Last Chance: try to read autodiscover SRV Record from DNS. If we
1177      // find one, use
1178      // the hostname returned to construct an Autodiscover endpoint URL.
1179      autodiscoverUrl = this
1180          .getRedirectionUrlFromDnsSrvRecord(domainName);
1181      if ((autodiscoverUrl != null) &&
1182          this
1183              .callRedirectionUrlValidationCallback(
1184                  autodiscoverUrl.toString()) &&
1185          this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1186              .getHost(), outParamUrl)) {
1187        autodiscoverUrl = outParamUrl.getParam();
1188        response = getSettingsMethod.func(identities, settings,
1189            requestedVersion,
1190            autodiscoverUrl);
1191
1192        // If we got this far, the response was successful, set Url.
1193        this.url = autodiscoverUrl;
1194
1195        return response;
1196      } else {
1197        throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1198      }
1199    }
1200  }
1201
1202  /**
1203   * Gets settings for one or more users.
1204   *
1205   * @param smtpAddresses    The SMTP addresses of the users.
1206   * @param settings         The settings.
1207   * @param requestedVersion Requested version of the Exchange service.
1208   * @param autodiscoverUrl  The autodiscover URL.
1209   * @return GetUserSettingsResponse collection.
1210   * @throws ServiceLocalException the service local exception
1211   * @throws Exception             the exception
1212   */
1213  private GetUserSettingsResponseCollection internalGetUserSettings(
1214      List<String> smtpAddresses, List<UserSettingName> settings,
1215      ExchangeVersion requestedVersion,
1216      URI autodiscoverUrl) throws ServiceLocalException, Exception {
1217    // The response to GetUserSettings can be a redirection. Execute
1218    // GetUserSettings until we get back
1219    // a valid response or we've followed too many redirections.
1220    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1221      GetUserSettingsRequest request = new GetUserSettingsRequest(this,
1222          autodiscoverUrl);
1223      request.setSmtpAddresses(smtpAddresses);
1224      request.setSettings(settings);
1225      GetUserSettingsResponseCollection response = request.execute();
1226
1227      // Did we get redirected?
1228      if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1229          && response.getRedirectionUrl() != null) {
1230        this.traceMessage(
1231            TraceFlags.AutodiscoverConfiguration,
1232            String.format("Request to %s returned redirection to %s",
1233                autodiscoverUrl.toString(), response.getRedirectionUrl()));
1234
1235        autodiscoverUrl = response.getRedirectionUrl();
1236      } else {
1237        return response;
1238      }
1239    }
1240
1241    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1242        "Maximum number of redirection hops %d exceeded",
1243        AutodiscoverMaxRedirections));
1244
1245    throw new MaximumRedirectionHopsExceededException();
1246  }
1247
1248  /**
1249   * Gets the domain settings using Autodiscover SOAP service.
1250   *
1251   * @param domains          The domains.
1252   * @param settings         The settings.
1253   * @param requestedVersion Requested version of the Exchange service.
1254   * @return GetDomainSettingsResponse collection.
1255   * @throws Exception the exception
1256   */
1257  protected GetDomainSettingsResponseCollection getDomainSettings(
1258      final List<String> domains, List<DomainSettingName> settings,
1259      ExchangeVersion requestedVersion)
1260      throws Exception {
1261    EwsUtilities.validateParam(domains, "domains");
1262    EwsUtilities.validateParam(settings, "settings");
1263
1264    return this.getSettings(
1265        GetDomainSettingsResponseCollection.class,
1266        DomainSettingName.class, domains, settings,
1267        requestedVersion, this,
1268        new IFuncDelegate<String>() {
1269          public String func() {
1270            return domains.get(0);
1271          }
1272        });
1273  }
1274
1275  /**
1276   * Gets settings for one or more domains.
1277   *
1278   * @param domains          The domains.
1279   * @param settings         The settings.
1280   * @param requestedVersion Requested version of the Exchange service.
1281   * @param autodiscoverUrl  The autodiscover URL.
1282   * @return GetDomainSettingsResponse Collection.
1283   * @throws ServiceLocalException the service local exception
1284   * @throws Exception             the exception
1285   */
1286  private GetDomainSettingsResponseCollection internalGetDomainSettings(
1287      List<String> domains, List<DomainSettingName> settings,
1288      ExchangeVersion requestedVersion,
1289      URI autodiscoverUrl) throws ServiceLocalException, Exception {
1290    // The response to GetDomainSettings can be a redirection. Execute
1291    // GetDomainSettings until we get back
1292    // a valid response or we've followed too many redirections.
1293    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1294      GetDomainSettingsRequest request = new GetDomainSettingsRequest(
1295          this, autodiscoverUrl);
1296      request.setDomains(domains);
1297      request.setSettings(settings);
1298      request.setRequestedVersion(requestedVersion);
1299      GetDomainSettingsResponseCollection response = request.execute();
1300
1301      // Did we get redirected?
1302      if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1303          && response.getRedirectionUrl() != null) {
1304        autodiscoverUrl = response.getRedirectionUrl();
1305      } else {
1306        return response;
1307      }
1308    }
1309
1310    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1311        "Maximum number of redirection hops %d exceeded",
1312        AutodiscoverMaxRedirections));
1313
1314    throw new MaximumRedirectionHopsExceededException();
1315  }
1316
1317  /**
1318   * Gets the autodiscover endpoint URL.
1319   *
1320   * @param host The host.
1321   * @return URI The URI.
1322   * @throws Exception the exception
1323   */
1324  private URI getAutodiscoverEndpointUrl(String host) throws Exception {
1325    URI autodiscoverUrl = null;
1326    OutParam<URI> outParam = new OutParam<URI>();
1327    if (this.tryGetAutodiscoverEndpointUrl(host, outParam)) {
1328      return autodiscoverUrl;
1329    } else {
1330      throw new AutodiscoverLocalException(
1331          "No appropriate Autodiscover SOAP or WS-Security endpoint is available.");
1332    }
1333  }
1334
1335  /**
1336   * Tries the get Autodiscover Service endpoint URL.
1337   *
1338   * @param host The host.
1339   * @param url  the url
1340   * @return boolean The boolean.
1341   * @throws Exception the exception
1342   */
1343  private boolean tryGetAutodiscoverEndpointUrl(String host,
1344      OutParam<URI> url)
1345      throws Exception {
1346    EnumSet<AutodiscoverEndpoints> endpoints;
1347    OutParam<EnumSet<AutodiscoverEndpoints>> outParam =
1348        new OutParam<EnumSet<AutodiscoverEndpoints>>();
1349    if (this.tryGetEnabledEndpointsForHost(host, outParam)) {
1350      endpoints = outParam.getParam();
1351      url
1352          .setParam(new URI(String.format(AutodiscoverSoapHttpsUrl,
1353              host)));
1354
1355      // Make sure that at least one of the non-legacy endpoints is
1356      // available.
1357      if ((!endpoints.contains(AutodiscoverEndpoints.Soap)) &&
1358          (!endpoints.contains(
1359              AutodiscoverEndpoints.WsSecurity))
1360        // (endpoints .contains( AutodiscoverEndpoints.WSSecuritySymmetricKey) ) &&
1361        //(endpoints .contains( AutodiscoverEndpoints.WSSecurityX509Cert))
1362          ) {
1363        this
1364            .traceMessage(
1365                TraceFlags.AutodiscoverConfiguration,
1366                String
1367                    .format(
1368                        "No Autodiscover endpoints " +
1369                            "are available  for host %s",
1370                        host));
1371
1372        return false;
1373      }
1374
1375      // If we have WLID credential, make sure that we have a WS-Security
1376      // endpoint
1377                        /*
1378                        if (this.getCredentials() instanceof WindowsLiveCredentials) {
1379                                if (endpoints.contains(AutodiscoverEndpoints.WsSecurity)) {
1380                                        this
1381                                                        .traceMessage(
1382                                                                        TraceFlags.AutodiscoverConfiguration,
1383                                                                        String
1384                                                                                        .format(
1385                                                                                                        "No Autodiscover " +
1386                                                                                                        "WS-Security " +
1387                                                                                                        "endpoint is available" +
1388                                                                                                        " for host %s",
1389                                                                                                        host));
1390
1391                                        return false;
1392                                } else {
1393                                        url.setParam(new URI(String.format(
1394                                                        AutodiscoverSoapWsSecurityHttpsUrl, host)));
1395                                }
1396                        }
1397                           else if (this.getCredentials() instanceof PartnerTokenCredentials)
1398                {
1399                    if (endpoints.contains( AutodiscoverEndpoints.WSSecuritySymmetricKey))
1400                    {
1401                        this.traceMessage(
1402                            TraceFlags.AutodiscoverConfiguration,
1403                            String.format("No Autodiscover WS-Security/SymmetricKey endpoint is available for host {0}", host));
1404
1405                        return false;
1406                    }
1407                    else
1408                    {
1409                        url.setParam( new URI(String.format(AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl, host)));
1410                    }
1411                }
1412                else if (this.getCredentials()instanceof X509CertificateCredentials)
1413                {
1414                    if ((endpoints.contains(AutodiscoverEndpoints.WSSecurityX509Cert))
1415                    {
1416                        this.traceMessage(
1417                            TraceFlags.AutodiscoverConfiguration,
1418                            String.format("No Autodiscover WS-Security/X509Cert endpoint is available for host {0}", host));
1419
1420                        return false;
1421                    }
1422                    else
1423                    {
1424                        url.setParam( new URI(String.format(AutodiscoverSoapWsSecurityX509CertHttpsUrl, host)));
1425                    }
1426                }
1427                                  */
1428      return true;
1429
1430
1431    } else {
1432      this
1433          .traceMessage(
1434              TraceFlags.AutodiscoverConfiguration,
1435              String
1436                  .format(
1437                      "No Autodiscover endpoints " +
1438                          "are available for host %s",
1439                      host));
1440
1441      return false;
1442    }
1443  }
1444
1445  /**
1446   * Gets the list of autodiscover service URLs.
1447   *
1448   * @param domainName   Domain name.
1449   * @param scpHostCount Count of hosts found via SCP lookup.
1450   * @return List of Autodiscover URLs.
1451   * @throws java.net.URISyntaxException the URI Syntax exception
1452   */
1453  protected List<URI> getAutodiscoverServiceUrls(String domainName,
1454      OutParam<Integer> scpHostCount) throws URISyntaxException {
1455    List<URI> urls;
1456
1457    urls = new ArrayList<URI>();
1458
1459    scpHostCount.setParam(urls.size());
1460
1461    // As a fallback, add autodiscover URLs base on the domain name.
1462    urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1463        domainName)));
1464    urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1465        "autodiscover." + domainName)));
1466
1467    return urls;
1468  }
1469
1470  /**
1471   * Gets the list of autodiscover service hosts.
1472   *
1473   * @param domainName Domain name.
1474   * @param outParam   the out param
1475   * @return List of hosts.
1476   * @throws java.net.URISyntaxException the uRI syntax exception
1477   * @throws ClassNotFoundException      the class not found exception
1478   */
1479  protected List<String> getAutodiscoverServiceHosts(String domainName,
1480      OutParam<Integer> outParam) throws URISyntaxException,
1481      ClassNotFoundException {
1482
1483    List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParam);
1484    List<String> lst = new ArrayList<String>();
1485    for (URI url : urls) {
1486      lst.add(url.getHost());
1487    }
1488    return lst;
1489  }
1490
1491  /**
1492   * Gets the enabled autodiscover endpoints on a specific host.
1493   *
1494   * @param host      The host.
1495   * @param endpoints Endpoints found for host.
1496   * @return Flags indicating which endpoints are enabled.
1497   * @throws Exception the exception
1498   */
1499  private boolean tryGetEnabledEndpointsForHost(String host,
1500      OutParam<EnumSet<AutodiscoverEndpoints>> endpoints) throws Exception {
1501    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1502        "Determining which endpoints are enabled for host %s", host));
1503
1504    // We may get redirected to another host. And therefore need to limit the number of redirections we'll
1505    // tolerate.
1506    for (int currentHop = 0; currentHop < AutodiscoverMaxRedirections; currentHop++) {
1507      URI autoDiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, host));
1508
1509      endpoints.setParam(EnumSet.of(AutodiscoverEndpoints.None));
1510
1511      HttpWebRequest request = null;
1512      try {
1513        request = new HttpClientWebRequest(httpClient, httpContext);
1514        request.setProxy(getWebProxy());
1515
1516        try {
1517          request.setUrl(autoDiscoverUrl.toURL());
1518        } catch (MalformedURLException e) {
1519          String strErr = String.format("Incorrect format : %s", url);
1520          throw new ServiceLocalException(strErr);
1521        }
1522
1523        request.setRequestMethod("GET");
1524        request.setAllowAutoRedirect(false);
1525        request.setPreAuthenticate(false);
1526        request.setUseDefaultCredentials(this.getUseDefaultCredentials());
1527        request.setTimeout(getTimeout());
1528
1529        prepareCredentials(request);
1530
1531        request.prepareConnection();
1532        try {
1533          request.executeRequest();
1534        } catch (IOException e) {
1535          return false;
1536        }
1537
1538        OutParam<URI> outParam = new OutParam<URI>();
1539        if (this.tryGetRedirectionResponse(request, outParam)) {
1540          URI redirectUrl = outParam.getParam();
1541          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1542              String.format("Host returned redirection to host '%s'", redirectUrl.getHost()));
1543
1544          host = redirectUrl.getHost();
1545        } else {
1546          endpoints.setParam(this.getEndpointsFromHttpWebResponse(request));
1547
1548          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1549              String.format("Host returned enabled endpoint flags: %s", endpoints.getParam().toString()));
1550
1551          return true;
1552        }
1553      } finally {
1554        if (request != null) {
1555          try {
1556            request.close();
1557          } catch (Exception e) {
1558            // Connection can't be closed. We'll ignore this...
1559          }
1560        }
1561      }
1562    }
1563
1564    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1565        String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));
1566
1567    throw new MaximumRedirectionHopsExceededException();
1568  }
1569
1570  /**
1571   * Gets the endpoints from HTTP web response.
1572   *
1573   * @param request the request
1574   * @return Endpoints enabled.
1575   * @throws EWSHttpException the EWS http exception
1576   */
1577  private EnumSet<AutodiscoverEndpoints> getEndpointsFromHttpWebResponse(
1578      HttpWebRequest request) throws EWSHttpException {
1579    EnumSet<AutodiscoverEndpoints> endpoints = EnumSet
1580        .noneOf(AutodiscoverEndpoints.class);
1581    endpoints.add(AutodiscoverEndpoints.Legacy);
1582
1583    if (!(request.getResponseHeaders().get(
1584        AutodiscoverSoapEnabledHeaderName) == null || request
1585        .getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName)
1586        .isEmpty())) {
1587      endpoints.add(AutodiscoverEndpoints.Soap);
1588    }
1589    if (!(request.getResponseHeaders().get(
1590        AutodiscoverWsSecurityEnabledHeaderName) == null || request
1591        .getResponseHeaders().get(
1592            AutodiscoverWsSecurityEnabledHeaderName).isEmpty())) {
1593      endpoints.add(AutodiscoverEndpoints.WsSecurity);
1594    }
1595                
1596                /* if (! (request.getResponseHeaders().get(
1597                                 AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName) !=null || request
1598                                 .getResponseHeaders().get(
1599                                 AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName).isEmpty()))
1600         {
1601             endpoints .add( AutodiscoverEndpoints.WSSecuritySymmetricKey);
1602         }
1603         if (!(request.getResponseHeaders().get(
1604                         AutodiscoverWsSecurityX509CertEnabledHeaderName)!=null ||
1605                         request.getResponseHeaders().get(
1606                                 AutodiscoverWsSecurityX509CertEnabledHeaderName).isEmpty()))
1607                         
1608         {
1609             endpoints .add(AutodiscoverEndpoints.WSSecurityX509Cert);
1610         }*/
1611
1612    return endpoints;
1613  }
1614
1615  /**
1616   * Traces the response.
1617   *
1618   * @param request      the request
1619   * @param memoryStream the memory stream
1620   * @throws XMLStreamException the XML stream exception
1621   * @throws IOException signals that an I/O exception has occurred.
1622   * @throws EWSHttpException the EWS http exception
1623   */
1624  public void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream) throws XMLStreamException,
1625      IOException, EWSHttpException {
1626    this.processHttpResponseHeaders(
1627        TraceFlags.AutodiscoverResponseHttpHeaders, request);
1628    String contentType = request.getResponseContentType();
1629    if (!(contentType == null || contentType.isEmpty())) {
1630      contentType = contentType.toLowerCase();
1631      if (contentType.toLowerCase().startsWith("text/") ||
1632          contentType.toLowerCase().
1633              startsWith("application/soap")) {
1634        this.traceXml(TraceFlags.AutodiscoverResponse, memoryStream);
1635      } else {
1636        this.traceMessage(TraceFlags.AutodiscoverResponse,
1637            "Non-textual response");
1638      }
1639    }
1640  }
1641
1642  /**
1643   * Creates an HttpWebRequest instance and initializes it with the
1644   * appropriate parameters, based on the configuration of this service
1645   * object.
1646   *
1647   * @param url The URL that the HttpWebRequest should target
1648   * @return HttpWebRequest The HttpWebRequest
1649   * @throws ServiceLocalException       the service local exception
1650   * @throws java.net.URISyntaxException the uRI syntax exception
1651   */
1652  public HttpWebRequest prepareHttpWebRequestForUrl(URI url)
1653      throws ServiceLocalException, URISyntaxException {
1654    return this.prepareHttpWebRequestForUrl(url, false,
1655        // acceptGzipEncoding
1656        false); // allowAutoRedirect
1657  }
1658
1659  /**
1660   * Calls the redirection URL validation callback. If the redirection URL
1661   * validation callback is null, use the default callback which does not
1662   * allow following any redirections.
1663   *
1664   * @param redirectionUrl The redirection URL.
1665   * @return True if redirection should be followed.
1666   * @throws AutodiscoverLocalException the autodiscover local exception
1667   */
1668  private boolean callRedirectionUrlValidationCallback(String redirectionUrl)
1669      throws AutodiscoverLocalException {
1670    IAutodiscoverRedirectionUrl callback =
1671        (this.redirectionUrlValidationCallback == null) ? this
1672            : this.redirectionUrlValidationCallback;
1673    return callback
1674        .autodiscoverRedirectionUrlValidationCallback(redirectionUrl);
1675  }
1676
1677  /**
1678   * Processes an HTTP error response.
1679   *
1680   * @param httpWebResponse The HTTP web response.
1681   * @throws Exception the exception
1682   */
1683  @Override public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception {
1684    this.internalProcessHttpErrorResponse(
1685        httpWebResponse,
1686        webException,
1687        TraceFlags.AutodiscoverResponseHttpHeaders,
1688        TraceFlags.AutodiscoverResponse);
1689  }
1690
1691  /*
1692   * (non-Javadoc)
1693   *
1694   * @see microsoft.exchange.webservices.AutodiscoverRedirectionUrlInterface#
1695   * autodiscoverRedirectionUrlValidationCallback(java.lang.String)
1696   */
1697  public boolean autodiscoverRedirectionUrlValidationCallback(
1698      String redirectionUrl) throws AutodiscoverLocalException {
1699    return defaultAutodiscoverRedirectionUrlValidationCallback(
1700        redirectionUrl);
1701  }
1702
1703  /**
1704   * Initializes a new instance of the "AutodiscoverService" class.
1705   *
1706   * @throws ArgumentException on validation error
1707   */
1708  public AutodiscoverService() throws ArgumentException {
1709    this(ExchangeVersion.Exchange2010);
1710  }
1711
1712  /**
1713   * Initializes a new instance of the "AutodiscoverService" class.
1714   *
1715   * @param requestedServerVersion The requested server version
1716   * @throws ArgumentException on validation error
1717   */
1718  public AutodiscoverService(ExchangeVersion requestedServerVersion)
1719      throws ArgumentException {
1720    this(null, null, requestedServerVersion);
1721  }
1722
1723  /**
1724   * Initializes a new instance of the "AutodiscoverService" class.
1725   *
1726   * @param domain The domain that will be used to determine the URL of the service
1727   * @throws ArgumentException on validation error
1728   */
1729  public AutodiscoverService(String domain) throws ArgumentException {
1730    this(null, domain);
1731  }
1732
1733  /**
1734   * Initializes a new instance of the "AutodiscoverService" class.
1735   *
1736   * @param domain                 The domain that will be used to determine the URL of the service
1737   * @param requestedServerVersion The requested server version
1738   * @throws ArgumentException on validation error
1739   */
1740  public AutodiscoverService(String domain,
1741      ExchangeVersion requestedServerVersion) throws ArgumentException {
1742    this(null, domain, requestedServerVersion);
1743  }
1744
1745  /**
1746   * Initializes a new instance of the "AutodiscoverService" class.
1747   *
1748   * @param url The URL of the service
1749   * @throws ArgumentException on validation error
1750   */
1751  public AutodiscoverService(URI url) throws ArgumentException {
1752    this(url, url.getHost());
1753  }
1754
1755  /**
1756   * Initializes a new instance of the "AutodiscoverService" class.
1757   *
1758   * @param url                    The URL of the service
1759   * @param requestedServerVersion The requested server version
1760   * @throws ArgumentException on validation error
1761   */
1762  public AutodiscoverService(URI url,
1763      ExchangeVersion requestedServerVersion) throws ArgumentException {
1764    this(url, url.getHost(), requestedServerVersion);
1765  }
1766
1767  /**
1768   * Initializes a new instance of the "AutodiscoverService" class.
1769   *
1770   * @param url    The URL of the service
1771   * @param domain The domain that will be used to determine the URL of the service
1772   * @throws ArgumentException on validation error
1773   */
1774  public AutodiscoverService(URI url, String domain)
1775      throws ArgumentException {
1776    super();
1777    EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1778    this.url = url;
1779    this.domain = domain;
1780    this.dnsClient = new AutodiscoverDnsClient(this);
1781  }
1782
1783  /**
1784   * Initializes a new instance of the "AutodiscoverService" class.
1785   *
1786   * @param url                    The URL of the service.
1787   * @param domain                 The domain that will be used to determine the URL of the
1788   *                               service.
1789   * @param requestedServerVersion The requested server version.
1790   * @throws ArgumentException on validation error
1791   */
1792  public AutodiscoverService(URI url, String domain,
1793      ExchangeVersion requestedServerVersion) throws ArgumentException {
1794    super(requestedServerVersion);
1795    EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1796
1797    this.url = url;
1798    this.domain = domain;
1799    this.dnsClient = new AutodiscoverDnsClient(this);
1800  }
1801
1802  /**
1803   * Initializes a new instance of the AutodiscoverService class.
1804   *
1805   * @param service                The other service.
1806   * @param requestedServerVersion The requested server version.
1807   */
1808  public AutodiscoverService(ExchangeServiceBase service,
1809      ExchangeVersion requestedServerVersion) {
1810    super(service, requestedServerVersion);
1811    this.dnsClient = new AutodiscoverDnsClient(this);
1812  }
1813
1814  /**
1815   * Initializes a new instance of the "AutodiscoverService" class.
1816   *
1817   * @param service The service.
1818   */
1819  public AutodiscoverService(ExchangeServiceBase service) {
1820    super(service, service.getRequestedServerVersion());
1821  }
1822
1823  /**
1824   * Retrieves the specified settings for single SMTP address.
1825   * <p>This method will run the entire Autodiscover "discovery"
1826   * algorithm and will follow address and URL redirections.</p>
1827
1828   * @param userSmtpAddress  The SMTP addresses of the user.
1829   * @param userSettingNames The user setting names.
1830   * @return A UserResponse object containing the requested settings for the
1831   * specified user.
1832   * @throws Exception on error
1833   */
1834  public GetUserSettingsResponse getUserSettings(String userSmtpAddress,
1835      UserSettingName... userSettingNames) throws Exception {
1836    List<UserSettingName> requestedSettings = new ArrayList<UserSettingName>();
1837    requestedSettings.addAll(Arrays.asList(userSettingNames));
1838
1839    if (userSmtpAddress == null || userSmtpAddress.isEmpty()) {
1840      throw new ServiceValidationException("A valid SMTP address must be specified.");
1841    }
1842
1843    if (requestedSettings.size() == 0) {
1844      throw new ServiceValidationException("At least one setting must be requested.");
1845    }
1846
1847    if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1848      return this.internalGetLegacyUserSettings(userSmtpAddress,
1849          requestedSettings);
1850    } else {
1851      return this.internalGetSoapUserSettings(userSmtpAddress,
1852          requestedSettings);
1853    }
1854
1855  }
1856
1857  /**
1858   * Retrieves the specified settings for a set of users.
1859   *
1860   * @param userSmtpAddresses the user smtp addresses
1861   * @param userSettingNames  The user setting names.
1862   * @return A GetUserSettingsResponseCollection object containing the
1863   * response for each individual user.
1864   * @throws Exception the exception
1865   */
1866  public GetUserSettingsResponseCollection getUsersSettings(
1867      Iterable<String> userSmtpAddresses,
1868      UserSettingName... userSettingNames) throws Exception {
1869    if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1870      throw new ServiceVersionException(
1871          String.format("The Autodiscover service only supports %s or a later version.",
1872              MinimumRequestVersionForAutoDiscoverSoapService));
1873    }
1874    List<String> smtpAddresses = new ArrayList<String>();
1875    smtpAddresses.addAll((Collection<? extends String>) userSmtpAddresses);
1876    List<UserSettingName> settings = new ArrayList<UserSettingName>();
1877    settings.addAll(Arrays.asList(userSettingNames));
1878    return this.getUserSettings(smtpAddresses, settings);
1879  }
1880
1881  /**
1882   * Retrieves the specified settings for a domain.
1883   *
1884   * @param domain             The domain.
1885   * @param requestedVersion   Requested version of the Exchange service.
1886   * @param domainSettingNames The domain setting names.
1887   * @return A DomainResponse object containing the requested settings for the
1888   * specified domain.
1889   * @throws Exception the exception
1890   */
1891  public GetDomainSettingsResponse getDomainSettings(String domain,
1892      ExchangeVersion requestedVersion,
1893      DomainSettingName... domainSettingNames) throws Exception {
1894    List<String> domains = new ArrayList<String>(1);
1895    domains.add(domain);
1896
1897    List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1898    settings.addAll(Arrays.asList(domainSettingNames));
1899
1900    return this.getDomainSettings(domains, settings, requestedVersion).
1901        getTResponseAtIndex(0);
1902  }
1903
1904  /**
1905   * Retrieves the specified settings for a set of domains.
1906   *
1907   * @param domains            the domains
1908   * @param requestedVersion   Requested version of the Exchange service.
1909   * @param domainSettingNames The domain setting names.
1910   * @return A GetDomainSettingsResponseCollection object containing the
1911   * response for each individual domain.
1912   * @throws Exception the exception
1913   */
1914  public GetDomainSettingsResponseCollection getDomainSettings(
1915      Iterable<String> domains, ExchangeVersion requestedVersion,
1916      DomainSettingName... domainSettingNames)
1917      throws Exception {
1918    List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1919    settings.addAll(Arrays.asList(domainSettingNames));
1920
1921    List<String> domainslst = new ArrayList<String>();
1922    domainslst.addAll((Collection<? extends String>) domains);
1923
1924    return this.getDomainSettings(domainslst, settings, requestedVersion);
1925  }
1926
1927  /**
1928   * Gets the domain this service is bound to. When this property is
1929   * set, the domain name is used to automatically determine the Autodiscover service URL.
1930   *
1931   * @return the domain
1932   */
1933  public String getDomain() {
1934    return this.domain;
1935  }
1936
1937  /**
1938   * Sets the domain this service is bound to. When this property is
1939   * set, the domain
1940   * name is used to automatically determine the Autodiscover service URL.
1941   *
1942   * @param value the new domain
1943   * @throws ArgumentException on validation error
1944   */
1945  public void setDomain(String value) throws ArgumentException {
1946    EwsUtilities.validateDomainNameAllowNull(value, "Domain");
1947
1948    // If Domain property is set to non-null value, Url property is nulled.
1949    if (value != null) {
1950      this.url = null;
1951    }
1952    this.domain = value;
1953  }
1954
1955  /**
1956   * Gets the url this service is bound to.
1957   *
1958   * @return the url
1959   */
1960  public URI getUrl() {
1961    return this.url;
1962  }
1963
1964  /**
1965   * Sets the url this service is bound to.
1966   *
1967   * @param value the new url
1968   */
1969  public void setUrl(URI value) {
1970    // If Url property is set to non-null value, Domain property is set to
1971    // host portion of Url.
1972    if (value != null) {
1973      this.domain = value.getHost();
1974    }
1975    this.url = value;
1976  }
1977
1978  public Boolean isExternal() {
1979    return this.isExternal;
1980  }
1981
1982  protected void setIsExternal(Boolean value) {
1983    this.isExternal = value;
1984  }
1985
1986
1987  /**
1988   * Gets the redirection url validation callback.
1989   *
1990   * @return the redirection url validation callback
1991   */
1992  public IAutodiscoverRedirectionUrl
1993  getRedirectionUrlValidationCallback() {
1994    return this.redirectionUrlValidationCallback;
1995  }
1996
1997  /**
1998   * Sets the redirection url validation callback.
1999   *
2000   * @param value the new redirection url validation callback
2001   */
2002  public void setRedirectionUrlValidationCallback(
2003      IAutodiscoverRedirectionUrl value) {
2004    this.redirectionUrlValidationCallback = value;
2005  }
2006
2007  /**
2008   * Gets the dns server address.
2009   *
2010   * @return the dns server address
2011   */
2012  protected String getDnsServerAddress() {
2013    return this.dnsServerAddress;
2014  }
2015
2016  /**
2017   * Sets the dns server address.
2018   *
2019   * @param value the new dns server address
2020   */
2021  protected void setDnsServerAddress(String value) {
2022    this.dnsServerAddress = value;
2023  }
2024
2025  /**
2026   * Gets a value indicating whether the AutodiscoverService should
2027   * perform SCP (ServiceConnectionPoint) record lookup when determining
2028   * the Autodiscover service URL.
2029   *
2030   * @return the enable scp lookup
2031   */
2032  public boolean getEnableScpLookup() {
2033    return this.enableScpLookup;
2034  }
2035
2036  /**
2037   * Sets the enable scp lookup.
2038   *
2039   * @param value the new enable scp lookup
2040   */
2041  public void setEnableScpLookup(boolean value) {
2042    this.enableScpLookup = value;
2043  }
2044
2045  /*
2046   * (non-Javadoc)
2047   *
2048   * @see
2049   * microsoft.exchange.webservices.FuncDelegateInterface#func(java.util.List,
2050   * java.util.List, java.net.URI)
2051   */
2052  @Override
2053  public Object func(List arg1, List arg2, ExchangeVersion arg3, URI arg4)
2054      throws ServiceLocalException, Exception {
2055    if (arg2.get(0).getClass().equals(DomainSettingName.class)) {
2056      return internalGetDomainSettings(arg1, arg2, arg3, arg4);
2057    } else if (arg2.get(0).getClass().equals(UserSettingName.class)) {
2058      return internalGetUserSettings(arg1, arg2, arg3, arg4);
2059    } else {
2060      return null;
2061    }
2062  }
2063
2064}