package com.btr.proxy.search.wpad;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import com.btr.proxy.search.ProxySearchStrategy;
import com.btr.proxy.search.wpad.dhcp.DHCPMessage;
import com.btr.proxy.search.wpad.dhcp.DHCPOptions;
import com.btr.proxy.search.wpad.dhcp.DHCPSocket;
import com.btr.proxy.util.Logger;
import com.btr.proxy.util.Logger.LogLevel;
import com.btr.proxy.util.ProxyException;
import com.btr.proxy.util.ProxyUtil;
/*****************************************************************************
* Uses automatic proxy script search (WPAD) to find an PAC file automatically.
* <p>
* Note: at the moment only the DNS name guessing schema is implemented. All
* others are missing.
* </p>
* <p>
* For more information about WPAD: <a
* href="http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol"
* >Web_Proxy_Autodiscovery_Protocol</a>
* </p>
* <p>
* Outdated RFC draft: <a href=
* "http://www.web-cache.com/Writings/Internet-Drafts/draft-ietf-wrec-wpad-01.txt"
* >draft-ietf-wrec-wpad-01.txt</a>
* </p>
*
* @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
****************************************************************************/
public class WpadProxySearchStrategyWithDHPC implements ProxySearchStrategy {
DHCPSocket bindSocket = null;
byte hwaddr[] = new byte[16];
InetAddress serverIP;
int portNum;
boolean gSentinel;
/*************************************************************************
* Constructor
************************************************************************/
public WpadProxySearchStrategyWithDHPC() {
super();
}
/*************************************************************************
* Loads the proxy settings from a PAC file. The location of the PAC file is
* determined automatically.
*
* @return a configured ProxySelector, null if none is found.
* @throws ProxyException
* on error.
************************************************************************/
public ProxySelector getProxySelector() throws ProxyException {
try {
Logger.log(getClass(), LogLevel.TRACE, "Using WPAD to find a proxy");
String pacScriptUrl = detectScriptUrlPerDHCP();
if (pacScriptUrl == null) {
pacScriptUrl = detectScriptUrlPerDNS();
}
if (pacScriptUrl == null) {
return null;
}
Logger.log(getClass(), LogLevel.TRACE, "PAC script url found: {0}",
pacScriptUrl);
return ProxyUtil.buildPacSelectorForUrl(pacScriptUrl);
} catch (IOException e) {
Logger.log(getClass(), LogLevel.ERROR, "Error during WPAD search.",
e);
throw new ProxyException(e);
}
}
/*************************************************************************
* Loads the settings and stores them in a properties map.
*
* @return the settings.
************************************************************************/
public Properties readSettings() {
try {
String pacScriptUrl = detectScriptUrlPerDHCP();
if (pacScriptUrl == null) {
pacScriptUrl = detectScriptUrlPerDNS();
}
if (pacScriptUrl == null) {
return null;
}
Properties result = new Properties();
result.setProperty("url", pacScriptUrl);
return result;
} catch (IOException e) {
// Ignore and return empty properties.
return new Properties();
}
}
/*************************************************************************
* Uses DNS to find the script URL. Attention: this detection method is
* known to have some severe security issues.
*
* @return the URL, null if not found.
************************************************************************/
private String detectScriptUrlPerDNS() throws IOException {
String result = null;
// String fqdn = InetAddress.getLocalHost().getCanonicalHostName();
Logger.log(getClass(), LogLevel.TRACE, "Searching per DNS guessing.");
// Logger.log(getClass(), LogLevel.INFO, "fqdn: ", fqdn);
/**
* Reading address from "/etc/resolv.conf"; file looks like:
*
* # Dynamic resolv.conf(5) file for glibc resolver(3) generated by
* resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE
* OVERWRITTEN nameserver 127.0.1.1 search ruf.uni-freiburg.de
* lp.ruf.uni-freiburg.de
*/
FileReader fr = new FileReader("/etc/resolv.conf");
BufferedReader br = new BufferedReader(fr);
String input;
// using the 4th line of the file.
br.readLine();
br.readLine();
br.readLine();
input = br.readLine();
String[] addresses = input.split(" ");
// the first one is "search" and afterwards addresses are following.
for (int i = 0; i < addresses.length; ++i) {
String address = addresses[i];
int index = -1;
do {
address = address.substring(index + 1);
// if we are already on TLD level then escape
if (address.indexOf('.') == -1) {
break;
}
// Try to connect to URL
try {
URL lookupURL = new URL("http://wpad." + address
+ "/wpad.dat");
Logger.log(getClass(), LogLevel.TRACE, "Trying url: {0}",
lookupURL);
HttpURLConnection con = (HttpURLConnection) lookupURL
.openConnection(Proxy.NO_PROXY);
con.setInstanceFollowRedirects(true);
con.setRequestProperty("accept",
"application/x-ns-proxy-autoconfig");
if (con.getResponseCode() == 200) {
result = lookupURL.toString();
return result;
}
con.disconnect();
} catch (UnknownHostException e) {
Logger.log(getClass(), LogLevel.DEBUG, "Not available!");
// Not a real error, try next address
}
index = address.indexOf('.');
} while (index != -1);
}
return null;
}
/*************************************************************************
* Uses DHCP to find the script URL.
*
* @return the URL, null if not found.
************************************************************************/
private String detectScriptUrlPerDHCP() {
Logger.log(getClass(), LogLevel.DEBUG,
"Searching per DHCP not supported yet.");
// create socket.
try {
// bindSocket = new DHCPSocket(DHCPMessage.CLIENT_PORT);
// --> Not able to use ports under 1024 without being root.
this.bindSocket = new DHCPSocket(1068);
} catch (SocketException e) {
System.err.println(e);
return null;
}
return null;
}
// Sends DHCPDISCOVER Message and returns server message
private DHCPMessage SendDiscover() {
DHCPSocket bindSocket = null;
try {
bindSocket = new DHCPSocket(1068);
} catch (SocketException e1) {
// TODO bjoern 29.10.2014 Auto-generated catch block
e1.printStackTrace();
return null;
}
Random ranXid = new Random();
DHCPMessage messageOut = new DHCPMessage(DHCPMessage.BROADCAST_ADDR,
DHCPMessage.SERVER_PORT);
DHCPMessage messageIn = new DHCPMessage(DHCPMessage.BROADCAST_ADDR,
DHCPMessage.SERVER_PORT);
try {
// Specify type of message: 1 = request - message, 2 = reply - message
messageOut.setOp((byte) 1);
// set Hardware type: 1 = Ethernet, 6 = IEEE 802 Networks, ...
messageOut.setHtype((byte) 1); // 1 = ethernet
// set hardware address length: 6 for MAC address
messageOut.setHlen((byte) 6);
messageOut.setHops((byte) 0);
// set session ID for later comparing with incoming message.
messageOut.setXid(ranXid.nextInt());
messageOut.setSecs((short) 0);
messageOut.setFlags((short) 0x8000);
// set client hardware address, in this case my own hardware address of eth0.
messageOut.setChaddr(ChaddrToByte("D8:D3:85:80:8F:C9"));
byte[] opt = new byte[1];
byte[] wpadOpt = new byte[1];
opt[0] = DHCPMessage.DHCPDISCOVER;
// change message type
messageOut.setOption(DHCPOptions.OPTION_DHCP_MESSAGE_TYPE, opt);
wpadOpt[0] = DHCPMessage.OPTION_DHCP_WPAD;
messageOut.setOption(DHCPOptions.OPTION_DHCP_PARAMETER_REQUEST_LIST, wpadOpt);
// messageOut.setOption(DHCPOptions.OPTION_DHCP_IP_ADRESS_REQUESTED,
// offerMessageIn.getYiaddr());
bindSocket.send(messageOut); // send DHCPREQUEST
System.out.println("Sending DHCPDISCOVER ...");
boolean sentinal = true;
int counter = 0;
while (sentinal) {
if (counter == 2) { return null; }
if (bindSocket.receive(messageIn)) {
if (messageOut.getXid() == messageIn.getXid()) {
sentinal = false;
} else {
bindSocket.send(messageOut);
counter++;
}
} else {
bindSocket.send(messageOut);
counter++;
}
}
} catch (SocketException e) {
System.err.println(e);
return null;
} catch (IOException e) {
System.err.println(e);
return null;
} finally {
try {
bindSocket.close();
} catch (Exception e) {
// do nothing.
}
} // end catch
return messageIn;
}
// Main method for testing.
public static void main(String[] args) throws UnsupportedEncodingException {
WpadProxySearchStrategyWithDHPC wPSSDHCP = new WpadProxySearchStrategyWithDHPC();
DHCPMessage serverAnswer = wPSSDHCP.SendDiscover();
Logger.log(WpadProxySearchStrategyWithDHPC.class, LogLevel.INFO,
"DHCP serverAnswer: {0}", new String(serverAnswer.getOption(DHCPMessage.OPTION_DHCP_WPAD), "UTF-8"));
}
private byte[] ChaddrToByte(String inChaddr) {
StringTokenizer token = new StringTokenizer(inChaddr, ":");
Integer tempInt = new Integer(0);
byte outHwaddr[] = new byte[16];
int temp;
int i = 0;
while (i < 6) {
temp = tempInt.parseInt(token.nextToken(), 16);
outHwaddr[i] = (byte) temp;
i++;
}
return outHwaddr;
}
}