com.vuze.plugin.azVPN_Helper.Checker_AirVPN.java Source code

Java tutorial

Introduction

Here is the source code for com.vuze.plugin.azVPN_Helper.Checker_AirVPN.java

Source

/*
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package com.vuze.plugin.azVPN_Helper;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.message.BasicNameValuePair;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.gudy.azureus2.core3.util.UrlUtils;
import org.gudy.azureus2.core3.xml.simpleparser.SimpleXMLParserDocumentFactory;
import org.gudy.azureus2.platform.PlatformManager;
import org.gudy.azureus2.platform.PlatformManagerFactory;
import org.gudy.azureus2.plugins.PluginConfig;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.platform.PlatformManagerException;
import org.gudy.azureus2.plugins.ui.config.*;
import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel;
import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocument;
import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentAttribute;
import org.gudy.azureus2.plugins.utils.xml.simpleparser.SimpleXMLParserDocumentNode;

import com.aelitis.azureus.core.proxy.AEProxySelector;
import com.aelitis.azureus.core.proxy.AEProxySelectorFactory;
import com.aelitis.azureus.util.MapUtils;

/**
 * AirVPN
 * 
 * https://airvpn.org/
 * 
 * No RPC for port forwarding, so we use https posting and scraping :(
 * 
 * TODO: Port Cycling
 */
public class Checker_AirVPN extends CheckerCommon {

    private static final String VPN_DOMAIN = "airvpn.org";

    private static final String VPN_LOGIN_URL = "https://" + VPN_DOMAIN + "/login";

    private static final String VPN_PORTS_URL = "https://" + VPN_DOMAIN + "/ports/";

    private static final String REGEX_ActionURL = "action=\"([^\"]+)\"";

    private static final String REGEX_AuthKey = "name=['\"]auth_key['\"]\\s*value=['\"]([^\"']+)['\"]";

    private static final String REGEX_Port = "class=\"ports_port\">([0-9]+)<";

    private static final String REGEX_Token = "name=['\"]csrf_token['\"]\\s*value=['\"]([^\"']+)['\"]";

    private static final String REGEX_PortForwardedTo = "<td>Forwarded to:</td><td>([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)</td>";

    private static final String REGEX_NotConnected = "\"Not connected\"";

    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36";

    private HttpClientContext httpClientContext;

    public Checker_AirVPN() {
        super();
    }

    public Checker_AirVPN(PluginInterface pi) {
        super(pi);
    }

    // no groups
    public static List<Parameter> setupConfigModel(PluginInterface pi, BasicPluginConfigModel configModel) {
        List<Parameter> params = new ArrayList<Parameter>(1);

        params.add(configModel.addLabelParameter2("airvpn.login.group.explain"));
        StringParameter paramUser = configModel.addStringParameter2(PluginConstants.CONFIG_USER,
                "vpnhelper.config.user", "");
        params.add(paramUser);
        PasswordParameter paramPass = configModel.addPasswordParameter2(PluginConstants.CONFIG_P,
                "vpnhelper.config.pass", PasswordParameter.ET_PLAIN, new byte[] {});
        params.add(paramPass);

        return params;
    }

    protected String getDefaultUsername() {
        try {
            File vpnConfigPath = getVPNConfigPath();
            if (vpnConfigPath == null) {
                return "";
            }

            File fileSettings = new File(vpnConfigPath, "AirVPN.xml");
            if (!fileSettings.isFile() || !fileSettings.canRead()) {
                return "";
            }
            SimpleXMLParserDocument xml = SimpleXMLParserDocumentFactory.create(fileSettings);
            SimpleXMLParserDocumentNode options = xml.getChild("options");
            SimpleXMLParserDocumentNode[] children = options.getChildren();

            Map<String, String> mapOptions = new HashMap<String, String>();

            for (SimpleXMLParserDocumentNode child : children) {
                SimpleXMLParserDocumentAttribute name = child.getAttribute("name");
                if (name != null) {
                    SimpleXMLParserDocumentAttribute value = child.getAttribute("value");
                    if (value != null) {
                        mapOptions.put(name.getValue(), value.getValue());
                    }
                }
            }
            String user = MapUtils.getMapString(mapOptions, "login", "");

            return user;
        } catch (Exception e) {
            PluginVPNHelper.log("Get login name: " + e.toString());
            return "";
        }
    }

