package com.btr.proxy.search.desktop.osx; import java.io.File; import java.io.IOException; import java.net.NetworkInterface; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import com.btr.proxy.search.ProxySearchStrategy; import com.btr.proxy.search.browser.ie.IELocalByPassFilter; import com.btr.proxy.search.wpad.WpadProxySearchStrategy; import com.btr.proxy.selector.direct.NoProxySelector; import com.btr.proxy.selector.misc.ProtocolDispatchSelector; import com.btr.proxy.selector.whitelist.ProxyBypassListSelector; import com.btr.proxy.util.Logger; import com.btr.proxy.util.Logger.LogLevel; import com.btr.proxy.util.PListParser; import com.btr.proxy.util.PListParser.Dict; import com.btr.proxy.util.PListParser.XmlParseException; import com.btr.proxy.util.ProxyException; import com.btr.proxy.util.ProxyUtil; import com.btr.proxy.util.UriFilter; /***************************************************************************** * Loads the OSX system proxy settings from the settings file. *

* All settings are stored in OSX in a special XML file format. * These settings file are named plist files and contain nested dictionaries, arrays and values. *

* To parse this file we use a parser that is derived from a plist parser that * comes with the xmlwise XML parser package: *

* http://code.google.com/p/xmlwise/ *

* I modified that parser to work with the default Java XML parsing library. *

* The plist file is located on OSX at: *

* /Library/Preferences/SystemConfiguration/preferences.plist *

* * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2011 ****************************************************************************/ public class OsxProxySearchStrategy implements ProxySearchStrategy { public static final String OVERRIDE_SETTINGS_FILE = "com.btr.proxy.osx.settingsFile"; public static final String OVERRIDE_ACCEPTED_DEVICES = "com.btr.proxy.osx.acceptedDevices"; private static final String SETTINGS_FILE = "/Library/Preferences/SystemConfiguration/preferences.plist"; /************************************************************************* * ProxySelector * @see java.net.ProxySelector#ProxySelector() ************************************************************************/ public OsxProxySearchStrategy() { super(); } /************************************************************************* * Loads the proxy settings and initializes a proxy selector for the OSX * proxy settings. * @return a configured ProxySelector, null if none is found. * @throws ProxyException on file reading error. ************************************************************************/ public ProxySelector getProxySelector() throws ProxyException { Logger.log(getClass(), LogLevel.TRACE, "Detecting OSX proxy settings"); try { List acceptedInterfaces = getNetworkInterfaces(); Dict settings = PListParser.load(getSettingsFile()); Object currentSet = settings.getAtPath("/CurrentSet"); if (currentSet == null) { throw new ProxyException("CurrentSet not defined"); } Dict networkSet = (Dict) settings.getAtPath(String.valueOf(currentSet)); List serviceOrder = (List) networkSet.getAtPath("/Network/Global/IPv4/ServiceOrder"); if (serviceOrder == null || serviceOrder.size() == 0) { throw new ProxyException("ServiceOrder not defined"); } // Look at the Services in priority order and pick the first one that was // also accepted above Dict proxySettings = null; for (int i = 0; i < serviceOrder.size() && proxySettings == null; i++) { Object candidateService = serviceOrder.get(i); Object networkService = networkSet.getAtPath("/Network/Service/"+candidateService+"/__LINK__"); if (networkService == null ) { throw new ProxyException("NetworkService not defined."); } Dict selectedServiceSettings = (Dict) settings.getAtPath(""+networkService); String interfaceName = (String) selectedServiceSettings.getAtPath("/Interface/DeviceName"); if (acceptedInterfaces.contains(interfaceName)) { Logger.log(getClass(), LogLevel.TRACE, "Looking up proxies for device " + interfaceName); proxySettings = (Dict) selectedServiceSettings.getAtPath("/Proxies"); } } if (proxySettings == null) { return NoProxySelector.getInstance(); } return buildSelector(proxySettings); } catch (XmlParseException e) { throw new ProxyException(e); } catch (IOException e) { throw new ProxyException(e); } } /************************************************************************* * Build a selector from the given settings. * @param proxySettings to parse * @return the configured selector * @throws ProxyException on error ************************************************************************/ private ProxySelector buildSelector(Dict proxySettings) throws ProxyException { ProtocolDispatchSelector ps = new ProtocolDispatchSelector(); installSelectorForProtocol(proxySettings, ps, "HTTP"); installSelectorForProtocol(proxySettings, ps, "HTTPS"); installSelectorForProtocol(proxySettings, ps, "FTP"); installSelectorForProtocol(proxySettings, ps, "Gopher"); installSelectorForProtocol(proxySettings, ps, "RTSP"); installSocksProxy(proxySettings, ps); ProxySelector result = ps; result = installPacProxyIfAvailable(proxySettings, result); result = autodetectProxyIfAvailable(proxySettings, result); result = installExceptionList(proxySettings, result); result = installSimpleHostFilter(proxySettings, result); return result; } /************************************************************************* * Create a list of Ethernet interfaces that are connected * @return * @throws SocketException ************************************************************************/ private List getNetworkInterfaces() throws SocketException { String override = System.getProperty(OVERRIDE_ACCEPTED_DEVICES); if (override != null && override.length() > 0) { return Arrays.asList(override.split(";")); } List acceptedInterfaces = new ArrayList(); Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface ni = interfaces.nextElement(); if (isInterfaceAllowed(ni)) { acceptedInterfaces.add(ni.getName()); } } return acceptedInterfaces; } /************************************************************************* * Check if a given network interface is interesting for us. * @param ni the interface to check * @return true if accepted else false. * @throws SocketException on error. ************************************************************************/ private boolean isInterfaceAllowed(NetworkInterface ni) throws SocketException { return !ni.isLoopback() && !ni.isPointToPoint() && // Not sure if we should filter the point to point interfaces? !ni.isVirtual() && ni.isUp(); } /************************************************************************* * @return ************************************************************************/ private File getSettingsFile() { File result = new File(SETTINGS_FILE); String overrideFile = System.getProperty(OVERRIDE_SETTINGS_FILE); if (overrideFile != null) { return new File(overrideFile); } return result; } /************************************************************************* * @param proxySettings * @param result * @return ************************************************************************/ private ProxySelector installSimpleHostFilter( Dict proxySettings, ProxySelector result) { if (isActive(proxySettings.get("ExcludeSimpleHostnames"))) { List localBypassFilter = new ArrayList(); localBypassFilter.add(new IELocalByPassFilter()); result = new ProxyBypassListSelector(localBypassFilter, result); } return result; } /************************************************************************* * @param proxySettings * @param result * @return ************************************************************************/ private ProxySelector installExceptionList( Dict proxySettings, ProxySelector result) { List proxyExceptions = (List) proxySettings.get("ExceptionsList"); if (proxyExceptions != null && proxyExceptions.size() > 0) { Logger.log(getClass(), LogLevel.TRACE, "OSX uses proxy bypass list: {0}", proxyExceptions); String noProxyList = toCommaSeparatedString(proxyExceptions); result = new ProxyBypassListSelector(noProxyList, result); } return result; } /************************************************************************* * Convert a list to a comma separated list. * @param proxyExceptions list of elements. * @return a comma separated string of the list's content. ************************************************************************/ private String toCommaSeparatedString(List proxyExceptions) { StringBuilder result = new StringBuilder(); for (Object object : proxyExceptions) { if (result.length() > 0) { result.append(","); } result.append(object); } return result.toString(); } /************************************************************************* * @param proxySettings * @param result * @return * @throws ProxyException ************************************************************************/ private ProxySelector autodetectProxyIfAvailable( Dict proxySettings, ProxySelector result) throws ProxyException { if (isActive(proxySettings.get("ProxyAutoDiscoveryEnable"))) { ProxySelector wp = new WpadProxySearchStrategy().getProxySelector(); if (wp != null) { result = wp; } } return result; } /************************************************************************* * @param proxySettings * @param result * @return ************************************************************************/ private ProxySelector installPacProxyIfAvailable(Dict proxySettings, ProxySelector result) { if (isActive(proxySettings.get("ProxyAutoConfigEnable"))) { String url = (String) proxySettings.get("ProxyAutoConfigURLString"); result = ProxyUtil.buildPacSelectorForUrl(url); } return result; } /************************************************************************* * Build a socks proxy and set it for the socks protocol. * @param proxySettings to read the config values from. * @param ps the ProtocolDispatchSelector to install the new proxy on. ************************************************************************/ private void installSocksProxy(Dict proxySettings, ProtocolDispatchSelector ps) { if (isActive(proxySettings.get("SOCKSEnable"))) { String proxyHost = (String) proxySettings.get("SOCKSProxy"); int proxyPort = (Integer) proxySettings.get("SOCKSPort"); ps.setSelector("socks", ProxyUtil.parseProxySettings(proxyHost.trim(), Proxy.Type.SOCKS, proxyPort)); Logger.log(getClass(), LogLevel.TRACE, "OSX socks proxy is {0}:{1}", proxyHost, proxyPort); } } /************************************************************************* * Installs a proxy selector for the given protocoll on the ProtocolDispatchSelector * @param proxySettings to read the config for the procotol from. * @param ps the ProtocolDispatchSelector to install the new selector on. * @param protocol to use. ************************************************************************/ private void installSelectorForProtocol(Dict proxySettings, ProtocolDispatchSelector ps, String protocol) { String prefix = protocol.trim(); if (isActive(proxySettings.get(prefix+"Enable"))) { String proxyHost = (String) proxySettings.get(prefix+"Proxy"); int proxyPort = (Integer) proxySettings.get(prefix+"Port"); ProxySelector fp = ProxyUtil.parseProxySettings(proxyHost.trim(), Proxy.Type.HTTP, proxyPort); ps.setSelector(protocol.toLowerCase(), fp); Logger.log(getClass(), LogLevel.TRACE, "OSX uses for {0} the proxy {1}:{2}", protocol, proxyHost, proxyPort); } } /************************************************************************* * Checks if the given value is set to "on". * @param value the value to test. * @return true if it is set else false. ************************************************************************/ private boolean isActive(Object value) { return Integer.valueOf(1).equals(value); } }