/* * The MIT License * Copyright (c) 2012 Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package microsoft.exchange.webservices.data.autodiscover; import microsoft.exchange.webservices.data.autodiscover.configuration.ConfigurationSettingsBase; import microsoft.exchange.webservices.data.autodiscover.configuration.outlook.OutlookConfigurationSettings; import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverEndpoints; import microsoft.exchange.webservices.data.autodiscover.enumeration.AutodiscoverErrorCode; import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverLocalException; import microsoft.exchange.webservices.data.autodiscover.exception.AutodiscoverRemoteException; import microsoft.exchange.webservices.data.autodiscover.request.AutodiscoverRequest; import microsoft.exchange.webservices.data.autodiscover.request.GetDomainSettingsRequest; import microsoft.exchange.webservices.data.autodiscover.request.GetUserSettingsRequest; import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponse; import microsoft.exchange.webservices.data.autodiscover.response.GetDomainSettingsResponseCollection; import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponse; import microsoft.exchange.webservices.data.autodiscover.response.GetUserSettingsResponseCollection; import microsoft.exchange.webservices.data.core.EwsUtilities; import microsoft.exchange.webservices.data.core.EwsXmlReader; import microsoft.exchange.webservices.data.core.ExchangeServiceBase; import microsoft.exchange.webservices.data.core.request.HttpClientWebRequest; import microsoft.exchange.webservices.data.core.request.HttpWebRequest; import microsoft.exchange.webservices.data.credential.WSSecurityBasedCredentials; import microsoft.exchange.webservices.data.autodiscover.enumeration.DomainSettingName; import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags; import microsoft.exchange.webservices.data.autodiscover.enumeration.UserSettingName; import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException; import microsoft.exchange.webservices.data.core.exception.misc.FormatException; import microsoft.exchange.webservices.data.autodiscover.exception.MaximumRedirectionHopsExceededException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; import microsoft.exchange.webservices.data.misc.OutParam; import microsoft.exchange.webservices.data.security.XmlNodeType; import javax.xml.stream.XMLStreamException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; /** * Represents a binding to the Exchange Autodiscover Service. */ public class AutodiscoverService extends ExchangeServiceBase implements IAutodiscoverRedirectionUrl, IFunctionDelegate { // region Private members /** * The domain. */ private String domain; /** * The is external. */ private Boolean isExternal = true; /** * The url. */ private URI url; /** * The redirection url validation callback. */ private IAutodiscoverRedirectionUrl redirectionUrlValidationCallback; /** * The dns client. */ private AutodiscoverDnsClient dnsClient; /** * The dns server address. */ private String dnsServerAddress; /** * The enable scp lookup. */ private boolean enableScpLookup = true; // Autodiscover legacy path /** * The Constant AutodiscoverLegacyPath. */ private static final String AutodiscoverLegacyPath = "/autodiscover/autodiscover.xml"; // Autodiscover legacy HTTPS Url /** * The Constant AutodiscoverLegacyHttpsUrl. */ private static final String AutodiscoverLegacyHttpsUrl = "https://%s" + AutodiscoverLegacyPath; // Autodiscover legacy HTTP Url /** * The Constant AutodiscoverLegacyHttpUrl. */ private static final String AutodiscoverLegacyHttpUrl = "http://%s" + AutodiscoverLegacyPath; // Autodiscover SOAP HTTPS Url /** * The Constant AutodiscoverSoapHttpsUrl. */ private static final String AutodiscoverSoapHttpsUrl = "https://%s/autodiscover/autodiscover.svc"; // Autodiscover SOAP WS-Security HTTPS Url /** * The Constant AutodiscoverSoapWsSecurityHttpsUrl. */ private static final String AutodiscoverSoapWsSecurityHttpsUrl = AutodiscoverSoapHttpsUrl + "/wssecurity"; /** * Autodiscover SOAP WS-Security symmetrickey HTTPS Url */ private static final String AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl = AutodiscoverSoapHttpsUrl + "/wssecurity/symmetrickey"; /** * Autodiscover SOAP WS-Security x509cert HTTPS Url */ private static final String AutodiscoverSoapWsSecurityX509CertHttpsUrl = AutodiscoverSoapHttpsUrl + "/wssecurity/x509cert"; // Autodiscover request namespace /** * The Constant AutodiscoverRequestNamespace. */ private static final String AutodiscoverRequestNamespace = "http://schemas.microsoft.com/exchange/autodiscover/" + "outlook/requestschema/2006"; // Maximum number of Url (or address) redirections that will be followed by // an Autodiscover call /** * The Constant AutodiscoverMaxRedirections. */ protected static final int AutodiscoverMaxRedirections = 10; // HTTP header indicating that SOAP Autodiscover service is enabled. /** * The Constant AutodiscoverSoapEnabledHeaderName. */ private static final String AutodiscoverSoapEnabledHeaderName = "X-SOAP-Enabled"; // HTTP header indicating that WS-Security Autodiscover service is enabled. /** * The Constant AutodiscoverWsSecurityEnabledHeaderName. */ private static final String AutodiscoverWsSecurityEnabledHeaderName = "X-WSSecurity-Enabled"; /** * HTTP header indicating that WS-Security/SymmetricKey Autodiscover service is enabled. */ private static final String AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName = "X-WSSecurity-SymmetricKey-Enabled"; /** * HTTP header indicating that WS-Security/X509Cert Autodiscover service is enabled. */ private static final String AutodiscoverWsSecurityX509CertEnabledHeaderName = "X-WSSecurity-X509Cert-Enabled"; // Minimum request version for Autodiscover SOAP service. /** * The Constant MinimumRequestVersionForAutoDiscoverSoapService. */ private static final ExchangeVersion MinimumRequestVersionForAutoDiscoverSoapService = ExchangeVersion.Exchange2010; /** * Default implementation of AutodiscoverRedirectionUrlValidationCallback. * Always returns true indicating that the URL can be used. * * @param redirectionUrl the redirection url * @return Returns true. * @throws AutodiscoverLocalException the autodiscover local exception */ private boolean defaultAutodiscoverRedirectionUrlValidationCallback( String redirectionUrl) throws AutodiscoverLocalException { throw new AutodiscoverLocalException(String.format( "Autodiscover blocked a potentially insecure redirection to %s. To allow Autodiscover to follow the " + "redirection, use the AutodiscoverUrl(string, AutodiscoverRedirectionUrlValidationCallback) " + "overload.", redirectionUrl)); } // Legacy Autodiscover /** * Calls the Autodiscover service to get configuration settings at the * specified URL. * * @param <TSettings> the generic type * @param cls the cls * @param emailAddress the email address * @param url the url * @return The requested configuration settings. (TSettings The type of the * settings to retrieve) * @throws Exception the exception */ private <TSettings extends ConfigurationSettingsBase> TSettings getLegacyUserSettingsAtUrl( Class<TSettings> cls, String emailAddress, URI url) throws Exception { this .traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Trying to call Autodiscover for %s on %s.", emailAddress, url)); TSettings settings = cls.newInstance(); HttpWebRequest request = null; try { request = this.prepareHttpWebRequestForUrl(url); this.traceHttpRequestHeaders( TraceFlags.AutodiscoverRequestHttpHeaders, request); // OutputStreamWriter out = new // OutputStreamWriter(request.getOutputStream()); OutputStream urlOutStream = request.getOutputStream(); // If tracing is enabled, we generate the request in-memory so that we // can pass it along to the ITraceListener. Then we copy the stream to // the request stream. if (this.isTraceEnabledFor(TraceFlags.AutodiscoverRequest)) { ByteArrayOutputStream memoryStream = new ByteArrayOutputStream(); PrintWriter writer = new PrintWriter(memoryStream); this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer); writer.flush(); this.traceXml(TraceFlags.AutodiscoverRequest, memoryStream); // out.write(memoryStream.toString()); // out.close(); memoryStream.writeTo(urlOutStream); urlOutStream.flush(); urlOutStream.close(); memoryStream.close(); } else { PrintWriter writer = new PrintWriter(urlOutStream); this.writeLegacyAutodiscoverRequest(emailAddress, settings, writer); /* Flush Start */ writer.flush(); urlOutStream.flush(); urlOutStream.close(); /* Flush End */ } request.executeRequest(); request.getResponseCode(); URI redirectUrl; OutParam<URI> outParam = new OutParam<URI>(); if (this.tryGetRedirectionResponse(request, outParam)) { redirectUrl = outParam.getParam(); settings.makeRedirectionResponse(redirectUrl); return settings; } InputStream serviceResponseStream = request.getInputStream(); // If tracing is enabled, we read the entire response into a // MemoryStream so that we // can pass it along to the ITraceListener. Then we parse the response // from the // MemoryStream. if (this.isTraceEnabledFor(TraceFlags.AutodiscoverResponse)) { ByteArrayOutputStream memoryStream = new ByteArrayOutputStream(); while (true) { int data = serviceResponseStream.read(); if (-1 == data) { break; } else { memoryStream.write(data); } } memoryStream.flush(); this.traceResponse(request, memoryStream); ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream( memoryStream.toByteArray()); EwsXmlReader reader = new EwsXmlReader(memoryStreamIn); reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT)); settings.loadFromXml(reader); } else { EwsXmlReader reader = new EwsXmlReader(serviceResponseStream); reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT)); settings.loadFromXml(reader); } serviceResponseStream.close(); } finally { if (request != null) { try { request.close(); } catch (Exception e2) { // Ignore exception while closing the request. } } } return settings; } /** * Writes the autodiscover request. * * @param emailAddress the email address * @param settings the settings * @param writer the writer * @throws java.io.IOException Signals that an I/O exception has occurred. */ private void writeLegacyAutodiscoverRequest(String emailAddress, ConfigurationSettingsBase settings, PrintWriter writer) throws IOException { writer.write(String.format("<Autodiscover xmlns=\"%s\">", AutodiscoverRequestNamespace)); writer.write("<Request>"); writer.write(String.format("<EMailAddress>%s</EMailAddress>", emailAddress)); writer.write( String.format("<AcceptableResponseSchema>%s</AcceptableResponseSchema>", settings.getNamespace())); writer.write("</Request>"); writer.write("</Autodiscover>"); } /** * Gets a redirection URL to an SSL-enabled Autodiscover service from the * standard non-SSL Autodiscover URL. * * @param domainName the domain name * @return A valid SSL-enabled redirection URL. (May be null) * @throws EWSHttpException the EWS http exception * @throws XMLStreamException the XML stream exception * @throws IOException Signals that an I/O exception has occurred. * @throws ServiceLocalException the service local exception * @throws URISyntaxException the uRI syntax exception */ private URI getRedirectUrl(String domainName) throws EWSHttpException, XMLStreamException, IOException, ServiceLocalException, URISyntaxException { String url = String.format(AutodiscoverLegacyHttpUrl, "autodiscover." + domainName); traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Trying to get Autodiscover redirection URL from %s.", url)); HttpWebRequest request = null; try { request = new HttpClientWebRequest(httpClient, httpContext); request.setProxy(getWebProxy()); try { request.setUrl(URI.create(url).toURL()); } catch (MalformedURLException e) { String strErr = String.format("Incorrect format : %s", url); throw new ServiceLocalException(strErr); } request.setRequestMethod("GET"); request.setAllowAutoRedirect(false); request.setTimeout(getTimeout()); // Do NOT allow authentication as this single request will be made over plain HTTP. request.setAllowAuthentication(false); prepareCredentials(request); request.prepareConnection(); try { request.executeRequest(); } catch (IOException e) { traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned."); return null; } OutParam<URI> outParam = new OutParam<URI>(); if (tryGetRedirectionResponse(request, outParam)) { return outParam.getParam(); } } finally { if (request != null) { try { request.close(); } catch (Exception e) { // Ignore exception when closing the request } } } traceMessage(TraceFlags.AutodiscoverConfiguration, "No Autodiscover redirection URL was returned."); return null; } /** * Tries the get redirection response. * * @param request the request * @param redirectUrl the redirect URL * @return true if a valid redirection URL was found * @throws XMLStreamException the XML stream exception * @throws IOException signals that an I/O exception has occurred. * @throws EWSHttpException the EWS http exception */ private boolean tryGetRedirectionResponse(HttpWebRequest request, OutParam<URI> redirectUrl) throws XMLStreamException, IOException, EWSHttpException { // redirectUrl = null; if (AutodiscoverRequest.isRedirectionResponse(request)) { // Get the redirect location and verify that it's valid. String location = request.getResponseHeaderField("Location"); if (!(location == null || location.isEmpty())) { try { redirectUrl.setParam(new URI(location)); // Check if URL is SSL and that the path matches. if ((redirectUrl.getParam().getScheme().toLowerCase() .equals("https")) && (redirectUrl.getParam().getPath() .equalsIgnoreCase( AutodiscoverLegacyPath))) { this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Redirection URL found: '%s'", redirectUrl.getParam().toString())); return true; } } catch (URISyntaxException ex) { this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "Invalid redirection URL " + "was returned: '%s'", location)); return false; } } } return false; } /** * Calls the legacy Autodiscover service to retrieve configuration settings. * * @param <TSettings> the generic type * @param cls the cls * @param emailAddress The email address to retrieve configuration settings for. * @return The requested configuration settings. * @throws Exception the exception */ protected <TSettings extends ConfigurationSettingsBase> TSettings getLegacyUserSettings( Class<TSettings> cls, String emailAddress) throws Exception { /*int currentHop = 1; return this.internalGetConfigurationSettings(cls, emailAddress, currentHop);*/ // If Url is specified, call service directly. if (this.url != null) { // this.Uri is intended for Autodiscover SOAP service, convert to Legacy endpoint URL. URI autodiscoverUrl = new URI(this.url.toString() + AutodiscoverLegacyPath); return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl); } // If Domain is specified, figure out the endpoint Url and call service. else if (!(this.domain == null || this.domain.isEmpty())) { URI autodiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, this.domain)); return this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl); } else { // No Url or Domain specified, need to //figure out which endpoint to use. int currentHop = 1; OutParam<Integer> outParam = new OutParam<Integer>(); outParam.setParam(currentHop); List<String> redirectionEmailAddresses = new ArrayList<String>(); return this.internalGetLegacyUserSettings( cls, emailAddress, redirectionEmailAddresses, outParam); } } /** * Calls the Autodiscover service to retrieve configuration settings. * * @param <TSettings> the generic type * @param cls the cls * @param emailAddress The email address to retrieve configuration settings for. * @param currentHop Current number of redirection urls/addresses attempted so far. * @return The requested configuration settings. * @throws Exception the exception */ private <TSettings extends ConfigurationSettingsBase> TSettings internalGetLegacyUserSettings( Class<TSettings> cls, String emailAddress, List<String> redirectionEmailAddresses, OutParam<Integer> currentHop) throws Exception { String domainName = EwsUtilities.domainFromEmailAddress(emailAddress); int scpUrlCount; OutParam<Integer> outParamInt = new OutParam<Integer>(); List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParamInt); scpUrlCount = outParamInt.getParam(); if (urls.size() == 0) { throw new ServiceValidationException( "This Autodiscover request requires that either the Domain or Url be specified."); } // Assume caller is not inside the Intranet, regardless of whether SCP // Urls // were returned or not. SCP Urls are only relevent if one of them // returns // valid Autodiscover settings. this.isExternal = true; int currentUrlIndex = 0; // Used to save exception for later reporting. Exception delayedException = null; TSettings settings; do { URI autodiscoverUrl = urls.get(currentUrlIndex); boolean isScpUrl = currentUrlIndex < scpUrlCount; try { settings = this.getLegacyUserSettingsAtUrl(cls, emailAddress, autodiscoverUrl); switch (settings.getResponseType()) { case Success: // Not external if Autodiscover endpoint found via SCP // returned the settings. if (isScpUrl) { this.isExternal = false; } this.url = autodiscoverUrl; return settings; case RedirectUrl: if (currentHop.getParam() < AutodiscoverMaxRedirections) { currentHop.setParam(currentHop.getParam() + 1); this .traceMessage( TraceFlags.AutodiscoverResponse, String .format( "Autodiscover " + "service " + "returned " + "redirection URL '%s'.", settings .getRedirectTarget())); urls.add(currentUrlIndex, new URI( settings.getRedirectTarget())); break; } else { throw new MaximumRedirectionHopsExceededException(); } case RedirectAddress: if (currentHop.getParam() < AutodiscoverMaxRedirections) { currentHop.setParam(currentHop.getParam() + 1); this .traceMessage( TraceFlags.AutodiscoverResponse, String .format( "Autodiscover " + "service " + "returned " + "redirection email " + "address '%s'.", settings .getRedirectTarget())); // Bug E14:255576 If this email address was already tried, we may have a loop // in SCP lookups. Disable consideration of SCP records. this.disableScpLookupIfDuplicateRedirection( settings.getRedirectTarget(), redirectionEmailAddresses); return this.internalGetLegacyUserSettings(cls, settings.getRedirectTarget(), redirectionEmailAddresses, currentHop); } else { throw new MaximumRedirectionHopsExceededException(); } case Error: // Don't treat errors from an SCP-based Autodiscover service // to be conclusive. // We'll try the next one and record the error for later. if (isScpUrl) { this .traceMessage( TraceFlags.AutodiscoverConfiguration, "Error returned by " + "Autodiscover service " + "found via SCP, treating " + "as inconclusive."); delayedException = new AutodiscoverRemoteException( "The Autodiscover service returned an error.", settings.getError()); currentUrlIndex++; } else { throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getError()); } break; default: EwsUtilities .ewsAssert(false, "Autodiscover.GetConfigurationSettings", "An unexpected error has occured. This code path should never be reached."); break; } } catch (XMLStreamException ex) { this.traceMessage(TraceFlags.AutodiscoverConfiguration, String .format("%s failed: XML parsing error: %s", url, ex .getMessage())); // The content at the URL wasn't a valid response, let's try the // next. currentUrlIndex++; } catch (IOException ex) { this.traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("%s failed: I/O error: %s", url, ex.getMessage())); // The content at the URL wasn't a valid response, let's try the next. currentUrlIndex++; } catch (Exception ex) { HttpWebRequest response = null; URI redirectUrl; OutParam<URI> outParam1 = new OutParam<URI>(); if ((response != null) && this.tryGetRedirectionResponse(response, outParam1)) { redirectUrl = outParam1.getParam(); this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "Host returned a redirection to url %s", redirectUrl.toString())); currentHop.setParam(currentHop.getParam() + 1); urls.add(currentUrlIndex, redirectUrl); } else { if (response != null) { this.processHttpErrorResponse(response, ex); } this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("%s failed: %s (%s)", url, ex .getClass().getName(), ex.getMessage())); // The url did not work, let's try the next. currentUrlIndex++; } } } while (currentUrlIndex < urls.size()); // If we got this far it's because none of the URLs we tried have // worked. As a next-to-last chance, use GetRedirectUrl to // try to get a redirection URL using an HTTP GET on a non-SSL // Autodiscover endpoint. If successful, use this // redirection URL to get the configuration settings for this email // address. (This will be a common scenario for // DataCenter deployments). URI redirectionUrl = this.getRedirectUrl(domainName); OutParam<TSettings> outParam = new OutParam<TSettings>(); if ((redirectionUrl != null) && this.tryLastChanceHostRedirection(cls, emailAddress, redirectionUrl, outParam)) { settings = outParam.getParam(); return settings; } else { // Getting a redirection URL from an HTTP GET failed too. As a last // chance, try to get an appropriate SRV Record // using DnsQuery. If successful, use this redirection URL to get // the configuration settings for this email address. redirectionUrl = this.getRedirectionUrlFromDnsSrvRecord(domainName); if ((redirectionUrl != null) && this.tryLastChanceHostRedirection(cls, emailAddress, redirectionUrl, outParam)) { return outParam.getParam(); } // If there was an earlier exception, throw it. if (delayedException != null) { throw delayedException; } throw new AutodiscoverLocalException("The Autodiscover service couldn't be located."); } } /** * Get an autodiscover SRV record in DNS and construct autodiscover URL. * * @param domainName Name of the domain. * @return Autodiscover URL (may be null if lookup failed) * @throws Exception the exception */ protected URI getRedirectionUrlFromDnsSrvRecord(String domainName) throws Exception { this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "Trying to get Autodiscover host " + "from DNS SRV record for %s.", domainName)); String hostname = this.dnsClient .findAutodiscoverHostFromSrv(domainName); if (!(hostname == null || hostname.isEmpty())) { this .traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "Autodiscover host %s was returned.", hostname)); return new URI(String.format(AutodiscoverLegacyHttpsUrl, hostname)); } else { this.traceMessage(TraceFlags.AutodiscoverConfiguration, "No matching Autodiscover DNS SRV records were found."); return null; } } /** * Tries to get Autodiscover settings using redirection Url. * * @param <TSettings> the generic type * @param cls the cls * @param emailAddress The email address. * @param redirectionUrl Redirection Url. * @param settings The settings. * @return boolean The boolean. * @throws AutodiscoverLocalException the autodiscover local exception * @throws AutodiscoverRemoteException the autodiscover remote exception * @throws Exception the exception */ private <TSettings extends ConfigurationSettingsBase> boolean tryLastChanceHostRedirection( Class<TSettings> cls, String emailAddress, URI redirectionUrl, OutParam<TSettings> settings) throws AutodiscoverLocalException, AutodiscoverRemoteException, Exception { List<String> redirectionEmailAddresses = new ArrayList<String>(); // Bug 60274: Performing a non-SSL HTTP GET to retrieve a redirection // URL is potentially unsafe. We allow the caller // to specify delegate to be called to determine whether we are allowed // to use the redirection URL. if (this .callRedirectionUrlValidationCallback(redirectionUrl.toString())) { for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) { try { settings.setParam(this.getLegacyUserSettingsAtUrl(cls, emailAddress, redirectionUrl)); switch (settings.getParam().getResponseType()) { case Success: return true; case Error: throw new AutodiscoverRemoteException("The Autodiscover service returned an error.", settings.getParam() .getError()); case RedirectAddress: // If this email address was already tried, //we may have a loop // in SCP lookups. Disable consideration of SCP records. this.disableScpLookupIfDuplicateRedirection(settings.getParam().getRedirectTarget(), redirectionEmailAddresses); OutParam<Integer> outParam = new OutParam<Integer>(); outParam.setParam(currentHop); settings.setParam( this.internalGetLegacyUserSettings(cls, emailAddress, redirectionEmailAddresses, outParam)); currentHop = outParam.getParam(); return true; case RedirectUrl: try { redirectionUrl = new URI(settings.getParam() .getRedirectTarget()); } catch (URISyntaxException ex) { this .traceMessage( TraceFlags. AutodiscoverConfiguration, String .format( "Service " + "returned " + "invalid " + "redirection " + "URL %s", settings .getParam() .getRedirectTarget())); return false; } break; default: String failureMessage = String.format( "Autodiscover call at %s failed with error %s, target %s", redirectionUrl, settings.getParam().getResponseType(), settings.getParam().getRedirectTarget()); this.traceMessage( TraceFlags.AutodiscoverConfiguration, failureMessage); return false; } } catch (XMLStreamException ex) { // If the response is malformed, it wasn't a valid // Autodiscover endpoint. this .traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "%s failed: XML parsing error: %s", redirectionUrl.toString(), ex .getMessage())); return false; } catch (IOException ex) { this.traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("%s failed: I/O error: %s", redirectionUrl, ex.getMessage())); return false; } catch (Exception ex) { // TODO: BUG response is always null HttpWebRequest response = null; OutParam<URI> outParam = new OutParam<URI>(); if ((response != null) && this.tryGetRedirectionResponse(response, outParam)) { redirectionUrl = outParam.getParam(); this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "Host returned a " + "redirection" + " to url %s", redirectionUrl)); } else { if (response != null) { this.processHttpErrorResponse(response, ex); } this .traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("%s failed: %s (%s)", url, ex.getClass().getName(), ex.getMessage())); return false; } } } } return false; } /** * Disables SCP lookup if duplicate email address redirection. * * @param emailAddress The email address to use. * @param redirectionEmailAddresses The list of prior redirection email addresses. */ private void disableScpLookupIfDuplicateRedirection( String emailAddress, List<String> redirectionEmailAddresses) { // SMTP addresses are case-insensitive so entries are converted to lower-case. emailAddress = emailAddress.toLowerCase(); if (redirectionEmailAddresses.contains(emailAddress)) { this.enableScpLookup = false; } else { redirectionEmailAddresses.add(emailAddress); } } /** * Gets user settings from Autodiscover legacy endpoint. * * @param emailAddress The email address to use. * @param requestedSettings The requested settings. * @return GetUserSettingsResponse * @throws Exception on error */ protected GetUserSettingsResponse internalGetLegacyUserSettings( String emailAddress, List<UserSettingName> requestedSettings) throws Exception { // Cannot call legacy Autodiscover service with WindowsLive and other WSSecurity-based credential if ((this.getCredentials() != null) && (this.getCredentials() instanceof WSSecurityBasedCredentials)) { throw new AutodiscoverLocalException( "WindowsLiveCredentials can't be used with this Autodiscover endpoint."); } OutlookConfigurationSettings settings = this.getLegacyUserSettings( OutlookConfigurationSettings.class, emailAddress); return settings.convertSettings(emailAddress, requestedSettings); } /** * Calls the SOAP Autodiscover service * for user settings for a single SMTP address. * * @param smtpAddress SMTP address. * @param requestedSettings The requested settings. * @return GetUserSettingsResponse * @throws Exception on error */ protected GetUserSettingsResponse internalGetSoapUserSettings( String smtpAddress, List<UserSettingName> requestedSettings) throws Exception { List<String> smtpAddresses = new ArrayList<String>(); smtpAddresses.add(smtpAddress); List<String> redirectionEmailAddresses = new ArrayList<String>(); redirectionEmailAddresses.add(smtpAddress.toLowerCase()); for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) { GetUserSettingsResponse response = this.getUserSettings(smtpAddresses, requestedSettings).getTResponseAtIndex(0); switch (response.getErrorCode()) { case RedirectAddress: this.traceMessage( TraceFlags.AutodiscoverResponse, String.format("Autodiscover service returned redirection email address '%s'.", response.getRedirectTarget())); smtpAddresses.clear(); smtpAddresses.add(response.getRedirectTarget(). toLowerCase()); this.url = null; this.domain = null; // If this email address was already tried, //we may have a loop // in SCP lookups. Disable consideration of SCP records. this.disableScpLookupIfDuplicateRedirection(response.getRedirectTarget(), redirectionEmailAddresses); break; case RedirectUrl: this.traceMessage( TraceFlags.AutodiscoverResponse, String.format("Autodiscover service returned redirection URL '%s'.", response.getRedirectTarget())); //this.url = new URI(response.getRedirectTarget()); this.url = this.getCredentials().adjustUrl(new URI(response.getRedirectTarget())); break; case NoError: default: return response; } } throw new AutodiscoverLocalException("The Autodiscover service couldn't be located."); } /** * Gets the user settings using Autodiscover SOAP service. * * @param smtpAddresses The SMTP addresses of the users. * @param settings The settings. * @return GetUserSettingsResponseCollection Object. * @throws Exception the exception */ protected GetUserSettingsResponseCollection getUserSettings( final List<String> smtpAddresses, List<UserSettingName> settings) throws Exception { EwsUtilities.validateParam(smtpAddresses, "smtpAddresses"); EwsUtilities.validateParam(settings, "settings"); return this.getSettings( GetUserSettingsResponseCollection.class, UserSettingName.class, smtpAddresses, settings, null, this, new IFuncDelegate<String>() { public String func() throws FormatException { return EwsUtilities .domainFromEmailAddress(smtpAddresses.get(0)); } }); } /** * Gets user or domain settings using Autodiscover SOAP service. * * @param <TGetSettingsResponseCollection> the generic type * @param <TSettingName> the generic type * @param cls the cls * @param cls1 the cls1 * @param identities Either the domains or the SMTP addresses of the users. * @param settings The settings. * @param requestedVersion Requested version of the Exchange service. * @param getSettingsMethod The method to use. * @param getDomainMethod The method to calculate the domain value. * @return TGetSettingsResponse Collection. * @throws Exception the exception */ private <TGetSettingsResponseCollection, TSettingName> TGetSettingsResponseCollection getSettings( Class<TGetSettingsResponseCollection> cls, Class<TSettingName> cls1, List<String> identities, List<TSettingName> settings, ExchangeVersion requestedVersion, IFunctionDelegate<List<String>, List<TSettingName>, TGetSettingsResponseCollection> getSettingsMethod, IFuncDelegate<String> getDomainMethod) throws Exception { TGetSettingsResponseCollection response; // Autodiscover service only exists in E14 or later. if (this.getRequestedServerVersion().compareTo( MinimumRequestVersionForAutoDiscoverSoapService) < 0) { throw new ServiceVersionException(String.format( "The Autodiscover service only supports %s or a later version.", MinimumRequestVersionForAutoDiscoverSoapService)); } // If Url is specified, call service directly. if (this.url != null) { URI autodiscoverUrl = this.url; response = getSettingsMethod.func(identities, settings, requestedVersion, this.url); this.url = autodiscoverUrl; return response; } // If Domain is specified, determine endpoint Url and call service. else if (!(this.domain == null || this.domain.isEmpty())) { URI autodiscoverUrl = this.getAutodiscoverEndpointUrl(this.domain); response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl); // If we got this far, response was successful, set Url. this.url = autodiscoverUrl; return response; } // No Url or Domain specified, need to figure out which endpoint(s) to // try. else { // Assume caller is not inside the Intranet, regardless of whether // SCP Urls // were returned or not. SCP Urls are only relevent if one of them // returns // valid Autodiscover settings. this.isExternal = true; URI autodiscoverUrl; String domainName = getDomainMethod.func(); int scpHostCount; OutParam<Integer> outParam = new OutParam<Integer>(); List<String> hosts = this.getAutodiscoverServiceHosts(domainName, outParam); scpHostCount = outParam.getParam(); if (hosts.size() == 0) { throw new ServiceValidationException( "This Autodiscover request requires that either the Domain or Url be specified."); } for (int currentHostIndex = 0; currentHostIndex < hosts.size(); currentHostIndex++) { String host = hosts.get(currentHostIndex); boolean isScpHost = currentHostIndex < scpHostCount; OutParam<URI> outParams = new OutParam<URI>(); if (this.tryGetAutodiscoverEndpointUrl(host, outParams)) { autodiscoverUrl = outParams.getParam(); response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl); // If we got this far, the response was successful, set Url. this.url = autodiscoverUrl; // Not external if Autodiscover endpoint found via SCP // returned the settings. if (isScpHost) { this.isExternal = false; } return response; } } // Next-to-last chance: try unauthenticated GET over HTTP to be // redirected to appropriate service endpoint. autodiscoverUrl = this.getRedirectUrl(domainName); OutParam<URI> outParamUrl = new OutParam<URI>(); if ((autodiscoverUrl != null) && this .callRedirectionUrlValidationCallback( autodiscoverUrl.toString()) && this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl .getHost(), outParamUrl)) { autodiscoverUrl = outParamUrl.getParam(); response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl); // If we got this far, the response was successful, set Url. this.url = autodiscoverUrl; return response; } // Last Chance: try to read autodiscover SRV Record from DNS. If we // find one, use // the hostname returned to construct an Autodiscover endpoint URL. autodiscoverUrl = this .getRedirectionUrlFromDnsSrvRecord(domainName); if ((autodiscoverUrl != null) && this .callRedirectionUrlValidationCallback( autodiscoverUrl.toString()) && this.tryGetAutodiscoverEndpointUrl(autodiscoverUrl .getHost(), outParamUrl)) { autodiscoverUrl = outParamUrl.getParam(); response = getSettingsMethod.func(identities, settings, requestedVersion, autodiscoverUrl); // If we got this far, the response was successful, set Url. this.url = autodiscoverUrl; return response; } else { throw new AutodiscoverLocalException("The Autodiscover service couldn't be located."); } } } /** * Gets settings for one or more users. * * @param smtpAddresses The SMTP addresses of the users. * @param settings The settings. * @param requestedVersion Requested version of the Exchange service. * @param autodiscoverUrl The autodiscover URL. * @return GetUserSettingsResponse collection. * @throws ServiceLocalException the service local exception * @throws Exception the exception */ private GetUserSettingsResponseCollection internalGetUserSettings( List<String> smtpAddresses, List<UserSettingName> settings, ExchangeVersion requestedVersion, URI autodiscoverUrl) throws ServiceLocalException, Exception { // The response to GetUserSettings can be a redirection. Execute // GetUserSettings until we get back // a valid response or we've followed too many redirections. for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) { GetUserSettingsRequest request = new GetUserSettingsRequest(this, autodiscoverUrl); request.setSmtpAddresses(smtpAddresses); request.setSettings(settings); GetUserSettingsResponseCollection response = request.execute(); // Did we get redirected? if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl && response.getRedirectionUrl() != null) { this.traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("Request to %s returned redirection to %s", autodiscoverUrl.toString(), response.getRedirectionUrl())); autodiscoverUrl = response.getRedirectionUrl(); } else { return response; } } this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections)); throw new MaximumRedirectionHopsExceededException(); } /** * Gets the domain settings using Autodiscover SOAP service. * * @param domains The domains. * @param settings The settings. * @param requestedVersion Requested version of the Exchange service. * @return GetDomainSettingsResponse collection. * @throws Exception the exception */ protected GetDomainSettingsResponseCollection getDomainSettings( final List<String> domains, List<DomainSettingName> settings, ExchangeVersion requestedVersion) throws Exception { EwsUtilities.validateParam(domains, "domains"); EwsUtilities.validateParam(settings, "settings"); return this.getSettings( GetDomainSettingsResponseCollection.class, DomainSettingName.class, domains, settings, requestedVersion, this, new IFuncDelegate<String>() { public String func() { return domains.get(0); } }); } /** * Gets settings for one or more domains. * * @param domains The domains. * @param settings The settings. * @param requestedVersion Requested version of the Exchange service. * @param autodiscoverUrl The autodiscover URL. * @return GetDomainSettingsResponse Collection. * @throws ServiceLocalException the service local exception * @throws Exception the exception */ private GetDomainSettingsResponseCollection internalGetDomainSettings( List<String> domains, List<DomainSettingName> settings, ExchangeVersion requestedVersion, URI autodiscoverUrl) throws ServiceLocalException, Exception { // The response to GetDomainSettings can be a redirection. Execute // GetDomainSettings until we get back // a valid response or we've followed too many redirections. for (int currentHop = 0; currentHop < AutodiscoverService.AutodiscoverMaxRedirections; currentHop++) { GetDomainSettingsRequest request = new GetDomainSettingsRequest( this, autodiscoverUrl); request.setDomains(domains); request.setSettings(settings); request.setRequestedVersion(requestedVersion); GetDomainSettingsResponseCollection response = request.execute(); // Did we get redirected? if (response.getErrorCode() == AutodiscoverErrorCode.RedirectUrl && response.getRedirectionUrl() != null) { autodiscoverUrl = response.getRedirectionUrl(); } else { return response; } } this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections)); throw new MaximumRedirectionHopsExceededException(); } /** * Gets the autodiscover endpoint URL. * * @param host The host. * @return URI The URI. * @throws Exception the exception */ private URI getAutodiscoverEndpointUrl(String host) throws Exception { URI autodiscoverUrl = null; OutParam<URI> outParam = new OutParam<URI>(); if (this.tryGetAutodiscoverEndpointUrl(host, outParam)) { return autodiscoverUrl; } else { throw new AutodiscoverLocalException( "No appropriate Autodiscover SOAP or WS-Security endpoint is available."); } } /** * Tries the get Autodiscover Service endpoint URL. * * @param host The host. * @param url the url * @return boolean The boolean. * @throws Exception the exception */ private boolean tryGetAutodiscoverEndpointUrl(String host, OutParam<URI> url) throws Exception { EnumSet<AutodiscoverEndpoints> endpoints; OutParam<EnumSet<AutodiscoverEndpoints>> outParam = new OutParam<EnumSet<AutodiscoverEndpoints>>(); if (this.tryGetEnabledEndpointsForHost(host, outParam)) { endpoints = outParam.getParam(); url .setParam(new URI(String.format(AutodiscoverSoapHttpsUrl, host))); // Make sure that at least one of the non-legacy endpoints is // available. if ((!endpoints.contains(AutodiscoverEndpoints.Soap)) && (!endpoints.contains( AutodiscoverEndpoints.WsSecurity)) // (endpoints .contains( AutodiscoverEndpoints.WSSecuritySymmetricKey) ) && //(endpoints .contains( AutodiscoverEndpoints.WSSecurityX509Cert)) ) { this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "No Autodiscover endpoints " + "are available for host %s", host)); return false; } // If we have WLID credential, make sure that we have a WS-Security // endpoint /* if (this.getCredentials() instanceof WindowsLiveCredentials) { if (endpoints.contains(AutodiscoverEndpoints.WsSecurity)) { this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "No Autodiscover " + "WS-Security " + "endpoint is available" + " for host %s", host)); return false; } else { url.setParam(new URI(String.format( AutodiscoverSoapWsSecurityHttpsUrl, host))); } } else if (this.getCredentials() instanceof PartnerTokenCredentials) { if (endpoints.contains( AutodiscoverEndpoints.WSSecuritySymmetricKey)) { this.traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("No Autodiscover WS-Security/SymmetricKey endpoint is available for host {0}", host)); return false; } else { url.setParam( new URI(String.format(AutodiscoverSoapWsSecuritySymmetricKeyHttpsUrl, host))); } } else if (this.getCredentials()instanceof X509CertificateCredentials) { if ((endpoints.contains(AutodiscoverEndpoints.WSSecurityX509Cert)) { this.traceMessage( TraceFlags.AutodiscoverConfiguration, String.format("No Autodiscover WS-Security/X509Cert endpoint is available for host {0}", host)); return false; } else { url.setParam( new URI(String.format(AutodiscoverSoapWsSecurityX509CertHttpsUrl, host))); } } */ return true; } else { this .traceMessage( TraceFlags.AutodiscoverConfiguration, String .format( "No Autodiscover endpoints " + "are available for host %s", host)); return false; } } /** * Gets the list of autodiscover service URLs. * * @param domainName Domain name. * @param scpHostCount Count of hosts found via SCP lookup. * @return List of Autodiscover URLs. * @throws java.net.URISyntaxException the URI Syntax exception */ protected List<URI> getAutodiscoverServiceUrls(String domainName, OutParam<Integer> scpHostCount) throws URISyntaxException { List<URI> urls; urls = new ArrayList<URI>(); scpHostCount.setParam(urls.size()); // As a fallback, add autodiscover URLs base on the domain name. urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl, domainName))); urls.add(new URI(String.format(AutodiscoverLegacyHttpsUrl, "autodiscover." + domainName))); return urls; } /** * Gets the list of autodiscover service hosts. * * @param domainName Domain name. * @param outParam the out param * @return List of hosts. * @throws java.net.URISyntaxException the uRI syntax exception * @throws ClassNotFoundException the class not found exception */ protected List<String> getAutodiscoverServiceHosts(String domainName, OutParam<Integer> outParam) throws URISyntaxException, ClassNotFoundException { List<URI> urls = this.getAutodiscoverServiceUrls(domainName, outParam); List<String> lst = new ArrayList<String>(); for (URI url : urls) { lst.add(url.getHost()); } return lst; } /** * Gets the enabled autodiscover endpoints on a specific host. * * @param host The host. * @param endpoints Endpoints found for host. * @return Flags indicating which endpoints are enabled. * @throws Exception the exception */ private boolean tryGetEnabledEndpointsForHost(String host, OutParam<EnumSet<AutodiscoverEndpoints>> endpoints) throws Exception { this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format( "Determining which endpoints are enabled for host %s", host)); // We may get redirected to another host. And therefore need to limit the number of redirections we'll // tolerate. for (int currentHop = 0; currentHop < AutodiscoverMaxRedirections; currentHop++) { URI autoDiscoverUrl = new URI(String.format(AutodiscoverLegacyHttpsUrl, host)); endpoints.setParam(EnumSet.of(AutodiscoverEndpoints.None)); HttpWebRequest request = null; try { request = new HttpClientWebRequest(httpClient, httpContext); request.setProxy(getWebProxy()); try { request.setUrl(autoDiscoverUrl.toURL()); } catch (MalformedURLException e) { String strErr = String.format("Incorrect format : %s", url); throw new ServiceLocalException(strErr); } request.setRequestMethod("GET"); request.setAllowAutoRedirect(false); request.setPreAuthenticate(false); request.setUseDefaultCredentials(this.getUseDefaultCredentials()); request.setTimeout(getTimeout()); prepareCredentials(request); request.prepareConnection(); try { request.executeRequest(); } catch (IOException e) { return false; } OutParam<URI> outParam = new OutParam<URI>(); if (this.tryGetRedirectionResponse(request, outParam)) { URI redirectUrl = outParam.getParam(); this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Host returned redirection to host '%s'", redirectUrl.getHost())); host = redirectUrl.getHost(); } else { endpoints.setParam(this.getEndpointsFromHttpWebResponse(request)); this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Host returned enabled endpoint flags: %s", endpoints.getParam().toString())); return true; } } finally { if (request != null) { try { request.close(); } catch (Exception e) { // Connection can't be closed. We'll ignore this... } } } } this.traceMessage(TraceFlags.AutodiscoverConfiguration, String.format("Maximum number of redirection hops %d exceeded", AutodiscoverMaxRedirections)); throw new MaximumRedirectionHopsExceededException(); } /** * Gets the endpoints from HTTP web response. * * @param request the request * @return Endpoints enabled. * @throws EWSHttpException the EWS http exception */ private EnumSet<AutodiscoverEndpoints> getEndpointsFromHttpWebResponse( HttpWebRequest request) throws EWSHttpException { EnumSet<AutodiscoverEndpoints> endpoints = EnumSet .noneOf(AutodiscoverEndpoints.class); endpoints.add(AutodiscoverEndpoints.Legacy); if (!(request.getResponseHeaders().get( AutodiscoverSoapEnabledHeaderName) == null || request .getResponseHeaders().get(AutodiscoverSoapEnabledHeaderName) .isEmpty())) { endpoints.add(AutodiscoverEndpoints.Soap); } if (!(request.getResponseHeaders().get( AutodiscoverWsSecurityEnabledHeaderName) == null || request .getResponseHeaders().get( AutodiscoverWsSecurityEnabledHeaderName).isEmpty())) { endpoints.add(AutodiscoverEndpoints.WsSecurity); } /* if (! (request.getResponseHeaders().get( AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName) !=null || request .getResponseHeaders().get( AutodiscoverWsSecuritySymmetricKeyEnabledHeaderName).isEmpty())) { endpoints .add( AutodiscoverEndpoints.WSSecuritySymmetricKey); } if (!(request.getResponseHeaders().get( AutodiscoverWsSecurityX509CertEnabledHeaderName)!=null || request.getResponseHeaders().get( AutodiscoverWsSecurityX509CertEnabledHeaderName).isEmpty())) { endpoints .add(AutodiscoverEndpoints.WSSecurityX509Cert); }*/ return endpoints; } /** * Traces the response. * * @param request the request * @param memoryStream the memory stream * @throws XMLStreamException the XML stream exception * @throws IOException signals that an I/O exception has occurred. * @throws EWSHttpException the EWS http exception */ public void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream) throws XMLStreamException, IOException, EWSHttpException { this.processHttpResponseHeaders( TraceFlags.AutodiscoverResponseHttpHeaders, request); String contentType = request.getResponseContentType(); if (!(contentType == null || contentType.isEmpty())) { contentType = contentType.toLowerCase(); if (contentType.toLowerCase().startsWith("text/") || contentType.toLowerCase(). startsWith("application/soap")) { this.traceXml(TraceFlags.AutodiscoverResponse, memoryStream); } else { this.traceMessage(TraceFlags.AutodiscoverResponse, "Non-textual response"); } } } /** * Creates an HttpWebRequest instance and initializes it with the * appropriate parameters, based on the configuration of this service * object. * * @param url The URL that the HttpWebRequest should target * @return HttpWebRequest The HttpWebRequest * @throws ServiceLocalException the service local exception * @throws java.net.URISyntaxException the uRI syntax exception */ public HttpWebRequest prepareHttpWebRequestForUrl(URI url) throws ServiceLocalException, URISyntaxException { return this.prepareHttpWebRequestForUrl(url, false, // acceptGzipEncoding false); // allowAutoRedirect } /** * Calls the redirection URL validation callback. If the redirection URL * validation callback is null, use the default callback which does not * allow following any redirections. * * @param redirectionUrl The redirection URL. * @return True if redirection should be followed. * @throws AutodiscoverLocalException the autodiscover local exception */ private boolean callRedirectionUrlValidationCallback(String redirectionUrl) throws AutodiscoverLocalException { IAutodiscoverRedirectionUrl callback = (this.redirectionUrlValidationCallback == null) ? this : this.redirectionUrlValidationCallback; return callback .autodiscoverRedirectionUrlValidationCallback(redirectionUrl); } /** * Processes an HTTP error response. * * @param httpWebResponse The HTTP web response. * @throws Exception the exception */ @Override public void processHttpErrorResponse(HttpWebRequest httpWebResponse, Exception webException) throws Exception { this.internalProcessHttpErrorResponse( httpWebResponse, webException, TraceFlags.AutodiscoverResponseHttpHeaders, TraceFlags.AutodiscoverResponse); } /* * (non-Javadoc) * * @see microsoft.exchange.webservices.AutodiscoverRedirectionUrlInterface# * autodiscoverRedirectionUrlValidationCallback(java.lang.String) */ public boolean autodiscoverRedirectionUrlValidationCallback( String redirectionUrl) throws AutodiscoverLocalException { return defaultAutodiscoverRedirectionUrlValidationCallback( redirectionUrl); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @throws ArgumentException on validation error */ public AutodiscoverService() throws ArgumentException { this(ExchangeVersion.Exchange2010); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param requestedServerVersion The requested server version * @throws ArgumentException on validation error */ public AutodiscoverService(ExchangeVersion requestedServerVersion) throws ArgumentException { this(null, null, requestedServerVersion); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param domain The domain that will be used to determine the URL of the service * @throws ArgumentException on validation error */ public AutodiscoverService(String domain) throws ArgumentException { this(null, domain); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param domain The domain that will be used to determine the URL of the service * @param requestedServerVersion The requested server version * @throws ArgumentException on validation error */ public AutodiscoverService(String domain, ExchangeVersion requestedServerVersion) throws ArgumentException { this(null, domain, requestedServerVersion); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param url The URL of the service * @throws ArgumentException on validation error */ public AutodiscoverService(URI url) throws ArgumentException { this(url, url.getHost()); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param url The URL of the service * @param requestedServerVersion The requested server version * @throws ArgumentException on validation error */ public AutodiscoverService(URI url, ExchangeVersion requestedServerVersion) throws ArgumentException { this(url, url.getHost(), requestedServerVersion); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param url The URL of the service * @param domain The domain that will be used to determine the URL of the service * @throws ArgumentException on validation error */ public AutodiscoverService(URI url, String domain) throws ArgumentException { super(); EwsUtilities.validateDomainNameAllowNull(domain, "domain"); this.url = url; this.domain = domain; this.dnsClient = new AutodiscoverDnsClient(this); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param url The URL of the service. * @param domain The domain that will be used to determine the URL of the * service. * @param requestedServerVersion The requested server version. * @throws ArgumentException on validation error */ public AutodiscoverService(URI url, String domain, ExchangeVersion requestedServerVersion) throws ArgumentException { super(requestedServerVersion); EwsUtilities.validateDomainNameAllowNull(domain, "domain"); this.url = url; this.domain = domain; this.dnsClient = new AutodiscoverDnsClient(this); } /** * Initializes a new instance of the AutodiscoverService class. * * @param service The other service. * @param requestedServerVersion The requested server version. */ public AutodiscoverService(ExchangeServiceBase service, ExchangeVersion requestedServerVersion) { super(service, requestedServerVersion); this.dnsClient = new AutodiscoverDnsClient(this); } /** * Initializes a new instance of the "AutodiscoverService" class. * * @param service The service. */ public AutodiscoverService(ExchangeServiceBase service) { super(service, service.getRequestedServerVersion()); } /** * Retrieves the specified settings for single SMTP address. * <p>This method will run the entire Autodiscover "discovery" * algorithm and will follow address and URL redirections.</p> * @param userSmtpAddress The SMTP addresses of the user. * @param userSettingNames The user setting names. * @return A UserResponse object containing the requested settings for the * specified user. * @throws Exception on error */ public GetUserSettingsResponse getUserSettings(String userSmtpAddress, UserSettingName... userSettingNames) throws Exception { List<UserSettingName> requestedSettings = new ArrayList<UserSettingName>(); requestedSettings.addAll(Arrays.asList(userSettingNames)); if (userSmtpAddress == null || userSmtpAddress.isEmpty()) { throw new ServiceValidationException("A valid SMTP address must be specified."); } if (requestedSettings.size() == 0) { throw new ServiceValidationException("At least one setting must be requested."); } if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) { return this.internalGetLegacyUserSettings(userSmtpAddress, requestedSettings); } else { return this.internalGetSoapUserSettings(userSmtpAddress, requestedSettings); } } /** * Retrieves the specified settings for a set of users. * * @param userSmtpAddresses the user smtp addresses * @param userSettingNames The user setting names. * @return A GetUserSettingsResponseCollection object containing the * response for each individual user. * @throws Exception the exception */ public GetUserSettingsResponseCollection getUsersSettings( Iterable<String> userSmtpAddresses, UserSettingName... userSettingNames) throws Exception { if (this.getRequestedServerVersion().compareTo(MinimumRequestVersionForAutoDiscoverSoapService) < 0) { throw new ServiceVersionException( String.format("The Autodiscover service only supports %s or a later version.", MinimumRequestVersionForAutoDiscoverSoapService)); } List<String> smtpAddresses = new ArrayList<String>(); smtpAddresses.addAll((Collection<? extends String>) userSmtpAddresses); List<UserSettingName> settings = new ArrayList<UserSettingName>(); settings.addAll(Arrays.asList(userSettingNames)); return this.getUserSettings(smtpAddresses, settings); } /** * Retrieves the specified settings for a domain. * * @param domain The domain. * @param requestedVersion Requested version of the Exchange service. * @param domainSettingNames The domain setting names. * @return A DomainResponse object containing the requested settings for the * specified domain. * @throws Exception the exception */ public GetDomainSettingsResponse getDomainSettings(String domain, ExchangeVersion requestedVersion, DomainSettingName... domainSettingNames) throws Exception { List<String> domains = new ArrayList<String>(1); domains.add(domain); List<DomainSettingName> settings = new ArrayList<DomainSettingName>(); settings.addAll(Arrays.asList(domainSettingNames)); return this.getDomainSettings(domains, settings, requestedVersion). getTResponseAtIndex(0); } /** * Retrieves the specified settings for a set of domains. * * @param domains the domains * @param requestedVersion Requested version of the Exchange service. * @param domainSettingNames The domain setting names. * @return A GetDomainSettingsResponseCollection object containing the * response for each individual domain. * @throws Exception the exception */ public GetDomainSettingsResponseCollection getDomainSettings( Iterable<String> domains, ExchangeVersion requestedVersion, DomainSettingName... domainSettingNames) throws Exception { List<DomainSettingName> settings = new ArrayList<DomainSettingName>(); settings.addAll(Arrays.asList(domainSettingNames)); List<String> domainslst = new ArrayList<String>(); domainslst.addAll((Collection<? extends String>) domains); return this.getDomainSettings(domainslst, settings, requestedVersion); } /** * Gets the domain this service is bound to. When this property is * set, the domain name is used to automatically determine the Autodiscover service URL. * * @return the domain */ public String getDomain() { return this.domain; } /** * Sets the domain this service is bound to. When this property is * set, the domain * name is used to automatically determine the Autodiscover service URL. * * @param value the new domain * @throws ArgumentException on validation error */ public void setDomain(String value) throws ArgumentException { EwsUtilities.validateDomainNameAllowNull(value, "Domain"); // If Domain property is set to non-null value, Url property is nulled. if (value != null) { this.url = null; } this.domain = value; } /** * Gets the url this service is bound to. * * @return the url */ public URI getUrl() { return this.url; } /** * Sets the url this service is bound to. * * @param value the new url */ public void setUrl(URI value) { // If Url property is set to non-null value, Domain property is set to // host portion of Url. if (value != null) { this.domain = value.getHost(); } this.url = value; } public Boolean isExternal() { return this.isExternal; } protected void setIsExternal(Boolean value) { this.isExternal = value; } /** * Gets the redirection url validation callback. * * @return the redirection url validation callback */ public IAutodiscoverRedirectionUrl getRedirectionUrlValidationCallback() { return this.redirectionUrlValidationCallback; } /** * Sets the redirection url validation callback. * * @param value the new redirection url validation callback */ public void setRedirectionUrlValidationCallback( IAutodiscoverRedirectionUrl value) { this.redirectionUrlValidationCallback = value; } /** * Gets the dns server address. * * @return the dns server address */ protected String getDnsServerAddress() { return this.dnsServerAddress; } /** * Sets the dns server address. * * @param value the new dns server address */ protected void setDnsServerAddress(String value) { this.dnsServerAddress = value; } /** * Gets a value indicating whether the AutodiscoverService should * perform SCP (ServiceConnectionPoint) record lookup when determining * the Autodiscover service URL. * * @return the enable scp lookup */ public boolean getEnableScpLookup() { return this.enableScpLookup; } /** * Sets the enable scp lookup. * * @param value the new enable scp lookup */ public void setEnableScpLookup(boolean value) { this.enableScpLookup = value; } /* * (non-Javadoc) * * @see * microsoft.exchange.webservices.FuncDelegateInterface#func(java.util.List, * java.util.List, java.net.URI) */ @Override public Object func(List arg1, List arg2, ExchangeVersion arg3, URI arg4) throws ServiceLocalException, Exception { if (arg2.get(0).getClass().equals(DomainSettingName.class)) { return internalGetDomainSettings(arg1, arg2, arg3, arg4); } else if (arg2.get(0).getClass().equals(UserSettingName.class)) { return internalGetUserSettings(arg1, arg2, arg3, arg4); } else { return null; } } }