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
405      // Do NOT allow authentication as this single request will be made over plain HTTP.
406      request.setAllowAuthentication(false);
407
408      prepareCredentials(request);
409
410      request.prepareConnection();
411      try {
412        request.executeRequest();
413      } catch (IOException e) {
414        traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
415        return null;
416      }
417
418      OutParam<URI> outParam = new OutParam<URI>();
419      if (tryGetRedirectionResponse(request, outParam)) {
420        return outParam.getParam();
421      }
422    } finally {
423      if (request != null) {
424        try {
425          request.close();
426        } catch (Exception e) {
427          // Ignore exception when closing the request
428        }
429      }
430    }
431
432    traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned.");
433    return null;
434  }
435
436  /**
437   * Tries the get redirection response.
438   *
439   * @param request     the request
440   * @param redirectUrl the redirect URL
441   * @return true if a valid redirection URL was found
442   * @throws XMLStreamException the XML stream exception
443   * @throws IOException signals that an I/O exception has occurred.
444   * @throws EWSHttpException the EWS http exception
445   */
446  private boolean tryGetRedirectionResponse(HttpWebRequest request,
447      OutParam<URI> redirectUrl) throws XMLStreamException, IOException,
448      EWSHttpException {
449    // redirectUrl = null;
450    if (AutodiscoverRequest.isRedirectionResponse(request)) {
451      // Get the redirect location and verify that it's valid.
452      String location = request.getResponseHeaderField("Location");
453
454      if (!(location == null || location.isEmpty())) {
455        try {
456          redirectUrl.setParam(new URI(location));
457
458          // Check if URL is SSL and that the path matches.
459          if ((redirectUrl.getParam().getScheme().toLowerCase()
460              .equals("https")) &&
461              (redirectUrl.getParam().getPath()
462                  .equalsIgnoreCase(
463                      AutodiscoverLegacyPath))) {
464            this.traceMessage(TraceFlags.AutodiscoverConfiguration,
465                String.format("Redirection URL found: '%s'",
466                    redirectUrl.getParam().toString()));
467
468            return true;
469          }
470        } catch (URISyntaxException ex) {
471          this
472              .traceMessage(
473                  TraceFlags.AutodiscoverConfiguration,
474                  String
475                      .format(
476                          "Invalid redirection URL " +
477                              "was returned: '%s'",
478                          location));
479          return false;
480        }
481      }
482    }
483    return false;
484  }
485
486  /**
487   * Calls the legacy Autodiscover service to retrieve configuration settings.
488   *
489   * @param <TSettings>  the generic type
490   * @param cls          the cls
491   * @param emailAddress The email address to retrieve configuration settings for.
492   * @return The requested configuration settings.
493   * @throws Exception the exception
494   */
495  protected <TSettings extends ConfigurationSettingsBase>
496  TSettings getLegacyUserSettings(
497      Class<TSettings> cls, String emailAddress) throws Exception {
498                /*int currentHop = 1;
499                return this.internalGetConfigurationSettings(cls, emailAddress,
500                                currentHop);*/
501
502    // If Url is specified, call service directly.
503    if (this.url != null) {
504      // this.Uri is intended for Autodiscover SOAP service, convert to Legacy endpoint URL.
505      URI autodiscoverUrl = new URI(this.url.toString() + AutodiscoverLegacyPath);
506      return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl);
507    }
508
509    // If Domain is specified, figure out the endpoint Url and call service.
510    else if (!(this.domain == null || this.domain.isEmpty())) {
511      URI autodiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, this.domain));
512      return this.getLegacyUserSettingsAtUrl(cls,
513          emailAddress, autodiscoverUrl);
514    } else {
515      // No Url or Domain specified, need to
516      //figure out which endpoint to use.
517      int currentHop = 1;
518      OutParam<Integer> outParam = new OutParam<Integer>();
519      outParam.setParam(currentHop);
520      List<String> redirectionEmailAddresses = new ArrayList<String>();
521      return this.internalGetLegacyUserSettings(
522          cls,
523          emailAddress,
524          redirectionEmailAddresses,
525          outParam);
526    }
527  }
528
529  /**
530   * Calls the Autodiscover service to retrieve configuration settings.
531   *
532   * @param <TSettings>  the generic type
533   * @param cls          the cls
534   * @param emailAddress The email address to retrieve configuration settings for.
535   * @param currentHop   Current number of redirection urls/addresses attempted so far.
536   * @return The requested configuration settings.
537   * @throws Exception the exception
538   */
539  private <TSettings extends ConfigurationSettingsBase>
540  TSettings internalGetLegacyUserSettings(
541      Class<TSettings> cls,
542      String emailAddress,
543      List<String> redirectionEmailAddresses,
544      OutParam<Integer> currentHop)
545      throws Exception {
546    String domainName = EwsUtilities.domainFromEmailAddress(emailAddress);
547
548    int scpUrlCount;
549    OutParam<Integer> outParamInt = new OutParam<Integer>();
550    List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParamInt);
551    scpUrlCount = outParamInt.getParam();
552    if (urls.size() == 0) {
553      throw new ServiceValidationException(
554          "This Autodiscover request requires that either the Domain or Url be specified.");
555    }
556
557    // Assume caller is not inside the Intranet, regardless of whether SCP
558    // Urls
559    // were returned or not. SCP Urls are only relevent if one of them
560    // returns
561    // valid Autodiscover settings.
562    this.isExternal = true;
563
564    int currentUrlIndex = 0;
565
566    // Used to save exception for later reporting.
567    Exception delayedException = null;
568    TSettings settings;
569
570    do {
571      URI autodiscoverUrl = urls.get(currentUrlIndex);
572      boolean isScpUrl = currentUrlIndex < scpUrlCount;
573
574      try {
575        settings = this.getLegacyUserSettingsAtUrl(cls,
576            emailAddress, autodiscoverUrl);
577
578        switch (settings.getResponseType()) {
579          case Success:
580            // Not external if Autodiscover endpoint found via SCP
581            // returned the settings.
582            if (isScpUrl) {
583              this.isExternal = false;
584            }
585            this.url = autodiscoverUrl;
586            return settings;
587          case RedirectUrl:
588            if (currentHop.getParam() < AutodiscoverMaxRedirections) {
589              currentHop.setParam(currentHop.getParam() + 1);
590
591              this
592                  .traceMessage(
593                      TraceFlags.AutodiscoverResponse,
594                      String
595                          .format(
596                              "Autodiscover " +
597                                  "service " +
598                                  "returned " +
599                                  "redirection URL '%s'.",
600                              settings
601                                  .getRedirectTarget()));
602
603              urls.add(currentUrlIndex, new URI(
604                  settings.getRedirectTarget()));
605
606              break;
607            } else {
608              throw new MaximumRedirectionHopsExceededException();
609            }
610          case RedirectAddress:
611            if (currentHop.getParam() < AutodiscoverMaxRedirections) {
612              currentHop.setParam(currentHop.getParam() + 1);
613
614              this
615                  .traceMessage(
616                      TraceFlags.AutodiscoverResponse,
617                      String
618                          .format(
619                              "Autodiscover " +
620                                  "service " +
621                                  "returned " +
622                                  "redirection email " +
623                                  "address '%s'.",
624                              settings
625                                  .getRedirectTarget()));
626              // Bug E14:255576 If this email address was already tried, we may have a loop
627              // in SCP lookups. Disable consideration of SCP records.
628              this.disableScpLookupIfDuplicateRedirection(
629                  settings.getRedirectTarget(),
630                  redirectionEmailAddresses);
631
632              return this.internalGetLegacyUserSettings(cls,
633                  settings.getRedirectTarget(),
634                  redirectionEmailAddresses,
635                  currentHop);
636            } else {
637              throw new MaximumRedirectionHopsExceededException();
638            }
639          case Error:
640            // Don't treat errors from an SCP-based Autodiscover service
641            // to be conclusive.
642            // We'll try the next one and record the error for later.
643            if (isScpUrl) {
644              this
645                  .traceMessage(
646                      TraceFlags.AutodiscoverConfiguration,
647                      "Error returned by " +
648                          "Autodiscover service " +
649                          "found via SCP, treating " +
650                          "as inconclusive.");
651
652              delayedException = new AutodiscoverRemoteException(
653                  "The Autodiscover service returned an error.", settings.getError());
654              currentUrlIndex++;
655            } else {
656              throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getError());
657            }
658            break;
659          default:
660            EwsUtilities
661                .ewsAssert(false, "Autodiscover.GetConfigurationSettings",
662                           "An unexpected error has occured. This code path should never be reached.");
663            break;
664        }
665      } catch (XMLStreamException ex) {
666        this.traceMessage(TraceFlags.AutodiscoverConfiguration, String
667            .format("%s failed: XML parsing error: %s", url, ex
668                .getMessage()));
669
670        // The content at the URL wasn't a valid response, let's try the
671        // next.
672        currentUrlIndex++;
673      } catch (IOException ex) {
674        this.traceMessage(
675            TraceFlags.AutodiscoverConfiguration,
676            String.format("%s failed: I/O error: %s",
677                url, ex.getMessage()));
678
679        // The content at the URL wasn't a valid response, let's try the next.
680        currentUrlIndex++;
681      } catch (Exception ex) {
682        HttpWebRequest response = null;
683        URI redirectUrl;
684        OutParam<URI> outParam1 = new OutParam<URI>();
685        if ((response != null) &&
686            this.tryGetRedirectionResponse(response, outParam1)) {
687          redirectUrl = outParam1.getParam();
688          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
689              String.format(
690                  "Host returned a redirection to url %s",
691                  redirectUrl.toString()));
692
693          currentHop.setParam(currentHop.getParam() + 1);
694          urls.add(currentUrlIndex, redirectUrl);
695        } else {
696          if (response != null) {
697            this.processHttpErrorResponse(response, ex);
698
699          }
700
701          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
702              String.format("%s failed: %s (%s)", url, ex
703                  .getClass().getName(), ex.getMessage()));
704
705          // The url did not work, let's try the next.
706          currentUrlIndex++;
707        }
708      }
709    } while (currentUrlIndex < urls.size());
710
711    // If we got this far it's because none of the URLs we tried have
712    // worked. As a next-to-last chance, use GetRedirectUrl to
713    // try to get a redirection URL using an HTTP GET on a non-SSL
714    // Autodiscover endpoint. If successful, use this
715    // redirection URL to get the configuration settings for this email
716    // address. (This will be a common scenario for
717    // DataCenter deployments).
718    URI redirectionUrl = this.getRedirectUrl(domainName);
719    OutParam<TSettings> outParam = new OutParam<TSettings>();
720    if ((redirectionUrl != null)
721        && this.tryLastChanceHostRedirection(cls, emailAddress,
722        redirectionUrl, outParam)) {
723      settings = outParam.getParam();
724      return settings;
725    } else {
726      // Getting a redirection URL from an HTTP GET failed too. As a last
727      // chance, try to get an appropriate SRV Record
728      // using DnsQuery. If successful, use this redirection URL to get
729      // the configuration settings for this email address.
730      redirectionUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName);
731      if ((redirectionUrl != null)
732          && this.tryLastChanceHostRedirection(cls, emailAddress,
733          redirectionUrl, outParam)) {
734        return outParam.getParam();
735      }
736
737      // If there was an earlier exception, throw it.
738      if (delayedException != null) {
739        throw delayedException;
740      }
741
742      throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
743    }
744  }
745
746  /**
747   * Get an autodiscover SRV record in DNS and construct autodiscover URL.
748   *
749   * @param domainName Name of the domain.
750   * @return Autodiscover URL (may be null if lookup failed)
751   * @throws Exception the exception
752   */
753  protected URI getRedirectionUrlFromDnsSrvRecord(String domainName)
754      throws Exception {
755
756    this
757        .traceMessage(
758            TraceFlags.AutodiscoverConfiguration,
759            String
760                .format(
761                    "Trying to get Autodiscover host " +
762                        "from DNS SRV record for %s.",
763                    domainName));
764
765    String hostname = this.dnsClient
766        .findAutodiscoverHostFromSrv(domainName);
767    if (!(hostname == null || hostname.isEmpty())) {
768      this
769          .traceMessage(TraceFlags.AutodiscoverConfiguration,
770              String.format(
771                  "Autodiscover host %s was returned.",
772                  hostname));
773
774      return new URI(String.format(AutodiscoverLegacyHttpsUrl,
775          hostname));
776    } else {
777      this.traceMessage(TraceFlags.AutodiscoverConfiguration,
778          "No matching Autodiscover DNS SRV records were found.");
779
780      return null;
781    }
782  }
783
784  /**
785   * Tries to get Autodiscover settings using redirection Url.
786   *
787   * @param <TSettings>    the generic type
788   * @param cls            the cls
789   * @param emailAddress   The email address.
790   * @param redirectionUrl Redirection Url.
791   * @param settings       The settings.
792   * @return boolean The boolean.
793   * @throws AutodiscoverLocalException  the autodiscover local exception
794   * @throws AutodiscoverRemoteException the autodiscover remote exception
795   * @throws Exception                   the exception
796   */
797  private <TSettings extends ConfigurationSettingsBase> boolean
798  tryLastChanceHostRedirection(
799      Class<TSettings> cls, String emailAddress, URI redirectionUrl,
800      OutParam<TSettings> settings) throws AutodiscoverLocalException,
801      AutodiscoverRemoteException, Exception {
802    List<String> redirectionEmailAddresses = new ArrayList<String>();
803
804    // Bug 60274: Performing a non-SSL HTTP GET to retrieve a redirection
805    // URL is potentially unsafe. We allow the caller
806    // to specify delegate to be called to determine whether we are allowed
807    // to use the redirection URL.
808    if (this
809        .callRedirectionUrlValidationCallback(redirectionUrl.toString())) {
810      for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
811        try {
812          settings.setParam(this.getLegacyUserSettingsAtUrl(cls,
813              emailAddress, redirectionUrl));
814
815          switch (settings.getParam().getResponseType()) {
816            case Success:
817              return true;
818            case Error:
819              throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getParam()
820                  .getError());
821            case RedirectAddress:
822              // If this email address was already tried,
823              //we may have a loop
824              // in SCP lookups. Disable consideration of SCP records.
825              this.disableScpLookupIfDuplicateRedirection(settings.getParam().getRedirectTarget(),
826                  redirectionEmailAddresses);
827              OutParam<Integer> outParam = new OutParam<Integer>();
828              outParam.setParam(currentHop);
829              settings.setParam(
830                  this.internalGetLegacyUserSettings(cls,
831                      emailAddress,
832                      redirectionEmailAddresses,
833                      outParam));
834              currentHop = outParam.getParam();
835              return true;
836            case RedirectUrl:
837              try {
838                redirectionUrl = new URI(settings.getParam()
839                    .getRedirectTarget());
840              } catch (URISyntaxException ex) {
841                this
842                    .traceMessage(
843                        TraceFlags.
844                            AutodiscoverConfiguration,
845                        String
846                            .format(
847                                "Service " +
848                                    "returned " +
849                                    "invalid " +
850                                    "redirection " +
851                                    "URL %s",
852                                settings
853                                    .getParam()
854                                    .getRedirectTarget()));
855                return false;
856              }
857              break;
858            default:
859              String failureMessage = String.format(
860                  "Autodiscover call at %s failed with error %s, target %s",
861                  redirectionUrl,
862                  settings.getParam().getResponseType(),
863                  settings.getParam().getRedirectTarget());
864              this.traceMessage(
865                  TraceFlags.AutodiscoverConfiguration, failureMessage);
866
867              return false;
868          }
869        } catch (XMLStreamException ex) {
870          // If the response is malformed, it wasn't a valid
871          // Autodiscover endpoint.
872          this
873              .traceMessage(TraceFlags.AutodiscoverConfiguration,
874                  String.format(
875                      "%s failed: XML parsing error: %s",
876                      redirectionUrl.toString(), ex
877                          .getMessage()));
878          return false;
879        } catch (IOException ex) {
880          this.traceMessage(
881              TraceFlags.AutodiscoverConfiguration,
882              String.format("%s failed: I/O error: %s",
883                  redirectionUrl, ex.getMessage()));
884          return false;
885        } catch (Exception ex) {
886          // TODO: BUG response is always null
887          HttpWebRequest response = null;
888          OutParam<URI> outParam = new OutParam<URI>();
889          if ((response != null)
890              && this.tryGetRedirectionResponse(response,
891              outParam)) {
892            redirectionUrl = outParam.getParam();
893            this
894                .traceMessage(
895                    TraceFlags.AutodiscoverConfiguration,
896                    String
897                        .format(
898                            "Host returned a " +
899                                "redirection" +
900                                " to url %s",
901                            redirectionUrl));
902
903          } else {
904            if (response != null) {
905              this.processHttpErrorResponse(response, ex);
906            }
907
908            this
909                .traceMessage(
910                    TraceFlags.AutodiscoverConfiguration,
911                    String.format("%s failed: %s (%s)",
912                        url, ex.getClass().getName(),
913                        ex.getMessage()));
914            return false;
915          }
916        }
917      }
918    }
919
920    return false;
921  }
922
923  /**
924   * Disables SCP lookup if duplicate email address redirection.
925   *
926   * @param emailAddress              The email address to use.
927   * @param redirectionEmailAddresses The list of prior redirection email addresses.
928   */
929  private void disableScpLookupIfDuplicateRedirection(
930      String emailAddress,
931      List<String> redirectionEmailAddresses) {
932    // SMTP addresses are case-insensitive so entries are converted to lower-case.
933    emailAddress = emailAddress.toLowerCase();
934
935    if (redirectionEmailAddresses.contains(emailAddress)) {
936      this.enableScpLookup = false;
937    } else {
938      redirectionEmailAddresses.add(emailAddress);
939    }
940  }
941
942  /**
943   * Gets user settings from Autodiscover legacy endpoint.
944   *
945   * @param emailAddress      The email address to use.
946   * @param requestedSettings The requested settings.
947   * @return GetUserSettingsResponse
948   * @throws Exception on error
949   */
950  protected GetUserSettingsResponse internalGetLegacyUserSettings(
951      String emailAddress,
952      List<UserSettingName> requestedSettings) throws Exception {
953    // Cannot call legacy Autodiscover service with WindowsLive and other WSSecurity-based credential
954    if ((this.getCredentials() != null) && (this.getCredentials() instanceof WSSecurityBasedCredentials)) {
955      throw new AutodiscoverLocalException(
956          "WindowsLiveCredentials can't be used with this Autodiscover endpoint.");
957    }
958
959    OutlookConfigurationSettings settings = this.getLegacyUserSettings(
960        OutlookConfigurationSettings.class,
961        emailAddress);
962
963
964
965    return settings.convertSettings(emailAddress, requestedSettings);
966  }
967
968  /**
969   * Calls the SOAP Autodiscover service
970   * for user settings for a single SMTP address.
971   *
972   * @param smtpAddress       SMTP address.
973   * @param requestedSettings The requested settings.
974   * @return GetUserSettingsResponse
975   * @throws Exception on error
976   */
977  protected GetUserSettingsResponse internalGetSoapUserSettings(
978      String smtpAddress,
979      List<UserSettingName> requestedSettings) throws Exception {
980    List<String> smtpAddresses = new ArrayList<String>();
981    smtpAddresses.add(smtpAddress);
982
983    List<String> redirectionEmailAddresses = new ArrayList<String>();
984    redirectionEmailAddresses.add(smtpAddress.toLowerCase());
985
986    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
987      GetUserSettingsResponse response = this.getUserSettings(smtpAddresses,
988          requestedSettings).getTResponseAtIndex(0);
989
990      switch (response.getErrorCode()) {
991        case RedirectAddress:
992          this.traceMessage(
993              TraceFlags.AutodiscoverResponse,
994              String.format("Autodiscover service returned redirection email address '%s'.",
995                  response.getRedirectTarget()));
996
997          smtpAddresses.clear();
998          smtpAddresses.add(response.getRedirectTarget().
999              toLowerCase());
1000          this.url = null;
1001          this.domain = null;
1002
1003          // If this email address was already tried,
1004          //we may have a loop
1005          // in SCP lookups. Disable consideration of SCP records.
1006          this.disableScpLookupIfDuplicateRedirection(response.getRedirectTarget(),
1007              redirectionEmailAddresses);
1008          break;
1009
1010        case RedirectUrl:
1011          this.traceMessage(
1012              TraceFlags.AutodiscoverResponse,
1013              String.format("Autodiscover service returned redirection URL '%s'.",
1014                  response.getRedirectTarget()));
1015
1016          //this.url = new URI(response.getRedirectTarget());
1017          this.url = this.getCredentials().adjustUrl(new URI(response.getRedirectTarget()));
1018          break;
1019
1020        case NoError:
1021        default:
1022          return response;
1023      }
1024    }
1025
1026    throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1027  }
1028
1029  /**
1030   * Gets the user settings using Autodiscover SOAP service.
1031   *
1032   * @param smtpAddresses The SMTP addresses of the users.
1033   * @param settings      The settings.
1034   * @return GetUserSettingsResponseCollection Object.
1035   * @throws Exception the exception
1036   */
1037  protected GetUserSettingsResponseCollection getUserSettings(
1038      final List<String> smtpAddresses, List<UserSettingName> settings)
1039      throws Exception {
1040    EwsUtilities.validateParam(smtpAddresses, "smtpAddresses");
1041    EwsUtilities.validateParam(settings, "settings");
1042
1043    return this.getSettings(
1044        GetUserSettingsResponseCollection.class, UserSettingName.class,
1045        smtpAddresses, settings, null, this,
1046        new IFuncDelegate<String>() {
1047          public String func() throws FormatException {
1048            return EwsUtilities
1049                .domainFromEmailAddress(smtpAddresses.get(0));
1050          }
1051        });
1052  }
1053
1054  /**
1055   * Gets user or domain settings using Autodiscover SOAP service.
1056   *
1057   * @param <TGetSettingsResponseCollection> the generic type
1058   * @param <TSettingName>                   the generic type
1059   * @param cls                              the cls
1060   * @param cls1                             the cls1
1061   * @param identities                       Either the domains or the SMTP addresses of the users.
1062   * @param settings                         The settings.
1063   * @param requestedVersion                 Requested version of the Exchange service.
1064   * @param getSettingsMethod                The method to use.
1065   * @param getDomainMethod                  The method to calculate the domain value.
1066   * @return TGetSettingsResponse Collection.
1067   * @throws Exception the exception
1068   */
1069  private <TGetSettingsResponseCollection, TSettingName>
1070  TGetSettingsResponseCollection getSettings(
1071      Class<TGetSettingsResponseCollection> cls,
1072      Class<TSettingName> cls1,
1073      List<String> identities,
1074      List<TSettingName> settings,
1075      ExchangeVersion requestedVersion,
1076      IFunctionDelegate<List<String>, List<TSettingName>,
1077          TGetSettingsResponseCollection> getSettingsMethod,
1078      IFuncDelegate<String> getDomainMethod) throws Exception {
1079    TGetSettingsResponseCollection response;
1080
1081    // Autodiscover service only exists in E14 or later.
1082    if (this.getRequestedServerVersion().compareTo(
1083        MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1084      throw new ServiceVersionException(String.format(
1085          "The Autodiscover service only supports %s or a later version.",
1086          MinimumRequestVersionForAutoDiscoverSoapService));
1087    }
1088
1089    // If Url is specified, call service directly.
1090    if (this.url != null) {
1091      URI autodiscoverUrl = this.url;
1092      response = getSettingsMethod.func(identities, settings,
1093          requestedVersion, this.url);
1094      this.url = autodiscoverUrl;
1095      return response;
1096    }
1097    // If Domain is specified, determine endpoint Url and call service.
1098    else if (!(this.domain == null || this.domain.isEmpty())) {
1099      URI autodiscoverUrl = this.getAutodiscoverEndpointUrl(this.domain);
1100      response = getSettingsMethod.func(identities, settings,
1101          requestedVersion,
1102          autodiscoverUrl);
1103
1104      // If we got this far, response was successful, set Url.
1105      this.url = autodiscoverUrl;
1106      return response;
1107    }
1108    // No Url or Domain specified, need to figure out which endpoint(s) to
1109    // try.
1110    else {
1111      // Assume caller is not inside the Intranet, regardless of whether
1112      // SCP Urls
1113      // were returned or not. SCP Urls are only relevent if one of them
1114      // returns
1115      // valid Autodiscover settings.
1116      this.isExternal = true;
1117
1118      URI autodiscoverUrl;
1119
1120      String domainName = getDomainMethod.func();
1121      int scpHostCount;
1122      OutParam<Integer> outParam = new OutParam<Integer>();
1123      List<String> hosts = this.getAutodiscoverServiceHosts(domainName,
1124          outParam);
1125      scpHostCount = outParam.getParam();
1126      if (hosts.size() == 0) {
1127        throw new ServiceValidationException(
1128            "This Autodiscover request requires that either the Domain or Url be specified.");
1129      }
1130
1131      for (int currentHostIndex = 0; currentHostIndex < hosts.size(); currentHostIndex++) {
1132        String host = hosts.get(currentHostIndex);
1133        boolean isScpHost = currentHostIndex < scpHostCount;
1134        OutParam<URI> outParams = new OutParam<URI>();
1135        if (this.tryGetAutodiscoverEndpointUrl(host, outParams)) {
1136          autodiscoverUrl = outParams.getParam();
1137          response = getSettingsMethod.func(identities, settings,
1138              requestedVersion,
1139              autodiscoverUrl);
1140
1141          // If we got this far, the response was successful, set Url.
1142          this.url = autodiscoverUrl;
1143
1144          // Not external if Autodiscover endpoint found via SCP
1145          // returned the settings.
1146          if (isScpHost) {
1147            this.isExternal = false;
1148          }
1149
1150          return response;
1151        }
1152      }
1153
1154      // Next-to-last chance: try unauthenticated GET over HTTP to be
1155      // redirected to appropriate service endpoint.
1156      autodiscoverUrl = this.getRedirectUrl(domainName);
1157      OutParam<URI> outParamUrl = new OutParam<URI>();
1158      if ((autodiscoverUrl != null) &&
1159          this
1160              .callRedirectionUrlValidationCallback(
1161                  autodiscoverUrl.toString()) &&
1162          this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1163              .getHost(), outParamUrl)) {
1164        autodiscoverUrl = outParamUrl.getParam();
1165        response = getSettingsMethod.func(identities, settings,
1166            requestedVersion,
1167            autodiscoverUrl);
1168
1169        // If we got this far, the response was successful, set Url.
1170        this.url = autodiscoverUrl;
1171
1172        return response;
1173      }
1174
1175      // Last Chance: try to read autodiscover SRV Record from DNS. If we
1176      // find one, use
1177      // the hostname returned to construct an Autodiscover endpoint URL.
1178      autodiscoverUrl = this
1179          .getRedirectionUrlFromDnsSrvRecord(domainName);
1180      if ((autodiscoverUrl != null) &&
1181          this
1182              .callRedirectionUrlValidationCallback(
1183                  autodiscoverUrl.toString()) &&
1184          this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl
1185              .getHost(), outParamUrl)) {
1186        autodiscoverUrl = outParamUrl.getParam();
1187        response = getSettingsMethod.func(identities, settings,
1188            requestedVersion,
1189            autodiscoverUrl);
1190
1191        // If we got this far, the response was successful, set Url.
1192        this.url = autodiscoverUrl;
1193
1194        return response;
1195      } else {
1196        throw new AutodiscoverLocalException("The Autodiscover service couldn't be located.");
1197      }
1198    }
1199  }
1200
1201  /**
1202   * Gets settings for one or more users.
1203   *
1204   * @param smtpAddresses    The SMTP addresses of the users.
1205   * @param settings         The settings.
1206   * @param requestedVersion Requested version of the Exchange service.
1207   * @param autodiscoverUrl  The autodiscover URL.
1208   * @return GetUserSettingsResponse collection.
1209   * @throws ServiceLocalException the service local exception
1210   * @throws Exception             the exception
1211   */
1212  private GetUserSettingsResponseCollection internalGetUserSettings(
1213      List<String> smtpAddresses, List<UserSettingName> settings,
1214      ExchangeVersion requestedVersion,
1215      URI autodiscoverUrl) throws ServiceLocalException, Exception {
1216    // The response to GetUserSettings can be a redirection. Execute
1217    // GetUserSettings until we get back
1218    // a valid response or we've followed too many redirections.
1219    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1220      GetUserSettingsRequest request = new GetUserSettingsRequest(this,
1221          autodiscoverUrl);
1222      request.setSmtpAddresses(smtpAddresses);
1223      request.setSettings(settings);
1224      GetUserSettingsResponseCollection response = request.execute();
1225
1226      // Did we get redirected?
1227      if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1228          && response.getRedirectionUrl() != null) {
1229        this.traceMessage(
1230            TraceFlags.AutodiscoverConfiguration,
1231            String.format("Request to %s returned redirection to %s",
1232                autodiscoverUrl.toString(), response.getRedirectionUrl()));
1233
1234        autodiscoverUrl = response.getRedirectionUrl();
1235      } else {
1236        return response;
1237      }
1238    }
1239
1240    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1241        "Maximum number of redirection hops %d exceeded",
1242        AutodiscoverMaxRedirections));
1243
1244    throw new MaximumRedirectionHopsExceededException();
1245  }
1246
1247  /**
1248   * Gets the domain settings using Autodiscover SOAP service.
1249   *
1250   * @param domains          The domains.
1251   * @param settings         The settings.
1252   * @param requestedVersion Requested version of the Exchange service.
1253   * @return GetDomainSettingsResponse collection.
1254   * @throws Exception the exception
1255   */
1256  protected GetDomainSettingsResponseCollection getDomainSettings(
1257      final List<String> domains, List<DomainSettingName> settings,
1258      ExchangeVersion requestedVersion)
1259      throws Exception {
1260    EwsUtilities.validateParam(domains, "domains");
1261    EwsUtilities.validateParam(settings, "settings");
1262
1263    return this.getSettings(
1264        GetDomainSettingsResponseCollection.class,
1265        DomainSettingName.class, domains, settings,
1266        requestedVersion, this,
1267        new IFuncDelegate<String>() {
1268          public String func() {
1269            return domains.get(0);
1270          }
1271        });
1272  }
1273
1274  /**
1275   * Gets settings for one or more domains.
1276   *
1277   * @param domains          The domains.
1278   * @param settings         The settings.
1279   * @param requestedVersion Requested version of the Exchange service.
1280   * @param autodiscoverUrl  The autodiscover URL.
1281   * @return GetDomainSettingsResponse Collection.
1282   * @throws ServiceLocalException the service local exception
1283   * @throws Exception             the exception
1284   */
1285  private GetDomainSettingsResponseCollection internalGetDomainSettings(
1286      List<String> domains, List<DomainSettingName> settings,
1287      ExchangeVersion requestedVersion,
1288      URI autodiscoverUrl) throws ServiceLocalException, Exception {
1289    // The response to GetDomainSettings can be a redirection. Execute
1290    // GetDomainSettings until we get back
1291    // a valid response or we've followed too many redirections.
1292    for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) {
1293      GetDomainSettingsRequest request = new GetDomainSettingsRequest(
1294          this, autodiscoverUrl);
1295      request.setDomains(domains);
1296      request.setSettings(settings);
1297      request.setRequestedVersion(requestedVersion);
1298      GetDomainSettingsResponseCollection response = request.execute();
1299
1300      // Did we get redirected?
1301      if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl
1302          && response.getRedirectionUrl() != null) {
1303        autodiscoverUrl = response.getRedirectionUrl();
1304      } else {
1305        return response;
1306      }
1307    }
1308
1309    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1310        "Maximum number of redirection hops %d exceeded",
1311        AutodiscoverMaxRedirections));
1312
1313    throw new MaximumRedirectionHopsExceededException();
1314  }
1315
1316  /**
1317   * Gets the autodiscover endpoint URL.
1318   *
1319   * @param host The host.
1320   * @return URI The URI.
1321   * @throws Exception the exception
1322   */
1323  private URI getAutodiscoverEndpointUrl(String host) throws Exception {
1324    URI autodiscoverUrl = null;
1325    OutParam<URI> outParam = new OutParam<URI>();
1326    if (this.tryGetAutodiscoverEndpointUrl(host, outParam)) {
1327      return autodiscoverUrl;
1328    } else {
1329      throw new AutodiscoverLocalException(
1330          "No appropriate Autodiscover SOAP or WS-Security endpoint is available.");
1331    }
1332  }
1333
1334  /**
1335   * Tries the get Autodiscover Service endpoint URL.
1336   *
1337   * @param host The host.
1338   * @param url  the url
1339   * @return boolean The boolean.
1340   * @throws Exception the exception
1341   */
1342  private boolean tryGetAutodiscoverEndpointUrl(String host,
1343      OutParam<URI> url)
1344      throws Exception {
1345    EnumSet<AutodiscoverEndpoints> endpoints;
1346    OutParam<EnumSet<AutodiscoverEndpoints>> outParam =
1347        new OutParam<EnumSet<AutodiscoverEndpoints>>();
1348    if (this.tryGetEnabledEndpointsForHost(host, outParam)) {
1349      endpoints = outParam.getParam();
1350      url
1351          .setParam(new URI(String.format(AutodiscoverSoapHttpsUrl,
1352              host)));
1353
1354      // Make sure that at least one of the non-legacy endpoints is
1355      // available.
1356      if ((!endpoints.contains(AutodiscoverEndpoints.Soap)) &&
1357          (!endpoints.contains(
1358              AutodiscoverEndpoints.WsSecurity))
1359        // (endpoints .contains( AutodiscoverEndpoints.WSSecuritySymmetricKey) ) &&
1360        //(endpoints .contains( AutodiscoverEndpoints.WSSecurityX509Cert))
1361          ) {
1362        this
1363            .traceMessage(
1364                TraceFlags.AutodiscoverConfiguration,
1365                String
1366                    .format(
1367                        "No Autodiscover endpoints " +
1368                            "are available  for host %s",
1369                        host));
1370
1371        return false;
1372      }
1373
1374      // If we have WLID credential, make sure that we have a WS-Security
1375      // endpoint
1376                        /*
1377                        if (this.getCredentials() instanceof WindowsLiveCredentials) {
1378                                if (endpoints.contains(AutodiscoverEndpoints.WsSecurity)) {
1379                                        this
1380                                                        .traceMessage(
1381                                                                        TraceFlags.AutodiscoverConfiguration,
1382                                                                        String
1383                                                                                        .format(
1384                                                                                                        "No Autodiscover " +
1385                                                                                                        "WS-Security " +
1386                                                                                                        "endpoint is available" +
1387                                                                                                        " for host %s",
1388                                                                                                        host));
1389
1390                                        return false;
1391                                } else {
1392                                        url.setParam(new URI(String.format(
1393                                                        AutodiscoverSoapWsSecurityHttpsUrl, host)));
1394                                }
1395                        }
1396                           else if (this.getCredentials() instanceof PartnerTokenCredentials)
1397                {
1398                    if (endpoints.contains( AutodiscoverEndpoints.WSSecuritySymmetricKey))
1399                    {
1400                        this.traceMessage(
1401                            TraceFlags.AutodiscoverConfiguration,
1402                            String.format("No Autodiscover WS-Security/SymmetricKey endpoint is available for host {0}", host));
1403
1404                        return false;
1405                    }
1406                    else
1407                    {
1408                        url.setParam( new URI(String.format(AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl, host)));
1409                    }
1410                }
1411                else if (this.getCredentials()instanceof X509CertificateCredentials)
1412                {
1413                    if ((endpoints.contains(AutodiscoverEndpoints.WSSecurityX509Cert))
1414                    {
1415                        this.traceMessage(
1416                            TraceFlags.AutodiscoverConfiguration,
1417                            String.format("No Autodiscover WS-Security/X509Cert endpoint is available for host {0}", host));
1418
1419                        return false;
1420                    }
1421                    else
1422                    {
1423                        url.setParam( new URI(String.format(AutodiscoverSoapWsSecurityX509CertHttpsUrl, host)));
1424                    }
1425                }
1426                                  */
1427      return true;
1428
1429
1430    } else {
1431      this
1432          .traceMessage(
1433              TraceFlags.AutodiscoverConfiguration,
1434              String
1435                  .format(
1436                      "No Autodiscover endpoints " +
1437                          "are available for host %s",
1438                      host));
1439
1440      return false;
1441    }
1442  }
1443
1444  /**
1445   * Gets the list of autodiscover service URLs.
1446   *
1447   * @param domainName   Domain name.
1448   * @param scpHostCount Count of hosts found via SCP lookup.
1449   * @return List of Autodiscover URLs.
1450   * @throws java.net.URISyntaxException the URI Syntax exception
1451   */
1452  protected List<URI> getAutodiscoverServiceUrls(String domainName,
1453      OutParam<Integer> scpHostCount) throws URISyntaxException {
1454    List<URI> urls;
1455
1456    urls = new ArrayList<URI>();
1457
1458    scpHostCount.setParam(urls.size());
1459
1460    // As a fallback, add autodiscover URLs base on the domain name.
1461    urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1462        domainName)));
1463    urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl,
1464        "autodiscover." + domainName)));
1465
1466    return urls;
1467  }
1468
1469  /**
1470   * Gets the list of autodiscover service hosts.
1471   *
1472   * @param domainName Domain name.
1473   * @param outParam   the out param
1474   * @return List of hosts.
1475   * @throws java.net.URISyntaxException the uRI syntax exception
1476   * @throws ClassNotFoundException      the class not found exception
1477   */
1478  protected List<String> getAutodiscoverServiceHosts(String domainName,
1479      OutParam<Integer> outParam) throws URISyntaxException,
1480      ClassNotFoundException {
1481
1482    List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParam);
1483    List<String> lst = new ArrayList<String>();
1484    for (URI url : urls) {
1485      lst.add(url.getHost());
1486    }
1487    return lst;
1488  }
1489
1490  /**
1491   * Gets the enabled autodiscover endpoints on a specific host.
1492   *
1493   * @param host      The host.
1494   * @param endpoints Endpoints found for host.
1495   * @return Flags indicating which endpoints are enabled.
1496   * @throws Exception the exception
1497   */
1498  private boolean tryGetEnabledEndpointsForHost(String host,
1499      OutParam<EnumSet<AutodiscoverEndpoints>> endpoints) throws Exception {
1500    this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format(
1501        "Determining which endpoints are enabled for host %s", host));
1502
1503    // We may get redirected to another host. And therefore need to limit the number of redirections we'll
1504    // tolerate.
1505    for (int currentHop = 0; currentHop < AutodiscoverMaxRedirections; currentHop++) {
1506      URI autoDiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, host));
1507
1508      endpoints.setParam(EnumSet.of(AutodiscoverEndpoints.None));
1509
1510      HttpWebRequest request = null;
1511      try {
1512        request = new HttpClientWebRequest(httpClient, httpContext);
1513        request.setProxy(getWebProxy());
1514
1515        try {
1516          request.setUrl(autoDiscoverUrl.toURL());
1517        } catch (MalformedURLException e) {
1518          String strErr = String.format("Incorrect format : %s", url);
1519          throw new ServiceLocalException(strErr);
1520        }
1521
1522        request.setRequestMethod("GET");
1523        request.setAllowAutoRedirect(false);
1524        request.setPreAuthenticate(false);
1525        request.setUseDefaultCredentials(this.getUseDefaultCredentials());
1526
1527        prepareCredentials(request);
1528
1529        request.prepareConnection();
1530        try {
1531          request.executeRequest();
1532        } catch (IOException e) {
1533          return false;
1534        }
1535
1536        OutParam<URI> outParam = new OutParam<URI>();
1537        if (this.tryGetRedirectionResponse(request, outParam)) {
1538          URI redirectUrl = outParam.getParam();
1539          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1540              String.format("Host returned redirection to host '%s'", redirectUrl.getHost()));
1541
1542          host = redirectUrl.getHost();
1543        } else {
1544          endpoints.setParam(this.getEndpointsFromHttpWebResponse(request));
1545
1546          this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1547              String.format("Host returned enabled endpoint flags: %s", endpoints.getParam().toString()));
1548
1549          return true;
1550        }
1551      } finally {
1552        if (request != null) {
1553          try {
1554            request.close();
1555          } catch (Exception e) {
1556            // Connection can't be closed. We'll ignore this...
1557          }
1558        }
1559      }
1560    }
1561
1562    this.traceMessage(TraceFlags.AutodiscoverConfiguration,
1563        String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections));
1564
1565    throw new MaximumRedirectionHopsExceededException();
1566  }
1567
1568  /**
1569   * Gets the endpoints from HTTP web response.
1570   *
1571   * @param request the request
1572   * @return Endpoints enabled.
1573   * @throws EWSHttpException the EWS http exception
1574   */
1575  private EnumSet<AutodiscoverEndpoints> getEndpointsFromHttpWebResponse(
1576      HttpWebRequest request) throws EWSHttpException {
1577    EnumSet<AutodiscoverEndpoints> endpoints = EnumSet
1578        .noneOf(AutodiscoverEndpoints.class);
1579    endpoints.add(AutodiscoverEndpoints.Legacy);
1580
1581    if (!(request.getResponseHeaders().get(
1582        AutodiscoverSoapEnabledHeaderName) == null || request
1583        .getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName)
1584        .isEmpty())) {
1585      endpoints.add(AutodiscoverEndpoints.Soap);
1586    }
1587    if (!(request.getResponseHeaders().get(
1588        AutodiscoverWsSecurityEnabledHeaderName) == null || request
1589        .getResponseHeaders().get(
1590            AutodiscoverWsSecurityEnabledHeaderName).isEmpty())) {
1591      endpoints.add(AutodiscoverEndpoints.WsSecurity);
1592    }
1593                
1594                /* if (! (request.getResponseHeaders().get(
1595                                 AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName) !=null || request
1596                                 .getResponseHeaders().get(
1597                                 AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName).isEmpty()))
1598         {
1599             endpoints .add( AutodiscoverEndpoints.WSSecuritySymmetricKey);
1600         }
1601         if (!(request.getResponseHeaders().get(
1602                         AutodiscoverWsSecurityX509CertEnabledHeaderName)!=null ||
1603                         request.getResponseHeaders().get(
1604                                 AutodiscoverWsSecurityX509CertEnabledHeaderName).isEmpty()))
1605                         
1606         {
1607             endpoints .add(AutodiscoverEndpoints.WSSecurityX509Cert);
1608         }*/
1609
1610    return endpoints;
1611  }
1612
1613  /**
1614   * Traces the response.
1615   *
1616   * @param request      the request
1617   * @param memoryStream the memory stream
1618   * @throws XMLStreamException the XML stream exception
1619   * @throws IOException signals that an I/O exception has occurred.
1620   * @throws EWSHttpException the EWS http exception
1621   */
1622  public void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream) throws XMLStreamException,
1623      IOException, EWSHttpException {
1624    this.processHttpResponseHeaders(
1625        TraceFlags.AutodiscoverResponseHttpHeaders, request);
1626    String contentType = request.getResponseContentType();
1627    if (!(contentType == null || contentType.isEmpty())) {
1628      contentType = contentType.toLowerCase();
1629      if (contentType.toLowerCase().startsWith("text/") ||
1630          contentType.toLowerCase().
1631              startsWith("application/soap")) {
1632        this.traceXml(TraceFlags.AutodiscoverResponse, memoryStream);
1633      } else {
1634        this.traceMessage(TraceFlags.AutodiscoverResponse,
1635            "Non-textual response");
1636      }
1637    }
1638  }
1639
1640  /**
1641   * Creates an HttpWebRequest instance and initializes it with the
1642   * appropriate parameters, based on the configuration of this service
1643   * object.
1644   *
1645   * @param url The URL that the HttpWebRequest should target
1646   * @return HttpWebRequest The HttpWebRequest
1647   * @throws ServiceLocalException       the service local exception
1648   * @throws java.net.URISyntaxException the uRI syntax exception
1649   */
1650  public HttpWebRequest prepareHttpWebRequestForUrl(URI url)
1651      throws ServiceLocalException, URISyntaxException {
1652    return this.prepareHttpWebRequestForUrl(url, false,
1653        // acceptGzipEncoding
1654        false); // allowAutoRedirect
1655  }
1656
1657  /**
1658   * Calls the redirection URL validation callback. If the redirection URL
1659   * validation callback is null, use the default callback which does not
1660   * allow following any redirections.
1661   *
1662   * @param redirectionUrl The redirection URL.
1663   * @return True if redirection should be followed.
1664   * @throws AutodiscoverLocalException the autodiscover local exception
1665   */
1666  private boolean callRedirectionUrlValidationCallback(String redirectionUrl)
1667      throws AutodiscoverLocalException {
1668    IAutodiscoverRedirectionUrl callback =
1669        (this.redirectionUrlValidationCallback == null) ? this
1670            : this.redirectionUrlValidationCallback;
1671    return callback
1672        .autodiscoverRedirectionUrlValidationCallback(redirectionUrl);
1673  }
1674
1675  /**
1676   * Processes an HTTP error response.
1677   *
1678   * @param httpWebResponse The HTTP web response.
1679   * @throws Exception the exception
1680   */
1681  @Override public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception {
1682    this.internalProcessHttpErrorResponse(
1683        httpWebResponse,
1684        webException,
1685        TraceFlags.AutodiscoverResponseHttpHeaders,
1686        TraceFlags.AutodiscoverResponse);
1687  }
1688
1689  /*
1690   * (non-Javadoc)
1691   *
1692   * @see microsoft.exchange.webservices.AutodiscoverRedirectionUrlInterface#
1693   * autodiscoverRedirectionUrlValidationCallback(java.lang.String)
1694   */
1695  public boolean autodiscoverRedirectionUrlValidationCallback(
1696      String redirectionUrl) throws AutodiscoverLocalException {
1697    return defaultAutodiscoverRedirectionUrlValidationCallback(
1698        redirectionUrl);
1699  }
1700
1701  /**
1702   * Initializes a new instance of the "AutodiscoverService" class.
1703   *
1704   * @throws ArgumentException on validation error
1705   */
1706  public AutodiscoverService() throws ArgumentException {
1707    this(ExchangeVersion.Exchange2010);
1708  }
1709
1710  /**
1711   * Initializes a new instance of the "AutodiscoverService" class.
1712   *
1713   * @param requestedServerVersion The requested server version
1714   * @throws ArgumentException on validation error
1715   */
1716  public AutodiscoverService(ExchangeVersion requestedServerVersion)
1717      throws ArgumentException {
1718    this(null, null, requestedServerVersion);
1719  }
1720
1721  /**
1722   * Initializes a new instance of the "AutodiscoverService" class.
1723   *
1724   * @param domain The domain that will be used to determine the URL of the service
1725   * @throws ArgumentException on validation error
1726   */
1727  public AutodiscoverService(String domain) throws ArgumentException {
1728    this(null, domain);
1729  }
1730
1731  /**
1732   * Initializes a new instance of the "AutodiscoverService" class.
1733   *
1734   * @param domain                 The domain that will be used to determine the URL of the service
1735   * @param requestedServerVersion The requested server version
1736   * @throws ArgumentException on validation error
1737   */
1738  public AutodiscoverService(String domain,
1739      ExchangeVersion requestedServerVersion) throws ArgumentException {
1740    this(null, domain, requestedServerVersion);
1741  }
1742
1743  /**
1744   * Initializes a new instance of the "AutodiscoverService" class.
1745   *
1746   * @param url The URL of the service
1747   * @throws ArgumentException on validation error
1748   */
1749  public AutodiscoverService(URI url) throws ArgumentException {
1750    this(url, url.getHost());
1751  }
1752
1753  /**
1754   * Initializes a new instance of the "AutodiscoverService" class.
1755   *
1756   * @param url                    The URL of the service
1757   * @param requestedServerVersion The requested server version
1758   * @throws ArgumentException on validation error
1759   */
1760  public AutodiscoverService(URI url,
1761      ExchangeVersion requestedServerVersion) throws ArgumentException {
1762    this(url, url.getHost(), requestedServerVersion);
1763  }
1764
1765  /**
1766   * Initializes a new instance of the "AutodiscoverService" class.
1767   *
1768   * @param url    The URL of the service
1769   * @param domain The domain that will be used to determine the URL of the service
1770   * @throws ArgumentException on validation error
1771   */
1772  public AutodiscoverService(URI url, String domain)
1773      throws ArgumentException {
1774    super();
1775    EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1776    this.url = url;
1777    this.domain = domain;
1778    this.dnsClient = new AutodiscoverDnsClient(this);
1779  }
1780
1781  /**
1782   * Initializes a new instance of the "AutodiscoverService" class.
1783   *
1784   * @param url                    The URL of the service.
1785   * @param domain                 The domain that will be used to determine the URL of the
1786   *                               service.
1787   * @param requestedServerVersion The requested server version.
1788   * @throws ArgumentException on validation error
1789   */
1790  public AutodiscoverService(URI url, String domain,
1791      ExchangeVersion requestedServerVersion) throws ArgumentException {
1792    super(requestedServerVersion);
1793    EwsUtilities.validateDomainNameAllowNull(domain, "domain");
1794
1795    this.url = url;
1796    this.domain = domain;
1797    this.dnsClient = new AutodiscoverDnsClient(this);
1798  }
1799
1800  /**
1801   * Initializes a new instance of the AutodiscoverService class.
1802   *
1803   * @param service                The other service.
1804   * @param requestedServerVersion The requested server version.
1805   */
1806  public AutodiscoverService(ExchangeServiceBase service,
1807      ExchangeVersion requestedServerVersion) {
1808    super(service, requestedServerVersion);
1809    this.dnsClient = new AutodiscoverDnsClient(this);
1810  }
1811
1812  /**
1813   * Initializes a new instance of the "AutodiscoverService" class.
1814   *
1815   * @param service The service.
1816   */
1817  public AutodiscoverService(ExchangeServiceBase service) {
1818    super(service, service.getRequestedServerVersion());
1819  }
1820
1821  /**
1822   * Retrieves the specified settings for single SMTP address.
1823   * <p>This method will run the entire Autodiscover "discovery"
1824   * algorithm and will follow address and URL redirections.</p>
1825
1826   * @param userSmtpAddress  The SMTP addresses of the user.
1827   * @param userSettingNames The user setting names.
1828   * @return A UserResponse object containing the requested settings for the
1829   * specified user.
1830   * @throws Exception on error
1831   */
1832  public GetUserSettingsResponse getUserSettings(String userSmtpAddress,
1833      UserSettingName... userSettingNames) throws Exception {
1834    List<UserSettingName> requestedSettings = new ArrayList<UserSettingName>();
1835    requestedSettings.addAll(Arrays.asList(userSettingNames));
1836
1837    if (userSmtpAddress == null || userSmtpAddress.isEmpty()) {
1838      throw new ServiceValidationException("A valid SMTP address must be specified.");
1839    }
1840
1841    if (requestedSettings.size() == 0) {
1842      throw new ServiceValidationException("At least one setting must be requested.");
1843    }
1844
1845    if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1846      return this.internalGetLegacyUserSettings(userSmtpAddress,
1847          requestedSettings);
1848    } else {
1849      return this.internalGetSoapUserSettings(userSmtpAddress,
1850          requestedSettings);
1851    }
1852
1853  }
1854
1855  /**
1856   * Retrieves the specified settings for a set of users.
1857   *
1858   * @param userSmtpAddresses the user smtp addresses
1859   * @param userSettingNames  The user setting names.
1860   * @return A GetUserSettingsResponseCollection object containing the
1861   * response for each individual user.
1862   * @throws Exception the exception
1863   */
1864  public GetUserSettingsResponseCollection getUsersSettings(
1865      Iterable<String> userSmtpAddresses,
1866      UserSettingName... userSettingNames) throws Exception {
1867    if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) {
1868      throw new ServiceVersionException(
1869          String.format("The Autodiscover service only supports %s or a later version.",
1870              MinimumRequestVersionForAutoDiscoverSoapService));
1871    }
1872    List<String> smtpAddresses = new ArrayList<String>();
1873    smtpAddresses.addAll((Collection<? extends String>) userSmtpAddresses);
1874    List<UserSettingName> settings = new ArrayList<UserSettingName>();
1875    settings.addAll(Arrays.asList(userSettingNames));
1876    return this.getUserSettings(smtpAddresses, settings);
1877  }
1878
1879  /**
1880   * Retrieves the specified settings for a domain.
1881   *
1882   * @param domain             The domain.
1883   * @param requestedVersion   Requested version of the Exchange service.
1884   * @param domainSettingNames The domain setting names.
1885   * @return A DomainResponse object containing the requested settings for the
1886   * specified domain.
1887   * @throws Exception the exception
1888   */
1889  public GetDomainSettingsResponse getDomainSettings(String domain,
1890      ExchangeVersion requestedVersion,
1891      DomainSettingName... domainSettingNames) throws Exception {
1892    List<String> domains = new ArrayList<String>(1);
1893    domains.add(domain);
1894
1895    List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1896    settings.addAll(Arrays.asList(domainSettingNames));
1897
1898    return this.getDomainSettings(domains, settings, requestedVersion).
1899        getTResponseAtIndex(0);
1900  }
1901
1902  /**
1903   * Retrieves the specified settings for a set of domains.
1904   *
1905   * @param domains            the domains
1906   * @param requestedVersion   Requested version of the Exchange service.
1907   * @param domainSettingNames The domain setting names.
1908   * @return A GetDomainSettingsResponseCollection object containing the
1909   * response for each individual domain.
1910   * @throws Exception the exception
1911   */
1912  public GetDomainSettingsResponseCollection getDomainSettings(
1913      Iterable<String> domains, ExchangeVersion requestedVersion,
1914      DomainSettingName... domainSettingNames)
1915      throws Exception {
1916    List<DomainSettingName> settings = new ArrayList<DomainSettingName>();
1917    settings.addAll(Arrays.asList(domainSettingNames));
1918
1919    List<String> domainslst = new ArrayList<String>();
1920    domainslst.addAll((Collection<? extends String>) domains);
1921
1922    return this.getDomainSettings(domainslst, settings, requestedVersion);
1923  }
1924
1925  /**
1926   * Gets the domain this service is bound to. When this property is
1927   * set, the domain name is used to automatically determine the Autodiscover service URL.
1928   *
1929   * @return the domain
1930   */
1931  public String getDomain() {
1932    return this.domain;
1933  }
1934
1935  /**
1936   * Sets the domain this service is bound to. When this property is
1937   * set, the domain
1938   * name is used to automatically determine the Autodiscover service URL.
1939   *
1940   * @param value the new domain
1941   * @throws ArgumentException on validation error
1942   */
1943  public void setDomain(String value) throws ArgumentException {
1944    EwsUtilities.validateDomainNameAllowNull(value, "Domain");
1945
1946    // If Domain property is set to non-null value, Url property is nulled.
1947    if (value != null) {
1948      this.url = null;
1949    }
1950    this.domain = value;
1951  }
1952
1953  /**
1954   * Gets the url this service is bound to.
1955   *
1956   * @return the url
1957   */
1958  public URI getUrl() {
1959    return this.url;
1960  }
1961
1962  /**
1963   * Sets the url this service is bound to.
1964   *
1965   * @param value the new url
1966   */
1967  public void setUrl(URI value) {
1968    // If Url property is set to non-null value, Domain property is set to
1969    // host portion of Url.
1970    if (value != null) {
1971      this.domain = value.getHost();
1972    }
1973    this.url = value;
1974  }
1975
1976  public Boolean isExternal() {
1977    return this.isExternal;
1978  }
1979
1980  protected void setIsExternal(Boolean value) {
1981    this.isExternal = value;
1982  }
1983
1984
1985  /**
1986   * Gets the redirection url validation callback.
1987   *
1988   * @return the redirection url validation callback
1989   */
1990  public IAutodiscoverRedirectionUrl
1991  getRedirectionUrlValidationCallback() {
1992    return this.redirectionUrlValidationCallback;
1993  }
1994
1995  /**
1996   * Sets the redirection url validation callback.
1997   *
1998   * @param value the new redirection url validation callback
1999   */
2000  public void setRedirectionUrlValidationCallback(
2001      IAutodiscoverRedirectionUrl value) {
2002    this.redirectionUrlValidationCallback = value;
2003  }
2004
2005  /**
2006   * Gets the dns server address.
2007   *
2008   * @return the dns server address
2009   */
2010  protected String getDnsServerAddress() {
2011    return this.dnsServerAddress;
2012  }
2013
2014  /**
2015   * Sets the dns server address.
2016   *
2017   * @param value the new dns server address
2018   */
2019  protected void setDnsServerAddress(String value) {
2020    this.dnsServerAddress = value;
2021  }
2022
2023  /**
2024   * Gets a value indicating whether the AutodiscoverService should
2025   * perform SCP (ServiceConnectionPoint) record lookup when determining
2026   * the Autodiscover service URL.
2027   *
2028   * @return the enable scp lookup
2029   */
2030  public boolean getEnableScpLookup() {
2031    return this.enableScpLookup;
2032  }
2033
2034  /**
2035   * Sets the enable scp lookup.
2036   *
2037   * @param value the new enable scp lookup
2038   */
2039  public void setEnableScpLookup(boolean value) {
2040    this.enableScpLookup = value;
2041  }
2042
2043  /*
2044   * (non-Javadoc)
2045   *
2046   * @see
2047   * microsoft.exchange.webservices.FuncDelegateInterface#func(java.util.List,
2048   * java.util.List, java.net.URI)
2049   */
2050  @Override
2051  public Object func(List arg1, List arg2, ExchangeVersion arg3, URI arg4)
2052      throws ServiceLocalException, Exception {
2053    if (arg2.get(0).getClass().equals(DomainSettingName.class)) {
2054      return internalGetDomainSettings(arg1, arg2, arg3, arg4);
2055    } else if (arg2.get(0).getClass().equals(UserSettingName.class)) {
2056      return internalGetUserSettings(arg1, arg2, arg3, arg4);
2057    } else {
2058      return null;
2059    }
2060  }
2061
2062}