    protected String getPassword() {
        try {
            File vpnConfigPath = getVPNConfigPath();
            if (vpnConfigPath == null) {
                return "";
            }

            File fileSettings = new File(vpnConfigPath, "AirVPN.xml");
            if (!fileSettings.isFile() || !fileSettings.canRead()) {
                return "";
            }
            SimpleXMLParserDocument xml = SimpleXMLParserDocumentFactory.create(fileSettings);
            SimpleXMLParserDocumentNode options = xml.getChild("options");
            SimpleXMLParserDocumentNode[] children = options.getChildren();

            Map<String, String> mapOptions = new HashMap<String, String>();

            for (SimpleXMLParserDocumentNode child : children) {
                SimpleXMLParserDocumentAttribute name = child.getAttribute("name");
                if (name != null) {
                    SimpleXMLParserDocumentAttribute value = child.getAttribute("value");
                    if (value != null) {
                        mapOptions.put(name.getValue(), value.getValue());
                    }
                }
            }
            String pw = MapUtils.getMapString(mapOptions, "password", "");

            return pw;
        } catch (Exception e) {
            PluginVPNHelper.log("Get login creds: " + e.toString());
            return "";
        }
    }

    protected boolean canReach(InetAddress addressToReach) {
        try {
            URI canReachURL = new URI("https://" + VPN_DOMAIN);
            return canReach(addressToReach, canReachURL);
        } catch (URISyntaxException e) {
            return false;
        }
    }

