diff options
author | Fil <fil.bergamo@riseup.net> | 2018-07-04 19:14:17 +0200 |
---|---|---|
committer | Fil <fil.bergamo@riseup.net> | 2018-07-04 19:29:53 +0200 |
commit | 121a4bec96d2f9b842c6d393d2b9c6356a9a4405 (patch) | |
tree | da1c557520d670def2f755492e3afd62ce891cf6 /app/src/fil/libre/repwifiapp/network | |
parent | a6b34d6c75109b831976bc872a5a0a47a08c1664 (diff) | |
download | RepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.tar.gz RepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.tar.bz2 RepWifiApp-121a4bec96d2f9b842c6d393d2b9c6356a9a4405.zip |
Add full integration with the Connectivity Frameworkv0.9-beta
Fixes Issue #1867 "Download app not working".
Uses proxy classes to hook into the application framework,
solving almost any problem with missing connectivity features,
creating a connection that is fully managed by the framework itself.
Introduces a background service that performs all backend operations.
Cleans up code design, refining structure of classes and entities.
Diffstat (limited to 'app/src/fil/libre/repwifiapp/network')
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/AccessPointInfo.java | 442 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/ConnectionResult.java | 95 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/ConnectionStatus.java | 298 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/DhcpSettings.java | 242 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/Engine.java | 168 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/Engine6p0.java | 315 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/IEngine.java | 37 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/NetworkButton.java | 39 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/NetworkManager.java | 379 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/WpaCli.java | 252 | ||||
-rw-r--r-- | app/src/fil/libre/repwifiapp/network/WpaSupplicant.java | 91 |
11 files changed, 2358 insertions, 0 deletions
diff --git a/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java b/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java new file mode 100644 index 0000000..862a852 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/AccessPointInfo.java @@ -0,0 +1,442 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import android.os.Parcel; +import android.os.Parcelable; +import fil.libre.repwifiapp.Utils; +import fil.libre.repwifiapp.helpers.Logger; +import org.json.JSONException; +import org.json.JSONObject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class AccessPointInfo implements Parcelable { + + public static final String DUMMY_VPN_PROFILE = "--------"; + + private static final int MAX_SSID_LENGTH = 32; + protected static final String SCAN_FILE_HDR = "bssid / frequency / signal level / flags / ssid"; + + private static final String JSONKEY_BSSID = "BSSID"; + private static final String JSONKEY_SSID = "SSID"; + private static final String JSONKEY_LASTUSED = "LastUsed"; + private static final String JSONKEY_AUTH = "Auth"; + private static final String JSONKEY_PSK = "PSK"; + private static final String JSONKEY_VPN_PROFILE = "VpnProfile"; + private static final String JSONKEY_DHCPSETS = "DhcpSettings"; + + private String _ssid; + private String _bssid; + private String _auth; + private String _level; + private String _freq; + private String _password; + private String _vpnProfileName = null; + private boolean _isHidden = false; + private long _lastTimeUsed; + private DhcpSettings _dhcpsets; + + public AccessPointInfo(String ssid, String bssid, String authType, String level, String freq) { + + this._ssid = ssid; + this._bssid = bssid; + this._auth = authType; + this._level = level; + this._freq = freq; + + } + + private AccessPointInfo(){ + // for inner use; + } + + public void setDhcpConfiguration(DhcpSettings sets) { + this._dhcpsets = sets; + } + + public DhcpSettings getDhcpConfiguration() { + + if (this._dhcpsets == null) { + return DhcpSettings.getDefault(); + } else { + return this._dhcpsets; + } + } + + public String getSsid() { + return this._ssid; + } + + public String getSsid(int maxLength) { + String txt = getSsid(); + if (maxLength > 4 && txt.length() > maxLength) { + txt = txt.substring(0, maxLength - 3) + "..."; + } + return txt; + } + + public void setHidden(boolean hidden) { + this._isHidden = hidden; + } + + public boolean isHidden() { + return this._isHidden; + } + + public String getBssid() { + return this._bssid; + } + + public void setBssid(String bssid) { + this._bssid = bssid; + } + + public String getVpnProfileName(){ + if (_vpnProfileName == null){ + return ""; + } else { + return _vpnProfileName; + } + } + + public void setVpnProfileName(String profileName){ + + if (profileName != null && profileName.equals(DUMMY_VPN_PROFILE)){ + profileName = ""; + } + + _vpnProfileName = profileName; + } + + public String getAuthType() { + if (_auth == null){ + return ""; + } + return this._auth; + } + + public int getSignlalStrength() { + // return this._level; + + if (this._level == null || this._level.isEmpty()) { + return 0; + } + + int retval = 0; + + try { + retval = Integer.parseInt(this._level); + } catch (NumberFormatException e) { + retval = 0; + } + + return retval; + + } + + public String getFrequency() { + return this._freq; + } + + public long getLastTimeUsed() { + return this._lastTimeUsed; + } + + public void setLastTimeUsed(long timeStampInMillis) { + this._lastTimeUsed = timeStampInMillis; + } + + public boolean isOlderThan(int days) { + + if (this._lastTimeUsed == 0) { + return false; + } + + long timeDiff = System.currentTimeMillis() - this._lastTimeUsed; + long spanMillis = Utils.daysToMilliseconds(days); + + if (timeDiff > spanMillis) { + return true; + } else { + return false; + } + + } + + public String getPassword() { + if (_password == null){ + return ""; + } + return this._password; + } + + public void setPassword(String password) { + this._password = password; + } + + public boolean needsPassword() { + + if ((this._auth == null) || (this._auth.equals(""))) { + // TODO + // check if default behavior should be with or without password, + // when no auth info is available. + return false; + } + + if (this._auth.contains("WPA2") || this._auth.contains("WPA")) { + return true; + } else { + return false; + } + + } + + protected static AccessPointInfo parseLine(String line) { + + try { + + String[] params = line.split("\t"); + if (params.length != 5) { + return null; + } + + String bssid = params[0]; + String freq = params[1]; + String level = params[2]; + String auth = params[3]; + String ssid = params[4]; + + if (ssid.length() == 0 || ssid.length() > MAX_SSID_LENGTH) { + // invalid SSID. + return null; + } + + AccessPointInfo info = new AccessPointInfo(ssid, bssid, auth, level, freq); + return info; + + } catch (Exception e) { + Logger.logError("Error while parsing line: " + line, e); + return null; + } + + } + + public static AccessPointInfo[] parseScanResult(String scanResultContent) { + + try { + + if (scanResultContent == null) { + return null; + } + + Logger.logDebug("AccesPointInfo trying to parse file scan content:\n" + + scanResultContent); + + String[] lines = scanResultContent.split("\n"); + List<AccessPointInfo> nets = new ArrayList<AccessPointInfo>(); + + if (lines == null) { + return null; + } + + for (String l : lines) { + if (l.startsWith(SCAN_FILE_HDR)) { + // strip off the header + continue; + } + + if (l.trim().equals("")) { + // empty line, skip. + continue; + } + + // try to parse line into network info + AccessPointInfo info = AccessPointInfo.parseLine(l); + if (info == null) { + Logger.logError("Failed to parse line into AccessPointInfo: " + l); + continue; + } + + nets.add(info); + + } + + sortInfosBySignalStrength(nets); + + AccessPointInfo[] a = new AccessPointInfo[nets.size()]; + a = nets.toArray(a); + return a; + + } catch (Exception e) { + Logger.logError("Error while parsing scan results in class AccessPointInfo", e); + return null; + } + + } + + public static AccessPointInfo[] fromParcellableArray(Parcelable[] p){ + if (p == null) { + return null; + } + + AccessPointInfo[] infos = new AccessPointInfo[p.length]; + for (int j = 0; j < infos.length; j++) { + infos[j] = (AccessPointInfo) p[j]; + } + + return infos; + } + + public JSONObject toJson(){ + + try { + + JSONObject j = new JSONObject(); + + j.put(JSONKEY_BSSID, getBssid()); + j.put(JSONKEY_SSID, getSsid()); + j.put(JSONKEY_PSK, getPassword()); + j.put(JSONKEY_AUTH, getAuthType()); + j.put(JSONKEY_LASTUSED, getLastTimeUsed()); + j.put(JSONKEY_VPN_PROFILE, getVpnProfileName()); + + DhcpSettings sets = getDhcpConfiguration(); + if (sets != null){ + JSONObject dhcpj = sets.toJson(); + if (dhcpj != null){ + j.put(JSONKEY_DHCPSETS, dhcpj); + } + + } + + return j; + + } catch (JSONException e) { + Logger.logError("Exception while converting AccessPointInfo to JSON.", e); + return null; + } + + } + + public static AccessPointInfo fromJsonObject(JSONObject json){ + + if (json == null || json.isNull(JSONKEY_BSSID) || json.isNull(JSONKEY_SSID)){ + return null; + } + + AccessPointInfo info = new AccessPointInfo(); + + try { + info._bssid = json.getString(JSONKEY_BSSID); + info._ssid = json.getString(JSONKEY_SSID); + info._auth = json.getString(JSONKEY_AUTH); + info._lastTimeUsed = json.getLong(JSONKEY_LASTUSED); + + if (json.has(JSONKEY_PSK) && ! json.isNull(JSONKEY_PSK)){ + info._password = json.getString(JSONKEY_PSK); + } + + if ( json.has(JSONKEY_VPN_PROFILE) && ! json.isNull(JSONKEY_VPN_PROFILE)){ + info._vpnProfileName = json.getString(JSONKEY_VPN_PROFILE); + } + + if (json.has(JSONKEY_DHCPSETS) && ! json.isNull(JSONKEY_DHCPSETS)){ + info._dhcpsets = DhcpSettings.fromJsonObject(json.getJSONObject(JSONKEY_DHCPSETS)); + } + + return info; + + } catch (JSONException e) { + Logger.logError("Exception while parsing json object to AccessPointInfo", e); + return null; + } + + + + + } + + private static void sortInfosBySignalStrength(List<AccessPointInfo> toSort) { + + Collections.sort(toSort, new Comparator<AccessPointInfo>() { + public int compare(AccessPointInfo o1, AccessPointInfo o2) { + if (o1.getSignlalStrength() == o2.getSignlalStrength()) + return 0; + return o1.getSignlalStrength() < o2.getSignlalStrength() ? -1 : 1; + } + }); + + } + + @Override + public int describeContents() { + return 0; + } + + public AccessPointInfo(Parcel in){ + + this._ssid = in.readString(); + this._bssid = in.readString(); + this._auth = in.readString(); + this._level = in.readString(); + this._freq = in.readString(); + this._password = in.readString(); + this._vpnProfileName = in.readString(); + this._isHidden = in.readInt() == 1 ? true : false; + this._lastTimeUsed = in.readLong(); + this._dhcpsets = in.readParcelable(DhcpSettings.class.getClassLoader()); + + } + + public static final Parcelable.Creator<AccessPointInfo> CREATOR = new Parcelable.Creator<AccessPointInfo>() { + public AccessPointInfo createFromParcel(Parcel in) { + return new AccessPointInfo(in); + } + + @Override + public AccessPointInfo[] newArray(int size) { + return new AccessPointInfo[size]; + } + + + + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + + dest.writeString(_ssid); + dest.writeString(_bssid); + dest.writeString(_auth); + dest.writeString(_level); + dest.writeString(_freq); + dest.writeString(_password); + dest.writeString(_vpnProfileName); + dest.writeInt(_isHidden ? 1 : 0); + dest.writeLong(_lastTimeUsed); + dest.writeParcelable(_dhcpsets, flags); + + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/ConnectionResult.java b/app/src/fil/libre/repwifiapp/network/ConnectionResult.java new file mode 100644 index 0000000..32ae771 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/ConnectionResult.java @@ -0,0 +1,95 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import android.os.Parcel; +import android.os.Parcelable; + +public class ConnectionResult implements Parcelable { + + public static final int CONN_OK = 0; + public static final int CONN_FAILED = 1; + public static final int CONN_TIMEOUT = 2; + // public static final int CONN_DNS_FAIL = 3; // Removed as dns is now set + // by the framework + public static final int CONN_GW_FAIL = 4; + public static final int CONN_ABORTED = 5; + public static final int CONN_NOTIFY_FAILED = 6; + public static final int NO_RESULT = -1; + + private ConnectionStatus _status = null; + private int _result = NO_RESULT; + + public ConnectionResult(int result) { + this._result = result; + this._status = null; + } + + public ConnectionStatus getStatus() { + return _status; + } + + public int getResult() { + return _result; + } + + public void setStatus(ConnectionStatus status) { + + // keep coherence between the result code and the status + if (status != null && status.isConnected() && this._result == CONN_OK) { + this._status = status; + } else if (this._result == CONN_OK) { + this._result = CONN_FAILED; + this._status = null; + } else { + this._status = null; + } + } + + @Override + public int describeContents() { + return 0; + } + + public ConnectionResult(Parcel in) { + this._result = in.readInt(); + this._status = in.readParcelable(ConnectionStatus.class.getClassLoader()); + } + + public static final Parcelable.Creator<ConnectionResult> CREATOR = new Parcelable.Creator<ConnectionResult>() { + public ConnectionResult createFromParcel(Parcel in) { + return new ConnectionResult(in); + } + + @Override + public ConnectionResult[] newArray(int size) { + return new ConnectionResult[size]; + } + + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this._result); + dest.writeParcelable(this._status, flags); + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java b/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java new file mode 100644 index 0000000..5fac0a1 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/ConnectionStatus.java @@ -0,0 +1,298 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import android.os.Parcel; +import android.os.Parcelable; +import fil.libre.repwifiapp.Utils; +import fil.libre.repwifiapp.helpers.Logger; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class ConnectionStatus implements Parcelable { + + public static final String STATUS_CONNECTED = "COMPLETED"; + public static final String STATUS_INACTIVE = "INACTIVE"; + public static final String STATUS_DISCONNECTED = "DISCONNECTED"; + public static final String STATUS_UNDEFINED = "UNDEFINED"; + + public String wpaStatus = null; + public String SSID = null; + public String BSSID = null; + public String IP = null; + public String gateway = null; + public String subnetMask = null; + public String hwAddress = null; + public String broadcastAddress = null; + + private static final String F_SEP = "="; + private static final String KeyStatus = "wpa_state"; + private static final String KeySSID = "ssid"; + private static final String KeyBSSID = "bssid"; + private static final String KeyIP = "ip_address"; + + private static final String IFCONFIG_BCAST = "Bcast"; + private static final String IFCONFIG_MASK = "Mask"; + private static final String IFCONFIG_HWADDR = "HWaddr"; + private static final String IFCONFIG_KVALSEP = ":"; + private static final String IFCONFIG_FSEP = " "; + + public ConnectionStatus() { + } + + public static ConnectionStatus parseWpaCliOutput(String wpaCliOutput) { + + if (wpaCliOutput == null) { + return null; + } + + if (wpaCliOutput.trim().length() == 0) { + return null; + } + + String[] lines = wpaCliOutput.split("\n"); + + ConnectionStatus s = new ConnectionStatus(); + for (String line : lines) { + + if (line.trim().equals("")) { + continue; + } + + String[] fields = line.split(F_SEP); + if (fields.length < 2) { + continue; + } + + String key = fields[0]; + String val = fields[1]; + + if (key.equals(KeyBSSID)) { + s.BSSID = val; + } else if (key.equals(KeySSID)) { + s.SSID = val; + } else if (key.equals(KeyStatus)) { + s.wpaStatus = val; + } else if (key.equals(KeyIP)) { + s.IP = val; + } + + } + + return s; + + } + + public static ConnectionStatus getDummyDisconnected() { + ConnectionStatus s = new ConnectionStatus(); + s.wpaStatus = STATUS_DISCONNECTED; + return s; + } + + public boolean parseIfconfigOutput(String ifconfigOutput) { + + if (ifconfigOutput == null) { + return false; + } + + String[] lines = ifconfigOutput.split("\n"); + if (lines.length < 2) { + return false; + } + + String[] fields1 = lines[0].split(IFCONFIG_FSEP); + String[] fields2 = lines[1].split(IFCONFIG_FSEP); + + for (String f : fields1) { + // first line uses a single blank space as key-val separator + String[] splt = f.split(" "); + if (splt.length == 2 && splt[0].equals(IFCONFIG_HWADDR)) { + this.hwAddress = splt[1]; + } + } + + for (String f : fields2) { + String[] splt = f.split(IFCONFIG_KVALSEP); + if (splt.length == 2) { + + String key = splt[0]; + String val = splt[1]; + + if (key.equals(IFCONFIG_MASK)) { + + this.subnetMask = val.trim(); + + } else if (key.equals(IFCONFIG_BCAST)) { + + this.broadcastAddress = val; + + } else if (key.equals(IFCONFIG_HWADDR)) { + + this.hwAddress = val; + + } + + } + } + + return true; + + } + + public boolean isConnected() { + + if (this.wpaStatus == null) { + return false; + } + + if (this.wpaStatus.equals(STATUS_CONNECTED)) { + return true; + } else { + return false; + } + } + + /*** + * @return Returns a string representation of the current IP address and + * subnet mask (e.g. "192.168.1.123/24"); returns null if either the + * address or the mask is null. + */ + public String addressAndMaskToString() { + + if (this.IP == null || this.subnetMask == null) { + return null; + } + + int mask = Utils.netmaskStringToInt(this.subnetMask); + return this.IP + "/" + mask; + + } + + public InetAddress getInetAddress() { + if (this.IP == null) { + return null; + } + + try { + return InetAddress.getByName(this.IP); + } catch (UnknownHostException e) { + Logger.logError("Exception while parsing InetAddress from string", e); + return null; + } + + } + + public int getSubnetMaskInt() { + return Utils.netmaskStringToInt(this.subnetMask); + } + + public InetAddress getGatewayInetAddress() { + + if (this.gateway == null) { + return null; + } + + try { + return InetAddress.getByName(this.gateway); + } catch (Exception e) { + Logger.logError("Exception while parsing gateway's InetAddress from string.", e); + return null; + } + + } + + public AccessPointInfo getNetworkDetails() { + AccessPointInfo i = new AccessPointInfo(SSID, BSSID, "", "", ""); + return NetworkManager.getSavedNetwork(i); + } + + @Override + public int describeContents() { + return 0; + } + + public ConnectionStatus(Parcel in) { + + this.SSID = in.readString(); + this.BSSID = in.readString(); + this.IP = in.readString(); + this.subnetMask = in.readString(); + this.gateway = in.readString(); + this.hwAddress = in.readString(); + this.broadcastAddress = in.readString(); + this.wpaStatus = in.readString(); + + } + + public static final Parcelable.Creator<ConnectionStatus> CREATOR = new Parcelable.Creator<ConnectionStatus>() { + public ConnectionStatus createFromParcel(Parcel in) { + return new ConnectionStatus(in); + } + + public ConnectionStatus[] newArray(int size) { + return new ConnectionStatus[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + + dest.writeString(this.SSID); + dest.writeString(this.BSSID); + dest.writeString(this.IP); + dest.writeString(this.subnetMask); + dest.writeString(this.gateway); + dest.writeString(this.hwAddress); + dest.writeString(this.broadcastAddress); + dest.writeString(this.wpaStatus); + + } + + public boolean equals(ConnectionStatus status) { + + if (status == null) { + return false; + } + + if (status.isConnected() != this.isConnected()) { + return false; + } + + return fieldEquals(this.IP, status.IP) && fieldEquals(this.BSSID, status.BSSID) + && fieldEquals(this.SSID, status.SSID) + && fieldEquals(this.subnetMask, status.subnetMask) + && fieldEquals(this.gateway, status.gateway) + && fieldEquals(this.hwAddress, status.hwAddress); + + } + + private boolean fieldEquals(String myField, String extField) { + return myField == null ? extField == null : myField.equals(extField); + } + + @Override + public String toString() { + return String.format( + "WPAsts: %s; \nIP: %s; \nMask: %s; \nGway: %s; \nBcast: %s; \nHWaddr: %s ", + wpaStatus, IP, subnetMask, gateway, broadcastAddress, hwAddress); + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/DhcpSettings.java b/app/src/fil/libre/repwifiapp/network/DhcpSettings.java new file mode 100644 index 0000000..65524d5 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/DhcpSettings.java @@ -0,0 +1,242 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import android.nfc.FormatException; +import android.os.Parcel; +import android.os.Parcelable; +import fil.libre.repwifiapp.Utils; +import fil.libre.repwifiapp.helpers.Logger; +import org.apache.http.conn.util.InetAddressUtils; +import org.json.JSONException; +import org.json.JSONObject; + +public class DhcpSettings implements Parcelable { + + public boolean useDhcp; + private String _staticIP; + private int _mask; + private String _defGw; + + private static final String JSONKEY_DHCP = "DHCP"; + private static final String JSONKEY_STATIC_IP = "StaticIP"; + private static final String JSONKEY_GW = "Gateway"; + + public DhcpSettings(boolean useDhcp, String staticIP, String subnetMask, String defaultGateway) + throws FormatException { + this(useDhcp, staticIP, Utils.netmaskStringToInt(subnetMask), defaultGateway); + } + + public DhcpSettings(boolean useDhcp, String staticIP, int subnetMask, String defaultGatweay) + throws FormatException { + + this.useDhcp = useDhcp; + + if (!useDhcp) { + + if (!validateParams(staticIP, defaultGatweay, subnetMask)) { + throw new FormatException("Invalid dhcp parameters!"); + } + + this._staticIP = staticIP; + this._mask = subnetMask; + this._defGw = defaultGatweay; + + } + + } + + private DhcpSettings() { + // inner use + } + + public static DhcpSettings parseSavedSettings(String staticIPwithMask, String defaultGateway) { + + try { + + String[] ipm = staticIPwithMask.split("/"); + String ip = ipm[0]; + int mask = Integer.parseInt(ipm[1]); + + return new DhcpSettings(false, ip, mask, defaultGateway); + + } catch (Exception e) { + Logger.logError("Exception while parsing DhcpSettings for saved network. Reverting to dhcp.", + e); + return null; + } + + } + + public static DhcpSettings getDefault() { + try { + return new DhcpSettings(true, null, 24, null); + } catch (FormatException e) { + // no format exception can happen. + return null; + } + } + + private boolean validateParams(String ip, String gateway, int mask) { + + if (isValidAddress(ip) && isValidAddress(gateway) && isValidMask(mask)) { + return true; + } else { + return false; + } + + } + + public static boolean isValidAddress(String ipAddress) { + return InetAddressUtils.isIPv4Address(ipAddress); + } + + public static boolean isValidMaks(String mask) { + int m = Utils.netmaskStringToInt(mask); + return isValidMask(m); + } + + public static boolean isValidMask(int mask) { + if (mask >= 8 && mask <= 32) { + return true; + } else { + return false; + } + } + + public String getStaticIP() { + if (_staticIP == null) { + return ""; + } + return _staticIP; + } + + public String getStaticIPwithMask() { + return getStaticIP() + "/" + String.valueOf(getSubnetMaskInt()); + } + + public int getSubnetMaskInt() { + return _mask; + } + + public String getSubnetMaskString() { + String v = Utils.netmaskIntToString(_mask); + if (v == null) { + return ""; + } + return v; + } + + public String getDefaultGateway() { + if (_defGw == null) { + return ""; + } + return _defGw; + } + + public JSONObject toJson() { + + JSONObject j = new JSONObject(); + + try { + j.put(JSONKEY_DHCP, useDhcp); + j.put(JSONKEY_GW, getDefaultGateway()); + j.put(JSONKEY_STATIC_IP, getStaticIPwithMask()); + + return j; + + } catch (JSONException e) { + Logger.logError("Exception while converting DhcpSettings to JSON.", e); + return null; + } + + } + + public static DhcpSettings fromJsonObject(JSONObject json) { + + if (json == null) { + return null; + } + + DhcpSettings sets = new DhcpSettings(); + + try { + + sets.useDhcp = json.getBoolean(JSONKEY_DHCP); + + if (json.has(JSONKEY_GW) && !json.isNull(JSONKEY_GW)) { + sets._defGw = json.getString(JSONKEY_GW); + } + + if (json.has(JSONKEY_STATIC_IP) && !json.isNull(JSONKEY_STATIC_IP)) { + + String[] splt = json.getString(JSONKEY_STATIC_IP).split("/"); + sets._staticIP = splt[0]; + sets._mask = Integer.parseInt(splt[1]); + + } + + return sets; + + } catch (Exception e) { + Logger.logError("Exception while parsing json object to DhcpSettings", e); + return null; + } + + } + + @Override + public int describeContents() { + return 0; + } + + public DhcpSettings(Parcel in) { + + this.useDhcp = in.readInt() == 1 ? true : false; + this._staticIP = in.readString(); + this._mask = in.readInt(); + this._defGw = in.readString(); + + } + + public static final Parcelable.Creator<DhcpSettings> CREATOR = new Parcelable.Creator<DhcpSettings>() { + public DhcpSettings createFromParcel(Parcel in) { + return new DhcpSettings(in); + } + + @Override + public DhcpSettings[] newArray(int size) { + return new DhcpSettings[size]; + } + + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + + dest.writeInt(useDhcp ? 1 : 0); + dest.writeString(_staticIP); + dest.writeInt(_mask); + dest.writeString(_defGw); + + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/Engine.java b/app/src/fil/libre/repwifiapp/network/Engine.java new file mode 100644 index 0000000..34de124 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/Engine.java @@ -0,0 +1,168 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import fil.libre.repwifiapp.Commons; +import fil.libre.repwifiapp.helpers.Logger; +import fil.libre.repwifiapp.helpers.RootCommand; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +public abstract class Engine implements IEngine { + + public static final String PIDFILE_DHCPCD = "/data/misc/dhcp/dhcpcd-wlan0.pid"; + + + @Override + public AccessPointInfo[] getAvailableNetworks() { + + Logger.logDebug("getAvailableNetworks():"); + + if (!WpaSupplicant.start()) { + Logger.logError("Failed starting wpa_supplicant"); + return null; + } + + if (!WpaCli.scanNetworks()) { + Logger.logError("failed scanning networks"); + return null; + } + + String scanRes = WpaCli.getScanResults(); + + if (scanRes == null) { + Logger.logError("failed getting scan results"); + return null; + } + + AccessPointInfo[] a = AccessPointInfo.parseScanResult(scanRes); + if (a == null) { + Logger.logError("Unable to parse scan file into AccessPointInfo array"); + return a; + } + + Logger.logDebug("# of APs found: " + a.length); + + return a; + + } + + public ConnectionStatus getConnectionStatus() { + + ConnectionStatus s = WpaCli.getConnectionStatus(); + if (s == null){ + return null; + } + + String ifcfg = getIfconfigString(); + s.parseIfconfigOutput(ifcfg); + return s; + } + + private String getIfconfigString(){ + + // needs root for accessing encapsulation and hardware address (tested). + RootCommand cmd = new RootCommand("ifconfig " + WpaSupplicant.INTERFACE_NAME); + + try { + if (cmd.execute() != 0){ + Logger.logError("FAILED to run ifconfig to obtain interface info."); + return null; + } + } catch (Exception e) { + Logger.logError("Exception while running ifconfig.", e); + return null; + } + + return cmd.getOutput(); + + } + + @Override + public abstract int connect(AccessPointInfo info); + + @Override + public boolean disconnect() { + + return WpaCli.disconnect(); + + } + + public static boolean isInterfaceAvailable(String ifaceName) throws SocketException { + + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface nif = interfaces.nextElement(); + String nm = nif.getName(); + if (nm.equals(ifaceName)) { + return true; + } + } + return false; + + } + + public int runDhcpcd() throws Exception{ + // needs root + // option -w avoids dhcpcd forking to background, + // in order to keep control over its exit code, and be able to wait for it. + // option -A avoids ARP IP checking, we use it to save some seconds in the connection process. + // option -t <timeout> sets time out for obtaining a lease. + String cmdText = "dhcpcd -w -A -t " + Commons.WAIT_FOR_DHCPCD + " " + WpaSupplicant.INTERFACE_NAME; + RootCommand su = new RootCommand(cmdText); + return su.execute(); + } + + public int runDhcpcd(DhcpSettings dhcpConfig) throws Exception{ + + if (dhcpConfig == null || dhcpConfig.useDhcp){ + Logger.logDebug("running dhchpc without dhcp settings, reverting to dhcp"); + return runDhcpcd(); + } + + Logger.logDebug("Running dhcpcd with custom ip settings"); + String cmdMask = "dhcpcd -w -A -S ip_address=%s -S routers=%s -t %d %s"; + String cmd = String.format(cmdMask, + dhcpConfig.getStaticIPwithMask(), + dhcpConfig.getDefaultGateway(), + Commons.WAIT_FOR_DHCPCD, + WpaSupplicant.INTERFACE_NAME); + + RootCommand su = new RootCommand(cmd); + return su.execute(); + + } + + public static boolean killDhcpcd(){ + if (! RootCommand.executeRootCmd("killall -SIGINT dhcpcd")){ + return false; + } + return RootCommand.executeRootCmd("rm " + PIDFILE_DHCPCD); + } + + public boolean interfaceUp() { + // needs root (tested) + return RootCommand.executeRootCmd("ifconfig " + WpaSupplicant.INTERFACE_NAME + " up"); + } + + +} diff --git a/app/src/fil/libre/repwifiapp/network/Engine6p0.java b/app/src/fil/libre/repwifiapp/network/Engine6p0.java new file mode 100644 index 0000000..c6475e2 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/Engine6p0.java @@ -0,0 +1,315 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import fil.libre.repwifiapp.Commons; +import fil.libre.repwifiapp.helpers.Logger; +import fil.libre.repwifiapp.helpers.RootCommand; +import fil.libre.repwifiapp.helpers.ShellCommand; +import org.apache.http.conn.util.InetAddressUtils; + +public class Engine6p0 extends Engine { + + private Object abortFlagSync = new Object(); + private boolean abortConnectionSignaled = false; + + @Override + public int connect(AccessPointInfo info) { + + WpaSupplicant.kill(); + + + if (info == null) { + Logger.logDebug("Engine's connect() received a null AccessPointInfo"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // clear any previously set network + if (!destroyNetwork()) { + Logger.logDebug("Unable to ndc destroy network"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // clear interface's ip + if (!clearAddrs()) { + Logger.logDebug("Unable to ndc clearaddrs"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // bring up interface + if (!interfaceUp()) { + Logger.logDebug("Unable to bring up interface."); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // launch wpa_supplicant specifying our custom configuration and the + // socket file + if (!WpaSupplicant.start()) { + Logger.logDebug("Unable to run wpa start"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // create new network and get network id + String netID = WpaCli.createNetworkGetId(); + if (netID == null) { + Logger.logDebug("Unable to fetch network id"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // set network SSID + if (!WpaCli.setNetworkSSID(info.getSsid(), netID)) { + Logger.logDebug("Failed to set network ssid"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + if (info.isHidden() && !WpaCli.setNetworkScanSSID(netID)) { + Logger.logDebug("Failed to set scan_ssid 1 for hidden network."); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // set password (if any) + if (!WpaCli.setNetworkPSK(info, netID)) { + Logger.logDebug("Failed to set network psk"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // select the network we just created + if (!WpaCli.selectNetwork(netID)) { + Logger.logDebug("Unable to wpa_cli select network"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // enable the newtork + if (!WpaCli.enableNetwork(netID)) { + Logger.logDebug("Unable to wpa_cli enable_newtork"); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // kill previous dhchcd instances + if (!killDhcpcd()) { + Logger.logError("Unable to kill previous instances of dhcpcd"); + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // get DHCP + Logger.logDebug("Attempt to run dhcpcd.."); + try { + + int retcode = runDhcpcd(info.getDhcpConfiguration()); + if (retcode == 1) { + Logger.logDebug("Dhcpcd exited on timeout exceeded."); + return ConnectionResult.CONN_TIMEOUT; + + } else if (retcode != 0) { + Logger.logDebug("Dhcpcd exited with unknown code: " + retcode); + return ConnectionResult.CONN_FAILED; + + } + } catch (Exception e) { + Logger.logError("Exception while executing dhcpcd: ", e); + return ConnectionResult.CONN_FAILED; + } + + if (!checkAbortSignal()) + return ConnectionResult.CONN_ABORTED; + + // try to fetch gateway + String gw = getGateWayTimeout(Commons.WAIT_FOR_GATEWAY); + if (gw == null || !InetAddressUtils.isIPv4Address(gw)) { + // failed to get gateway + Logger.logDebug("Failed to get gateway"); + return ConnectionResult.CONN_GW_FAIL; + } + + /* + * Calls to ndc to set gateway and dns were removed 2018-04-09 + * Starting from 2018-04-20, RepWifi registers itself as a NetworkAgent. + * When we register, all the following steps are performed by + * ConnectivityService itself: + * - creating a new network + * - setting the gateway + * - setting DNS + * There is no reason to perform those actions on our side via the command line; + * it would conflict with ConnectivityService's calls to "ndc" + */ + + return ConnectionResult.CONN_OK; + + } + + public void abortConnection() { + synchronized (abortFlagSync) { + this.abortConnectionSignaled = true; + } + } + + @Override + public ConnectionStatus getConnectionStatus() { + ConnectionStatus s = super.getConnectionStatus(); + if (s == null) { + return null; + } + + s.gateway = this.getGateway(); + + return s; + } + + private boolean checkAbortSignal() { + + synchronized (abortFlagSync) { + if (abortConnectionSignaled) { + abortConnectionSignaled = false; + Logger.logDebug("Engine received abort connection signal. Aborting connection..."); + killDhcpcd(); + disconnect(); + return false; + } else { + return true; + } + } + } + + private boolean destroyNetwork() { + + // needs root (tested) + return RootCommand.executeRootCmd("ndc network destroy 1"); + } + + private String getGateWayTimeout(int timeoutMillis) { + + String gw = getGateway(); + if (gw != null && !gw.trim().isEmpty()) { + return gw; + } + + Logger.logDebug("Gateway not available.. going into wait loop.."); + + // gateway not (yet) available + // waits for a maximum of timeoutMillis milliseconds + // to let the interface being registered. + int msWaited = 0; + while (msWaited < timeoutMillis) { + + try { + Thread.sleep(100); + } catch (Exception e) { + return null; + } + msWaited += 100; + + gw = getGateway(); + if (gw != null && !gw.trim().isEmpty()) { + Logger.logDebug("Gateway found after wait loop!"); + return gw; + } + } + + // unable to get gateway + Logger.logError("Gateway not found after wait loop."); + return null; + + } + + private String getGateway() { + + try { + + // doesn't need root (tested) + ShellCommand cmd = new ShellCommand("ip route show dev " + WpaSupplicant.INTERFACE_NAME); + if (cmd.execute() != 0) { + Logger.logDebug("command failed show route"); + return null; + } + + // read command output + String out = cmd.getOutput(); + if (out == null) { + return null; + } + + String[] lines = out.split("\n"); + + for (String l : lines) { + + if (l.contains("default via")) { + + String[] f = l.split(" "); + if (f.length > 2) { + + // found route's address: + return f[2]; + + } + } + } + + return null; + + } catch (Exception e) { + Logger.logError("Error while trying to fetch route", e); + return null; + } + + } + + private boolean clearAddrs() { + // needs root (tested) + return RootCommand.executeRootCmd("ndc interface clearaddrs " + + WpaSupplicant.INTERFACE_NAME); + } + +}
\ No newline at end of file diff --git a/app/src/fil/libre/repwifiapp/network/IEngine.java b/app/src/fil/libre/repwifiapp/network/IEngine.java new file mode 100644 index 0000000..d598550 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/IEngine.java @@ -0,0 +1,37 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +public interface IEngine { + + public AccessPointInfo[] getAvailableNetworks(); + + public int connect(AccessPointInfo info); + + public boolean disconnect(); + + public ConnectionStatus getConnectionStatus(); + + // public boolean isInterfaceAvailable(String ifaceName) throws SocketException; + + public void abortConnection(); + +} diff --git a/app/src/fil/libre/repwifiapp/network/NetworkButton.java b/app/src/fil/libre/repwifiapp/network/NetworkButton.java new file mode 100644 index 0000000..49c816c --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/NetworkButton.java @@ -0,0 +1,39 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import android.content.Context; +import android.widget.Button; + +public class NetworkButton extends Button { + + private String _bssid = ""; + + public NetworkButton(Context context, String networkBSSID) { + super(context); + this._bssid = networkBSSID; + } + + public String getNetworkBSSID() { + return this._bssid; + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/NetworkManager.java b/app/src/fil/libre/repwifiapp/network/NetworkManager.java new file mode 100644 index 0000000..4d5635d --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/NetworkManager.java @@ -0,0 +1,379 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp is free software: you can redistribute it and/or modify +// it under the terms of the GNU General public static License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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 static License for more details. +// +// You should have received a copy of the GNU General public static License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import fil.libre.repwifiapp.Utils; +import fil.libre.repwifiapp.helpers.Logger; +import org.json.JSONArray; +import org.json.JSONObject; +import java.io.File; +import java.util.ArrayList; + +public abstract class NetworkManager { + + private static final String VERSION_NOTE = "RepWifiStorageVersion: 2.0"; + private static final String F_SEP = "\t"; + private static final int NET_MAX_AGE = 1095; // Expressed in days + + private static String knownNetworksFile = null; + + public static void init(String storageFilePath){ + knownNetworksFile = storageFilePath; + } + + private static AccessPointInfo getSavedInfo(AccessPointInfo i) { + + if (i == null) { + return null; + } + + String bssid = i.getBssid(); + String ssid = i.getSsid(); + + if (bssid == null || ssid == null || bssid.trim().equals("") || ssid.trim().equals("")) { + return null; + } + + AccessPointInfo ret = null; + AccessPointInfo[] list = getKnownNetworks(); + + if (list == null) { + return null; + } + + for (AccessPointInfo toTest : list) { + + // try to match both bssid and ssid. + // if bssid doesn't match, but ssid does, + // then the network is a candidate. + // if no bssid equality is found, + // then return the best match (only ssid), if any + if (toTest.getSsid().equals(ssid)) { + + i.setPassword(toTest.getPassword()); + i.setDhcpConfiguration(toTest.getDhcpConfiguration()); + i.setVpnProfileName(toTest.getVpnProfileName()); + + if (toTest.getBssid().equals(bssid)) { + // complete match, return. + return i; + + } else { + // probable match + ret = i; + } + + } + + } + + return ret; + + } + + private static boolean saveOrRemove(AccessPointInfo info, boolean save) { + + AccessPointInfo[] existingNets = getKnownNetworks(); + + ArrayList<AccessPointInfo> newlist = new ArrayList<AccessPointInfo>(); + + if (existingNets == null || existingNets.length == 0) { + // no existing storage yet, create it + + if (save) { + // set timestamp + info.setLastTimeUsed(System.currentTimeMillis()); + newlist.add(info); + AccessPointInfo[] newContents = new AccessPointInfo[newlist.size()]; + newContents = newlist.toArray(newContents); + + return saveList(newContents); + + } else { + // nothing to do, return + return true; + } + + } + + if (save) { + // add the updated info to the storage + info.setLastTimeUsed(System.currentTimeMillis()); + newlist.add(info); + } + + for (AccessPointInfo old : existingNets) { + + if (old == null) { + // error while loading from file. skip. + continue; + } + + // keep network only if it's not older than the max age for a + // network + else if (old.isOlderThan(NET_MAX_AGE)) { + // skip it + continue; + } + + else if (old.getBssid().equals(info.getBssid()) && old.getSsid().equals(info.getSsid())) { + // found previously saved entry for the same network we are + // managing + // skip it + continue; + } + + else { + // old network info that can be kept in the storage + newlist.add(old); + } + + } + + AccessPointInfo[] newContents = new AccessPointInfo[newlist.size()]; + newContents = newlist.toArray(newContents); + + return saveList(newContents); + + } + + private static AccessPointInfo getFromStringOld(String savedString) { + + if (savedString == null || savedString.trim().equals("")) { + return null; + } + + String[] fields = savedString.split(F_SEP); + + if (fields.length < 4) { + return null; + } + + String bssid = fields[0]; + String ssid = fields[1]; + String pass = fields[2]; + String lastUsed = fields[3]; + String auth = null; + String ipWmask = null; + String gw = null; + boolean useDhcp = true; + String vpnProfile = null; + + if (fields.length > 4) { + auth = fields[4]; + } + + if (fields.length > 6) { + ipWmask = fields[5]; + gw = fields[6]; + useDhcp = false; + } + + if (fields.length > 7) { + vpnProfile = fields[7]; + } + + long lastusedmillis = 0; + try { + lastusedmillis = Long.parseLong(lastUsed); + } catch (NumberFormatException e) { + // invalid format + Logger.logError("Invalid time format in network manager \"" + lastUsed + + "\". Network BSSID: " + bssid, e); + } + + if (bssid.trim().equals("") || ssid.trim().equals("") || pass.trim().equals("")) { + return null; + } + + AccessPointInfo i = new AccessPointInfo(ssid, bssid, auth, null, null); + i.setPassword(pass); + i.setLastTimeUsed(lastusedmillis); + i.setVpnProfileName(vpnProfile); + + if (!useDhcp) { + DhcpSettings s = DhcpSettings.parseSavedSettings(ipWmask, gw); + i.setDhcpConfiguration(s); + } + + return i; + + } + + private static boolean saveList(AccessPointInfo[] list) { + + try { + + JSONArray jarr = new JSONArray(); + for (AccessPointInfo i : list) { + + JSONObject jo = i.toJson(); + if (jo == null) { + return false; + } + + jarr.put(jo); + + } + + StringBuilder sb = new StringBuilder(); + sb.append(VERSION_NOTE); + sb.append("\n"); + + sb.append(jarr.toString(2)); + + return Utils.writeFile(knownNetworksFile, sb.toString(), true); + + } catch (Exception e) { + Logger.logError("Exception while saving AccessPointInfo array to JSON-formatted file.", + e); + return false; + } + + } + + public static boolean updateStorageVersion() { + + try{ + String[] lines = Utils.readFileLines(knownNetworksFile); + + if (lines == null){ + return false; + } + + if (lines.length == 0) { + return true; + } + + if (lines[0].trim().equals(VERSION_NOTE)) { + return true; + + } else { + + AccessPointInfo[] infos = getKnownNetworksOld(); + if (infos == null || infos.length == 0) { + return true; + } + return saveList(infos); + + } + }catch (Exception e){ + Logger.logError("Exception while trying to update network storage version",e); + return false; + } + + } + + public static AccessPointInfo[] getKnownNetworks() { + + try { + + String fconts = Utils.readFile(knownNetworksFile); + if (fconts == null) { + return null; + } + + if (!fconts.startsWith(VERSION_NOTE)) { + // wrong version, try to convert it + if (updateStorageVersion()) { + return getKnownNetworks(); + } else { + return null; + } + } + + JSONArray jarr = new JSONArray(fconts.replace(VERSION_NOTE, "")); + ArrayList<AccessPointInfo> list = new ArrayList<AccessPointInfo>(); + + int count = 0; + for (int i = 0; i < jarr.length(); i++) { + AccessPointInfo info = AccessPointInfo.fromJsonObject(jarr.getJSONObject(i)); + if (info == null) { + continue; + } + count += 1; + list.add(info); + } + + AccessPointInfo[] arr = new AccessPointInfo[count]; + return list.toArray(arr); + + } catch (Exception e) { + Logger.logError("Exception while parsing JSON content from storage file.", e); + return null; + } + + } + + public static AccessPointInfo[] getKnownNetworksOld() { + + ArrayList<AccessPointInfo> list = new ArrayList<AccessPointInfo>(); + + File f = new File(knownNetworksFile); + if (!f.exists()) { + return null; + } + + String[] lines = Utils.readFileLines(knownNetworksFile); + if (lines == null || lines.length == 0) { + return null; + } + + for (String l : lines) { + + AccessPointInfo info = getFromStringOld(l); + if (info != null) { + list.add(info); + } + + } + + AccessPointInfo[] ret = new AccessPointInfo[list.size()]; + ret = list.toArray(ret); + + return ret; + + } + + public static boolean isKnown(AccessPointInfo info) { + + AccessPointInfo i = getSavedInfo(info); + if (i == null) { + return false; + } else { + return true; + } + + } + + public static boolean save(AccessPointInfo info) { + return saveOrRemove(info, true); + } + + public static boolean remove(AccessPointInfo info) { + return saveOrRemove(info, false); + } + + public static AccessPointInfo getSavedNetwork(AccessPointInfo i) { + return getSavedInfo(i); + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/WpaCli.java b/app/src/fil/libre/repwifiapp/network/WpaCli.java new file mode 100644 index 0000000..10f6b7c --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/WpaCli.java @@ -0,0 +1,252 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + + +package fil.libre.repwifiapp.network; + +import fil.libre.repwifiapp.helpers.Logger; +import fil.libre.repwifiapp.helpers.RootCommand; + +public abstract class WpaCli { + + private static final int SCAN_WAIT_INTERVAL = 4; + private static final String BASE_COMMAND = "wpa_cli -p" + WpaSupplicant.SOCKET_DIR + " -P" + + WpaSupplicant.PID_FILE + " -i" + WpaSupplicant.INTERFACE_NAME; + + public static String createNetworkGetId() { + + try { + + RootCommand su = new RootCommand(BASE_COMMAND + " add_network"); + if (su.execute() == 0) { + String out = su.getOutput(); + if (out == null || out.trim().equals("")) { + return null; + } else { + return out.replace("\n", ""); + } + } else { + return null; + } + + } catch (Exception e) { + Logger.logError("Error while creating network", e); + return null; + } + + } + + public static boolean setNetworkSSID(String ssid, String networkID) { + + try { + + // needs root (wpa_cli) + return executeCmd(BASE_COMMAND + " set_network " + networkID + " ssid '\"" + ssid + + "\"'"); + + } catch (Exception e) { + Logger.logError("Error while setting network SSID", e); + return false; + } + + } + + public static boolean setNetworkPSK(AccessPointInfo info, String networkID) { + + try { + + // needs root (wpa_cli) + + String cmdSetPass = null; + if (info.needsPassword()) { + cmdSetPass = BASE_COMMAND + " set_network " + networkID + " psk '\"" + + info.getPassword() + "\"'"; + } else { + cmdSetPass = BASE_COMMAND + " set_network " + networkID + " key_mgmt NONE"; + } + + return executeCmd(cmdSetPass); + + } catch (Exception e) { + Logger.logError("Error while setting network PSK", e); + return false; + } + + } + + public static boolean setNetworkScanSSID(String networkID) { + + try { + + // needs root (wpa_cli) + return executeCmd(BASE_COMMAND + " set_network " + networkID + " scan_ssid 1"); + + } catch (Exception e) { + Logger.logError("Error while setting network SSID", e); + return false; + } + } + + public static boolean selectNetwork(String networkID) { + + try { + + // needs root (wpa_cli) + return executeCmd(BASE_COMMAND + " select_network " + networkID); + + } catch (Exception e) { + Logger.logError("Error while selecting network", e); + return false; + } + + } + + public static boolean enableNetwork(String networkID) { + + try { + + // needs root (wpa_cli) + return executeCmd(BASE_COMMAND + " enable_network " + networkID); + + } catch (Exception e) { + Logger.logError("Error while enabling network", e); + return false; + } + + } + + public static boolean scanNetworks() { + + // needs root (for wpa_supplicant and wpa_cli) + return RootCommand.executeRootCmd(BASE_COMMAND + + " scan; if [ $? -ne 0 ]; then exit 1; fi; sleep " + SCAN_WAIT_INTERVAL); + + } + + public static String getScanResults() { + + try { + + RootCommand su = new RootCommand(BASE_COMMAND + + " scan_results; if [ $? -ne 0 ]; then exit 1; fi "); + if (su.execute() == 0) { + String out = su.getOutput(); + if (out == null || out.trim().equals("")) { + return null; + } else { + return out; + } + } else { + return null; + } + + } catch (Exception e) { + Logger.logError("Error while executing wpa_cli status", e); + return null; + } + + } + + /*** + * returns null if unable to determine connection status for any reason. + */ + public static ConnectionStatus getConnectionStatus() { + + Logger.logDebug("called getConnecitonStatus()"); + if (!WpaSupplicant.isRunning()) { + // wpa_supplicant is not running. + // unable to determine status. + Logger.logDebug("wpa not running, cannot get connection status."); + return null; + + } + + try { + + RootCommand su = new RootCommand(BASE_COMMAND + " status"); + if (su.execute() == 0) { + String out = su.getOutput(); + if (out == null || out.trim().equals("")) { + return null; + } else { + return ConnectionStatus.parseWpaCliOutput(out); + } + } else { + return null; + } + + } catch (Exception e) { + Logger.logError("Error while executing wpa_cli status", e); + return null; + } + + } + + public static boolean disconnect() { + + // needs root (for wpa_cli) + + if (!WpaSupplicant.isRunning()) { + return true; + } + + try { + + return executeCmd(BASE_COMMAND + " disconnect"); + + } catch (Exception e) { + Logger.logError("Error while enabling network", e); + return false; + } + } + + public static boolean terminateSupplicant() { + + if (!WpaSupplicant.isRunning()){ + return true; + } + try { + + return executeCmd(BASE_COMMAND + " terminate"); + + } catch (Exception e) { + Logger.logError("Error while enabling network", e); + return false; + } + + } + + private static boolean executeCmd(String cmdTxt) throws Exception { + + RootCommand su = new RootCommand(cmdTxt); + if (su.execute() == 0) { + String out = su.getOutput(); + if (out != null && out.trim().replace("\n", "").equals("OK")) { + return true; + } else { + return false; + } + } else { + return false; + } + + } + +} diff --git a/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java b/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java new file mode 100644 index 0000000..d8079b9 --- /dev/null +++ b/app/src/fil/libre/repwifiapp/network/WpaSupplicant.java @@ -0,0 +1,91 @@ +// +// Copyright 2017, 2018 Filippo "Fil" Bergamo <fil.bergamo@riseup.net> +// +// This file is part of RepWifiApp. +// +// RepWifiApp 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 3 of the License, or +// (at your option) any later version. +// +// RepWifiApp 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. +// +// You should have received a copy of the GNU General Public License +// along with RepWifiApp. If not, see <http://www.gnu.org/licenses/>. +// +// ******************************************************************** + +package fil.libre.repwifiapp.network; + +import fil.libre.repwifiapp.helpers.Logger; +import fil.libre.repwifiapp.helpers.RootCommand; + + +public abstract class WpaSupplicant { + + public static final String INTERFACE_NAME = "wlan0"; + public static final String WORKDIR = "/data/misc/wifi"; + public static final String PID_FILE = WORKDIR + "/pidfile"; + public static final String SOCKET_DIR = WORKDIR + "/sockets"; + public static final String SOFTAP_FILE = WORKDIR + "/softap.conf"; + public static final String P2P_CONF = WORKDIR + "/p2p_supplicant.conf"; + public static final String WPA_CONF = WORKDIR + "/wpa_supplicant.conf"; + public static final String ENTROPY_FILE = WORKDIR + "/entropy.bin"; + public static final String OVERLAY_FILE = "/system/etc/wifi/wpa_supplicant_overlay.conf"; + + protected static final String BASE_COMMNAD = "wpa_supplicant -B -dd -i" + INTERFACE_NAME + " -C" + SOCKET_DIR + " -P" + PID_FILE + + " -I" + OVERLAY_FILE + " -e" + ENTROPY_FILE; + + public static boolean start() { + + Logger.logDebug("startWpaSupplicant():"); + if (isRunning()){ + return true; + } + + // needs root (for wpa_supplicant) + if (RootCommand.executeRootCmd(BASE_COMMNAD)) { + return true; + } else { + Logger.logDebug("Failed to start wpa"); + return false; + } + + } + + public static boolean kill(){ + return RootCommand.executeRootCmd("killall -SIGINT wpa_supplicant"); + } + + public static boolean isRunning() { + + boolean retval = false; + + try { + + RootCommand su = new RootCommand("pidof wpa_supplicant"); + if (su.execute() == 0) { + + if (su.getOutput().trim().equals("")) { + retval = false; + } else { + retval = true; + } + + } else { + retval = false; + } + + } catch (Exception e) { + Logger.logError("Exception during isWpaSupplicantRunning()", e); + retval = false; + } + + return retval; + + } + +} |