    @Override
    protected boolean callRPCforPort(InetAddress bindIP, StringBuilder sReply) {
        InetAddress[] resolve = null;
        try {

            String user = getDefaultUsername();
            String pass = null;
            if (user == null || user.length() == 0) {
                user = config.getPluginStringParameter(PluginConstants.CONFIG_USER);
                pass = new String(config.getPluginByteParameter(PluginConstants.CONFIG_P, new byte[0]), "utf-8");
            } else {
                pass = getPassword();
            }

            if (user == null || user.length() == 0 || pass == null || pass.length() == 0) {
                addReply(sReply, CHAR_WARN, "airvpn.rpc.nocreds");
                return false;
            }

            // If Vuze has a proxy set up (Tools->Options->Connection->Proxy), then
            // we'll need to disable it for the URL
            AEProxySelector selector = AEProxySelectorFactory.getSelector();
            if (selector != null) {
                resolve = SystemDefaultDnsResolver.INSTANCE.resolve(VPN_DOMAIN);

                for (InetAddress address : resolve) {
                    selector.setProxy(new InetSocketAddress(address, 443), Proxy.NO_PROXY);
                }
            }

            RequestConfig requestConfig;
            StringBuffer token = new StringBuffer();

            boolean skipLoginPage = false;
            boolean alreadyLoggedIn = false;
            PortInfo[] ports = null;

            if (httpClientContext == null) {
                httpClientContext = HttpClientContext.create();
            } else {
                PluginVPNHelper.log("Have existing context.  Trying to grab port list without logging in.");
                ports = scrapePorts(bindIP, token);
                // assume no token means we aren't logged in
                if (token.length() > 0) {
                    PluginVPNHelper.log("Valid ports page. Skipping Login");
                    skipLoginPage = true;
                    alreadyLoggedIn = true;

                    if (ports == null) {
                        addReply(sReply, CHAR_WARN, "airvpn.vpnhelper.rpc.notconnected");
                        return false;
                    }
                } else {
                    ports = null;
                }
            }

            if (!skipLoginPage) {
                String loginURL = null;
                String authKey = null;

                PluginVPNHelper.log("Getting Login post URL and auth_key");

                HttpGet getLoginPage = new HttpGet(VPN_LOGIN_URL);
                requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(15000).build();
                getLoginPage.setConfig(requestConfig);
                getLoginPage.setHeader("User-Agent", USER_AGENT);

                CloseableHttpClient httpClientLoginPage = HttpClients.createDefault();
                CloseableHttpResponse loginPageResponse = httpClientLoginPage.execute(getLoginPage,
                        httpClientContext);

                BufferedReader rd = new BufferedReader(
                        new InputStreamReader(loginPageResponse.getEntity().getContent()));

                Pattern patAuthKey = Pattern.compile(REGEX_AuthKey);

                String line = "";
                while ((line = rd.readLine()) != null) {
                    if (line.contains("<form") && line.matches(".*id=['\"]login['\"].*")) {
                        Matcher matcher = Pattern.compile(REGEX_ActionURL).matcher(line);
                        if (matcher.find()) {
                            loginURL = matcher.group(1);
                            if (authKey != null) {
                                break;
                            }
                        }
                    }
                    Matcher matcherAuthKey = patAuthKey.matcher(line);
                    if (matcherAuthKey.find()) {
                        authKey = matcherAuthKey.group(1);
                        if (loginURL != null) {
                            break;
                        }
                    }

                    if (line.contains("['member_id']") && line.matches(".*parseInt\\s*\\(\\s*[1-9][0-9]*\\s*.*")) {
                        alreadyLoggedIn = true;
                    }
                }
                rd.close();

                if (loginURL == null) {
                    PluginVPNHelper.log("Could not scrape Login URL.  Using default");
                    loginURL = "https://airvpn.org/index.php?app=core&module=global&section=login&do=process";
                }
                if (authKey == null) {
                    addReply(sReply, CHAR_WARN, "vpnhelper.rpc.noauthkey");
                    return false;
                }

                loginURL = UrlUtils.unescapeXML(loginURL);

                ///////////////////////////////

                if (alreadyLoggedIn) {
                    PluginVPNHelper.log("Already Logged In");
                } else {
                    PluginVPNHelper.log("Login URL:" + loginURL);
                    //https://airvpn.org/index.php?app=core&module=global&section=login&do=process
                    //https://airvpn.org/index.php?app=core&module=global&section=login&do=process

                    HttpPost httpPostLogin = new HttpPost(loginURL);

                    requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(15000).build();

                    httpPostLogin.setConfig(requestConfig);
                    httpPostLogin.setHeader("User-Agent", USER_AGENT);

                    CloseableHttpClient httpClient = HttpClients.createDefault();

                    List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
                    urlParameters.add(new BasicNameValuePair("ips_username", user));
                    urlParameters.add(new BasicNameValuePair("ips_password", pass));
                    urlParameters.add(new BasicNameValuePair("auth_key", authKey));
                    urlParameters.add(new BasicNameValuePair("referer", "http://airvpn.org/"));
                    urlParameters.add(new BasicNameValuePair("anonymous", "1"));
                    urlParameters.add(new BasicNameValuePair("rememberMe", "1"));

                    httpPostLogin.setEntity(new UrlEncodedFormEntity(urlParameters));

                    CloseableHttpResponse response = httpClient.execute(httpPostLogin, httpClientContext);

                    rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

                    line = "";
                    while ((line = rd.readLine()) != null) {
                    }

                    PluginVPNHelper.log("Login Result: " + response.getStatusLine().toString());
                }
            }

            ////////////////////////////

            if (ports == null) {
                ports = scrapePorts(bindIP, token);
                if (ports == null && token.length() > 0) {
                    addReply(sReply, CHAR_WARN, "airvpn.vpnhelper.rpc.notconnected");
                    return false;
                }
            }

            PluginVPNHelper.log("Found Ports: " + Arrays.toString(ports));

            int existingIndex = ourPortInList(ports);
            if (existingIndex >= 0) {
                addReply(sReply, CHAR_GOOD, "vpnhelper.port.from.rpc.match",
                        new String[] { ports[existingIndex].port });
                return true;
            }

            boolean gotPort = false;

            // There's a limit of 20 ports.  If [0] isn't ours and 20 of them are
            // created, then assume our detection of "ours" is broke and just use
            // the first one
            if (ports != null && ((ports.length > 0 && ports[0].ourBinding) || ports.length == 20)) {
                int port = Integer.parseInt(ports[0].port);
                gotPort = true;

                addReply(sReply, CHAR_GOOD, "vpnhelper.port.from.rpc", new String[] { Integer.toString(port) });

                changePort(port, sReply);
            } else if (ports != null) {
                // create port
                ports = createPort(bindIP, token);
                if (ports.length == 0) {
                    // form post should have got the new port, but if it didn't, try
                    // reloading the ports page again.
                    token.setLength(0);
                    ports = scrapePorts(bindIP, token);
                }

                PluginVPNHelper.log("Added a port. Ports: " + Arrays.toString(ports));

                existingIndex = ourPortInList(ports);
                if (existingIndex >= 0) {
                    addReply(sReply, CHAR_GOOD, "vpnhelper.port.from.rpc.match",
                            new String[] { ports[existingIndex].port });
                    return true;
                }

                if ((ports.length > 0 && ports[0].ourBinding) || ports.length == 20) {
                    int port = Integer.parseInt(ports[0].port);
                    gotPort = true;

                    addReply(sReply, CHAR_GOOD, "vpnhelper.port.from.rpc", new String[] { Integer.toString(port) });

                    changePort(port, sReply);
                }

            }

            if (!gotPort) {
                addReply(sReply, CHAR_WARN, "vpnhelper.rpc.no.connect", new String[] { bindIP.toString() });

                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            addReply(sReply, CHAR_BAD, "vpnhelper.rpc.no.connect", new String[] { bindIP + ": " + e.getMessage() });

            return false;
        } finally {
            AEProxySelector selector = AEProxySelectorFactory.getSelector();
            if (selector != null && resolve != null) {
                for (InetAddress address : resolve) {
                    AEProxySelectorFactory.getSelector().removeProxy(new InetSocketAddress(address, 443));
                }
            }
        }
        return true;
    }

    private PortInfo[] createPort(InetAddress bindIP, StringBuffer token)
            throws ClientProtocolException, IOException {
        HttpPost httpPostCreatePort = new HttpPost(VPN_PORTS_URL);

        RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(15000)
                .build();

        httpPostCreatePort.setConfig(requestConfig);
        httpPostCreatePort.setHeader("User-Agent", USER_AGENT);

        CloseableHttpClient httpClientCreatPort = HttpClients.createDefault();

        List<NameValuePair> urlParamsAddPort = new ArrayList<NameValuePair>();
        urlParamsAddPort.add(new BasicNameValuePair("csrf_token", token.toString()));
        urlParamsAddPort.add(new BasicNameValuePair("action", "ports_ins"));
        urlParamsAddPort.add(new BasicNameValuePair("ports_ins_port", ""));
        urlParamsAddPort.add(new BasicNameValuePair("ports_ins_protocol", "both"));
        urlParamsAddPort.add(new BasicNameValuePair("ports_ins_local", ""));
        urlParamsAddPort.add(new BasicNameValuePair("ports_ins_dns_name", ""));
        urlParamsAddPort.add(new BasicNameValuePair("ports_ins", "ports_ins"));

        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(urlParamsAddPort);
        httpPostCreatePort.setEntity(entity);

        CloseableHttpResponse responseCreatePort = httpClientCreatPort.execute(httpPostCreatePort,
                httpClientContext);
        BufferedReader rd = new BufferedReader(new InputStreamReader(responseCreatePort.getEntity().getContent()));

        String bindIPString = bindIP == null ? null : bindIP.getHostAddress();

        PortInfo[] ports = parsePorts(rd, bindIPString, token);
        rd.close();

        return ports;
    }

    private PortInfo[] scrapePorts(InetAddress bindIP, StringBuffer token)
            throws ClientProtocolException, IOException {
        String bindIPString = bindIP == null ? null : bindIP.getHostAddress();
        HttpGet getPortsPage = new HttpGet(VPN_PORTS_URL);
        RequestConfig requestConfig = RequestConfig.custom().setLocalAddress(bindIP).setConnectTimeout(15000)
                .build();
        getPortsPage.setConfig(requestConfig);
        getPortsPage.setHeader("User-Agent", USER_AGENT);

        CloseableHttpClient httpClientPortsPage = HttpClients.createDefault();
        CloseableHttpResponse portsPageResponse = httpClientPortsPage.execute(getPortsPage, httpClientContext);

        BufferedReader rd = new BufferedReader(new InputStreamReader(portsPageResponse.getEntity().getContent()));

        PortInfo[] ports = parsePorts(rd, bindIPString, token);

        rd.close();

        return ports;
    }

    private PortInfo[] parsePorts(BufferedReader rd, String bindIPString, StringBuffer token) throws IOException {
        Pattern patPort = Pattern.compile(REGEX_Port);
        Pattern patToken = Pattern.compile(REGEX_Token);
        Pattern patFwdToIP = Pattern.compile(REGEX_PortForwardedTo);
        Pattern patNotConnected = Pattern.compile(REGEX_NotConnected);

        Map<String, PortInfo> mapPorts = new HashMap<String, PortInfo>();

        String line = "";
        String lastPortFound = null;
        while ((line = rd.readLine()) != null) {
            Matcher matcher = patPort.matcher(line);
            boolean found = matcher.find();
            if (found) {
                while (found) {
                    lastPortFound = matcher.group(1);
                    mapPorts.put(lastPortFound, new PortInfo(lastPortFound, false));

                    Matcher matcherFwdToIP = patFwdToIP.matcher(line);
                    while (matcherFwdToIP.find()) {
                        String ip = matcherFwdToIP.group(1);
                        if (ip.equals(bindIPString)) {
                            mapPorts.put(lastPortFound, new PortInfo(lastPortFound, true));
                        }
                    }

                    found = matcher.find();
                }
            } else {
                if (lastPortFound != null) {
                    Matcher matcherFwdToIP = patFwdToIP.matcher(line);
                    while (matcherFwdToIP.find()) {
                        String ip = matcherFwdToIP.group(1);
                        if (ip.equals(bindIPString)) {
                            mapPorts.put(lastPortFound, new PortInfo(lastPortFound, true));
                        }
                    }
                }
            }

            if (token != null && token.length() == 0) {
                Matcher matcherToken = patToken.matcher(line);
                if (matcherToken.find()) {
                    token.append(matcherToken.group(1));
                }
            }

            Matcher matcherNC = patNotConnected.matcher(line);
            if (matcherNC.find()) {
                PluginVPNHelper.log("AirVPN's ports page indicates you are not connected to VPN");
                return null;
            }
        }

        PortInfo[] array = mapPorts.values().toArray(new PortInfo[0]);
        Arrays.sort(array, new Comparator<PortInfo>() {
            public int compare(PortInfo x, PortInfo y) {
                return Boolean.valueOf(x.ourBinding).compareTo(Boolean.valueOf(y.ourBinding));
            }
        });
        return array;
    }

    protected File getVPNConfigPath() {
        PlatformManager platformManager = PlatformManagerFactory.getPlatformManager();

        try {
            File fDocPath = platformManager.getLocation(PlatformManager.LOC_USER_DATA);
            if (fDocPath != null) {
                File f = new File(fDocPath.getParentFile(), Constants.isLinux ? ".airvpn" : "AirVPN");
                if (f.isDirectory()) {
                    return f;
                }
            }
        } catch (PlatformManagerException e) {
        }

        String appData;
        String userhome = System.getProperty("user.home");

        if (Constants.isWindows) {
            appData = SystemProperties.getEnvironmentalVariable("LOCALAPPDATA");

            if (appData != null && appData.length() > 0) {
            } else {
                appData = userhome + SystemProperties.SEP + "Application Data";
            }

        } else if (Constants.isOSX) {
            appData = userhome + SystemProperties.SEP + "Library" + SystemProperties.SEP + "Application Support";

            appData = userhome;
        } else {
            // unix type
            appData = userhome;
        }

        File f = new File(appData, Constants.isWindows ? "AirVPN" : ".airvpn");
        if (f.isDirectory()) {
            return f;
        }
        return null;
    }

    private int ourPortInList(PortInfo[] ports) {
        if (ports == null) {
            return -1;
        }
        PluginConfig pluginConfig = pi.getPluginconfig();
        int coreTCPPort = pluginConfig.getCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_TCP_PORT);
        int coreUDPPort = pluginConfig.getCoreIntParameter(PluginConfig.CORE_PARAM_INT_INCOMING_UDP_PORT);
        if (coreTCPPort != coreUDPPort) {
            return -1;
        }

        boolean skipIfNotOurBinding = ports.length != 20;

        String sPort = Integer.toString(coreUDPPort);
        for (int i = 0; i < ports.length; i++) {
            PortInfo portInfo = ports[i];
            if (!portInfo.ourBinding && skipIfNotOurBinding) {
                return -1;
            }
            if (portInfo.port.equals(sPort)) {
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Checker_AirVPN checker = new Checker_AirVPN();
        File vpnConfigPath = checker.getVPNConfigPath();
        System.out.println(vpnConfigPath);
        String defaultUsername = checker.getDefaultUsername();
        System.out.println("user=" + defaultUsername + "/" + checker.getPassword());

        checker.callRPCforPort(null, new StringBuilder());
    }

    private static class PortInfo {
        String port;

        boolean ourBinding;

        public PortInfo(String port, boolean ourBinding) {
            super();
            this.port = port;
            this.ourBinding = ourBinding;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return port + ";" + ourBinding;
        }
    }

    public boolean showLoginConfig() {
        return getDefaultUsername().length() == 0;
    }
}