remove unused openvpn module
This commit is contained in:
parent
958f212c8f
commit
b0bd74c5b4
|
@ -52,8 +52,7 @@ dependencies {
|
|||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
androidTestImplementation 'androidx.test.ext:junit:' + testXSupportLibraryVersion
|
||||
|
||||
//implementation project(':openvpn')
|
||||
|
||||
implementation project(':sharedutils')
|
||||
implementation project(':mediaserviceinterfaces')
|
||||
implementation project(':youtubeapi')
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,54 +0,0 @@
|
|||
apply from: gradle.ext.sharedModulesConstants
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
// FIX: Default interface methods are only supported starting with Android N (--min-api 24)
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion
|
||||
buildToolsVersion project.properties.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
//noinspection ExpiredTargetSdkVersion
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a","arm64-v8a"//,"x86","x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
// gradle 4.6 migration: disable dimensions mechanism
|
||||
// more: https://proandroiddev.com/advanced-android-flavors-part-4-a-new-version-fc2ad80c01bb
|
||||
flavorDimensions "default"
|
||||
|
||||
productFlavors {
|
||||
stbeta {}
|
||||
ststable {}
|
||||
storig {}
|
||||
strtarmenia {}
|
||||
stbolshoetv {}
|
||||
stredboxtv {}
|
||||
stsibsetru {}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar','*.so'])
|
||||
implementation 'junit:junit:' + junitVersion
|
||||
implementation 'androidx.core:core-ktx:' + kotlinCoreVersion
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + kotlinVersion
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:' + kotlinCoreVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXLibraryVersion
|
||||
implementation project(':sharedutils')
|
||||
implementation project(':common')
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,21 +0,0 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -1,45 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.liskovsoft.openvpn">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application android:extractNativeLibs="true"
|
||||
tools:targetApi="m">
|
||||
<!--activity android:name=".VPNService" /-->
|
||||
<!-- Begin Activity by ics-openvpn -->
|
||||
<service
|
||||
android:name="de.blinkt.openvpn.core.OpenVPNService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="de.blinkt.openvpn.api.ExternalOpenVPNService"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="de.blinkt.openvpn.api.IOpenVPNAPIService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name="de.blinkt.openvpn.LaunchVPN"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="@string/vpn_launch_title"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Light.Panel"
|
||||
tools:ignore="ExportedActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,3 +0,0 @@
|
|||
package de.blinkt.openvpn.api;
|
||||
|
||||
parcelable APIVpnProfile;
|
|
@ -1,66 +0,0 @@
|
|||
// IOpenVPNAPIService.aidl
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import de.blinkt.openvpn.api.APIVpnProfile;
|
||||
import de.blinkt.openvpn.api.IOpenVPNStatusCallback;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
interface IOpenVPNAPIService {
|
||||
List<APIVpnProfile> getProfiles();
|
||||
|
||||
void startProfile (String profileUUID);
|
||||
|
||||
/** Use a profile with all certificates etc. embedded,
|
||||
* old version which does not return the UUID of the addded profile, see
|
||||
* below for a version that return the UUID on add */
|
||||
boolean addVPNProfile (String name, String config);
|
||||
|
||||
/** start a profile using a config as inline string. Make sure that all needed data is inlined,
|
||||
* e.g., using <ca>...</ca> or <auth-user-data>...</auth-user-data>
|
||||
* See the OpenVPN manual page for more on inlining files */
|
||||
void startVPN (in String inlineconfig);
|
||||
|
||||
/** This permission framework is used to avoid confused deputy style attack to the VPN
|
||||
* calling this will give null if the app is allowed to use the external API and an Intent
|
||||
* that can be launched to request permissions otherwise */
|
||||
Intent prepare (in String packagename);
|
||||
|
||||
/** Used to trigger to the Android VPN permission dialog (VPNService.prepare()) in advance,
|
||||
* if this return null OpenVPN for ANdroid already has the permissions otherwise you can start the returned Intent
|
||||
* to let OpenVPN for Android request the permission */
|
||||
Intent prepareVPNService ();
|
||||
|
||||
/* Disconnect the VPN */
|
||||
void disconnect();
|
||||
|
||||
/* Pause the VPN (same as using the pause feature in the notifcation bar) */
|
||||
void pause();
|
||||
|
||||
/* Resume the VPN (same as using the pause feature in the notifcation bar) */
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* Registers to receive OpenVPN Status Updates
|
||||
*/
|
||||
void registerStatusCallback(in IOpenVPNStatusCallback cb);
|
||||
|
||||
/**
|
||||
* Remove a previously registered callback interface.
|
||||
*/
|
||||
void unregisterStatusCallback(in IOpenVPNStatusCallback cb);
|
||||
|
||||
/** Remove a profile by UUID */
|
||||
void removeProfile (in String profileUUID);
|
||||
|
||||
/** Request a socket to be protected as a VPN socket would be. Useful for creating
|
||||
* a helper socket for an app controlling OpenVPN
|
||||
* Before calling this function you should make sure OpenVPN for Android may actually
|
||||
* this function by checking if prepareVPNService returns null; */
|
||||
boolean protectSocket(in ParcelFileDescriptor fd);
|
||||
|
||||
|
||||
/** Use a profile with all certificates etc. embedded */
|
||||
APIVpnProfile addNewVPNProfile (String name, boolean userEditable, String config);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package de.blinkt.openvpn.api;
|
||||
|
||||
/**
|
||||
* Example of a callback interface used by IRemoteService to send
|
||||
* synchronous notifications back to its clients. Note that this is a
|
||||
* one-way interface so the server does not block waiting for the client.
|
||||
*/
|
||||
interface IOpenVPNStatusCallback {
|
||||
/**
|
||||
* Called when the service has a new status for you.
|
||||
*/
|
||||
oneway void newStatus(in String uuid, in String state, in String message, in String level);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package de.blinkt.openvpn.core;
|
||||
|
||||
parcelable ConnectionStatus;
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
/**
|
||||
* Created by arne on 15.11.16.
|
||||
*/
|
||||
|
||||
interface IOpenVPNServiceInternal {
|
||||
|
||||
boolean protect(int fd);
|
||||
|
||||
void userPause(boolean b);
|
||||
|
||||
/**
|
||||
* @param replaceConnection True if the VPN is connected by a new connection.
|
||||
* @return true if there was a process that has been send a stop signal
|
||||
*/
|
||||
boolean stopVPN(boolean replaceConnection);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// StatusIPC.aidl
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
import de.blinkt.openvpn.core.IStatusCallbacks;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import de.blinkt.openvpn.core.TrafficHistory;
|
||||
|
||||
|
||||
interface IServiceStatus {
|
||||
/**
|
||||
* Registers to receive OpenVPN Status Updates and gets a
|
||||
* ParcelFileDescript back that contains the log up to that point
|
||||
*/
|
||||
ParcelFileDescriptor registerStatusCallback(in IStatusCallbacks cb);
|
||||
|
||||
/**
|
||||
* Remove a previously registered callback interface.
|
||||
*/
|
||||
void unregisterStatusCallback(in IStatusCallbacks cb);
|
||||
|
||||
/**
|
||||
* Returns the last connedcted VPN
|
||||
*/
|
||||
String getLastConnectedVPN();
|
||||
|
||||
/**
|
||||
* Sets a cached password
|
||||
*/
|
||||
void setCachedPassword(in String uuid, int type, String password);
|
||||
|
||||
/**
|
||||
* Gets the traffic history
|
||||
*/
|
||||
TrafficHistory getTrafficHistory();
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import de.blinkt.openvpn.core.LogItem;
|
||||
import de.blinkt.openvpn.core.ConnectionStatus;
|
||||
|
||||
|
||||
|
||||
interface IStatusCallbacks {
|
||||
/**
|
||||
* Called when the service has a new status for you.
|
||||
*/
|
||||
oneway void newLogItem(in LogItem item);
|
||||
|
||||
oneway void updateStateString(in String state, in String msg, in int resid, in ConnectionStatus level);
|
||||
|
||||
oneway void updateByteCount(long inBytes, long outBytes);
|
||||
|
||||
oneway void connectedVPN(String uuid);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package de.blinkt.openvpn.core;
|
||||
|
||||
parcelable LogItem;
|
|
@ -1,4 +0,0 @@
|
|||
package de.blinkt.openvpn.core;
|
||||
|
||||
|
||||
parcelable TrafficHistory;
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
const val SEARCH_RESULTS_MAX = 60 // max search results for voice search
|
||||
const val MAX_CHANNEL_CAP = 100 // max home channels elements
|
||||
|
||||
const val mainHost = "http://releases.yourok.ru"
|
||||
const val updatePath = "$mainHost/num/version.json"
|
||||
const val emptyPosterPath = "$mainHost/empty_poster.png"
|
||||
|
||||
const val AntiZapretProfile = "https://antizapret.prostovpn.org/antizapret-tcp.ovpn"
|
||||
const val ZaboronaProfile = "https://zaborona.help/openvpn-client-config/zaborona-help_maxroutes.ovpn"
|
|
@ -1,54 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object Coroutines {
|
||||
private val scopeList = hashMapOf<String, Pair<Job, CoroutineScope>>()
|
||||
private val jobList = hashMapOf<String, MutableList<Job>>()
|
||||
|
||||
private fun getScope(name: String, dispatcher: CoroutineDispatcher = Dispatchers.IO): CoroutineScope {
|
||||
synchronized(scopeList) {
|
||||
val scope = scopeList.get(name)
|
||||
scope?.let { return it.second }
|
||||
}
|
||||
|
||||
val job = Job()
|
||||
val sc = CoroutineScope(dispatcher + job)
|
||||
synchronized(scopeList) { scopeList[name] = Pair(job, sc) }
|
||||
jobList[name] = mutableListOf()
|
||||
return sc
|
||||
}
|
||||
|
||||
fun launch(name: String, fn: suspend () -> Unit) {
|
||||
synchronized(scopeList) {
|
||||
val scope = getScope(name)
|
||||
val jb = scope.launch { fn() }
|
||||
jobList[name]?.add(jb)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(name: String) {
|
||||
cancel(name)
|
||||
synchronized(scopeList) {
|
||||
scopeList.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel(name: String) {
|
||||
synchronized(scopeList) {
|
||||
val scope = scopeList.get(name)
|
||||
scope?.let {
|
||||
it.first.cancelChildren()
|
||||
jobList[name]?.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun join(name: String) {
|
||||
runBlocking {
|
||||
//TODO may be crash
|
||||
jobList[name]?.joinAll()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
import android.net.Uri
|
||||
import com.liskovsoft.sharedutils.helpers.FileHelpers
|
||||
import com.liskovsoft.sharedutils.helpers.Helpers
|
||||
import java.io.File
|
||||
|
||||
object Download {
|
||||
suspend fun download(link: String, file: File, onProgress: (suspend (prc: Int) -> Boolean)?): Boolean {
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
var isEnd = true
|
||||
|
||||
if (Helpers.isUrl(link)) {
|
||||
val conn = Http(Uri.parse(link))
|
||||
conn.connect()
|
||||
conn.getInputStream().use { input ->
|
||||
file.outputStream().use { fileOut ->
|
||||
val contentLength = conn.getSize()
|
||||
if (onProgress == null)
|
||||
input?.copyTo(fileOut)
|
||||
else {
|
||||
val buffer = ByteArray(65535)
|
||||
val length = contentLength + 1
|
||||
var offset: Long = 0
|
||||
while (true) {
|
||||
val readed = input?.read(buffer) ?: 0
|
||||
offset += readed
|
||||
val prc = (offset * 100 / length).toInt()
|
||||
if (!onProgress(prc)) {
|
||||
isEnd = false
|
||||
break
|
||||
}
|
||||
if (readed <= 0)
|
||||
break
|
||||
fileOut.write(buffer, 0, readed)
|
||||
}
|
||||
fileOut.flush()
|
||||
}
|
||||
fileOut.flush()
|
||||
fileOut.close()
|
||||
}
|
||||
}
|
||||
conn.close()
|
||||
} else {
|
||||
FileHelpers.copy(File(link), file)
|
||||
}
|
||||
|
||||
return isEnd
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.HttpURLConnection.*
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
|
||||
/**
|
||||
* Created by yourok on 07.11.17.
|
||||
*/
|
||||
|
||||
class Http(url: Uri) {
|
||||
private var currUrl: String = url.toString()
|
||||
private var isConn: Boolean = false
|
||||
private var connection: HttpURLConnection? = null
|
||||
private var errMsg: String = ""
|
||||
private var inputStream: InputStream? = null
|
||||
|
||||
private var timeout = 30000
|
||||
|
||||
fun connect() {
|
||||
connect(0)
|
||||
}
|
||||
|
||||
fun connect(pos: Long): Long {
|
||||
|
||||
var responseCode: Int
|
||||
var redirCount = 0
|
||||
do {
|
||||
if (!currUrl.contains("://"))
|
||||
currUrl = currUrl.replace(":/", "://")
|
||||
|
||||
val url = URL(currUrl)
|
||||
connection = url.openConnection() as HttpURLConnection
|
||||
connection!!.connectTimeout = timeout
|
||||
connection!!.readTimeout = 15000
|
||||
connection!!.requestMethod = "GET"
|
||||
connection!!.doInput = true
|
||||
|
||||
connection!!.setRequestProperty("UserAgent", "DWL/1.1.0 (Linux; Android;)")
|
||||
connection!!.setRequestProperty("Accept", "*/*")
|
||||
connection!!.setRequestProperty("Accept-Encoding", "gzip")
|
||||
if (pos > 0)
|
||||
connection!!.setRequestProperty("Range", "bytes=$pos-")
|
||||
|
||||
connection!!.connect()
|
||||
|
||||
responseCode = connection!!.responseCode
|
||||
var redirected =
|
||||
responseCode == HTTP_MOVED_PERM || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER
|
||||
if (redirected) {
|
||||
currUrl = connection!!.getHeaderField("Location")
|
||||
connection!!.disconnect()
|
||||
redirCount++
|
||||
}
|
||||
|
||||
if (responseCode == 429) {
|
||||
var retry = connection!!.getHeaderField("Retry-After")
|
||||
if (retry.isNullOrEmpty() || retry == "0")
|
||||
retry = "1"
|
||||
redirCount++
|
||||
redirected = true
|
||||
Thread.sleep(retry.toLong() * 1000L)
|
||||
}
|
||||
|
||||
if (redirCount > 5) {
|
||||
throw IOException("Error connect to: $currUrl too many redirects")
|
||||
}
|
||||
} while (redirected)
|
||||
|
||||
|
||||
if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_PARTIAL) {
|
||||
throw IOException("Error connect to: " + currUrl + " " + connection!!.responseMessage)
|
||||
}
|
||||
isConn = true
|
||||
if ((connection!!.getHeaderField("Accept-Ranges")?.lowercase(Locale.getDefault()) ?: "") == "none")
|
||||
return -1
|
||||
return getSize()
|
||||
}
|
||||
|
||||
fun setTimeout(timeout: Int) {
|
||||
this.timeout = timeout
|
||||
}
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
return isConn
|
||||
}
|
||||
|
||||
fun getSize(): Long {
|
||||
if (!isConn)
|
||||
return 0
|
||||
|
||||
var cl = connection!!.getHeaderField("Content-Range")
|
||||
try {
|
||||
if (!cl.isNullOrEmpty()) {
|
||||
val cr = cl.split("/")
|
||||
if (cr.isNotEmpty())
|
||||
cl = cr.last()
|
||||
return cl.toLong()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
cl = connection!!.getHeaderField("Content-Length")
|
||||
try {
|
||||
if (!cl.isNullOrEmpty()) {
|
||||
return cl.toLong()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
fun getUrl(): String {
|
||||
return currUrl
|
||||
}
|
||||
|
||||
fun getInputStream(): InputStream? {
|
||||
if (inputStream == null && connection != null) {
|
||||
inputStream = if ("gzip" == connection?.contentEncoding)
|
||||
GZIPInputStream(connection!!.inputStream)
|
||||
else
|
||||
connection!!.inputStream
|
||||
}
|
||||
|
||||
return inputStream
|
||||
}
|
||||
|
||||
fun read(b: ByteArray): Int {
|
||||
if (!isConn or (getInputStream() == null))
|
||||
throw IOException("connect before read")
|
||||
var sz = getInputStream()!!.read(b)
|
||||
var size = sz
|
||||
while (sz > 0 && sz < b.size / 2) {
|
||||
try {
|
||||
sz = getInputStream()!!.read(b, size, b.size - size)
|
||||
if (sz > 0)
|
||||
size += sz
|
||||
else
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
break
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
fun getErrorMessage(): String {
|
||||
return errMsg
|
||||
}
|
||||
|
||||
fun close() {
|
||||
try {
|
||||
inputStream?.close()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
connection?.disconnect()
|
||||
isConn = false
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.pm.PackageManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.liskovsoft.openvpn.R
|
||||
import com.liskovsoft.sharedutils.helpers.FileHelpers
|
||||
import com.liskovsoft.sharedutils.helpers.KeyHelpers
|
||||
import com.liskovsoft.sharedutils.helpers.PermissionHelpers
|
||||
import com.liskovsoft.sharedutils.mylogger.Log
|
||||
import com.liskovsoft.smartyoutubetv2.common.misc.MotherActivity
|
||||
|
||||
class OpenVPNDialog(private val context: Context): OpenVPNManager.OpenVPNCallback, MotherActivity.OnPermissions {
|
||||
private var pendingHandler: (() -> Unit)? = null
|
||||
private var openVPNConfigDialog: AlertDialog? = null
|
||||
private val openVPNManager: OpenVPNManager = OpenVPNManager.instance(context, this)
|
||||
|
||||
val isOpenVPNSupported: Boolean
|
||||
get() = openVPNManager.isOpenVPNSupported
|
||||
val isOpenVPNEnabled: Boolean
|
||||
get() = openVPNManager.isOpenVPNEnabled
|
||||
|
||||
private companion object {
|
||||
val TAG: String = OpenVPNDialog::class.java.simpleName
|
||||
}
|
||||
|
||||
@RequiresApi(19)
|
||||
fun enable(enabled: Boolean) {
|
||||
if (isOpenVPNSupported) {
|
||||
openVPNManager.saveOpenVPNInfoToPrefs(enabled = enabled)
|
||||
if (enabled) {
|
||||
showProxyConfigDialog()
|
||||
} else {
|
||||
checkPermissionsAndConfigureOpenVPN()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPermissions(requestCode: Int, permissions: Array<out String>?, grantResults: IntArray?) {
|
||||
if (requestCode == PermissionHelpers.REQUEST_EXTERNAL_STORAGE) {
|
||||
if (grantResults != null && grantResults.size >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "REQUEST_EXTERNAL_STORAGE permission has been granted");
|
||||
|
||||
pendingHandler?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProfileLoaded(profileLoaded: Boolean) {
|
||||
appendStatusMessage("Profile loaded: %s", if (profileLoaded) "YES" else "NO")
|
||||
}
|
||||
|
||||
override fun onVPNStatusChanged(vpnActivated: Boolean) {
|
||||
appendStatusMessage("OpenVPN activated: %s", if (vpnActivated) "YES" else "NO")
|
||||
}
|
||||
|
||||
override fun onConfigDownloadStart() {
|
||||
appendStatusMessage("Config download start")
|
||||
}
|
||||
|
||||
//override fun onConfigDownloadProgress(progress: Int) {
|
||||
// appendStatusMessage("Config download progress: %d", progress)
|
||||
//}
|
||||
|
||||
override fun onConfigDownloadEnd() {
|
||||
appendStatusMessage("Config download end")
|
||||
}
|
||||
|
||||
override fun onConfigDownloadError() {
|
||||
appendStatusMessage("Config download error")
|
||||
}
|
||||
|
||||
private fun appendStatusMessage(msgFormat: String?, vararg args: Any?) {
|
||||
if (openVPNConfigDialog == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val statusView = openVPNConfigDialog!!.findViewById<TextView>(R.id.openvpn_config_message)
|
||||
val message = String.format(msgFormat!!, *args)
|
||||
if (statusView!!.text.toString().isEmpty()) {
|
||||
statusView.append(message)
|
||||
} else {
|
||||
statusView.append("\n$message")
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendStatusMessage(resId: Int, vararg args: Any?) {
|
||||
appendStatusMessage(context.getString(resId), *args)
|
||||
}
|
||||
|
||||
private fun validateOpenVPNConfigFields(): OpenVPNManager.OpenVPNInfo? {
|
||||
var isConfigValid = true
|
||||
val openVPNAddress = (openVPNConfigDialog!!.findViewById<View>(R.id.openvpn_config_address) as EditText?)!!.text.toString()
|
||||
if (openVPNAddress.isEmpty()) {
|
||||
isConfigValid = false
|
||||
appendStatusMessage(R.string.openvpn_address_invalid)
|
||||
}
|
||||
if (!isConfigValid) {
|
||||
return null
|
||||
}
|
||||
return OpenVPNManager.OpenVPNInfo(openVPNAddress)
|
||||
}
|
||||
|
||||
private fun testOpenVPNConnection() {
|
||||
val openVPNInfo = validateOpenVPNConfigFields()
|
||||
if (openVPNInfo == null) {
|
||||
appendStatusMessage(R.string.openvpn_test_aborted)
|
||||
return
|
||||
}
|
||||
openVPNManager.saveOpenVPNInfoToPrefs(openVPNInfo, true)
|
||||
checkPermissionsAndConfigureOpenVPN()
|
||||
}
|
||||
|
||||
@RequiresApi(19)
|
||||
private fun showProxyConfigDialog() {
|
||||
val builder = AlertDialog.Builder(context, R.style.AppDialog)
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val contentView = inflater.inflate(R.layout.openvpn_dialog, null)
|
||||
|
||||
KeyHelpers.fixEnterKey(contentView!!.findViewById(R.id.openvpn_config_address))
|
||||
|
||||
(contentView!!.findViewById<View>(R.id.openvpn_config_address) as EditText?)!!.setText(openVPNManager.openVPNConfigUri)
|
||||
|
||||
// keep empty, will override below.
|
||||
// https://stackoverflow.com/a/15619098/5379584
|
||||
openVPNConfigDialog = builder
|
||||
.setTitle(R.string.openvpn_settings_title)
|
||||
.setView(contentView)
|
||||
.setNeutralButton(R.string.proxy_test_btn) { dialog: DialogInterface?, which: Int -> }
|
||||
.setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int -> }
|
||||
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface?, which: Int -> }
|
||||
.create()
|
||||
openVPNConfigDialog!!.show()
|
||||
openVPNConfigDialog!!.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { view: View? ->
|
||||
(openVPNConfigDialog!!.findViewById<View>(R.id.openvpn_config_message) as TextView?)!!.text = ""
|
||||
val openVPNInfo = validateOpenVPNConfigFields()
|
||||
if (openVPNInfo == null) {
|
||||
appendStatusMessage(R.string.openvpn_application_aborted)
|
||||
} else {
|
||||
Log.d(TAG, "Saving OpenVPN info: $openVPNInfo")
|
||||
openVPNManager.saveOpenVPNInfoToPrefs(openVPNInfo, true)
|
||||
checkPermissionsAndConfigureOpenVPN()
|
||||
openVPNConfigDialog!!.dismiss()
|
||||
}
|
||||
}
|
||||
openVPNConfigDialog!!.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { view: View? ->
|
||||
(openVPNConfigDialog!!.findViewById<View>(R.id.openvpn_config_message) as TextView?)!!.text = ""
|
||||
testOpenVPNConnection()
|
||||
}
|
||||
openVPNConfigDialog!!.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { view: View? ->
|
||||
// TODO: cancel OpenVPN application
|
||||
openVPNConfigDialog!!.dismiss()
|
||||
}
|
||||
//openVPNConfigDialog!!.setOnDismissListener { dialog: DialogInterface? ->
|
||||
// val openVPNInfo = validateOpenVPNConfigFields()
|
||||
// if (openVPNInfo != null) {
|
||||
// Log.d(TAG, "Saving OpenVPN info: $openVPNInfo")
|
||||
// openVPNManager.saveOpenVPNInfoToPrefs(openVPNInfo, true)
|
||||
// checkPermissionsAndConfigureOpenVPN()
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
private fun checkPermissionsAndConfigureOpenVPN() {
|
||||
if (FileHelpers.isExternalStorageReadable()) {
|
||||
if (PermissionHelpers.hasStoragePermissions(context)) {
|
||||
openVPNManager.configureOpenVPN()
|
||||
} else {
|
||||
pendingHandler = { openVPNManager.configureOpenVPN() }
|
||||
PermissionHelpers.verifyStoragePermissions(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package com.liskovsoft.openvpn.manager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.liskovsoft.openvpn.service.OnVPNStatusChangeListener
|
||||
import com.liskovsoft.openvpn.service.VPNService
|
||||
import com.liskovsoft.sharedutils.helpers.FileHelpers
|
||||
import com.liskovsoft.smartyoutubetv2.common.prefs.AppPrefs
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class OpenVPNManager private constructor(context: Context, callback: OpenVPNCallback? = null) {
|
||||
companion object {
|
||||
private val TAG: String = OpenVPNManager::class.java.simpleName
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private var instance: OpenVPNManager? = null
|
||||
@JvmStatic fun instance(context: Context, callback: OpenVPNCallback? = null): OpenVPNManager {
|
||||
if (instance == null) {
|
||||
instance = OpenVPNManager(context, callback)
|
||||
} else {
|
||||
instance!!.context = context.applicationContext
|
||||
instance!!.callback = callback
|
||||
}
|
||||
|
||||
return instance!!
|
||||
}
|
||||
@JvmStatic fun unhold() {
|
||||
instance = null
|
||||
}
|
||||
}
|
||||
|
||||
class OpenVPNInfo(val configAddress: String)
|
||||
|
||||
interface OpenVPNCallback {
|
||||
fun onProfileLoaded(profileLoaded: Boolean) {}
|
||||
fun onVPNStatusChanged(vpnActivated: Boolean) {}
|
||||
fun onConfigDownloadStart() {}
|
||||
fun onConfigDownloadProgress(progress: Int) {}
|
||||
fun onConfigDownloadEnd() {}
|
||||
fun onConfigDownloadError() {}
|
||||
}
|
||||
|
||||
private var context = context.applicationContext
|
||||
private var _callback = WeakReference(callback)
|
||||
private var callback: OpenVPNCallback?
|
||||
get() = _callback.get()
|
||||
set(value) { _callback = WeakReference(value) }
|
||||
private val prefs = AppPrefs.instance(context.applicationContext)
|
||||
private val routineName = "AZDownload"
|
||||
private var isConnected = false
|
||||
private var isDownload = true
|
||||
private val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
private val ovpnFile = File(downloadDir, "config.ovpn")
|
||||
private val numVPNService by lazy {
|
||||
VPNService(context).apply {
|
||||
// TODO: maybe below not needed
|
||||
launchVPN(prefs.openVPNConfigUri)
|
||||
setOnVPNStatusChangeListener(object : OnVPNStatusChangeListener {
|
||||
override fun onProfileLoaded(profileLoaded: Boolean) {
|
||||
this@OpenVPNManager.callback?.onProfileLoaded(profileLoaded)
|
||||
}
|
||||
|
||||
override fun onVPNStatusChanged(vpnActivated: Boolean) {
|
||||
isConnected = vpnActivated
|
||||
this@OpenVPNManager.callback?.onVPNStatusChanged(vpnActivated)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val isOpenVPNSupported: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= 19
|
||||
|
||||
val isOpenVPNEnabled: Boolean
|
||||
get() = prefs.isOpenVPNEnabled
|
||||
|
||||
val openVPNConfigUri: String
|
||||
get() = prefs.openVPNConfigUri
|
||||
|
||||
fun saveOpenVPNInfoToPrefs(info: OpenVPNInfo? = null, enabled: Boolean) {
|
||||
info?.apply {
|
||||
prefs.openVPNConfigUri = configAddress
|
||||
}
|
||||
|
||||
prefs.isOpenVPNEnabled = enabled
|
||||
}
|
||||
|
||||
fun configureOpenVPN() {
|
||||
if (isOpenVPNEnabled) {
|
||||
|
||||
// TODO: download config doesn't work. Only uri!
|
||||
downloadConfig()
|
||||
|
||||
//numVPNService.launchVPN(prefs.openVPNConfigUri)
|
||||
numVPNService.start(true)
|
||||
} else {
|
||||
numVPNService.start(false)
|
||||
unhold()
|
||||
}
|
||||
|
||||
isConnected = numVPNService.cStatus
|
||||
}
|
||||
|
||||
private fun downloadConfig() {
|
||||
isDownload = true
|
||||
downloadOVPN()
|
||||
}
|
||||
|
||||
private fun cancelDownloadConfig() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Coroutines.cancel(routineName)
|
||||
isDownload = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadOVPN() {
|
||||
callback?.onConfigDownloadStart()
|
||||
|
||||
Coroutines.launch(routineName) {
|
||||
try {
|
||||
Download.download(prefs.openVPNConfigUri, ovpnFile) { prc ->
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
callback?.onConfigDownloadProgress(prc)
|
||||
} catch (e: Exception) {
|
||||
isDownload = false
|
||||
}
|
||||
}
|
||||
isDownload
|
||||
}
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
callback?.onConfigDownloadEnd()
|
||||
openOVPN()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
callback?.onConfigDownloadError()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openOVPN() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val path = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(ovpnFile)
|
||||
} else {
|
||||
intent.flags += Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
FileHelpers.getFileUri(context, ovpnFile)
|
||||
}
|
||||
intent.setDataAndType(path, "application/x-openvpn-profile")
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities(intent, 0)
|
||||
if (activities.isNotEmpty()) { // && vpnConnected
|
||||
context.startActivity(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package com.liskovsoft.openvpn.service
|
||||
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
enum class Status {
|
||||
PENDING,
|
||||
RUNNING,
|
||||
FINISHED
|
||||
}
|
||||
|
||||
abstract class CoroutinesAsyncTask<Params, Progress, Result>(private val taskName: String) {
|
||||
|
||||
private val TAG by lazy {
|
||||
CoroutinesAsyncTask::class.java.simpleName
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var threadPoolExecutor: CoroutineDispatcher? = null
|
||||
}
|
||||
|
||||
var status: Status = Status.PENDING
|
||||
private var preJob: Job? = null
|
||||
private var bgJob: Deferred<Result>? = null
|
||||
abstract fun doInBackground(vararg params: Params?): Result
|
||||
open fun onProgressUpdate(vararg values: Progress?) {}
|
||||
open fun onPostExecute(result: Result?) {}
|
||||
open fun onPreExecute() {}
|
||||
open fun onCancelled(result: Result?) {}
|
||||
var isCancelled = false
|
||||
|
||||
/**
|
||||
* Executes background task parallel with other background tasks in the queue using
|
||||
* default thread pool
|
||||
*/
|
||||
fun execute(vararg params: Params?) {
|
||||
execute(Dispatchers.Default, *params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes background tasks sequentially with other background tasks in the queue using
|
||||
* single thread executor @Executors.newSingleThreadExecutor().
|
||||
*/
|
||||
fun executeOnExecutor(vararg params: Params?) {
|
||||
if (threadPoolExecutor == null) {
|
||||
threadPoolExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
}
|
||||
execute(threadPoolExecutor!!, *params)
|
||||
}
|
||||
|
||||
private fun execute(dispatcher: CoroutineDispatcher, vararg params: Params?) {
|
||||
|
||||
if (status != Status.PENDING) {
|
||||
when (status) {
|
||||
Status.RUNNING -> throw IllegalStateException("Cannot execute task:" + " the task is already running.")
|
||||
Status.FINISHED -> throw IllegalStateException(
|
||||
"Cannot execute task:"
|
||||
+ " the task has already been executed "
|
||||
+ "(a task can be executed only once)"
|
||||
)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = Status.RUNNING
|
||||
|
||||
// it can be used to setup UI - it should have access to Main Thread
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
preJob = launch(Dispatchers.Main) {
|
||||
printLog("$taskName onPreExecute started")
|
||||
onPreExecute()
|
||||
printLog("$taskName onPreExecute finished")
|
||||
bgJob = async(dispatcher) {
|
||||
printLog("$taskName doInBackground started")
|
||||
doInBackground(*params)
|
||||
}
|
||||
}
|
||||
preJob!!.join()
|
||||
if (!isCancelled) {
|
||||
withContext(Dispatchers.Main) {
|
||||
onPostExecute(bgJob!!.await())
|
||||
printLog("$taskName doInBackground finished")
|
||||
status = Status.FINISHED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel(mayInterruptIfRunning: Boolean) {
|
||||
if (preJob == null || bgJob == null) {
|
||||
printLog("$taskName has already been cancelled/finished/not yet started.")
|
||||
return
|
||||
}
|
||||
if (mayInterruptIfRunning || (!preJob!!.isActive && !bgJob!!.isActive)) {
|
||||
isCancelled = true
|
||||
status = Status.FINISHED
|
||||
if (bgJob!!.isCompleted) {
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
onCancelled(bgJob!!.await())
|
||||
}
|
||||
}
|
||||
preJob?.cancel(CancellationException("PreExecute: Coroutine Task cancelled"))
|
||||
bgJob?.cancel(CancellationException("doInBackground: Coroutine Task cancelled"))
|
||||
printLog("$taskName has been cancelled.")
|
||||
}
|
||||
}
|
||||
|
||||
fun publishProgress(vararg progress: Progress) {
|
||||
//need to update main thread
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
if (!isCancelled) {
|
||||
onProgressUpdate(*progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printLog(message: String) {
|
||||
Log.d(TAG, message)
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package com.liskovsoft.openvpn.service
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
object DataCleanManager {
|
||||
// TODO: check junk
|
||||
fun cleanApplicationData(context: Context) {
|
||||
cleanCache(context)
|
||||
deleteDatabases(context)
|
||||
cleanSharedPreference(context)
|
||||
cleanFiles(context)
|
||||
}
|
||||
|
||||
private fun cleanCache(context: Context) {
|
||||
deleteFilesByDirectory(context.cacheDir)
|
||||
cleanExternalCache(context)
|
||||
}
|
||||
|
||||
private fun cleanExternalCache(context: Context) {
|
||||
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
|
||||
deleteFilesByDirectory(context.externalCacheDir)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDatabases(context: Context) {
|
||||
for (database in context.databaseList()) {
|
||||
context.deleteDatabase(database)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanSharedPreference(context: Context) {
|
||||
//deleteFilesByDirectory(File(context.filesDir?.parent + "/shared_prefs"))
|
||||
val pref = File(context.filesDir?.parent + "/shared_prefs/VPNList.xml")
|
||||
if (pref.exists() && pref.isFile)
|
||||
pref.delete()
|
||||
}
|
||||
|
||||
private fun cleanFiles(context: Context) {
|
||||
//deleteFilesByDirectory(context.filesDir)
|
||||
val folder = File(context.filesDir.toString())
|
||||
val fList = folder.listFiles()
|
||||
for (i in fList!!.indices) {
|
||||
val f: File? = fList[i]
|
||||
if (f?.path?.endsWith(".vp") == true || f?.path?.contains("uuid.dat") == true) {
|
||||
f.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteFilesByDirectory(directory: File?) {
|
||||
directory?.let {
|
||||
if (directory.exists() && directory.isDirectory) {
|
||||
for (item in directory.listFiles() ?: emptyArray()) {
|
||||
item.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fun getFolderSize(file: File): Float {
|
||||
// var size = 0f
|
||||
// try {
|
||||
// val fileList = file.listFiles()
|
||||
// for (aFileList in fileList) {
|
||||
// size = if (aFileList.isDirectory) {
|
||||
// size + getFolderSize(aFileList)
|
||||
// } else {
|
||||
// size + aFileList.length()
|
||||
// }
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// e.printStackTrace()
|
||||
// return 0f
|
||||
// }
|
||||
// return size
|
||||
// }
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package com.liskovsoft.openvpn.service
|
||||
|
||||
interface OnVPNStatusChangeListener {
|
||||
fun onProfileLoaded(profileLoaded: Boolean)
|
||||
fun onVPNStatusChanged(vpnActivated: Boolean)
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package com.liskovsoft.openvpn.service
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import de.blinkt.openvpn.core.ConfigParser
|
||||
import de.blinkt.openvpn.core.ConfigParser.ConfigParseError
|
||||
import de.blinkt.openvpn.core.ProfileManager
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
class ProfileAsync(
|
||||
context: Context,
|
||||
private val onProfileLoadListener: OnProfileLoadListener?,
|
||||
private val ovpnUrl: String?
|
||||
) : CoroutinesAsyncTask<Void?, Void?, Boolean>("ProfileAsync") {
|
||||
private val context: WeakReference<Context> = WeakReference(context)
|
||||
override fun onPreExecute() {
|
||||
super.onPreExecute()
|
||||
val context = context.get()
|
||||
if (context == null || onProfileLoadListener == null) {
|
||||
cancel(true)
|
||||
} else if (!isNetworkAvailable(context)) {
|
||||
cancel(true)
|
||||
onProfileLoadListener.onProfileLoadFailed("No Network")
|
||||
}
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg params: Void?): Boolean {
|
||||
try {
|
||||
val url = URL(ovpnUrl)
|
||||
val httpURLConnection = url.openConnection() as HttpURLConnection
|
||||
httpURLConnection.connectTimeout = 10 * 1000
|
||||
httpURLConnection.readTimeout = 10 * 1000
|
||||
val inputStream = httpURLConnection.inputStream
|
||||
val bufferedReader =
|
||||
BufferedReader(InputStreamReader(inputStream /*, Charset.forName("UTF-8")*/))
|
||||
val cp = ConfigParser()
|
||||
cp.parseConfig(bufferedReader)
|
||||
val vp = cp.convertProfile()
|
||||
val vpl = ProfileManager.getInstance(context.get())
|
||||
vp.mName = Build.MODEL
|
||||
vp.mUsername = null
|
||||
vp.mPassword = null
|
||||
vpl.addProfile(vp)
|
||||
vpl.saveProfile(context.get(), vp)
|
||||
vpl.saveProfileList(context.get())
|
||||
return true
|
||||
} catch (e: MalformedURLException) {
|
||||
cancel(true)
|
||||
onProfileLoadListener?.onProfileLoadFailed("MalformedURLException")
|
||||
} catch (configParseError: ConfigParseError) {
|
||||
cancel(true)
|
||||
onProfileLoadListener?.onProfileLoadFailed("ConfigParseError")
|
||||
} catch (e: IOException) {
|
||||
cancel(true)
|
||||
onProfileLoadListener?.onProfileLoadFailed("IOException")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: Boolean?) {
|
||||
super.onPostExecute(result)
|
||||
if (result == true) {
|
||||
onProfileLoadListener?.onProfileLoadSuccess()
|
||||
} else {
|
||||
onProfileLoadListener?.onProfileLoadFailed("unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
interface OnProfileLoadListener {
|
||||
fun onProfileLoadSuccess()
|
||||
fun onProfileLoadFailed(msg: String?)
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(context: Context): Boolean {
|
||||
val connectivityManager =
|
||||
context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
?: return false
|
||||
val info = connectivityManager.activeNetworkInfo
|
||||
return info != null && info.isAvailable && info.isConnected
|
||||
}
|
||||
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
package com.liskovsoft.openvpn.service
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.VpnService
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import de.blinkt.openvpn.LaunchVPN
|
||||
import de.blinkt.openvpn.VpnProfile
|
||||
import de.blinkt.openvpn.core.*
|
||||
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener
|
||||
import de.blinkt.openvpn.core.VpnStatus.StateListener
|
||||
import kotlinx.coroutines.*
|
||||
import com.liskovsoft.openvpn.service.ProfileAsync.OnProfileLoadListener
|
||||
import com.liskovsoft.openvpn.BuildConfig
|
||||
import com.liskovsoft.openvpn.R
|
||||
import java.lang.Runnable
|
||||
|
||||
|
||||
class VPNService(private val context: Context) : ByteCountListener, StateListener {
|
||||
private var profileAsync: ProfileAsync? = null
|
||||
private var profileStatus = false
|
||||
private var listener: OnVPNStatusChangeListener? = null
|
||||
private var value = false
|
||||
|
||||
fun setOnVPNStatusChangeListener(listener: OnVPNStatusChangeListener?) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
private val mConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
mService = IOpenVPNServiceInternal.Stub.asInterface(service)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
mService = null
|
||||
}
|
||||
}
|
||||
|
||||
fun launchVPN(url: String?) {
|
||||
if (!App.isStart) {
|
||||
// TODO: store antizapret profile
|
||||
DataCleanManager.cleanApplicationData(context)
|
||||
setProfileLoadStatus(false)
|
||||
profileAsync = url?.let {
|
||||
ProfileAsync(context, object : OnProfileLoadListener {
|
||||
override fun onProfileLoadSuccess() {
|
||||
setProfileLoadStatus(true)
|
||||
}
|
||||
|
||||
override fun onProfileLoadFailed(msg: String?) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.init_fail) + msg,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
profileAsync?.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun start(start: Boolean) {
|
||||
val r = Runnable {
|
||||
if (start) {
|
||||
if (!App.isStart) {
|
||||
startVPN()
|
||||
App.isStart = true
|
||||
}
|
||||
} else {
|
||||
if (App.isStart) {
|
||||
stopVPN()
|
||||
App.isStart = false
|
||||
}
|
||||
}
|
||||
}
|
||||
r.run()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
val r = Runnable {
|
||||
if (!App.isStart) {
|
||||
startVPN()
|
||||
App.isStart = true
|
||||
} else {
|
||||
stopVPN()
|
||||
App.isStart = false
|
||||
}
|
||||
}
|
||||
r.run()
|
||||
}
|
||||
|
||||
private fun onStop() {
|
||||
VpnStatus.removeStateListener(this)
|
||||
VpnStatus.removeByteCountListener(this)
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
VpnStatus.addStateListener(this)
|
||||
VpnStatus.addByteCountListener(this)
|
||||
val intent = Intent(context, OpenVPNService::class.java)
|
||||
intent.action = OpenVPNService.START_SERVICE
|
||||
context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
fun onPause() {
|
||||
context.unbindService(mConnection)
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
if (profileAsync != null && !profileAsync!!.isCancelled) {
|
||||
profileAsync?.cancel(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVPN() {
|
||||
try {
|
||||
val pm = ProfileManager.getInstance(context)
|
||||
val profile = pm.getProfileByName(Build.MODEL)
|
||||
startVPNConnection(profile)
|
||||
} catch (ex: Exception) {
|
||||
App.isStart = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopVPN() {
|
||||
stopVPNConnection()
|
||||
cStatus = false
|
||||
}
|
||||
|
||||
var cStatus: Boolean
|
||||
get() = value
|
||||
private set(value) {
|
||||
this.value = value
|
||||
listener?.onVPNStatusChanged(value)
|
||||
}
|
||||
|
||||
private fun setProfileLoadStatus(profileStatus: Boolean) {
|
||||
this.profileStatus = profileStatus
|
||||
listener?.onProfileLoaded(profileStatus)
|
||||
}
|
||||
|
||||
private fun startVPNConnection(vp: VpnProfile) {
|
||||
val intent = Intent(context, LaunchVPN::class.java)
|
||||
intent.putExtra(LaunchVPN.EXTRA_KEY, vp.uuid.toString())
|
||||
intent.action = Intent.ACTION_MAIN
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun stopVPNConnection() {
|
||||
ProfileManager.setConnectedVpnProfileDisconnected(context)
|
||||
if (mService != null) {
|
||||
try {
|
||||
mService?.stopVPN(false)
|
||||
onStop()
|
||||
} catch (e: RemoteException) {
|
||||
VpnStatus.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateState(
|
||||
state: String,
|
||||
logmessage: String,
|
||||
localizedResId: Int,
|
||||
level: ConnectionStatus
|
||||
) {
|
||||
//context.runOnUiThread {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
when (state) {
|
||||
"CONNECTED" -> {
|
||||
Log.i("VPNService", "status: connected")
|
||||
App.isStart = true
|
||||
withContext(Dispatchers.Main) {
|
||||
cStatus = true
|
||||
}
|
||||
}
|
||||
"DISCONNECTED" -> {
|
||||
Log.i("VPNService", "status: disconnected")
|
||||
withContext(Dispatchers.Main) {
|
||||
cStatus = false
|
||||
}
|
||||
}
|
||||
"AUTH_FAILED" -> {
|
||||
Log.i("VPNService", "status: auth failed")
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Wrong Username or Password!", Toast.LENGTH_SHORT).show()
|
||||
cStatus = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setConnectedVPN(uuid: String) {}
|
||||
override fun updateByteCount(`in`: Long, out: Long, diffIn: Long, diffOut: Long) {}
|
||||
|
||||
fun startVpnService(context: Context) {
|
||||
val pm = ProfileManager.getInstance(context)
|
||||
val vp = pm.getProfileByName(Build.MODEL)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var count = 60
|
||||
while (!isNetworkAvailable(context) && count > 0) {
|
||||
delay(1000) // wait for network
|
||||
count--
|
||||
}
|
||||
if (isNetworkAvailable(context)) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d("*****", "startVpnService with profile $vp (${vp.mConnections[0].mServerName})")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val vpnPermissionIntent = VpnService.prepare(context) // context = this@ExternalOpenVPNService
|
||||
/* Check if we need to show the confirmation dialog */
|
||||
if (vpnPermissionIntent != null) {
|
||||
launchVPN(vp, context)
|
||||
} else {
|
||||
VPNLaunchHelper.startOpenVpn(vp, context)
|
||||
}
|
||||
} else {
|
||||
launchVPN(vp, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchVPN(vp: VpnProfile, context: Context) {
|
||||
val startVpnIntent = Intent(Intent.ACTION_MAIN)
|
||||
startVpnIntent.setClass(context, LaunchVPN::class.java)
|
||||
startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY, vp.uuidString)
|
||||
startVpnIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true) // true
|
||||
context.startActivity(startVpnIntent)
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(context: Context): Boolean {
|
||||
val connectivityManager =
|
||||
context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
?: return false
|
||||
val info = connectivityManager.activeNetworkInfo
|
||||
return info != null && info.isAvailable && info.isConnected
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var mService: IOpenVPNServiceInternal? = null
|
||||
}
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.blinkt.openvpn.core.ConnectionStatus;
|
||||
import de.blinkt.openvpn.core.IServiceStatus;
|
||||
import de.blinkt.openvpn.core.OpenVPNStatusService;
|
||||
import de.blinkt.openvpn.core.PasswordCache;
|
||||
import de.blinkt.openvpn.core.Preferences;
|
||||
import de.blinkt.openvpn.core.ProfileManager;
|
||||
import de.blinkt.openvpn.core.VPNLaunchHelper;
|
||||
import de.blinkt.openvpn.core.VpnStatus;
|
||||
|
||||
/**
|
||||
* This Activity actually handles two stages of a launcher shortcut's life cycle.
|
||||
* <p/>
|
||||
* 1. Your application offers to provide shortcuts to the launcher. When
|
||||
* the user installs a shortcut, an activity within your application
|
||||
* generates the actual shortcut and returns it to the launcher, where it
|
||||
* is shown to the user as an icon.
|
||||
* <p/>
|
||||
* 2. Any time the user clicks on an installed shortcut, an intent is sent.
|
||||
* Typically this would then be handled as necessary by an activity within
|
||||
* your application.
|
||||
* <p/>
|
||||
* We handle stage 1 (creating a shortcut) by simply sending back the information (in the form
|
||||
* of an {@link Intent} that the launcher will use to create the shortcut.
|
||||
* <p/>
|
||||
* You can also implement this in an interactive way, by having your activity actually present
|
||||
* UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL,
|
||||
* media item, or action.
|
||||
* <p/>
|
||||
* We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents
|
||||
* of the incoming {@link Intent}.
|
||||
* <p/>
|
||||
* In a real application, you would probably use the shortcut intent to display specific content
|
||||
* or start a particular operation.
|
||||
*/
|
||||
public class LaunchVPN extends Activity {
|
||||
public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID";
|
||||
public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
|
||||
public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
|
||||
public static final String CLEARLOG = "clearlogconnect";
|
||||
private static final int START_VPN_PROFILE = 70;
|
||||
private VpnProfile mSelectedProfile;
|
||||
private boolean mhideLog = false;
|
||||
private boolean mCmfixed = false;
|
||||
private String mTransientAuthPW;
|
||||
private String mTransientCertOrPCKS12PW;
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
IServiceStatus service = IServiceStatus.Stub.asInterface(binder);
|
||||
try {
|
||||
if (mTransientAuthPW != null) service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.AUTHPASSWORD, mTransientAuthPW);
|
||||
if (mTransientCertOrPCKS12PW != null) service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.PCKS12ORCERTPASSWORD, mTransientCertOrPCKS12PW);
|
||||
onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
unbindService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.launchvpn);
|
||||
//Log.e("test","VPN start");
|
||||
startVpnFromIntent();
|
||||
}
|
||||
|
||||
protected void startVpnFromIntent() {
|
||||
// Resolve the intent
|
||||
final Intent intent = getIntent();
|
||||
final String action = intent.getAction();
|
||||
// If the intent is a request to create a shortcut, we'll do that and exit
|
||||
if (Intent.ACTION_MAIN.equals(action)) {
|
||||
// Check if we need to clear the log
|
||||
if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) VpnStatus.clearLog();
|
||||
// we got called to be the starting point, most likely a shortcut
|
||||
String shortcutUUID = intent.getStringExtra(EXTRA_KEY);
|
||||
String shortcutName = intent.getStringExtra(EXTRA_NAME);
|
||||
mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
|
||||
VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID);
|
||||
if (shortcutName != null && profileToConnect == null) profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
|
||||
if (profileToConnect == null) {
|
||||
VpnStatus.logError(R.string.shortcut_profile_notfound);
|
||||
// show Log window to display error
|
||||
showLogWindow();
|
||||
finish();
|
||||
} else {
|
||||
mSelectedProfile = profileToConnect;
|
||||
launchVPN();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void askForPW(final int type) {
|
||||
final EditText entry = new EditText(this);
|
||||
entry.setSingleLine();
|
||||
entry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
entry.setTransformationMethod(new PasswordTransformationMethod());
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
|
||||
dialog.setTitle(getString(R.string.pw_request_dialog_title, getString(type)));
|
||||
dialog.setMessage(getString(R.string.pw_request_dialog_prompt, mSelectedProfile.mName));
|
||||
@SuppressLint("InflateParams") final View userpwlayout = getLayoutInflater().inflate(R.layout.userpass, null, false);
|
||||
if (type == R.string.password) {
|
||||
((EditText) userpwlayout.findViewById(R.id.username)).setText(mSelectedProfile.mUsername);
|
||||
((EditText) userpwlayout.findViewById(R.id.password)).setText(mSelectedProfile.mPassword);
|
||||
((CheckBox) userpwlayout.findViewById(R.id.save_password)).setChecked(!TextUtils.isEmpty(mSelectedProfile.mPassword));
|
||||
((CheckBox) userpwlayout.findViewById(R.id.show_password)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) ((EditText) userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||
else ((EditText) userpwlayout.findViewById(R.id.password)).setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
}
|
||||
});
|
||||
dialog.setView(userpwlayout);
|
||||
} else {
|
||||
dialog.setView(entry);
|
||||
}
|
||||
AlertDialog.Builder builder = dialog.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (type == R.string.password) {
|
||||
mSelectedProfile.mUsername = ((EditText) userpwlayout.findViewById(R.id.username)).getText().toString();
|
||||
String pw = ((EditText) userpwlayout.findViewById(R.id.password)).getText().toString();
|
||||
if (((CheckBox) userpwlayout.findViewById(R.id.save_password)).isChecked()) {
|
||||
mSelectedProfile.mPassword = pw;
|
||||
} else {
|
||||
mSelectedProfile.mPassword = null;
|
||||
mTransientAuthPW = pw;
|
||||
}
|
||||
} else {
|
||||
mTransientCertOrPCKS12PW = entry.getText().toString();
|
||||
}
|
||||
Intent intent = new Intent(LaunchVPN.this, OpenVPNStatusService.class);
|
||||
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
});
|
||||
dialog.setNegativeButton(android.R.string.cancel, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
VpnStatus.updateStateString("USER_VPN_PASSWORD_CANCELLED", "", R.string.state_user_vpn_password_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
dialog.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == START_VPN_PROFILE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
int needpw = mSelectedProfile.needUserPWInput(mTransientCertOrPCKS12PW, mTransientAuthPW);
|
||||
if (needpw != 0) {
|
||||
VpnStatus.updateStateString("USER_VPN_PASSWORD", "", R.string.state_user_vpn_password, ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
|
||||
askForPW(needpw);
|
||||
} else {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
|
||||
boolean showLogWindow = prefs.getBoolean("showlogwindow", true);
|
||||
if (!mhideLog && showLogWindow) showLogWindow();
|
||||
ProfileManager.updateLRU(this, mSelectedProfile);
|
||||
VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
|
||||
finish();
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
// User does not want us to start, so we just vanish
|
||||
VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) VpnStatus.logError(R.string.nought_alwayson_warning);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showLogWindow() {
|
||||
//TODO : Implement your own logwindow, apparently
|
||||
}
|
||||
|
||||
void showConfigErrorDialog(int vpnok) {
|
||||
AlertDialog.Builder d = new AlertDialog.Builder(this);
|
||||
d.setTitle(R.string.config_error_found);
|
||||
d.setMessage(vpnok);
|
||||
d.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
d.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) setOnDismissListener(d);
|
||||
d.show();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
|
||||
private void setOnDismissListener(AlertDialog.Builder d) {
|
||||
d.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void launchVPN() {
|
||||
int vpnok = mSelectedProfile.checkProfile(this);
|
||||
if (vpnok != R.string.no_error_found) {
|
||||
showConfigErrorDialog(vpnok);
|
||||
return;
|
||||
}
|
||||
Intent intent = VpnService.prepare(this);
|
||||
// Check if we want to fix /dev/tun
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
|
||||
boolean usecm9fix = prefs.getBoolean("useCM9Fix", false);
|
||||
boolean loadTunModule = prefs.getBoolean("loadTunModule", false);
|
||||
if (loadTunModule) execeuteSUcmd("insmod /system/lib/modules/tun.ko");
|
||||
if (usecm9fix && !mCmfixed) {
|
||||
execeuteSUcmd("chown system /dev/tun");
|
||||
}
|
||||
if (intent != null) {
|
||||
VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT);
|
||||
// Start the query
|
||||
try {
|
||||
startActivityForResult(intent, START_VPN_PROFILE);
|
||||
} catch (ActivityNotFoundException ane) {
|
||||
// Shame on you Sony! At least one user reported that
|
||||
// an official Sony Xperia Arc S image triggers this exception
|
||||
VpnStatus.logError(R.string.no_vpn_support_image);
|
||||
showLogWindow();
|
||||
}
|
||||
} else {
|
||||
onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void execeuteSUcmd(String command) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder("su", "-c", command);
|
||||
Process p = pb.start();
|
||||
int ret = p.waitFor();
|
||||
if (ret == 0) mCmfixed = true;
|
||||
} catch (InterruptedException | IOException e) {
|
||||
VpnStatus.logException("SU command", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,867 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import org.spongycastle.util.io.pem.PemObject;
|
||||
import org.spongycastle.util.io.pem.PemWriter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
import de.blinkt.openvpn.core.Connection;
|
||||
import de.blinkt.openvpn.core.NativeUtils;
|
||||
import de.blinkt.openvpn.core.OpenVPNService;
|
||||
import de.blinkt.openvpn.core.PasswordCache;
|
||||
import de.blinkt.openvpn.core.VPNLaunchHelper;
|
||||
import de.blinkt.openvpn.core.VpnStatus;
|
||||
import de.blinkt.openvpn.core.X509Utils;
|
||||
|
||||
public class VpnProfile implements Serializable, Cloneable {
|
||||
// Note that this class cannot be moved to core where it belongs since
|
||||
// the profile loading depends on it being here
|
||||
// The Serializable documentation mentions that class name change are possible
|
||||
// but the how is unclear
|
||||
//
|
||||
transient public static final long MAX_EMBED_FILE_SIZE = 2048 * 1024; // 2048kB
|
||||
// Don't change this, not all parts of the program use this constant
|
||||
public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
|
||||
public static final String INLINE_TAG = "[[INLINE]]";
|
||||
public static final String DISPLAYNAME_TAG = "[[NAME]]";
|
||||
public static final int MAXLOGLEVEL = 4;
|
||||
public static final int CURRENT_PROFILE_VERSION = 6;
|
||||
public static final int DEFAULT_MSSFIX_SIZE = 1280;
|
||||
public static final int TYPE_CERTIFICATES = 0;
|
||||
public static final int TYPE_PKCS12 = 1;
|
||||
public static final int TYPE_KEYSTORE = 2;
|
||||
public static final int TYPE_USERPASS = 3;
|
||||
public static final int TYPE_STATICKEYS = 4;
|
||||
public static final int TYPE_USERPASS_CERTIFICATES = 5;
|
||||
public static final int TYPE_USERPASS_PKCS12 = 6;
|
||||
public static final int TYPE_USERPASS_KEYSTORE = 7;
|
||||
public static final int X509_VERIFY_TLSREMOTE = 0;
|
||||
public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
|
||||
public static final int X509_VERIFY_TLSREMOTE_DN = 2;
|
||||
public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
|
||||
public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
|
||||
public static final int AUTH_RETRY_NONE_FORGET = 0;
|
||||
public static final int AUTH_RETRY_NOINTERACT = 2;
|
||||
public static final boolean mIsOpenVPN22 = false;
|
||||
private static final long serialVersionUID = 7085688938959334563L;
|
||||
private static final int AUTH_RETRY_NONE_KEEP = 1;
|
||||
private static final int AUTH_RETRY_INTERACT = 3;
|
||||
public static String DEFAULT_DNS1 = "8.8.8.8";
|
||||
public static String DEFAULT_DNS2 = "1.1.1.1";
|
||||
// variable named wrong and should haven beeen transient
|
||||
// but needs to keep wrong name to guarante loading of old
|
||||
// profiles
|
||||
public transient boolean profileDeleted = false;
|
||||
public int mAuthenticationType = TYPE_KEYSTORE;
|
||||
public String mName;
|
||||
public String mAlias;
|
||||
public String mClientCertFilename;
|
||||
public String mTLSAuthDirection = "";
|
||||
public String mTLSAuthFilename;
|
||||
public String mClientKeyFilename;
|
||||
public String mCaFilename;
|
||||
public boolean mUseLzo = true;
|
||||
public String mPKCS12Filename;
|
||||
public String mPKCS12Password;
|
||||
public boolean mUseTLSAuth = false;
|
||||
public String mDNS1 = DEFAULT_DNS1;
|
||||
public String mDNS2 = DEFAULT_DNS2;
|
||||
public String mIPv4Address;
|
||||
public String mIPv6Address;
|
||||
public boolean mOverrideDNS = false;
|
||||
public String mSearchDomain = "blinkt.de";
|
||||
public boolean mUseDefaultRoute = true;
|
||||
public boolean mUsePull = true;
|
||||
public String mCustomRoutes;
|
||||
public boolean mCheckRemoteCN = true;
|
||||
public boolean mExpectTLSCert = false;
|
||||
public String mRemoteCN = "";
|
||||
public String mPassword = "";
|
||||
public String mUsername = "";
|
||||
public boolean mRoutenopull = false;
|
||||
public boolean mUseRandomHostname = false;
|
||||
public boolean mUseFloat = false;
|
||||
public boolean mUseCustomConfig = false;
|
||||
public String mCustomConfigOptions = "";
|
||||
public String mVerb = "1"; //ignored
|
||||
public String mCipher = "";
|
||||
public boolean mNobind = false;
|
||||
public boolean mUseDefaultRoutev6 = true;
|
||||
public String mCustomRoutesv6 = "";
|
||||
public String mKeyPassword = "";
|
||||
public boolean mPersistTun = false;
|
||||
public String mConnectRetryMax = "-1";
|
||||
public String mConnectRetry = "2";
|
||||
public String mConnectRetryMaxTime = "300";
|
||||
public boolean mUserEditable = true;
|
||||
public String mAuth = "";
|
||||
public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
|
||||
public String mx509UsernameField = null;
|
||||
public boolean mAllowLocalLAN;
|
||||
public String mExcludedRoutes;
|
||||
public String mExcludedRoutesv6;
|
||||
public int mMssFix = 0; // -1 is default,
|
||||
public Connection[] mConnections = new Connection[0];
|
||||
public boolean mRemoteRandom = false;
|
||||
public HashSet<String> mAllowedAppsVpn = new HashSet<>();
|
||||
public boolean mAllowedAppsVpnAreDisallowed = true;
|
||||
public String mCrlFilename;
|
||||
public String mProfileCreator;
|
||||
public int mAuthRetry = AUTH_RETRY_NONE_FORGET;
|
||||
public int mTunMtu;
|
||||
public boolean mPushPeerInfo = false;
|
||||
public int mVersion = 0;
|
||||
// timestamp when the profile was last used
|
||||
public long mLastUsed;
|
||||
/* Options no longer used in new profiles */
|
||||
public String mServerName = "openvpn.example.com";
|
||||
public String mServerPort = "11940";
|
||||
public boolean mUseUdp = true;
|
||||
private transient PrivateKey mPrivateKey;
|
||||
// Public attributes, since I got mad with getter/setter
|
||||
// set members to default values
|
||||
private UUID mUuid;
|
||||
private int mProfileVersion;
|
||||
|
||||
|
||||
public VpnProfile(String name) {
|
||||
mUuid = UUID.randomUUID();
|
||||
mName = name;
|
||||
mProfileVersion = CURRENT_PROFILE_VERSION;
|
||||
mConnections = new Connection[1];
|
||||
mConnections[0] = new Connection();
|
||||
mLastUsed = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static String openVpnEscape(String unescaped) {
|
||||
if (unescaped == null) return null;
|
||||
String escapedString = unescaped.replace("\\", "\\\\");
|
||||
escapedString = escapedString.replace("\"", "\\\"");
|
||||
escapedString = escapedString.replace("\n", "\\n");
|
||||
if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#") && !escapedString.contains(";") && !escapedString.equals("")) return unescaped;
|
||||
else return '"' + escapedString + '"';
|
||||
}
|
||||
|
||||
//! Put inline data inline and other data as normal escaped filename
|
||||
public static String insertFileData(String cfgentry, String filedata) {
|
||||
if (filedata == null) {
|
||||
return String.format("%s %s\n", cfgentry, "file missing in config profile");
|
||||
} else if (isEmbedded(filedata)) {
|
||||
String dataWithOutHeader = getEmbeddedContent(filedata);
|
||||
return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
|
||||
} else {
|
||||
return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDisplayName(String embeddedFile) {
|
||||
int start = DISPLAYNAME_TAG.length();
|
||||
int end = embeddedFile.indexOf(INLINE_TAG);
|
||||
return embeddedFile.substring(start, end);
|
||||
}
|
||||
|
||||
public static String getEmbeddedContent(String data) {
|
||||
if (!data.contains(INLINE_TAG)) return data;
|
||||
int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
|
||||
return data.substring(start);
|
||||
}
|
||||
|
||||
public static boolean isEmbedded(String data) {
|
||||
if (data == null) return false;
|
||||
return data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof VpnProfile) {
|
||||
VpnProfile vpnProfile = (VpnProfile) obj;
|
||||
return mUuid.equals(vpnProfile.mUuid);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearDefaults() {
|
||||
mServerName = "unknown";
|
||||
mUsePull = false;
|
||||
mUseLzo = false;
|
||||
mUseDefaultRoute = false;
|
||||
mUseDefaultRoutev6 = false;
|
||||
mExpectTLSCert = false;
|
||||
mCheckRemoteCN = false;
|
||||
mPersistTun = false;
|
||||
mAllowLocalLAN = true;
|
||||
mPushPeerInfo = false;
|
||||
mMssFix = 0;
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return mUuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (TextUtils.isEmpty(mName)) return "No profile name";
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void upgradeProfile() {
|
||||
if (mProfileVersion < 2) {
|
||||
/* default to the behaviour the OS used */
|
||||
mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
if (mProfileVersion < 4) {
|
||||
moveOptionsToConnection();
|
||||
mAllowedAppsVpnAreDisallowed = true;
|
||||
}
|
||||
if (mAllowedAppsVpn == null) mAllowedAppsVpn = new HashSet<>();
|
||||
if (mConnections == null) mConnections = new Connection[0];
|
||||
if (mProfileVersion < 6) {
|
||||
if (TextUtils.isEmpty(mProfileCreator)) mUserEditable = true;
|
||||
}
|
||||
mProfileVersion = CURRENT_PROFILE_VERSION;
|
||||
}
|
||||
|
||||
private void moveOptionsToConnection() {
|
||||
mConnections = new Connection[1];
|
||||
Connection conn = new Connection();
|
||||
conn.mServerName = mServerName;
|
||||
conn.mServerPort = mServerPort;
|
||||
conn.mUseUdp = mUseUdp;
|
||||
conn.mCustomConfiguration = "";
|
||||
mConnections[0] = conn;
|
||||
}
|
||||
|
||||
public String getConfigFile(Context context, boolean configForOvpn3) {
|
||||
File cacheDir = context.getCacheDir();
|
||||
String cfg = "";
|
||||
// Enable management interface
|
||||
cfg += "# Enables connection to GUI\n";
|
||||
cfg += "management ";
|
||||
cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket";
|
||||
cfg += " unix\n";
|
||||
cfg += "management-client\n";
|
||||
// Not needed, see updated man page in 2.3
|
||||
//cfg += "management-signal\n";
|
||||
cfg += "management-query-passwords\n";
|
||||
cfg += "management-hold\n\n";
|
||||
if (!configForOvpn3) {
|
||||
cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context)));
|
||||
String versionString = String.format(Locale.US, "%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE, NativeUtils.getNativeAPI(), Build.BRAND, Build.BOARD, Build.MODEL);
|
||||
cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString));
|
||||
}
|
||||
cfg += "machine-readable-output\n";
|
||||
cfg += "allow-recursive-routing\n";
|
||||
// Users are confused by warnings that are misleading...
|
||||
cfg += "ifconfig-nowarn\n";
|
||||
boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS);
|
||||
if (useTLSClient && mUsePull) cfg += "client\n";
|
||||
else if (mUsePull) cfg += "pull\n";
|
||||
else if (useTLSClient) cfg += "tls-client\n";
|
||||
//cfg += "verb " + mVerb + "\n";
|
||||
cfg += "verb " + MAXLOGLEVEL + "\n";
|
||||
if (mConnectRetryMax == null) {
|
||||
mConnectRetryMax = "-1";
|
||||
}
|
||||
if (!mConnectRetryMax.equals("-1")) cfg += "connect-retry-max " + mConnectRetryMax + "\n";
|
||||
if (TextUtils.isEmpty(mConnectRetry)) mConnectRetry = "2";
|
||||
if (TextUtils.isEmpty(mConnectRetryMaxTime)) mConnectRetryMaxTime = "300";
|
||||
if (!mIsOpenVPN22) cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n";
|
||||
else if (mIsOpenVPN22 && mUseUdp) cfg += "connect-retry " + mConnectRetry + "\n";
|
||||
cfg += "resolv-retry 60\n";
|
||||
// We cannot use anything else than tun
|
||||
cfg += "dev tun\n";
|
||||
boolean canUsePlainRemotes = true;
|
||||
if (mConnections.length == 1) {
|
||||
cfg += mConnections[0].getConnectionBlock();
|
||||
} else {
|
||||
for (Connection conn : mConnections) {
|
||||
canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote();
|
||||
}
|
||||
if (mRemoteRandom) cfg += "remote-random\n";
|
||||
if (canUsePlainRemotes) {
|
||||
for (Connection conn : mConnections) {
|
||||
if (conn.mEnabled) {
|
||||
cfg += conn.getConnectionBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (mAuthenticationType) {
|
||||
case VpnProfile.TYPE_USERPASS_CERTIFICATES:
|
||||
cfg += "auth-user-pass\n";
|
||||
case VpnProfile.TYPE_CERTIFICATES:
|
||||
// Ca
|
||||
cfg += insertFileData("ca", mCaFilename);
|
||||
// Client Cert + Key
|
||||
cfg += insertFileData("key", mClientKeyFilename);
|
||||
cfg += insertFileData("cert", mClientCertFilename);
|
||||
break;
|
||||
case VpnProfile.TYPE_USERPASS_PKCS12:
|
||||
cfg += "auth-user-pass\n";
|
||||
case VpnProfile.TYPE_PKCS12:
|
||||
cfg += insertFileData("pkcs12", mPKCS12Filename);
|
||||
break;
|
||||
case VpnProfile.TYPE_USERPASS_KEYSTORE:
|
||||
cfg += "auth-user-pass\n";
|
||||
case VpnProfile.TYPE_KEYSTORE:
|
||||
if (!configForOvpn3) {
|
||||
String[] ks = getKeyStoreCertificates(context);
|
||||
cfg += "### From Keystore ####\n";
|
||||
if (ks != null) {
|
||||
cfg += "<ca>\n" + ks[0] + "\n</ca>\n";
|
||||
if (ks[1] != null) cfg += "<extra-certs>\n" + ks[1] + "\n</extra-certs>\n";
|
||||
cfg += "<cert>\n" + ks[2] + "\n</cert>\n";
|
||||
cfg += "management-external-key\n";
|
||||
} else {
|
||||
cfg += context.getString(R.string.keychain_access) + "\n";
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) if (!mAlias.matches("^[a-zA-Z0-9]$")) cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VpnProfile.TYPE_USERPASS:
|
||||
cfg += "auth-user-pass\n";
|
||||
cfg += insertFileData("ca", mCaFilename);
|
||||
}
|
||||
if (isUserPWAuth()) {
|
||||
if (mAuthenticationType == AUTH_RETRY_NOINTERACT) cfg += "auth-retry nointeract";
|
||||
}
|
||||
if (!TextUtils.isEmpty(mCrlFilename)) cfg += insertFileData("crl-verify", mCrlFilename);
|
||||
if (mUseLzo) {
|
||||
cfg += "comp-lzo\n";
|
||||
}
|
||||
if (mUseTLSAuth) {
|
||||
boolean useTlsCrypt = mTLSAuthDirection.equals("tls-crypt");
|
||||
if (mAuthenticationType == TYPE_STATICKEYS) cfg += insertFileData("secret", mTLSAuthFilename);
|
||||
else if (useTlsCrypt) cfg += insertFileData("tls-crypt", mTLSAuthFilename);
|
||||
else cfg += insertFileData("tls-auth", mTLSAuthFilename);
|
||||
if (!TextUtils.isEmpty(mTLSAuthDirection) && !useTlsCrypt) {
|
||||
cfg += "key-direction ";
|
||||
cfg += mTLSAuthDirection;
|
||||
cfg += "\n";
|
||||
}
|
||||
}
|
||||
if (!mUsePull) {
|
||||
if (!TextUtils.isEmpty(mIPv4Address)) cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n";
|
||||
if (!TextUtils.isEmpty(mIPv6Address)) cfg += "ifconfig-ipv6 " + mIPv6Address + "\n";
|
||||
}
|
||||
if (mUsePull && mRoutenopull) cfg += "route-nopull\n";
|
||||
String routes = "";
|
||||
if (mUseDefaultRoute) routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n";
|
||||
else {
|
||||
for (String route : getCustomRoutes(mCustomRoutes)) {
|
||||
routes += "route " + route + " vpn_gateway\n";
|
||||
}
|
||||
for (String route : getCustomRoutes(mExcludedRoutes)) {
|
||||
routes += "route " + route + " net_gateway\n";
|
||||
}
|
||||
}
|
||||
if (mUseDefaultRoutev6) cfg += "route-ipv6 ::/0\n";
|
||||
else for (String route : getCustomRoutesv6(mCustomRoutesv6)) {
|
||||
routes += "route-ipv6 " + route + "\n";
|
||||
}
|
||||
cfg += routes;
|
||||
if (mOverrideDNS || !mUsePull) {
|
||||
if (!TextUtils.isEmpty(mDNS1)) {
|
||||
if (mDNS1.contains(":")) cfg += "dhcp-option DNS6 " + mDNS1 + "\n";
|
||||
else cfg += "dhcp-option DNS " + mDNS1 + "\n";
|
||||
}
|
||||
if (!TextUtils.isEmpty(mDNS2)) {
|
||||
if (mDNS2.contains(":")) cfg += "dhcp-option DNS6 " + mDNS2 + "\n";
|
||||
else cfg += "dhcp-option DNS " + mDNS2 + "\n";
|
||||
}
|
||||
if (!TextUtils.isEmpty(mSearchDomain)) cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n";
|
||||
}
|
||||
if (mMssFix != 0) {
|
||||
if (mMssFix != 1450) {
|
||||
cfg += String.format(Locale.US, "mssfix %d\n", mMssFix);
|
||||
} else cfg += "mssfix\n";
|
||||
}
|
||||
if (mTunMtu >= 48 && mTunMtu != 1500) {
|
||||
cfg += String.format(Locale.US, "tun-mtu %d\n", mTunMtu);
|
||||
}
|
||||
if (mNobind) cfg += "nobind\n";
|
||||
// Authentication
|
||||
if (mAuthenticationType != TYPE_STATICKEYS) {
|
||||
if (mCheckRemoteCN) {
|
||||
if (mRemoteCN == null || mRemoteCN.equals("")) cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n";
|
||||
else switch (mX509AuthType) {
|
||||
// 2.2 style x509 checks
|
||||
case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING:
|
||||
cfg += "compat-names no-remapping\n";
|
||||
case X509_VERIFY_TLSREMOTE:
|
||||
cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n";
|
||||
break;
|
||||
case X509_VERIFY_TLSREMOTE_RDN:
|
||||
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n";
|
||||
break;
|
||||
case X509_VERIFY_TLSREMOTE_RDN_PREFIX:
|
||||
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n";
|
||||
break;
|
||||
case X509_VERIFY_TLSREMOTE_DN:
|
||||
cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n";
|
||||
break;
|
||||
}
|
||||
if (!TextUtils.isEmpty(mx509UsernameField)) cfg += "x509-username-field " + openVpnEscape(mx509UsernameField) + "\n";
|
||||
}
|
||||
if (mExpectTLSCert) cfg += "remote-cert-tls server\n";
|
||||
}
|
||||
if (!TextUtils.isEmpty(mCipher)) {
|
||||
cfg += "cipher " + mCipher + "\n";
|
||||
}
|
||||
if (!TextUtils.isEmpty(mAuth)) {
|
||||
cfg += "auth " + mAuth + "\n";
|
||||
}
|
||||
// Obscure Settings dialog
|
||||
if (mUseRandomHostname) cfg += "#my favorite options :)\nremote-random-hostname\n";
|
||||
if (mUseFloat) cfg += "float\n";
|
||||
if (mPersistTun) {
|
||||
cfg += "persist-tun\n";
|
||||
cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n";
|
||||
cfg += "preresolve\n";
|
||||
}
|
||||
if (mPushPeerInfo) cfg += "push-peer-info\n";
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true);
|
||||
if (usesystemproxy && !mIsOpenVPN22) {
|
||||
cfg += "# Use system proxy setting\n";
|
||||
cfg += "management-query-proxy\n";
|
||||
}
|
||||
if (mUseCustomConfig) {
|
||||
cfg += "# Custom configuration options\n";
|
||||
cfg += "# You are on your on own here :)\n";
|
||||
cfg += mCustomConfigOptions;
|
||||
cfg += "\n";
|
||||
}
|
||||
if (!canUsePlainRemotes) {
|
||||
cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n";
|
||||
for (Connection conn : mConnections) {
|
||||
if (conn.mEnabled) {
|
||||
cfg += "<connection>\n";
|
||||
cfg += conn.getConnectionBlock();
|
||||
cfg += "</connection>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public String getVersionEnvString(Context c) {
|
||||
String version = "unknown";
|
||||
try {
|
||||
PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
|
||||
version = packageinfo.versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
return String.format(Locale.US, "%s %s", c.getPackageName(), version);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Collection<String> getCustomRoutes(String routes) {
|
||||
Vector<String> cidrRoutes = new Vector<>();
|
||||
if (routes == null) {
|
||||
// No routes set, return empty vector
|
||||
return cidrRoutes;
|
||||
}
|
||||
for (String route : routes.split("[\n \t]")) {
|
||||
if (!route.equals("")) {
|
||||
String cidrroute = cidrToIPAndNetmask(route);
|
||||
if (cidrroute == null) return cidrRoutes;
|
||||
cidrRoutes.add(cidrroute);
|
||||
}
|
||||
}
|
||||
return cidrRoutes;
|
||||
}
|
||||
|
||||
private Collection<String> getCustomRoutesv6(String routes) {
|
||||
Vector<String> cidrRoutes = new Vector<>();
|
||||
if (routes == null) {
|
||||
// No routes set, return empty vector
|
||||
return cidrRoutes;
|
||||
}
|
||||
for (String route : routes.split("[\n \t]")) {
|
||||
if (!route.equals("")) {
|
||||
cidrRoutes.add(route);
|
||||
}
|
||||
}
|
||||
return cidrRoutes;
|
||||
}
|
||||
|
||||
private String cidrToIPAndNetmask(String route) {
|
||||
String[] parts = route.split("/");
|
||||
// No /xx, assume /32 as netmask
|
||||
if (parts.length == 1) parts = (route + "/32").split("/");
|
||||
if (parts.length != 2) return null;
|
||||
int len;
|
||||
try {
|
||||
len = Integer.parseInt(parts[1]);
|
||||
} catch (NumberFormatException ne) {
|
||||
return null;
|
||||
}
|
||||
if (len < 0 || len > 32) return null;
|
||||
long nm = 0xffffffffL;
|
||||
nm = (nm << (32 - len)) & 0xffffffffL;
|
||||
String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff);
|
||||
return parts[0] + " " + netmask;
|
||||
}
|
||||
|
||||
public Intent prepareStartService(Context context) {
|
||||
Intent intent = getStartServiceIntent(context);
|
||||
// TODO: Handle this?!
|
||||
// if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
|
||||
// if (getKeyStoreCertificates(context) == null)
|
||||
// return null;
|
||||
// }
|
||||
return intent;
|
||||
}
|
||||
|
||||
public void writeConfigFile(Context context) throws IOException {
|
||||
FileWriter cfg = new FileWriter(VPNLaunchHelper.getConfigFilePath(context));
|
||||
cfg.write(getConfigFile(context, false));
|
||||
cfg.flush();
|
||||
cfg.close();
|
||||
}
|
||||
|
||||
public Intent getStartServiceIntent(Context context) {
|
||||
String prefix = context.getPackageName();
|
||||
Intent intent = new Intent(context, OpenVPNService.class);
|
||||
intent.putExtra(prefix + ".profileUUID", mUuid.toString());
|
||||
intent.putExtra(prefix + ".profileVersion", mVersion);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public String[] getKeyStoreCertificates(Context context) {
|
||||
return getKeyStoreCertificates(context, 5);
|
||||
}
|
||||
|
||||
public void checkForRestart(final Context context) {
|
||||
/* This method is called when OpenVPNService is restarted */
|
||||
if ((mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) && mPrivateKey == null) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getKeyStoreCertificates(context);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VpnProfile clone() throws CloneNotSupportedException {
|
||||
VpnProfile copy = (VpnProfile) super.clone();
|
||||
copy.mUuid = UUID.randomUUID();
|
||||
copy.mConnections = new Connection[mConnections.length];
|
||||
int i = 0;
|
||||
for (Connection conn : mConnections) {
|
||||
copy.mConnections[i++] = conn.clone();
|
||||
}
|
||||
copy.mAllowedAppsVpn = (HashSet<String>) mAllowedAppsVpn.clone();
|
||||
return copy;
|
||||
}
|
||||
|
||||
public VpnProfile copy(String name) {
|
||||
try {
|
||||
VpnProfile copy = clone();
|
||||
copy.mName = name;
|
||||
return copy;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void pwDidFail(Context c) {
|
||||
}
|
||||
|
||||
synchronized String[] getKeyStoreCertificates(Context context, int tries) {
|
||||
// Force application context- KeyChain methods will block long enough that by the time they
|
||||
// are finished and try to unbind, the original activity context might have been destroyed.
|
||||
context = context.getApplicationContext();
|
||||
try {
|
||||
PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
|
||||
mPrivateKey = privateKey;
|
||||
String keystoreChain = null;
|
||||
X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
|
||||
if (caChain == null) throw new NoCertReturnedException("No certificate returned from Keystore");
|
||||
if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) {
|
||||
VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert));
|
||||
} else {
|
||||
StringWriter ksStringWriter = new StringWriter();
|
||||
PemWriter pw = new PemWriter(ksStringWriter);
|
||||
for (int i = 1; i < caChain.length; i++) {
|
||||
X509Certificate cert = caChain[i];
|
||||
pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
|
||||
}
|
||||
pw.close();
|
||||
keystoreChain = ksStringWriter.toString();
|
||||
}
|
||||
String caout = null;
|
||||
if (!TextUtils.isEmpty(mCaFilename)) {
|
||||
try {
|
||||
Certificate[] cacerts = X509Utils.getCertificatesFromFile(mCaFilename);
|
||||
StringWriter caoutWriter = new StringWriter();
|
||||
PemWriter pw = new PemWriter(caoutWriter);
|
||||
for (Certificate cert : cacerts)
|
||||
pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
|
||||
pw.close();
|
||||
caout = caoutWriter.toString();
|
||||
} catch (Exception e) {
|
||||
VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
StringWriter certout = new StringWriter();
|
||||
if (caChain.length >= 1) {
|
||||
X509Certificate usercert = caChain[0];
|
||||
PemWriter upw = new PemWriter(certout);
|
||||
upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded()));
|
||||
upw.close();
|
||||
}
|
||||
String user = certout.toString();
|
||||
String ca, extra;
|
||||
if (caout == null) {
|
||||
ca = keystoreChain;
|
||||
extra = null;
|
||||
} else {
|
||||
ca = caout;
|
||||
extra = keystoreChain;
|
||||
}
|
||||
return new String[]{ca, extra, user};
|
||||
} catch (InterruptedException | IOException | KeyChainException | NoCertReturnedException | IllegalArgumentException | CertificateException e) {
|
||||
e.printStackTrace();
|
||||
VpnStatus.logError(R.string.keyChainAccessError, e.getLocalizedMessage());
|
||||
VpnStatus.logError(R.string.keychain_access);
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
|
||||
if (!mAlias.matches("^[a-zA-Z0-9]$")) {
|
||||
VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (AssertionError e) {
|
||||
if (tries == 0) return null;
|
||||
VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying", e.getLocalizedMessage()));
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException e1) {
|
||||
VpnStatus.logException(e1);
|
||||
}
|
||||
return getKeyStoreCertificates(context, tries - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//! Return an error if something is wrong
|
||||
public int checkProfile(Context context) {
|
||||
if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
|
||||
if (mAlias == null) return R.string.no_keystore_cert_selected;
|
||||
} else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
|
||||
if (TextUtils.isEmpty(mCaFilename)) return R.string.no_ca_cert_selected;
|
||||
}
|
||||
if (mCheckRemoteCN && mX509AuthType == X509_VERIFY_TLSREMOTE) return R.string.deprecated_tls_remote;
|
||||
if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) {
|
||||
if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) return R.string.ipv4_format_error;
|
||||
}
|
||||
if (!mUseDefaultRoute) {
|
||||
if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0) return R.string.custom_route_format_error;
|
||||
if (!TextUtils.isEmpty(mExcludedRoutes) && getCustomRoutes(mExcludedRoutes).size() == 0) return R.string.custom_route_format_error;
|
||||
}
|
||||
if (mUseTLSAuth && TextUtils.isEmpty(mTLSAuthFilename)) return R.string.missing_tlsauth;
|
||||
if ((mAuthenticationType == TYPE_USERPASS_CERTIFICATES || mAuthenticationType == TYPE_CERTIFICATES) && (TextUtils.isEmpty(mClientCertFilename) || TextUtils.isEmpty(mClientKeyFilename)))
|
||||
return R.string.missing_certificates;
|
||||
if ((mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) && TextUtils.isEmpty(mCaFilename)) return R.string.missing_ca_certificate;
|
||||
boolean noRemoteEnabled = true;
|
||||
for (Connection c : mConnections)
|
||||
if (c.mEnabled) noRemoteEnabled = false;
|
||||
if (noRemoteEnabled) return R.string.remote_no_server_selected;
|
||||
// Everything okay
|
||||
return R.string.no_error_found;
|
||||
}
|
||||
|
||||
//! Openvpn asks for a "Private Key", this should be pkcs12 key
|
||||
//
|
||||
public String getPasswordPrivateKey() {
|
||||
String cachedPw = PasswordCache.getPKCS12orCertificatePassword(mUuid, true);
|
||||
if (cachedPw != null) {
|
||||
return cachedPw;
|
||||
}
|
||||
switch (mAuthenticationType) {
|
||||
case TYPE_PKCS12:
|
||||
case TYPE_USERPASS_PKCS12:
|
||||
return mPKCS12Password;
|
||||
case TYPE_CERTIFICATES:
|
||||
case TYPE_USERPASS_CERTIFICATES:
|
||||
return mKeyPassword;
|
||||
case TYPE_USERPASS:
|
||||
case TYPE_STATICKEYS:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUserPWAuth() {
|
||||
switch (mAuthenticationType) {
|
||||
case TYPE_USERPASS:
|
||||
case TYPE_USERPASS_CERTIFICATES:
|
||||
case TYPE_USERPASS_KEYSTORE:
|
||||
case TYPE_USERPASS_PKCS12:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requireTLSKeyPassword() {
|
||||
if (TextUtils.isEmpty(mClientKeyFilename)) return false;
|
||||
String data = "";
|
||||
if (isEmbedded(mClientKeyFilename)) data = mClientKeyFilename;
|
||||
else {
|
||||
char[] buf = new char[2048];
|
||||
FileReader fr;
|
||||
try {
|
||||
fr = new FileReader(mClientKeyFilename);
|
||||
int len = fr.read(buf);
|
||||
while (len > 0) {
|
||||
data += new String(buf, 0, len);
|
||||
len = fr.read(buf);
|
||||
}
|
||||
fr.close();
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (data.contains("Proc-Type: 4,ENCRYPTED")) return true;
|
||||
else return data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----");
|
||||
}
|
||||
|
||||
public int needUserPWInput(String transientCertOrPkcs12PW, String mTransientAuthPW) {
|
||||
if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) && (mPKCS12Password == null || mPKCS12Password.equals(""))) {
|
||||
if (transientCertOrPkcs12PW == null) return R.string.pkcs12_file_encryption_key;
|
||||
}
|
||||
if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
|
||||
if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword)) if (transientCertOrPkcs12PW == null) {
|
||||
return R.string.private_key_password;
|
||||
}
|
||||
}
|
||||
if (isUserPWAuth() && (TextUtils.isEmpty(mUsername) || (TextUtils.isEmpty(mPassword) && mTransientAuthPW == null))) {
|
||||
return R.string.password;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public String getPasswordAuth() {
|
||||
String cachedPw = PasswordCache.getAuthPassword(mUuid, true);
|
||||
if (cachedPw != null) {
|
||||
return cachedPw;
|
||||
} else {
|
||||
return mPassword;
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the Array Adapter
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getUUIDString() {
|
||||
return mUuid.toString();
|
||||
}
|
||||
|
||||
public PrivateKey getKeystoreKey() {
|
||||
return mPrivateKey;
|
||||
}
|
||||
|
||||
public String getSignedData(String b64data) {
|
||||
PrivateKey privkey = getKeystoreKey();
|
||||
byte[] data = Base64.decode(b64data, Base64.DEFAULT);
|
||||
// The Jelly Bean *evil* Hack
|
||||
// 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return processSignJellyBeans(privkey, data);
|
||||
}
|
||||
try {
|
||||
/* ECB is perfectly fine in this special case, since we are using it for
|
||||
the public/private part in the TLS exchange
|
||||
*/
|
||||
@SuppressLint("GetInstance") Cipher rsaSigner = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
rsaSigner.init(Cipher.ENCRYPT_MODE, privkey);
|
||||
byte[] signed_bytes = rsaSigner.doFinal(data);
|
||||
return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
|
||||
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
|
||||
try {
|
||||
Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
|
||||
getKey.setAccessible(true);
|
||||
// Real object type is OpenSSLKey
|
||||
Object opensslkey = getKey.invoke(privkey);
|
||||
getKey.setAccessible(false);
|
||||
Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext");
|
||||
// integer pointer to EVP_pkey
|
||||
getPkeyContext.setAccessible(true);
|
||||
int pkey = (Integer) getPkeyContext.invoke(opensslkey);
|
||||
getPkeyContext.setAccessible(false);
|
||||
// 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
|
||||
byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
|
||||
return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
|
||||
} catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
|
||||
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class NoCertReturnedException extends Exception {
|
||||
public NoCertReturnedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class APIVpnProfile implements Parcelable {
|
||||
public static final Creator<APIVpnProfile> CREATOR
|
||||
= new Creator<APIVpnProfile>() {
|
||||
public APIVpnProfile createFromParcel(Parcel in) {
|
||||
return new APIVpnProfile(in);
|
||||
}
|
||||
public APIVpnProfile[] newArray(int size) {
|
||||
return new APIVpnProfile[size];
|
||||
}
|
||||
};
|
||||
public final String mUUID;
|
||||
public final String mName;
|
||||
//public final String mProfileCreator;
|
||||
public final boolean mUserEditable;
|
||||
public APIVpnProfile(Parcel in) {
|
||||
mUUID = in.readString();
|
||||
mName = in.readString();
|
||||
mUserEditable = in.readInt() != 0;
|
||||
//mProfileCreator = in.readString();
|
||||
}
|
||||
public APIVpnProfile(String uuidString, String name, boolean userEditable, String profileCreator) {
|
||||
mUUID = uuidString;
|
||||
mName = name;
|
||||
mUserEditable = userEditable;
|
||||
//mProfileCreator = profileCreator;
|
||||
}
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mUUID);
|
||||
dest.writeString(mName);
|
||||
if (mUserEditable)
|
||||
dest.writeInt(0);
|
||||
else
|
||||
dest.writeInt(1);
|
||||
//dest.writeString(mProfileCreator);
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnShowListener;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener {
|
||||
private static final String TAG = "OpenVPNVpnConfirm";
|
||||
private String mPackage;
|
||||
private Button mButton;
|
||||
private AlertDialog mAlert;
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
try {
|
||||
mPackage = getCallingPackage();
|
||||
if (mPackage == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
PackageManager pm = getPackageManager();
|
||||
ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
|
||||
View view = View.inflate(this, R.layout.api_confirm, null);
|
||||
((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm));
|
||||
((TextView) view.findViewById(R.id.prompt)).setText(getString(R.string.prompt, app.loadLabel(pm), getString(R.string.app_name)));
|
||||
((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this);
|
||||
Builder builder = new Builder(this);
|
||||
builder.setView(view);
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setTitle(android.R.string.dialog_alert_title);
|
||||
builder.setPositiveButton(android.R.string.ok, this);
|
||||
builder.setNegativeButton(android.R.string.cancel, this);
|
||||
mAlert = builder.create();
|
||||
mAlert.setCanceledOnTouchOutside(false);
|
||||
mAlert.setOnShowListener(new OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
|
||||
mButton.setEnabled(false);
|
||||
}
|
||||
});
|
||||
//setCloseOnTouchOutside(false);
|
||||
mAlert.show();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "onResume", e);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton button, boolean checked) {
|
||||
mButton.setEnabled(checked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
ExternalAppDatabase extapps = new ExternalAppDatabase(this);
|
||||
extapps.addApp(mPackage);
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
if (which == DialogInterface.BUTTON_NEGATIVE) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.blinkt.openvpn.core.Preferences;
|
||||
|
||||
public class ExternalAppDatabase {
|
||||
private final String PREFERENCES_KEY = "allowed_apps";
|
||||
Context mContext;
|
||||
public ExternalAppDatabase(Context c) {
|
||||
mContext = c;
|
||||
}
|
||||
boolean isAllowed(String packagename) {
|
||||
Set<String> allowedapps = getExtAppList();
|
||||
return allowedapps.contains(packagename);
|
||||
}
|
||||
public Set<String> getExtAppList() {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
|
||||
return prefs.getStringSet(PREFERENCES_KEY, new HashSet<String>());
|
||||
}
|
||||
void addApp(String packagename) {
|
||||
Set<String> allowedapps = getExtAppList();
|
||||
allowedapps.add(packagename);
|
||||
saveExtAppList(allowedapps);
|
||||
}
|
||||
private void saveExtAppList(Set<String> allowedapps) {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
|
||||
Editor prefedit = prefs.edit();
|
||||
// Workaround for bug
|
||||
prefedit.putStringSet(PREFERENCES_KEY, allowedapps);
|
||||
int counter = prefs.getInt("counter", 0);
|
||||
prefedit.putInt("counter", counter + 1);
|
||||
prefedit.apply();
|
||||
}
|
||||
public void clearAllApiApps() {
|
||||
saveExtAppList(new HashSet<String>());
|
||||
}
|
||||
public void removeApp(String packagename) {
|
||||
Set<String> allowedapps = getExtAppList();
|
||||
allowedapps.remove(packagename);
|
||||
saveExtAppList(allowedapps);
|
||||
}
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.VpnService;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
import de.blinkt.openvpn.core.ConfigParser;
|
||||
import de.blinkt.openvpn.core.ConfigParser.ConfigParseError;
|
||||
import de.blinkt.openvpn.core.ConnectionStatus;
|
||||
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
|
||||
import de.blinkt.openvpn.core.OpenVPNService;
|
||||
import de.blinkt.openvpn.core.ProfileManager;
|
||||
import de.blinkt.openvpn.core.VPNLaunchHelper;
|
||||
import de.blinkt.openvpn.core.VpnStatus;
|
||||
import de.blinkt.openvpn.core.VpnStatus.StateListener;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
public class ExternalOpenVPNService extends Service implements StateListener {
|
||||
private static final int SEND_TOALL = 0;
|
||||
private static final OpenVPNServiceHandler mHandler = new OpenVPNServiceHandler();
|
||||
final RemoteCallbackList<IOpenVPNStatusCallback> mCallbacks = new RemoteCallbackList<>();
|
||||
private IOpenVPNServiceInternal mService;
|
||||
private ExternalAppDatabase mExtAppDb;
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
mService = (IOpenVPNServiceInternal) (service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName arg0) {
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent != null && Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
|
||||
// Check if the running config is temporary and installed by the app being uninstalled
|
||||
VpnProfile vp = ProfileManager.getLastConnectedVpn();
|
||||
if (ProfileManager.isTempProfile()) {
|
||||
if (intent.getPackage().equals(vp.mProfileCreator)) {
|
||||
if (mService != null) try {
|
||||
mService.stopVPN(false);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private UpdateMessage mMostRecentState;
|
||||
private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() {
|
||||
private String checkOpenVPNPermission() throws SecurityRemoteException {
|
||||
PackageManager pm = getPackageManager();
|
||||
for (String appPackage : mExtAppDb.getExtAppList()) {
|
||||
ApplicationInfo app;
|
||||
try {
|
||||
app = pm.getApplicationInfo(appPackage, 0);
|
||||
if (Binder.getCallingUid() == app.uid) {
|
||||
return appPackage;
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
// App not found. Remove it from the list
|
||||
mExtAppDb.removeApp(appPackage);
|
||||
}
|
||||
}
|
||||
throw new SecurityException("Unauthorized OpenVPN API Caller");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<APIVpnProfile> getProfiles() throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
ProfileManager pm = ProfileManager.getInstance(getBaseContext());
|
||||
List<APIVpnProfile> profiles = new LinkedList<>();
|
||||
for (VpnProfile vp : pm.getProfiles()) {
|
||||
if (!vp.profileDeleted) profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator));
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
private void startProfile(VpnProfile vp) {
|
||||
Intent vpnPermissionIntent = VpnService.prepare(ExternalOpenVPNService.this);
|
||||
/* Check if we need to show the confirmation dialog,
|
||||
* Check if we need to ask for username/password */
|
||||
int neddPassword = vp.needUserPWInput(null, null);
|
||||
if (vpnPermissionIntent != null || neddPassword != 0) {
|
||||
Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN);
|
||||
shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class);
|
||||
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString());
|
||||
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_HIDELOG, true);
|
||||
shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(shortVPNIntent);
|
||||
} else {
|
||||
VPNLaunchHelper.startOpenVpn(vp, getBaseContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startProfile(String profileUUID) throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
|
||||
if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) throw new RemoteException(getString(vp.checkProfile(getApplicationContext())));
|
||||
startProfile(vp);
|
||||
}
|
||||
|
||||
public void startVPN(String inlineConfig) throws RemoteException {
|
||||
String callingApp = checkOpenVPNPermission();
|
||||
ConfigParser cp = new ConfigParser();
|
||||
try {
|
||||
cp.parseConfig(new StringReader(inlineConfig));
|
||||
VpnProfile vp = cp.convertProfile();
|
||||
vp.mName = "Remote APP VPN";
|
||||
if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) throw new RemoteException(getString(vp.checkProfile(getApplicationContext())));
|
||||
vp.mProfileCreator = callingApp;
|
||||
/*int needpw = vp.needUserPWInput(false);
|
||||
if(needpw !=0)
|
||||
throw new RemoteException("The inline file would require user input: " + getString(needpw));
|
||||
*/
|
||||
ProfileManager.setTemporaryProfile(ExternalOpenVPNService.this, vp);
|
||||
startProfile(vp);
|
||||
} catch (IOException | ConfigParseError e) {
|
||||
throw new RemoteException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addVPNProfile(String name, String config) throws RemoteException {
|
||||
return addNewVPNProfile(name, true, config) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIVpnProfile addNewVPNProfile(String name, boolean userEditable, String config) throws RemoteException {
|
||||
String callingPackage = checkOpenVPNPermission();
|
||||
ConfigParser cp = new ConfigParser();
|
||||
try {
|
||||
cp.parseConfig(new StringReader(config));
|
||||
VpnProfile vp = cp.convertProfile();
|
||||
vp.mName = name;
|
||||
vp.mProfileCreator = callingPackage;
|
||||
vp.mUserEditable = userEditable;
|
||||
ProfileManager pm = ProfileManager.getInstance(getBaseContext());
|
||||
pm.addProfile(vp);
|
||||
pm.saveProfile(ExternalOpenVPNService.this, vp);
|
||||
pm.saveProfileList(ExternalOpenVPNService.this);
|
||||
return new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator);
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException(e);
|
||||
return null;
|
||||
} catch (ConfigParseError e) {
|
||||
VpnStatus.logException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProfile(String profileUUID) throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
ProfileManager pm = ProfileManager.getInstance(getBaseContext());
|
||||
VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
|
||||
pm.removeProfile(ExternalOpenVPNService.this, vp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean protectSocket(ParcelFileDescriptor pfd) throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
try {
|
||||
boolean success = mService.protect(pfd.getFd());
|
||||
pfd.close();
|
||||
return success;
|
||||
} catch (IOException e) {
|
||||
throw new RemoteException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent prepare(String packageName) {
|
||||
if (new ExternalAppDatabase(ExternalOpenVPNService.this).isAllowed(packageName)) return null;
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(ExternalOpenVPNService.this, ConfirmDialog.class);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent prepareVPNService() throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (VpnService.prepare(ExternalOpenVPNService.this) == null) return null;
|
||||
else return new Intent(getBaseContext(), GrantPermissionsActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStatusCallback(IOpenVPNStatusCallback cb) throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (cb != null) {
|
||||
cb.newStatus(mMostRecentState.vpnUUID, mMostRecentState.state, mMostRecentState.logmessage, mMostRecentState.level.name());
|
||||
mCallbacks.register(cb);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterStatusCallback(IOpenVPNStatusCallback cb) throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (cb != null) mCallbacks.unregister(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (mService != null) mService.stopVPN(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (mService != null) mService.userPause(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() throws RemoteException {
|
||||
checkOpenVPNPermission();
|
||||
if (mService != null) mService.userPause(false);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
VpnStatus.addStateListener(this);
|
||||
mExtAppDb = new ExternalAppDatabase(this);
|
||||
Intent intent = new Intent(getBaseContext(), OpenVPNService.class);
|
||||
intent.setAction(OpenVPNService.START_SERVICE);
|
||||
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
mHandler.setService(this);
|
||||
IntentFilter uninstallBroadcast = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
|
||||
registerReceiver(mBroadcastReceiver, uninstallBroadcast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mCallbacks.kill();
|
||||
unbindService(mConnection);
|
||||
VpnStatus.removeStateListener(this);
|
||||
unregisterReceiver(mBroadcastReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
|
||||
mMostRecentState = new UpdateMessage(state, logmessage, level);
|
||||
if (ProfileManager.getLastConnectedVpn() != null) mMostRecentState.vpnUUID = ProfileManager.getLastConnectedVpn().getUUIDString();
|
||||
Message msg = mHandler.obtainMessage(SEND_TOALL, mMostRecentState);
|
||||
msg.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnectedVPN(String uuid) {
|
||||
}
|
||||
|
||||
static class OpenVPNServiceHandler extends Handler {
|
||||
WeakReference<ExternalOpenVPNService> service = null;
|
||||
|
||||
private void setService(ExternalOpenVPNService eos) {
|
||||
service = new WeakReference<>(eos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
RemoteCallbackList<IOpenVPNStatusCallback> callbacks;
|
||||
switch (msg.what) {
|
||||
case SEND_TOALL:
|
||||
if (service == null || service.get() == null) return;
|
||||
callbacks = service.get().mCallbacks;
|
||||
// Broadcast to all clients the new value.
|
||||
final int N = callbacks.beginBroadcast();
|
||||
for (int i = 0; i < N; i++) {
|
||||
try {
|
||||
sendUpdate(callbacks.getBroadcastItem(i), (UpdateMessage) msg.obj);
|
||||
} catch (RemoteException e) {
|
||||
// The RemoteCallbackList will take care of removing
|
||||
// the dead object for us.
|
||||
}
|
||||
}
|
||||
callbacks.finishBroadcast();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdate(IOpenVPNStatusCallback broadcastItem, UpdateMessage um) throws RemoteException {
|
||||
broadcastItem.newStatus(um.vpnUUID, um.state, um.logmessage, um.level.name());
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateMessage {
|
||||
public String state;
|
||||
public String logmessage;
|
||||
public ConnectionStatus level;
|
||||
String vpnUUID;
|
||||
|
||||
UpdateMessage(String state, String logmessage, ConnectionStatus level) {
|
||||
this.state = state;
|
||||
this.logmessage = logmessage;
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
|
||||
public class GrantPermissionsActivity extends Activity {
|
||||
private static final int VPN_PREPARE = 0;
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
Intent i = VpnService.prepare(this);
|
||||
if (i == null)
|
||||
onActivityResult(VPN_PREPARE, RESULT_OK, null);
|
||||
else
|
||||
startActivityForResult(i, VPN_PREPARE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class SecurityRemoteException extends RemoteException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
PRNGFixes.apply();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannels();
|
||||
}
|
||||
StatusListener mStatus = new StatusListener();
|
||||
mStatus.init(getApplicationContext());
|
||||
}
|
||||
|
||||
public static boolean isStart;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void createNotificationChannels() {
|
||||
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
// Background message
|
||||
String name = getString(R.string.channel_name_background);
|
||||
NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, name, NotificationManager.IMPORTANCE_MIN);
|
||||
mChannel.setDescription(getString(R.string.channel_description_background));
|
||||
mChannel.enableLights(false);
|
||||
mChannel.setLightColor(Color.DKGRAY);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
// Connection status change messages
|
||||
name = getString(R.string.channel_name_status);
|
||||
mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
mChannel.setDescription(getString(R.string.channel_description_status));
|
||||
mChannel.enableLights(true);
|
||||
mChannel.setLightColor(Color.BLUE);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
import java.util.Locale;
|
||||
|
||||
class CIDRIP {
|
||||
String mIp;
|
||||
int len;
|
||||
public CIDRIP(String ip, String mask) {
|
||||
mIp = ip;
|
||||
long netmask = getInt(mask);
|
||||
// Add 33. bit to ensure the loop terminates
|
||||
netmask += 1l << 32;
|
||||
int lenZeros = 0;
|
||||
while ((netmask & 0x1) == 0) {
|
||||
lenZeros++;
|
||||
netmask = netmask >> 1;
|
||||
}
|
||||
// Check if rest of netmask is only 1s
|
||||
if (netmask != (0x1ffffffffl >> lenZeros)) {
|
||||
// Asume no CIDR, set /32
|
||||
len = 32;
|
||||
} else {
|
||||
len = 32 - lenZeros;
|
||||
}
|
||||
}
|
||||
public CIDRIP(String address, int prefix_length) {
|
||||
len = prefix_length;
|
||||
mIp = address;
|
||||
}
|
||||
static long getInt(String ipaddr) {
|
||||
String[] ipt = ipaddr.split("\\.");
|
||||
long ip = 0;
|
||||
ip += Long.parseLong(ipt[0]) << 24;
|
||||
ip += Integer.parseInt(ipt[1]) << 16;
|
||||
ip += Integer.parseInt(ipt[2]) << 8;
|
||||
ip += Integer.parseInt(ipt[3]);
|
||||
return ip;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ENGLISH, "%s/%d", mIp, len);
|
||||
}
|
||||
public boolean normalise() {
|
||||
long ip = getInt(mIp);
|
||||
long newip = ip & (0xffffffffL << (32 - len));
|
||||
if (newip != ip) {
|
||||
mIp = String.format(Locale.US, "%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public long getInt() {
|
||||
return getInt(mIp);
|
||||
}
|
||||
}
|
|
@ -1,794 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import android.util.Pair;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
|
||||
//! Openvpn Config FIle Parser, probably not 100% accurate but close enough
|
||||
// And remember, this is valid :)
|
||||
// --<foo>
|
||||
// bar
|
||||
// </foo>
|
||||
public class ConfigParser {
|
||||
public static final String CONVERTED_PROFILE = "converted Profile";
|
||||
final String[] unsupportedOptions = {"config",
|
||||
"tls-server"
|
||||
};
|
||||
// Ignore all scripts
|
||||
// in most cases these won't work and user who wish to execute scripts will
|
||||
// figure out themselves
|
||||
final String[] ignoreOptions = {"tls-client",
|
||||
"askpass",
|
||||
"auth-nocache",
|
||||
"up",
|
||||
"down",
|
||||
"route-up",
|
||||
"ipchange",
|
||||
"route-up",
|
||||
"route-pre-down",
|
||||
"auth-user-pass-verify",
|
||||
"block-outside-dns",
|
||||
"dhcp-release",
|
||||
"dhcp-renew",
|
||||
"dh",
|
||||
"group",
|
||||
"allow-recursive-routing",
|
||||
"ip-win32",
|
||||
"management-hold",
|
||||
"management",
|
||||
"management-client",
|
||||
"management-query-remote",
|
||||
"management-query-passwords",
|
||||
"management-query-proxy",
|
||||
"management-external-key",
|
||||
"management-forget-disconnect",
|
||||
"management-signal",
|
||||
"management-log-cache",
|
||||
"management-up-down",
|
||||
"management-client-user",
|
||||
"management-client-group",
|
||||
"pause-exit",
|
||||
"plugin",
|
||||
"machine-readable-output",
|
||||
"persist-key",
|
||||
"push",
|
||||
"register-dns",
|
||||
"route-delay",
|
||||
"route-gateway",
|
||||
"route-metric",
|
||||
"route-method",
|
||||
"status",
|
||||
"script-security",
|
||||
"show-net-up",
|
||||
"suppress-timestamps",
|
||||
"tmp-dir",
|
||||
"tun-ipv6",
|
||||
"topology",
|
||||
"user",
|
||||
"win-sys",
|
||||
};
|
||||
final String[][] ignoreOptionsWithArg =
|
||||
{
|
||||
{"setenv", "IV_GUI_VER"},
|
||||
{"setenv", "IV_OPENVPN_GUI_VERSION"},
|
||||
{"engine", "dynamic"},
|
||||
{"setenv", "CLIENT_CERT"}
|
||||
};
|
||||
final String[] connectionOptions = {
|
||||
"local",
|
||||
"remote",
|
||||
"float",
|
||||
"port",
|
||||
"connect-retry",
|
||||
"connect-timeout",
|
||||
"connect-retry-max",
|
||||
"link-mtu",
|
||||
"tun-mtu",
|
||||
"tun-mtu-extra",
|
||||
"fragment",
|
||||
"mtu-disc",
|
||||
"local-port",
|
||||
"remote-port",
|
||||
"bind",
|
||||
"nobind",
|
||||
"proto",
|
||||
"http-proxy",
|
||||
"http-proxy-retry",
|
||||
"http-proxy-timeout",
|
||||
"http-proxy-option",
|
||||
"socks-proxy",
|
||||
"socks-proxy-retry",
|
||||
"explicit-exit-notify",
|
||||
};
|
||||
private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
|
||||
private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
|
||||
private String auth_user_pass_file;
|
||||
static public void useEmbbedUserAuth(VpnProfile np, String inlinedata) {
|
||||
String data = VpnProfile.getEmbeddedContent(inlinedata);
|
||||
String[] parts = data.split("\n");
|
||||
if (parts.length >= 2) {
|
||||
np.mUsername = parts[0];
|
||||
np.mPassword = parts[1];
|
||||
}
|
||||
}
|
||||
public void parseConfig(Reader reader) throws IOException, ConfigParseError {
|
||||
HashMap<String, String> optionAliases = new HashMap<>();
|
||||
optionAliases.put("server-poll-timeout", "timeout-connect");
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
int lineno = 0;
|
||||
try {
|
||||
while (true) {
|
||||
String line = br.readLine();
|
||||
lineno++;
|
||||
if (line == null)
|
||||
break;
|
||||
if (lineno == 1) {
|
||||
if ((line.startsWith("PK\003\004")
|
||||
|| (line.startsWith("PK\007\008")))) {
|
||||
throw new ConfigParseError("Input looks like a ZIP Archive. Import is only possible for OpenVPN config files (.ovpn/.conf)");
|
||||
}
|
||||
if (line.startsWith("\uFEFF")) {
|
||||
line = line.substring(1);
|
||||
}
|
||||
}
|
||||
// Check for OpenVPN Access Server Meta information
|
||||
if (line.startsWith("# OVPN_ACCESS_SERVER_")) {
|
||||
Vector<String> metaarg = parsemeta(line);
|
||||
meta.put(metaarg.get(0), metaarg);
|
||||
continue;
|
||||
}
|
||||
Vector<String> args = parseline(line);
|
||||
if (args.size() == 0)
|
||||
continue;
|
||||
if (args.get(0).startsWith("--"))
|
||||
args.set(0, args.get(0).substring(2));
|
||||
checkinlinefile(args, br);
|
||||
String optionname = args.get(0);
|
||||
if (optionAliases.get(optionname) != null)
|
||||
optionname = optionAliases.get(optionname);
|
||||
if (!options.containsKey(optionname)) {
|
||||
options.put(optionname, new Vector<Vector<String>>());
|
||||
}
|
||||
options.get(optionname).add(args);
|
||||
}
|
||||
} catch (OutOfMemoryError memoryError) {
|
||||
throw new ConfigParseError("File too large to parse: " + memoryError.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
private Vector<String> parsemeta(String line) {
|
||||
String meta = line.split("#\\sOVPN_ACCESS_SERVER_", 2)[1];
|
||||
String[] parts = meta.split("=", 2);
|
||||
Vector<String> rval = new Vector<String>();
|
||||
Collections.addAll(rval, parts);
|
||||
return rval;
|
||||
}
|
||||
private void checkinlinefile(Vector<String> args, BufferedReader br) throws IOException, ConfigParseError {
|
||||
String arg0 = args.get(0).trim();
|
||||
// CHeck for <foo>
|
||||
if (arg0.startsWith("<") && arg0.endsWith(">")) {
|
||||
String argname = arg0.substring(1, arg0.length() - 1);
|
||||
String inlinefile = VpnProfile.INLINE_TAG;
|
||||
String endtag = String.format("</%s>", argname);
|
||||
do {
|
||||
String line = br.readLine();
|
||||
if (line == null) {
|
||||
throw new ConfigParseError(String.format("No endtag </%s> for starttag <%s> found", argname, argname));
|
||||
}
|
||||
if (line.trim().equals(endtag))
|
||||
break;
|
||||
else {
|
||||
inlinefile += line;
|
||||
inlinefile += "\n";
|
||||
}
|
||||
} while (true);
|
||||
if (inlinefile.endsWith("\n"))
|
||||
inlinefile = inlinefile.substring(0, inlinefile.length() - 1);
|
||||
args.clear();
|
||||
args.add(argname);
|
||||
args.add(inlinefile);
|
||||
}
|
||||
}
|
||||
public String getAuthUserPassFile() {
|
||||
return auth_user_pass_file;
|
||||
}
|
||||
private boolean space(char c) {
|
||||
// I really hope nobody is using zero bytes inside his/her config file
|
||||
// to sperate parameter but here we go:
|
||||
return Character.isWhitespace(c) || c == '\0';
|
||||
}
|
||||
// adapted openvpn's parse function to java
|
||||
private Vector<String> parseline(String line) throws ConfigParseError {
|
||||
Vector<String> parameters = new Vector<String>();
|
||||
if (line.length() == 0)
|
||||
return parameters;
|
||||
linestate state = linestate.initial;
|
||||
boolean backslash = false;
|
||||
char out = 0;
|
||||
int pos = 0;
|
||||
String currentarg = "";
|
||||
do {
|
||||
// Emulate the c parsing ...
|
||||
char in;
|
||||
if (pos < line.length())
|
||||
in = line.charAt(pos);
|
||||
else
|
||||
in = '\0';
|
||||
if (!backslash && in == '\\' && state != linestate.readin_single_quote) {
|
||||
backslash = true;
|
||||
} else {
|
||||
if (state == linestate.initial) {
|
||||
if (!space(in)) {
|
||||
if (in == ';' || in == '#') /* comment */
|
||||
break;
|
||||
if (!backslash && in == '\"')
|
||||
state = linestate.reading_quoted;
|
||||
else if (!backslash && in == '\'')
|
||||
state = linestate.readin_single_quote;
|
||||
else {
|
||||
out = in;
|
||||
state = linestate.reading_unquoted;
|
||||
}
|
||||
}
|
||||
} else if (state == linestate.reading_unquoted) {
|
||||
if (!backslash && space(in))
|
||||
state = linestate.done;
|
||||
else
|
||||
out = in;
|
||||
} else if (state == linestate.reading_quoted) {
|
||||
if (!backslash && in == '\"')
|
||||
state = linestate.done;
|
||||
else
|
||||
out = in;
|
||||
} else if (state == linestate.readin_single_quote) {
|
||||
if (in == '\'')
|
||||
state = linestate.done;
|
||||
else
|
||||
out = in;
|
||||
}
|
||||
if (state == linestate.done) {
|
||||
/* ASSERT (parm_len > 0); */
|
||||
state = linestate.initial;
|
||||
parameters.add(currentarg);
|
||||
currentarg = "";
|
||||
out = 0;
|
||||
}
|
||||
if (backslash && out != 0) {
|
||||
if (!(out == '\\' || out == '\"' || space(out))) {
|
||||
throw new ConfigParseError("Options warning: Bad backslash ('\\') usage");
|
||||
}
|
||||
}
|
||||
backslash = false;
|
||||
}
|
||||
/* store parameter character */
|
||||
if (out != 0) {
|
||||
currentarg += out;
|
||||
}
|
||||
} while (pos++ < line.length());
|
||||
return parameters;
|
||||
}
|
||||
// This method is far too long
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public VpnProfile convertProfile() throws ConfigParseError, IOException {
|
||||
boolean noauthtypeset = true;
|
||||
VpnProfile np = new VpnProfile(CONVERTED_PROFILE);
|
||||
// Pull, client, tls-client
|
||||
np.clearDefaults();
|
||||
if (options.containsKey("client") || options.containsKey("pull")) {
|
||||
np.mUsePull = true;
|
||||
options.remove("pull");
|
||||
options.remove("client");
|
||||
}
|
||||
Vector<String> secret = getOption("secret", 1, 2);
|
||||
if (secret != null) {
|
||||
np.mAuthenticationType = VpnProfile.TYPE_STATICKEYS;
|
||||
noauthtypeset = false;
|
||||
np.mUseTLSAuth = true;
|
||||
np.mTLSAuthFilename = secret.get(1);
|
||||
if (secret.size() == 3)
|
||||
np.mTLSAuthDirection = secret.get(2);
|
||||
}
|
||||
Vector<Vector<String>> routes = getAllOption("route", 1, 4);
|
||||
if (routes != null) {
|
||||
String routeopt = "";
|
||||
String routeExcluded = "";
|
||||
for (Vector<String> route : routes) {
|
||||
String netmask = "255.255.255.255";
|
||||
String gateway = "vpn_gateway";
|
||||
if (route.size() >= 3)
|
||||
netmask = route.get(2);
|
||||
if (route.size() >= 4)
|
||||
gateway = route.get(3);
|
||||
String net = route.get(1);
|
||||
try {
|
||||
CIDRIP cidr = new CIDRIP(net, netmask);
|
||||
if (gateway.equals("net_gateway"))
|
||||
routeExcluded += cidr.toString() + " ";
|
||||
else
|
||||
routeopt += cidr.toString() + " ";
|
||||
} catch (ArrayIndexOutOfBoundsException aioob) {
|
||||
throw new ConfigParseError("Could not parse netmask of route " + netmask);
|
||||
} catch (NumberFormatException ne) {
|
||||
throw new ConfigParseError("Could not parse netmask of route " + netmask);
|
||||
}
|
||||
}
|
||||
np.mCustomRoutes = routeopt;
|
||||
np.mExcludedRoutes = routeExcluded;
|
||||
}
|
||||
Vector<Vector<String>> routesV6 = getAllOption("route-ipv6", 1, 4);
|
||||
if (routesV6 != null) {
|
||||
String customIPv6Routes = "";
|
||||
for (Vector<String> route : routesV6) {
|
||||
customIPv6Routes += route.get(1) + " ";
|
||||
}
|
||||
np.mCustomRoutesv6 = customIPv6Routes;
|
||||
}
|
||||
Vector<String> routeNoPull = getOption("route-nopull", 1, 1);
|
||||
if (routeNoPull != null)
|
||||
np.mRoutenopull = true;
|
||||
// Also recognize tls-auth [inline] direction ...
|
||||
Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
|
||||
if (tlsauthoptions != null) {
|
||||
for (Vector<String> tlsauth : tlsauthoptions) {
|
||||
if (tlsauth != null) {
|
||||
if (!tlsauth.get(1).equals("[inline]")) {
|
||||
np.mTLSAuthFilename = tlsauth.get(1);
|
||||
np.mUseTLSAuth = true;
|
||||
}
|
||||
if (tlsauth.size() == 3)
|
||||
np.mTLSAuthDirection = tlsauth.get(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector<String> direction = getOption("key-direction", 1, 1);
|
||||
if (direction != null)
|
||||
np.mTLSAuthDirection = direction.get(1);
|
||||
Vector<String> tlscrypt = getOption("tls-crypt", 1, 1);
|
||||
if (tlscrypt != null) {
|
||||
np.mUseTLSAuth = true;
|
||||
np.mTLSAuthFilename = tlscrypt.get(1);
|
||||
np.mTLSAuthDirection = "tls-crypt";
|
||||
}
|
||||
Vector<Vector<String>> defgw = getAllOption("redirect-gateway", 0, 7);
|
||||
if (defgw != null) {
|
||||
checkRedirectParameters(np, defgw, true);
|
||||
}
|
||||
Vector<Vector<String>> redirectPrivate = getAllOption("redirect-private", 0, 5);
|
||||
if (redirectPrivate != null) {
|
||||
checkRedirectParameters(np, redirectPrivate, false);
|
||||
}
|
||||
Vector<String> dev = getOption("dev", 1, 1);
|
||||
Vector<String> devtype = getOption("dev-type", 1, 1);
|
||||
if ((devtype != null && devtype.get(1).equals("tun")) ||
|
||||
(dev != null && dev.get(1).startsWith("tun")) ||
|
||||
(devtype == null && dev == null)) {
|
||||
//everything okay
|
||||
} else {
|
||||
throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail");
|
||||
}
|
||||
Vector<String> mssfix = getOption("mssfix", 0, 1);
|
||||
if (mssfix != null) {
|
||||
if (mssfix.size() >= 2) {
|
||||
try {
|
||||
np.mMssFix = Integer.parseInt(mssfix.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigParseError("Argument to --mssfix has to be an integer");
|
||||
}
|
||||
} else {
|
||||
np.mMssFix = 1450; // OpenVPN default size
|
||||
}
|
||||
}
|
||||
Vector<String> tunmtu = getOption("mtu", 1, 1);
|
||||
if (tunmtu != null) {
|
||||
try {
|
||||
np.mTunMtu = Integer.parseInt(tunmtu.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigParseError("Argument to --tun-mtu has to be an integer");
|
||||
}
|
||||
}
|
||||
Vector<String> mode = getOption("mode", 1, 1);
|
||||
if (mode != null) {
|
||||
if (!mode.get(1).equals("p2p"))
|
||||
throw new ConfigParseError("Invalid mode for --mode specified, need p2p");
|
||||
}
|
||||
Vector<Vector<String>> dhcpoptions = getAllOption("dhcp-option", 2, 2);
|
||||
if (dhcpoptions != null) {
|
||||
for (Vector<String> dhcpoption : dhcpoptions) {
|
||||
String type = dhcpoption.get(1);
|
||||
String arg = dhcpoption.get(2);
|
||||
if (type.equals("DOMAIN")) {
|
||||
np.mSearchDomain = dhcpoption.get(2);
|
||||
} else if (type.equals("DNS")) {
|
||||
np.mOverrideDNS = true;
|
||||
if (np.mDNS1.equals(VpnProfile.DEFAULT_DNS1))
|
||||
np.mDNS1 = arg;
|
||||
else
|
||||
np.mDNS2 = arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector<String> ifconfig = getOption("ifconfig", 2, 2);
|
||||
if (ifconfig != null) {
|
||||
try {
|
||||
CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2));
|
||||
np.mIPv4Address = cidr.toString();
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new ConfigParseError("Could not pase ifconfig IP address: " + nfe.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
if (getOption("remote-random-hostname", 0, 0) != null)
|
||||
np.mUseRandomHostname = true;
|
||||
if (getOption("float", 0, 0) != null)
|
||||
np.mUseFloat = true;
|
||||
if (getOption("comp-lzo", 0, 1) != null)
|
||||
np.mUseLzo = true;
|
||||
Vector<String> cipher = getOption("cipher", 1, 1);
|
||||
if (cipher != null)
|
||||
np.mCipher = cipher.get(1);
|
||||
Vector<String> auth = getOption("auth", 1, 1);
|
||||
if (auth != null)
|
||||
np.mAuth = auth.get(1);
|
||||
Vector<String> ca = getOption("ca", 1, 1);
|
||||
if (ca != null) {
|
||||
np.mCaFilename = ca.get(1);
|
||||
}
|
||||
Vector<String> cert = getOption("cert", 1, 1);
|
||||
if (cert != null) {
|
||||
np.mClientCertFilename = cert.get(1);
|
||||
np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES;
|
||||
noauthtypeset = false;
|
||||
}
|
||||
Vector<String> key = getOption("key", 1, 1);
|
||||
if (key != null)
|
||||
np.mClientKeyFilename = key.get(1);
|
||||
Vector<String> pkcs12 = getOption("pkcs12", 1, 1);
|
||||
if (pkcs12 != null) {
|
||||
np.mPKCS12Filename = pkcs12.get(1);
|
||||
np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE;
|
||||
noauthtypeset = false;
|
||||
}
|
||||
Vector<String> cryptoapicert = getOption("cryptoapicert", 1, 1);
|
||||
if (cryptoapicert != null) {
|
||||
np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE;
|
||||
noauthtypeset = false;
|
||||
}
|
||||
Vector<String> compatnames = getOption("compat-names", 1, 2);
|
||||
Vector<String> nonameremapping = getOption("no-name-remapping", 1, 1);
|
||||
Vector<String> tlsremote = getOption("tls-remote", 1, 1);
|
||||
if (tlsremote != null) {
|
||||
np.mRemoteCN = tlsremote.get(1);
|
||||
np.mCheckRemoteCN = true;
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE;
|
||||
if ((compatnames != null && compatnames.size() > 2) ||
|
||||
(nonameremapping != null))
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING;
|
||||
}
|
||||
Vector<String> verifyx509name = getOption("verify-x509-name", 1, 2);
|
||||
if (verifyx509name != null) {
|
||||
np.mRemoteCN = verifyx509name.get(1);
|
||||
np.mCheckRemoteCN = true;
|
||||
if (verifyx509name.size() > 2) {
|
||||
if (verifyx509name.get(2).equals("name"))
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN;
|
||||
else if (verifyx509name.get(2).equals("subject"))
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
|
||||
else if (verifyx509name.get(2).equals("name-prefix"))
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX;
|
||||
else
|
||||
throw new ConfigParseError("Unknown parameter to verify-x509-name: " + verifyx509name.get(2));
|
||||
} else {
|
||||
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
|
||||
}
|
||||
}
|
||||
Vector<String> x509usernamefield = getOption("x509-username-field", 1, 1);
|
||||
if (x509usernamefield != null) {
|
||||
np.mx509UsernameField = x509usernamefield.get(1);
|
||||
}
|
||||
Vector<String> verb = getOption("verb", 1, 1);
|
||||
if (verb != null) {
|
||||
np.mVerb = verb.get(1);
|
||||
}
|
||||
if (getOption("nobind", 0, 0) != null)
|
||||
np.mNobind = true;
|
||||
if (getOption("persist-tun", 0, 0) != null)
|
||||
np.mPersistTun = true;
|
||||
if (getOption("push-peer-info", 0, 0) != null)
|
||||
np.mPushPeerInfo = true;
|
||||
Vector<String> connectretry = getOption("connect-retry", 1, 2);
|
||||
if (connectretry != null) {
|
||||
np.mConnectRetry = connectretry.get(1);
|
||||
if (connectretry.size() > 2)
|
||||
np.mConnectRetryMaxTime = connectretry.get(2);
|
||||
}
|
||||
Vector<String> connectretrymax = getOption("connect-retry-max", 1, 1);
|
||||
if (connectretrymax != null)
|
||||
np.mConnectRetryMax = connectretrymax.get(1);
|
||||
Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 1);
|
||||
if (remotetls != null)
|
||||
if (remotetls.get(0).get(1).equals("server"))
|
||||
np.mExpectTLSCert = true;
|
||||
else
|
||||
options.put("remotetls", remotetls);
|
||||
Vector<String> authuser = getOption("auth-user-pass", 0, 1);
|
||||
if (authuser != null) {
|
||||
if (noauthtypeset) {
|
||||
np.mAuthenticationType = VpnProfile.TYPE_USERPASS;
|
||||
} else if (np.mAuthenticationType == VpnProfile.TYPE_CERTIFICATES) {
|
||||
np.mAuthenticationType = VpnProfile.TYPE_USERPASS_CERTIFICATES;
|
||||
} else if (np.mAuthenticationType == VpnProfile.TYPE_KEYSTORE) {
|
||||
np.mAuthenticationType = VpnProfile.TYPE_USERPASS_KEYSTORE;
|
||||
}
|
||||
if (authuser.size() > 1) {
|
||||
if (!authuser.get(1).startsWith(VpnProfile.INLINE_TAG))
|
||||
auth_user_pass_file = authuser.get(1);
|
||||
np.mUsername = null;
|
||||
useEmbbedUserAuth(np, authuser.get(1));
|
||||
}
|
||||
}
|
||||
Vector<String> authretry = getOption("auth-retry", 1, 1);
|
||||
if (authretry != null) {
|
||||
if (authretry.get(1).equals("none"))
|
||||
np.mAuthRetry = VpnProfile.AUTH_RETRY_NONE_FORGET;
|
||||
else if (authretry.get(1).equals("nointeract"))
|
||||
np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT;
|
||||
else if (authretry.get(1).equals("interact"))
|
||||
np.mAuthRetry = VpnProfile.AUTH_RETRY_NOINTERACT;
|
||||
else
|
||||
throw new ConfigParseError("Unknown parameter to auth-retry: " + authretry.get(2));
|
||||
}
|
||||
Vector<String> crlfile = getOption("crl-verify", 1, 2);
|
||||
if (crlfile != null) {
|
||||
// If the 'dir' parameter is present just add it as custom option ..
|
||||
if (crlfile.size() == 3 && crlfile.get(2).equals("dir"))
|
||||
np.mCustomConfigOptions += TextUtils.join(" ", crlfile) + "\n";
|
||||
else
|
||||
// Save the filename for the config converter to add later
|
||||
np.mCrlFilename = crlfile.get(1);
|
||||
}
|
||||
Pair<Connection, Connection[]> conns = parseConnectionOptions(null);
|
||||
np.mConnections = conns.second;
|
||||
Vector<Vector<String>> connectionBlocks = getAllOption("connection", 1, 1);
|
||||
if (np.mConnections.length > 0 && connectionBlocks != null) {
|
||||
throw new ConfigParseError("Using a <connection> block and --remote is not allowed.");
|
||||
}
|
||||
if (connectionBlocks != null) {
|
||||
np.mConnections = new Connection[connectionBlocks.size()];
|
||||
int connIndex = 0;
|
||||
for (Vector<String> conn : connectionBlocks) {
|
||||
Pair<Connection, Connection[]> connectionBlockConnection =
|
||||
parseConnection(conn.get(1), conns.first);
|
||||
if (connectionBlockConnection.second.length != 1)
|
||||
throw new ConfigParseError("A <connection> block must have exactly one remote");
|
||||
np.mConnections[connIndex] = connectionBlockConnection.second[0];
|
||||
connIndex++;
|
||||
}
|
||||
}
|
||||
if (getOption("remote-random", 0, 0) != null)
|
||||
np.mRemoteRandom = true;
|
||||
Vector<String> protoforce = getOption("proto-force", 1, 1);
|
||||
if (protoforce != null) {
|
||||
boolean disableUDP;
|
||||
String protoToDisable = protoforce.get(1);
|
||||
if (protoToDisable.equals("udp"))
|
||||
disableUDP = true;
|
||||
else if (protoToDisable.equals("tcp"))
|
||||
disableUDP = false;
|
||||
else
|
||||
throw new ConfigParseError(String.format("Unknown protocol %s in proto-force", protoToDisable));
|
||||
for (Connection conn : np.mConnections)
|
||||
if (conn.mUseUdp == disableUDP)
|
||||
conn.mEnabled = false;
|
||||
}
|
||||
// Parse OpenVPN Access Server extra
|
||||
Vector<String> friendlyname = meta.get("FRIENDLY_NAME");
|
||||
if (friendlyname != null && friendlyname.size() > 1)
|
||||
np.mName = friendlyname.get(1);
|
||||
Vector<String> ocusername = meta.get("USERNAME");
|
||||
if (ocusername != null && ocusername.size() > 1)
|
||||
np.mUsername = ocusername.get(1);
|
||||
checkIgnoreAndInvalidOptions(np);
|
||||
fixup(np);
|
||||
return np;
|
||||
}
|
||||
private Pair<Connection, Connection[]> parseConnection(String connection, Connection defaultValues) throws IOException, ConfigParseError {
|
||||
// Parse a connection Block as a new configuration file
|
||||
ConfigParser connectionParser = new ConfigParser();
|
||||
StringReader reader = new StringReader(connection.substring(VpnProfile.INLINE_TAG.length()));
|
||||
connectionParser.parseConfig(reader);
|
||||
Pair<Connection, Connection[]> conn = connectionParser.parseConnectionOptions(defaultValues);
|
||||
return conn;
|
||||
}
|
||||
private Pair<Connection, Connection[]> parseConnectionOptions(Connection connDefault) throws ConfigParseError {
|
||||
Connection conn;
|
||||
if (connDefault != null)
|
||||
try {
|
||||
conn = connDefault.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
else
|
||||
conn = new Connection();
|
||||
Vector<String> port = getOption("port", 1, 1);
|
||||
if (port != null) {
|
||||
conn.mServerPort = port.get(1);
|
||||
}
|
||||
Vector<String> rport = getOption("rport", 1, 1);
|
||||
if (rport != null) {
|
||||
conn.mServerPort = rport.get(1);
|
||||
}
|
||||
Vector<String> proto = getOption("proto", 1, 1);
|
||||
if (proto != null) {
|
||||
conn.mUseUdp = isUdpProto(proto.get(1));
|
||||
}
|
||||
Vector<String> connectTimeout = getOption("connect-timeout", 1, 1);
|
||||
if (connectTimeout != null) {
|
||||
try {
|
||||
conn.mConnectTimeout = Integer.parseInt(connectTimeout.get(1));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new ConfigParseError(String.format("Argument to connect-timeout (%s) must to be an integer: %s",
|
||||
connectTimeout.get(1), nfe.getLocalizedMessage()));
|
||||
}
|
||||
}
|
||||
// Parse remote config
|
||||
Vector<Vector<String>> remotes = getAllOption("remote", 1, 3);
|
||||
// Assume that we need custom options if connectionDefault are set
|
||||
if (connDefault != null) {
|
||||
for (Vector<Vector<String>> option : options.values()) {
|
||||
conn.mCustomConfiguration += getOptionStrings(option);
|
||||
}
|
||||
if (!TextUtils.isEmpty(conn.mCustomConfiguration))
|
||||
conn.mUseCustomConfig = true;
|
||||
}
|
||||
// Make remotes empty to simplify code
|
||||
if (remotes == null)
|
||||
remotes = new Vector<Vector<String>>();
|
||||
Connection[] connections = new Connection[remotes.size()];
|
||||
int i = 0;
|
||||
for (Vector<String> remote : remotes) {
|
||||
try {
|
||||
connections[i] = conn.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
switch (remote.size()) {
|
||||
case 4:
|
||||
connections[i].mUseUdp = isUdpProto(remote.get(3));
|
||||
case 3:
|
||||
connections[i].mServerPort = remote.get(2);
|
||||
case 2:
|
||||
connections[i].mServerName = remote.get(1);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return Pair.create(conn, connections);
|
||||
}
|
||||
private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw, boolean defaultRoute) {
|
||||
boolean noIpv4 = false;
|
||||
if (defaultRoute)
|
||||
for (Vector<String> redirect : defgw)
|
||||
for (int i = 1; i < redirect.size(); i++) {
|
||||
if (redirect.get(i).equals("block-local"))
|
||||
np.mAllowLocalLAN = false;
|
||||
else if (redirect.get(i).equals("unblock-local"))
|
||||
np.mAllowLocalLAN = true;
|
||||
else if (redirect.get(i).equals("!ipv4"))
|
||||
noIpv4 = true;
|
||||
else if (redirect.get(i).equals("ipv6"))
|
||||
np.mUseDefaultRoutev6 = true;
|
||||
}
|
||||
if (defaultRoute && !noIpv4)
|
||||
np.mUseDefaultRoute = true;
|
||||
}
|
||||
private boolean isUdpProto(String proto) throws ConfigParseError {
|
||||
boolean isudp;
|
||||
if (proto.equals("udp") || proto.equals("udp4") || proto.equals("udp6"))
|
||||
isudp = true;
|
||||
else if (proto.equals("tcp-client") ||
|
||||
proto.equals("tcp") ||
|
||||
proto.equals("tcp4") ||
|
||||
proto.endsWith("tcp4-client") ||
|
||||
proto.equals("tcp6") ||
|
||||
proto.endsWith("tcp6-client"))
|
||||
isudp = false;
|
||||
else
|
||||
throw new ConfigParseError("Unsupported option to --proto " + proto);
|
||||
return isudp;
|
||||
}
|
||||
private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError {
|
||||
for (String option : unsupportedOptions)
|
||||
if (options.containsKey(option))
|
||||
throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting", option));
|
||||
for (String option : ignoreOptions)
|
||||
// removing an item which is not in the map is no error
|
||||
options.remove(option);
|
||||
if (options.size() > 0) {
|
||||
np.mCustomConfigOptions = "# These options found in the config file do not map to config settings:\n"
|
||||
+ np.mCustomConfigOptions;
|
||||
for (Vector<Vector<String>> option : options.values()) {
|
||||
np.mCustomConfigOptions += getOptionStrings(option);
|
||||
}
|
||||
np.mUseCustomConfig = true;
|
||||
}
|
||||
}
|
||||
boolean ignoreThisOption(Vector<String> option) {
|
||||
for (String[] ignoreOption : ignoreOptionsWithArg) {
|
||||
if (option.size() < ignoreOption.length)
|
||||
continue;
|
||||
boolean ignore = true;
|
||||
for (int i = 0; i < ignoreOption.length; i++) {
|
||||
if (!ignoreOption[i].equals(option.get(i)))
|
||||
ignore = false;
|
||||
}
|
||||
if (ignore)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//! Generate options for custom options
|
||||
private String getOptionStrings(Vector<Vector<String>> option) {
|
||||
String custom = "";
|
||||
for (Vector<String> optionsline : option) {
|
||||
if (!ignoreThisOption(optionsline)) {
|
||||
// Check if option had been inlined and inline again
|
||||
if (optionsline.size() == 2 &&
|
||||
("extra-certs".equals(optionsline.get(0)) || "http-proxy-user-pass".equals(optionsline.get(0)))) {
|
||||
custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1));
|
||||
} else {
|
||||
for (String arg : optionsline)
|
||||
custom += VpnProfile.openVpnEscape(arg) + " ";
|
||||
custom += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
private void fixup(VpnProfile np) {
|
||||
if (np.mRemoteCN.equals(np.mServerName)) {
|
||||
np.mRemoteCN = "";
|
||||
}
|
||||
}
|
||||
private Vector<String> getOption(String option, int minarg, int maxarg) throws ConfigParseError {
|
||||
Vector<Vector<String>> alloptions = getAllOption(option, minarg, maxarg);
|
||||
if (alloptions == null)
|
||||
return null;
|
||||
else
|
||||
return alloptions.lastElement();
|
||||
}
|
||||
private Vector<Vector<String>> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError {
|
||||
Vector<Vector<String>> args = options.get(option);
|
||||
if (args == null)
|
||||
return null;
|
||||
for (Vector<String> optionline : args)
|
||||
if (optionline.size() < (minarg + 1) || optionline.size() > maxarg + 1) {
|
||||
String err = String.format(Locale.getDefault(), "Option %s has %d parameters, expected between %d and %d",
|
||||
option, optionline.size() - 1, minarg, maxarg);
|
||||
throw new ConfigParseError(err);
|
||||
}
|
||||
options.remove(option);
|
||||
return args;
|
||||
}
|
||||
enum linestate {
|
||||
initial,
|
||||
readin_single_quote, reading_quoted, reading_unquoted, done
|
||||
}
|
||||
public static class ConfigParseError extends Exception {
|
||||
private static final long serialVersionUID = -60L;
|
||||
public ConfigParseError(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Connection implements Serializable, Cloneable {
|
||||
public static final int CONNECTION_DEFAULT_TIMEOUT = 120;
|
||||
private static final long serialVersionUID = 92031902903829089L;
|
||||
public String mServerName = "openvpn.example.com";
|
||||
public String mServerPort = "1194";
|
||||
public boolean mUseUdp = true;
|
||||
public String mCustomConfiguration = "";
|
||||
public boolean mUseCustomConfig = false;
|
||||
public boolean mEnabled = true;
|
||||
public int mConnectTimeout = 0;
|
||||
public String getConnectionBlock() {
|
||||
String cfg = "";
|
||||
// Server Address
|
||||
cfg += "remote ";
|
||||
cfg += mServerName;
|
||||
cfg += " ";
|
||||
cfg += mServerPort;
|
||||
if (mUseUdp)
|
||||
cfg += " udp\n";
|
||||
else
|
||||
cfg += " tcp-client\n";
|
||||
if (mConnectTimeout != 0)
|
||||
cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout);
|
||||
if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) {
|
||||
cfg += mCustomConfiguration;
|
||||
cfg += "\n";
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
@Override
|
||||
public Connection clone() throws CloneNotSupportedException {
|
||||
return (Connection) super.clone();
|
||||
}
|
||||
public boolean isOnlyRemote() {
|
||||
return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig;
|
||||
}
|
||||
public int getTimeout() {
|
||||
if (mConnectTimeout <= 0)
|
||||
return CONNECTION_DEFAULT_TIMEOUT;
|
||||
else
|
||||
return mConnectTimeout;
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Created by arne on 08.11.16.
|
||||
*/
|
||||
public enum ConnectionStatus implements Parcelable {
|
||||
LEVEL_CONNECTED,
|
||||
LEVEL_VPNPAUSED,
|
||||
LEVEL_CONNECTING_SERVER_REPLIED,
|
||||
LEVEL_CONNECTING_NO_SERVER_REPLY_YET,
|
||||
LEVEL_NONETWORK,
|
||||
LEVEL_NOTCONNECTED,
|
||||
LEVEL_START,
|
||||
LEVEL_AUTH_FAILED,
|
||||
LEVEL_WAITING_FOR_USER_INPUT,
|
||||
UNKNOWN_LEVEL;
|
||||
public static final Creator<ConnectionStatus> CREATOR = new Creator<ConnectionStatus>() {
|
||||
@Override
|
||||
public ConnectionStatus createFromParcel(Parcel in) {
|
||||
return ConnectionStatus.values()[in.readInt()];
|
||||
}
|
||||
@Override
|
||||
public ConnectionStatus[] newArray(int size) {
|
||||
return new ConnectionStatus[size];
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(ordinal());
|
||||
}
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkInfo.State;
|
||||
import android.os.Handler;
|
||||
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason;
|
||||
|
||||
public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback {
|
||||
private final Handler mDisconnectHandler;
|
||||
// Window time in s
|
||||
private final int TRAFFIC_WINDOW = 60;
|
||||
// Data traffic limit in bytes
|
||||
private final long TRAFFIC_LIMIT = 64 * 1024;
|
||||
// Time to wait after network disconnect to pause the VPN
|
||||
private final int DISCONNECT_WAIT = 20;
|
||||
connectState network = connectState.DISCONNECTED;
|
||||
connectState screen = connectState.SHOULDBECONNECTED;
|
||||
connectState userpause = connectState.SHOULDBECONNECTED;
|
||||
private int lastNetwork = -1;
|
||||
private OpenVPNManagement mManagement;
|
||||
private String lastStateMsg = null;
|
||||
private Runnable mDelayDisconnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!(network == connectState.PENDINGDISCONNECT)) return;
|
||||
network = connectState.DISCONNECTED;
|
||||
// Set screen state to be disconnected if disconnect pending
|
||||
if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED;
|
||||
mManagement.pause(getPauseReason());
|
||||
}
|
||||
};
|
||||
private NetworkInfo lastConnectedNetwork;
|
||||
private LinkedList<Datapoint> trafficdata = new LinkedList<>();
|
||||
|
||||
public DeviceStateReceiver(OpenVPNManagement magnagement) {
|
||||
super();
|
||||
mManagement = magnagement;
|
||||
mManagement.setPauseCallback(this);
|
||||
mDisconnectHandler = new Handler();
|
||||
}
|
||||
|
||||
public static boolean equalsObj(Object a, Object b) {
|
||||
return (a == null) ? (b == null) : a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBeRunning() {
|
||||
return shouldBeConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByteCount(long in, long out, long diffIn, long diffOut) {
|
||||
if (screen != connectState.PENDINGDISCONNECT) return;
|
||||
long total = diffIn + diffOut;
|
||||
trafficdata.add(new Datapoint(System.currentTimeMillis(), total));
|
||||
while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) {
|
||||
trafficdata.removeFirst();
|
||||
}
|
||||
long windowtraffic = 0;
|
||||
for (Datapoint dp : trafficdata)
|
||||
windowtraffic += dp.data;
|
||||
if (windowtraffic < TRAFFIC_LIMIT) {
|
||||
screen = connectState.DISCONNECTED;
|
||||
VpnStatus.logInfo(R.string.screenoff_pause, "64 kB", TRAFFIC_WINDOW);
|
||||
mManagement.pause(getPauseReason());
|
||||
}
|
||||
}
|
||||
|
||||
public void userPause(boolean pause) {
|
||||
if (pause) {
|
||||
userpause = connectState.DISCONNECTED;
|
||||
// Check if we should disconnect
|
||||
mManagement.pause(getPauseReason());
|
||||
} else {
|
||||
boolean wereConnected = shouldBeConnected();
|
||||
userpause = connectState.SHOULDBECONNECTED;
|
||||
if (shouldBeConnected() && !wereConnected) mManagement.resume();
|
||||
else
|
||||
// Update the reason why we currently paused
|
||||
mManagement.pause(getPauseReason());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
networkStateChange(context);
|
||||
} else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
||||
boolean screenOffPause = prefs.getBoolean("screenoff", false);
|
||||
if (screenOffPause) {
|
||||
if (ProfileManager.getLastConnectedVpn() != null && !ProfileManager.getLastConnectedVpn().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun);
|
||||
screen = connectState.PENDINGDISCONNECT;
|
||||
fillTrafficData();
|
||||
if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) screen = connectState.DISCONNECTED;
|
||||
}
|
||||
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
|
||||
// Network was disabled because screen off
|
||||
boolean connected = shouldBeConnected();
|
||||
screen = connectState.SHOULDBECONNECTED;
|
||||
/* We should connect now, cancel any outstanding disconnect timer */
|
||||
mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
|
||||
/* should be connected has changed because the screen is on now, connect the VPN */
|
||||
if (shouldBeConnected() != connected) mManagement.resume();
|
||||
else if (!shouldBeConnected())
|
||||
/*Update the reason why we are still paused */ mManagement.pause(getPauseReason());
|
||||
}
|
||||
}
|
||||
|
||||
private void fillTrafficData() {
|
||||
trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT));
|
||||
}
|
||||
|
||||
public void networkStateChange(Context context) {
|
||||
NetworkInfo networkInfo = getCurrentNetworkInfo(context);
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
|
||||
boolean sendusr1 = prefs.getBoolean("netchangereconnect", true);
|
||||
String netstatestring;
|
||||
if (networkInfo == null) {
|
||||
netstatestring = "not connected";
|
||||
} else {
|
||||
String subtype = networkInfo.getSubtypeName();
|
||||
if (subtype == null) subtype = "";
|
||||
String extrainfo = networkInfo.getExtraInfo();
|
||||
if (extrainfo == null) extrainfo = "";
|
||||
/*
|
||||
if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) {
|
||||
WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
|
||||
extrainfo+=wifiinfo.getBSSID();
|
||||
subtype += wifiinfo.getNetworkId();
|
||||
}*/
|
||||
netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), networkInfo.getDetailedState(), extrainfo, subtype);
|
||||
}
|
||||
if (networkInfo != null && networkInfo.getState() == State.CONNECTED) {
|
||||
int newnet = networkInfo.getType();
|
||||
boolean pendingDisconnect = (network == connectState.PENDINGDISCONNECT);
|
||||
network = connectState.SHOULDBECONNECTED;
|
||||
boolean sameNetwork;
|
||||
sameNetwork = !(lastConnectedNetwork == null || lastConnectedNetwork.getType() != networkInfo.getType() || !equalsObj(lastConnectedNetwork.getExtraInfo(), networkInfo.getExtraInfo()));
|
||||
/* Same network, connection still 'established' */
|
||||
if (pendingDisconnect && sameNetwork) {
|
||||
mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
|
||||
// Reprotect the sockets just be sure
|
||||
mManagement.networkChange(true);
|
||||
} else {
|
||||
/* Different network or connection not established anymore */
|
||||
if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED;
|
||||
if (shouldBeConnected()) {
|
||||
mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable);
|
||||
if (pendingDisconnect || !sameNetwork) mManagement.networkChange(sameNetwork);
|
||||
else mManagement.resume();
|
||||
}
|
||||
lastNetwork = newnet;
|
||||
lastConnectedNetwork = networkInfo;
|
||||
}
|
||||
} else if (networkInfo == null) {
|
||||
// Not connected, stop openvpn, set last connected network to no network
|
||||
lastNetwork = -1;
|
||||
if (sendusr1) {
|
||||
network = connectState.PENDINGDISCONNECT;
|
||||
mDisconnectHandler.postDelayed(mDelayDisconnectRunnable, DISCONNECT_WAIT * 1000);
|
||||
}
|
||||
}
|
||||
if (!netstatestring.equals(lastStateMsg)) VpnStatus.logInfo(R.string.netstatus, netstatestring);
|
||||
VpnStatus.logDebug(String.format("Debug state info: %s, pause: %s, shouldbeconnected: %s, network: %s ", netstatestring, getPauseReason(), shouldBeConnected(), network));
|
||||
lastStateMsg = netstatestring;
|
||||
}
|
||||
|
||||
public boolean isUserPaused() {
|
||||
return userpause == connectState.DISCONNECTED;
|
||||
}
|
||||
|
||||
private boolean shouldBeConnected() {
|
||||
return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && network == connectState.SHOULDBECONNECTED);
|
||||
}
|
||||
|
||||
private pauseReason getPauseReason() {
|
||||
if (userpause == connectState.DISCONNECTED) return pauseReason.userPause;
|
||||
if (screen == connectState.DISCONNECTED) return pauseReason.screenOff;
|
||||
if (network == connectState.DISCONNECTED) return pauseReason.noNetwork;
|
||||
return pauseReason.userPause;
|
||||
}
|
||||
|
||||
private NetworkInfo getCurrentNetworkInfo(Context context) {
|
||||
ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return conn.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
private enum connectState {
|
||||
SHOULDBECONNECTED, PENDINGDISCONNECT, DISCONNECTED
|
||||
}
|
||||
|
||||
private static class Datapoint {
|
||||
long timestamp;
|
||||
long data;
|
||||
|
||||
private Datapoint(long t, long d) {
|
||||
timestamp = t;
|
||||
data = d;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2015 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
/**
|
||||
* Created by arne on 23.01.16.
|
||||
*/
|
||||
class LogFileHandler extends Handler {
|
||||
public static final int LOG_MESSAGE = 103;
|
||||
public static final int MAGIC_BYTE = 0x55;
|
||||
public static final String LOGFILE_NAME = "logcache.dat";
|
||||
static final int TRIM_LOG_FILE = 100;
|
||||
static final int FLUSH_TO_DISK = 101;
|
||||
static final int LOG_INIT = 102;
|
||||
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||
protected OutputStream mLogFile;
|
||||
public LogFileHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
public static String bytesToHex(byte[] bytes, int len) {
|
||||
len = Math.min(bytes.length, len);
|
||||
char[] hexChars = new char[len * 2];
|
||||
for (int j = 0; j < len; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
try {
|
||||
if (msg.what == LOG_INIT) {
|
||||
if (mLogFile != null)
|
||||
throw new RuntimeException("mLogFile not null");
|
||||
readLogCache((File) msg.obj);
|
||||
openLogFile((File) msg.obj);
|
||||
} else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) {
|
||||
// Ignore log messages if not yet initialized
|
||||
if (mLogFile == null)
|
||||
return;
|
||||
writeLogItemToDisk((LogItem) msg.obj);
|
||||
} else if (msg.what == TRIM_LOG_FILE) {
|
||||
trimLogFile();
|
||||
for (LogItem li : VpnStatus.getlogbuffer())
|
||||
writeLogItemToDisk(li);
|
||||
} else if (msg.what == FLUSH_TO_DISK) {
|
||||
flushToDisk();
|
||||
}
|
||||
} catch (IOException | BufferOverflowException e) {
|
||||
e.printStackTrace();
|
||||
VpnStatus.logError("Error during log cache: " + msg.what);
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
}
|
||||
private void flushToDisk() throws IOException {
|
||||
mLogFile.flush();
|
||||
}
|
||||
private void trimLogFile() {
|
||||
try {
|
||||
mLogFile.flush();
|
||||
((FileOutputStream) mLogFile).getChannel().truncate(0);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void writeLogItemToDisk(LogItem li) throws IOException {
|
||||
// We do not really care if the log cache breaks between Android upgrades,
|
||||
// write binary format to disc
|
||||
byte[] liBytes = li.getMarschaledBytes();
|
||||
writeEscapedBytes(liBytes);
|
||||
}
|
||||
public void writeEscapedBytes(byte[] bytes) throws IOException {
|
||||
int magic = 0;
|
||||
for (byte b : bytes)
|
||||
if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1)
|
||||
magic++;
|
||||
byte eBytes[] = new byte[bytes.length + magic];
|
||||
int i = 0;
|
||||
for (byte b : bytes) {
|
||||
if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) {
|
||||
eBytes[i++] = MAGIC_BYTE + 1;
|
||||
eBytes[i++] = (byte) (b - MAGIC_BYTE);
|
||||
} else {
|
||||
eBytes[i++] = b;
|
||||
}
|
||||
}
|
||||
byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array();
|
||||
synchronized (mLogFile) {
|
||||
mLogFile.write(MAGIC_BYTE);
|
||||
mLogFile.write(lenBytes);
|
||||
mLogFile.write(eBytes);
|
||||
}
|
||||
}
|
||||
private void openLogFile(File cacheDir) throws FileNotFoundException {
|
||||
File logfile = new File(cacheDir, LOGFILE_NAME);
|
||||
mLogFile = new FileOutputStream(logfile);
|
||||
}
|
||||
private void readLogCache(File cacheDir) {
|
||||
try {
|
||||
File logfile = new File(cacheDir, LOGFILE_NAME);
|
||||
if (!logfile.exists() || !logfile.canRead())
|
||||
return;
|
||||
readCacheContents(new FileInputStream(logfile));
|
||||
} catch (IOException | RuntimeException e) {
|
||||
VpnStatus.logError("Reading cached logfile failed");
|
||||
VpnStatus.logException(e);
|
||||
e.printStackTrace();
|
||||
// ignore reading file error
|
||||
} finally {
|
||||
synchronized (VpnStatus.readFileLock) {
|
||||
VpnStatus.readFileLog = true;
|
||||
VpnStatus.readFileLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
protected void readCacheContents(InputStream in) throws IOException {
|
||||
BufferedInputStream logFile = new BufferedInputStream(in);
|
||||
byte[] buf = new byte[16384];
|
||||
int read = logFile.read(buf, 0, 5);
|
||||
int itemsRead = 0;
|
||||
readloop:
|
||||
while (read >= 5) {
|
||||
int skipped = 0;
|
||||
while (buf[skipped] != MAGIC_BYTE) {
|
||||
skipped++;
|
||||
if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) {
|
||||
VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped));
|
||||
break readloop;
|
||||
}
|
||||
}
|
||||
if (skipped > 0)
|
||||
VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped));
|
||||
int len = ByteBuffer.wrap(buf, skipped + 1, 4).asIntBuffer().get();
|
||||
// Marshalled LogItem
|
||||
int pos = 0;
|
||||
byte buf2[] = new byte[buf.length];
|
||||
while (pos < len) {
|
||||
byte b = (byte) logFile.read();
|
||||
if (b == MAGIC_BYTE) {
|
||||
VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos));
|
||||
read = logFile.read(buf, 1, 4) + 1;
|
||||
continue readloop;
|
||||
} else if (b == MAGIC_BYTE + 1) {
|
||||
b = (byte) logFile.read();
|
||||
if (b == 0)
|
||||
b = MAGIC_BYTE;
|
||||
else if (b == 1)
|
||||
b = MAGIC_BYTE + 1;
|
||||
else {
|
||||
VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b));
|
||||
read = logFile.read(buf, 1, 4) + 1;
|
||||
continue readloop;
|
||||
}
|
||||
}
|
||||
buf2[pos++] = b;
|
||||
}
|
||||
restoreLogItem(buf2, len);
|
||||
//Next item
|
||||
read = logFile.read(buf, 0, 5);
|
||||
itemsRead++;
|
||||
if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) {
|
||||
VpnStatus.logError("Too many logentries read from cache, aborting.");
|
||||
read = 0;
|
||||
}
|
||||
}
|
||||
VpnStatus.logDebug(R.string.reread_log, itemsRead);
|
||||
}
|
||||
protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException {
|
||||
LogItem li = new LogItem(buf, len);
|
||||
if (li.verify()) {
|
||||
VpnStatus.newLogItem(li, true);
|
||||
} else {
|
||||
VpnStatus.logError(String.format(Locale.getDefault(),
|
||||
"Could not read log item from file: %d: %s",
|
||||
len, bytesToHex(buf, Math.max(len, 80))));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.FormatFlagsConversionMismatchException;
|
||||
import java.util.Locale;
|
||||
import java.util.UnknownFormatConversionException;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
/**
|
||||
* Created by arne on 24.04.16.
|
||||
*/
|
||||
public class LogItem implements Parcelable {
|
||||
public static final Creator<LogItem> CREATOR = new Creator<LogItem>() {
|
||||
public LogItem createFromParcel(Parcel in) {
|
||||
return new LogItem(in);
|
||||
}
|
||||
|
||||
public LogItem[] newArray(int size) {
|
||||
return new LogItem[size];
|
||||
}
|
||||
};
|
||||
// Default log priority
|
||||
VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO;
|
||||
private Object[] mArgs = null;
|
||||
private String mMessage = null;
|
||||
private int mRessourceId;
|
||||
private long logtime = System.currentTimeMillis();
|
||||
private int mVerbosityLevel = -1;
|
||||
|
||||
private LogItem(int ressourceId, Object[] args) {
|
||||
mRessourceId = ressourceId;
|
||||
mArgs = args;
|
||||
}
|
||||
|
||||
public LogItem(VpnStatus.LogLevel level, int verblevel, String message) {
|
||||
mMessage = message;
|
||||
mLevel = level;
|
||||
mVerbosityLevel = verblevel;
|
||||
}
|
||||
|
||||
public LogItem(byte[] in, int length) throws UnsupportedEncodingException {
|
||||
ByteBuffer bb = ByteBuffer.wrap(in, 0, length);
|
||||
bb.get(); // ignore version
|
||||
logtime = bb.getLong();
|
||||
mVerbosityLevel = bb.getInt();
|
||||
mLevel = VpnStatus.LogLevel.getEnumByValue(bb.getInt());
|
||||
mRessourceId = bb.getInt();
|
||||
int len = bb.getInt();
|
||||
if (len == 0) {
|
||||
mMessage = null;
|
||||
} else {
|
||||
if (len > bb.remaining()) throw new IndexOutOfBoundsException("String length " + len + " is bigger than remaining bytes " + bb.remaining());
|
||||
byte[] utf8bytes = new byte[len];
|
||||
bb.get(utf8bytes);
|
||||
mMessage = new String(utf8bytes, "UTF-8");
|
||||
}
|
||||
int numArgs = bb.getInt();
|
||||
if (numArgs > 30) {
|
||||
throw new IndexOutOfBoundsException("Too many arguments for Logitem to unmarschal");
|
||||
}
|
||||
if (numArgs == 0) {
|
||||
mArgs = null;
|
||||
} else {
|
||||
mArgs = new Object[numArgs];
|
||||
for (int i = 0; i < numArgs; i++) {
|
||||
char type = bb.getChar();
|
||||
switch (type) {
|
||||
case 's':
|
||||
mArgs[i] = unmarschalString(bb);
|
||||
break;
|
||||
case 'i':
|
||||
mArgs[i] = bb.getInt();
|
||||
break;
|
||||
case 'd':
|
||||
mArgs[i] = bb.getDouble();
|
||||
break;
|
||||
case 'f':
|
||||
mArgs[i] = bb.getFloat();
|
||||
break;
|
||||
case 'l':
|
||||
mArgs[i] = bb.getLong();
|
||||
break;
|
||||
case '0':
|
||||
mArgs[i] = null;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedEncodingException("Unknown format type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bb.hasRemaining()) throw new UnsupportedEncodingException(bb.remaining() + " bytes left after unmarshaling everything");
|
||||
}
|
||||
|
||||
public LogItem(Parcel in) {
|
||||
mArgs = in.readArray(Object.class.getClassLoader());
|
||||
mMessage = in.readString();
|
||||
mRessourceId = in.readInt();
|
||||
mLevel = VpnStatus.LogLevel.getEnumByValue(in.readInt());
|
||||
mVerbosityLevel = in.readInt();
|
||||
logtime = in.readLong();
|
||||
}
|
||||
|
||||
public LogItem(VpnStatus.LogLevel loglevel, int ressourceId, Object... args) {
|
||||
mRessourceId = ressourceId;
|
||||
mArgs = args;
|
||||
mLevel = loglevel;
|
||||
}
|
||||
|
||||
public LogItem(VpnStatus.LogLevel loglevel, String msg) {
|
||||
mLevel = loglevel;
|
||||
mMessage = msg;
|
||||
}
|
||||
|
||||
public LogItem(VpnStatus.LogLevel loglevel, int ressourceId) {
|
||||
mRessourceId = ressourceId;
|
||||
mLevel = loglevel;
|
||||
}
|
||||
|
||||
// TextUtils.join will cause not macked exeception in tests ....
|
||||
public static String join(CharSequence delimiter, Object[] tokens) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean firstTime = true;
|
||||
for (Object token : tokens) {
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
} else {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
sb.append(token);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeArray(mArgs);
|
||||
dest.writeString(mMessage);
|
||||
dest.writeInt(mRessourceId);
|
||||
dest.writeInt(mLevel.getInt());
|
||||
dest.writeInt(mVerbosityLevel);
|
||||
dest.writeLong(logtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof LogItem)) return obj.equals(this);
|
||||
LogItem other = (LogItem) obj;
|
||||
return Arrays.equals(mArgs, other.mArgs) && ((other.mMessage == null && mMessage == other.mMessage) || mMessage.equals(other.mMessage)) && mRessourceId == other.mRessourceId && ((mLevel == null && other.mLevel == mLevel) || other.mLevel.equals(mLevel)) && mVerbosityLevel == other.mVerbosityLevel && logtime == other.logtime;
|
||||
}
|
||||
|
||||
public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(16384);
|
||||
bb.put((byte) 0x0); //version
|
||||
bb.putLong(logtime); //8
|
||||
bb.putInt(mVerbosityLevel); //4
|
||||
bb.putInt(mLevel.getInt());
|
||||
bb.putInt(mRessourceId);
|
||||
if (mMessage == null || mMessage.length() == 0) {
|
||||
bb.putInt(0);
|
||||
} else {
|
||||
marschalString(mMessage, bb);
|
||||
}
|
||||
if (mArgs == null || mArgs.length == 0) {
|
||||
bb.putInt(0);
|
||||
} else {
|
||||
bb.putInt(mArgs.length);
|
||||
for (Object o : mArgs) {
|
||||
if (o instanceof String) {
|
||||
bb.putChar('s');
|
||||
marschalString((String) o, bb);
|
||||
} else if (o instanceof Integer) {
|
||||
bb.putChar('i');
|
||||
bb.putInt((Integer) o);
|
||||
} else if (o instanceof Float) {
|
||||
bb.putChar('f');
|
||||
bb.putFloat((Float) o);
|
||||
} else if (o instanceof Double) {
|
||||
bb.putChar('d');
|
||||
bb.putDouble((Double) o);
|
||||
} else if (o instanceof Long) {
|
||||
bb.putChar('l');
|
||||
bb.putLong((Long) o);
|
||||
} else if (o == null) {
|
||||
bb.putChar('0');
|
||||
} else {
|
||||
VpnStatus.logDebug("Unknown object for LogItem marschaling " + o);
|
||||
bb.putChar('s');
|
||||
marschalString(o.toString(), bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
int pos = bb.position();
|
||||
bb.rewind();
|
||||
return Arrays.copyOf(bb.array(), pos);
|
||||
}
|
||||
|
||||
private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException {
|
||||
byte[] utf8bytes = str.getBytes("UTF-8");
|
||||
bb.putInt(utf8bytes.length);
|
||||
bb.put(utf8bytes);
|
||||
}
|
||||
|
||||
private String unmarschalString(ByteBuffer bb) throws UnsupportedEncodingException {
|
||||
int len = bb.getInt();
|
||||
byte[] utf8bytes = new byte[len];
|
||||
bb.get(utf8bytes);
|
||||
return new String(utf8bytes, "UTF-8");
|
||||
}
|
||||
|
||||
public String getString(Context c) {
|
||||
try {
|
||||
if (mMessage != null) {
|
||||
return mMessage;
|
||||
} else {
|
||||
if (c != null) {
|
||||
if (mRessourceId == R.string.mobile_info) return getMobileInfoString(c);
|
||||
if (mArgs == null) return c.getString(mRessourceId);
|
||||
else return c.getString(mRessourceId, mArgs);
|
||||
} else {
|
||||
String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId);
|
||||
if (mArgs != null) str += join("|", mArgs);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
} catch (UnknownFormatConversionException e) {
|
||||
if (c != null) throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null));
|
||||
else throw e;
|
||||
} catch (FormatFlagsConversionMismatchException e) {
|
||||
if (c != null) throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion());
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public VpnStatus.LogLevel getLogLevel() {
|
||||
return mLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getString(null);
|
||||
}
|
||||
|
||||
// The lint is wrong here
|
||||
@SuppressLint("StringFormatMatches")
|
||||
private String getMobileInfoString(Context c) {
|
||||
c.getPackageManager();
|
||||
String apksign = "error getting package signature";
|
||||
String version = "error getting version";
|
||||
try {
|
||||
@SuppressLint("PackageManagerGetSignatures") Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
byte[] der = cert.getEncoded();
|
||||
md.update(der);
|
||||
byte[] digest = md.digest();
|
||||
if (Arrays.equals(digest, VpnStatus.officalkey)) apksign = c.getString(R.string.official_build);
|
||||
else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) apksign = c.getString(R.string.debug_build);
|
||||
else if (Arrays.equals(digest, VpnStatus.amazonkey)) apksign = "amazon version";
|
||||
else if (Arrays.equals(digest, VpnStatus.fdroidkey)) apksign = "F-Droid built and signed version";
|
||||
else apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName());
|
||||
PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
|
||||
version = packageinfo.versionName;
|
||||
} catch (PackageManager.NameNotFoundException | CertificateException | NoSuchAlgorithmException ignored) {
|
||||
}
|
||||
Object[] argsext = Arrays.copyOf(mArgs, mArgs.length);
|
||||
argsext[argsext.length - 1] = apksign;
|
||||
argsext[argsext.length - 2] = version;
|
||||
return c.getString(R.string.mobile_info, argsext);
|
||||
}
|
||||
|
||||
public long getLogtime() {
|
||||
return logtime;
|
||||
}
|
||||
|
||||
public int getVerbosityLevel() {
|
||||
if (mVerbosityLevel == -1) {
|
||||
// Hack:
|
||||
// For message not from OpenVPN, report the status level as log level
|
||||
return mLevel.getInt();
|
||||
}
|
||||
return mVerbosityLevel;
|
||||
}
|
||||
|
||||
public boolean verify() {
|
||||
if (mLevel == null) return false;
|
||||
return !(mMessage == null && mRessourceId == 0);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
import android.annotation.TargetApi;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Created by arne on 26.11.14.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class LollipopDeviceStateListener extends ConnectivityManager.NetworkCallback {
|
||||
private String mLastConnectedStatus;
|
||||
private String mLastLinkProperties;
|
||||
private String mLastNetworkCapabilities;
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
super.onAvailable(network);
|
||||
if (!network.toString().equals(mLastConnectedStatus)) {
|
||||
mLastConnectedStatus = network.toString();
|
||||
VpnStatus.logDebug("Connected to " + mLastConnectedStatus);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
|
||||
super.onLinkPropertiesChanged(network, linkProperties);
|
||||
if (!linkProperties.toString().equals(mLastLinkProperties)) {
|
||||
mLastLinkProperties = linkProperties.toString();
|
||||
VpnStatus.logDebug(String.format("Linkproperties of %s: %s", network, linkProperties));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
|
||||
super.onCapabilitiesChanged(network, networkCapabilities);
|
||||
if (!networkCapabilities.toString().equals(mLastNetworkCapabilities)) {
|
||||
mLastNetworkCapabilities = networkCapabilities.toString();
|
||||
VpnStatus.logDebug(String.format("Network capabilities of %s: %s", network, networkCapabilities));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
public class NativeUtils {
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("ovpnutil");
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
|
||||
System.loadLibrary("jbcrypto");
|
||||
} catch (UnsatisfiedLinkError e) { // Unsupported arch fix
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public static native byte[] rsasign(byte[] input, int pkey) throws InvalidKeyException;
|
||||
public static native String[] getIfconfig() throws IllegalArgumentException;
|
||||
static native void jniclose(int fdint);
|
||||
public static String getNativeAPI() {
|
||||
return getJNIAPI();
|
||||
}
|
||||
private static native String getJNIAPI();
|
||||
}
|
|
@ -1,293 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import com.liskovsoft.openvpn.BuildConfig;
|
||||
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Vector;
|
||||
|
||||
public class NetworkSpace {
|
||||
TreeSet<ipAddress> mIpAddresses = new TreeSet<ipAddress>();
|
||||
public Collection<ipAddress> getNetworks(boolean included) {
|
||||
Vector<ipAddress> ips = new Vector<ipAddress>();
|
||||
for (ipAddress ip : mIpAddresses) {
|
||||
if (ip.included == included)
|
||||
ips.add(ip);
|
||||
}
|
||||
return ips;
|
||||
}
|
||||
public void clear() {
|
||||
mIpAddresses.clear();
|
||||
}
|
||||
void addIP(CIDRIP cidrIp, boolean include) {
|
||||
mIpAddresses.add(new ipAddress(cidrIp, include));
|
||||
}
|
||||
public void addIPSplit(CIDRIP cidrIp, boolean include) {
|
||||
ipAddress newIP = new ipAddress(cidrIp, include);
|
||||
ipAddress[] splitIps = newIP.split();
|
||||
for (ipAddress split : splitIps)
|
||||
mIpAddresses.add(split);
|
||||
}
|
||||
void addIPv6(Inet6Address address, int mask, boolean included) {
|
||||
mIpAddresses.add(new ipAddress(address, mask, included));
|
||||
}
|
||||
TreeSet<ipAddress> generateIPList() {
|
||||
PriorityQueue<ipAddress> networks = new PriorityQueue<ipAddress>(mIpAddresses);
|
||||
TreeSet<ipAddress> ipsDone = new TreeSet<ipAddress>();
|
||||
ipAddress currentNet = networks.poll();
|
||||
if (currentNet == null)
|
||||
return ipsDone;
|
||||
while (currentNet != null) {
|
||||
// Check if it and the next of it are compatible
|
||||
ipAddress nextNet = networks.poll();
|
||||
if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet);
|
||||
if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) {
|
||||
// Everything good, no overlapping nothing to do
|
||||
ipsDone.add(currentNet);
|
||||
currentNet = nextNet;
|
||||
} else {
|
||||
// This network is smaller or equal to the next but has the same base address
|
||||
if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) {
|
||||
if (currentNet.included == nextNet.included) {
|
||||
// Included in the next next and same type
|
||||
// Simply forget our current network
|
||||
currentNet = nextNet;
|
||||
} else {
|
||||
// our currentNet is included in next and types differ. Need to split the next network
|
||||
ipAddress[] newNets = nextNet.split();
|
||||
// TODO: The contains method of the Priority is stupid linear search
|
||||
// First add the second half to keep the order in networks
|
||||
if (!networks.contains(newNets[1]))
|
||||
networks.add(newNets[1]);
|
||||
if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) {
|
||||
if (BuildConfig.DEBUG)
|
||||
Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask);
|
||||
// Don't add the lower half that would conflict with currentNet
|
||||
} else {
|
||||
if (!networks.contains(newNets[0]))
|
||||
networks.add(newNets[0]);
|
||||
}
|
||||
// Keep currentNet as is
|
||||
}
|
||||
} else {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Assert.assertTrue(currentNet.networkMask < nextNet.networkMask);
|
||||
Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1);
|
||||
Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1);
|
||||
}
|
||||
// This network is bigger than the next and last ip of current >= next
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (currentNet.included == nextNet.included) {
|
||||
// Next network is in included in our network with the same type,
|
||||
// simply ignore the next and move on
|
||||
} else {
|
||||
// We need to split our network
|
||||
ipAddress[] newNets = currentNet.split();
|
||||
if (newNets[1].networkMask == nextNet.networkMask) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress()));
|
||||
Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress()));
|
||||
// split second equal the next network, do not add it
|
||||
}
|
||||
networks.add(nextNet);
|
||||
} else {
|
||||
// Add the smaller network first
|
||||
networks.add(newNets[1]);
|
||||
networks.add(nextNet);
|
||||
}
|
||||
currentNet = newNets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ipsDone;
|
||||
}
|
||||
Collection<ipAddress> getPositiveIPList() {
|
||||
TreeSet<ipAddress> ipsSorted = generateIPList();
|
||||
Vector<ipAddress> ips = new Vector<ipAddress>();
|
||||
for (ipAddress ia : ipsSorted) {
|
||||
if (ia.included)
|
||||
ips.add(ia);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
// Include postive routes from the original set under < 4.4 since these might overrule the local
|
||||
// network but only if no smaller negative route exists
|
||||
for (ipAddress origIp : mIpAddresses) {
|
||||
if (!origIp.included)
|
||||
continue;
|
||||
// The netspace exists
|
||||
if (ipsSorted.contains(origIp))
|
||||
continue;
|
||||
boolean skipIp = false;
|
||||
// If there is any smaller net that is excluded we may not add the positive route back
|
||||
for (ipAddress calculatedIp : ipsSorted) {
|
||||
if (!calculatedIp.included && origIp.containsNet(calculatedIp)) {
|
||||
skipIp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skipIp)
|
||||
continue;
|
||||
// It is safe to include the IP
|
||||
ips.add(origIp);
|
||||
}
|
||||
}
|
||||
return ips;
|
||||
}
|
||||
static class ipAddress implements Comparable<ipAddress> {
|
||||
public int networkMask;
|
||||
private BigInteger netAddress;
|
||||
private boolean included;
|
||||
private boolean isV4;
|
||||
private BigInteger firstAddress;
|
||||
private BigInteger lastAddress;
|
||||
public ipAddress(CIDRIP ip, boolean include) {
|
||||
included = include;
|
||||
netAddress = BigInteger.valueOf(ip.getInt());
|
||||
networkMask = ip.len;
|
||||
isV4 = true;
|
||||
}
|
||||
public ipAddress(Inet6Address address, int mask, boolean include) {
|
||||
networkMask = mask;
|
||||
included = include;
|
||||
int s = 128;
|
||||
netAddress = BigInteger.ZERO;
|
||||
for (byte b : address.getAddress()) {
|
||||
s -= 8;
|
||||
netAddress = netAddress.add(BigInteger.valueOf((b & 0xFF)).shiftLeft(s));
|
||||
}
|
||||
}
|
||||
ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) {
|
||||
this.netAddress = baseAddress;
|
||||
this.networkMask = mask;
|
||||
this.included = included;
|
||||
this.isV4 = isV4;
|
||||
}
|
||||
/**
|
||||
* sorts the networks with following criteria:
|
||||
* 1. compares first 1 of the network
|
||||
* 2. smaller networks are returned as smaller
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(@NonNull ipAddress another) {
|
||||
int comp = getFirstAddress().compareTo(another.getFirstAddress());
|
||||
if (comp != 0)
|
||||
return comp;
|
||||
if (networkMask > another.networkMask)
|
||||
return -1;
|
||||
else if (another.networkMask == networkMask)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Warning ignores the included integer
|
||||
*
|
||||
* @param o the object to compare this instance with.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ipAddress))
|
||||
return super.equals(o);
|
||||
ipAddress on = (ipAddress) o;
|
||||
return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress());
|
||||
}
|
||||
public BigInteger getLastAddress() {
|
||||
if (lastAddress == null)
|
||||
lastAddress = getMaskedAddress(true);
|
||||
return lastAddress;
|
||||
}
|
||||
public BigInteger getFirstAddress() {
|
||||
if (firstAddress == null)
|
||||
firstAddress = getMaskedAddress(false);
|
||||
return firstAddress;
|
||||
}
|
||||
private BigInteger getMaskedAddress(boolean one) {
|
||||
BigInteger numAddress = netAddress;
|
||||
int numBits;
|
||||
if (isV4) {
|
||||
numBits = 32 - networkMask;
|
||||
} else {
|
||||
numBits = 128 - networkMask;
|
||||
}
|
||||
for (int i = 0; i < numBits; i++) {
|
||||
if (one)
|
||||
numAddress = numAddress.setBit(i);
|
||||
else
|
||||
numAddress = numAddress.clearBit(i);
|
||||
}
|
||||
return numAddress;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
//String in = included ? "+" : "-";
|
||||
if (isV4)
|
||||
return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask);
|
||||
else
|
||||
return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask);
|
||||
}
|
||||
public ipAddress[] split() {
|
||||
ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4);
|
||||
ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4);
|
||||
if (BuildConfig.DEBUG)
|
||||
Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress()));
|
||||
return new ipAddress[]{firstHalf, secondHalf};
|
||||
}
|
||||
String getIPv4Address() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Assert.assertTrue(isV4);
|
||||
Assert.assertTrue(netAddress.longValue() <= 0xffffffffl);
|
||||
Assert.assertTrue(netAddress.longValue() >= 0);
|
||||
}
|
||||
long ip = netAddress.longValue();
|
||||
return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256);
|
||||
}
|
||||
String getIPv6Address() {
|
||||
if (BuildConfig.DEBUG) Assert.assertTrue(!isV4);
|
||||
BigInteger r = netAddress;
|
||||
String ipv6str = null;
|
||||
boolean lastPart = true;
|
||||
while (r.compareTo(BigInteger.ZERO) == 1) {
|
||||
long part = r.mod(BigInteger.valueOf(0x10000)).longValue();
|
||||
if (ipv6str != null || part != 0) {
|
||||
if (ipv6str == null && !lastPart)
|
||||
ipv6str = ":";
|
||||
if (lastPart)
|
||||
ipv6str = String.format(Locale.US, "%x", part, ipv6str);
|
||||
else
|
||||
ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str);
|
||||
}
|
||||
r = r.shiftRight(16);
|
||||
lastPart = false;
|
||||
}
|
||||
if (ipv6str == null)
|
||||
return "::";
|
||||
return ipv6str;
|
||||
}
|
||||
public boolean containsNet(ipAddress network) {
|
||||
// this.first >= net.first && this.last <= net.last
|
||||
BigInteger ourFirst = getFirstAddress();
|
||||
BigInteger ourLast = getLastAddress();
|
||||
BigInteger netFirst = network.getFirstAddress();
|
||||
BigInteger netLast = network.getLastAddress();
|
||||
boolean a = ourFirst.compareTo(netFirst) != 1;
|
||||
boolean b = ourLast.compareTo(netLast) != -1;
|
||||
return a && b;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
public interface OpenVPNManagement {
|
||||
int mBytecountInterval = 2;
|
||||
void reconnect();
|
||||
void pause(pauseReason reason);
|
||||
void resume();
|
||||
/**
|
||||
* @param replaceConnection True if the VPN is connected by a new connection.
|
||||
* @return true if there was a process that has been send a stop signal
|
||||
*/
|
||||
boolean stopVPN(boolean replaceConnection);
|
||||
/*
|
||||
* Rebind the interface
|
||||
*/
|
||||
void networkChange(boolean sameNetwork);
|
||||
void setPauseCallback(PausedStateCallback callback);
|
||||
enum pauseReason {
|
||||
noNetwork,
|
||||
userPause,
|
||||
screenOff,
|
||||
}
|
||||
interface PausedStateCallback {
|
||||
boolean shouldBeRunning();
|
||||
}
|
||||
}
|
|
@ -1,982 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
|
||||
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
|
||||
import static de.blinkt.openvpn.core.NetworkSpace.ipAddress;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Handler.Callback;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.system.OsConstants;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import de.blinkt.openvpn.LaunchVPN;
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
|
||||
import de.blinkt.openvpn.core.VpnStatus.StateListener;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
import com.liskovsoft.openvpn.service.VPNService;
|
||||
|
||||
public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal {
|
||||
public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
|
||||
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
|
||||
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
|
||||
public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
|
||||
public static final String NOTIFICATION_CHANNEL_BG_ID = "freedom_vpn_bg";
|
||||
public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "freedom_openvpn_newstat";
|
||||
private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN";
|
||||
private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN";
|
||||
private static final int PRIORITY_MIN = -2;
|
||||
private static final int PRIORITY_DEFAULT = 0;
|
||||
private static final int PRIORITY_MAX = 2;
|
||||
private static boolean mNotificationAlwaysVisible = false;
|
||||
private static Class mNotificationActivityClass;
|
||||
private final Vector<String> mDnslist = new Vector<>();
|
||||
private final NetworkSpace mRoutes = new NetworkSpace();
|
||||
private final NetworkSpace mRoutesv6 = new NetworkSpace();
|
||||
private final Object mProcessLock = new Object();
|
||||
private String lastChannel;
|
||||
private Thread mProcessThread = null;
|
||||
private VpnProfile mProfile;
|
||||
private String mDomain = null;
|
||||
private CIDRIP mLocalIP = null;
|
||||
private int mMtu;
|
||||
private String mLocalIPv6 = null;
|
||||
private DeviceStateReceiver mDeviceStateReceiver;
|
||||
private boolean mDisplayBytecount = false;
|
||||
private boolean mStarting = false;
|
||||
private long mConnecttime;
|
||||
private boolean mOvpn3 = false;
|
||||
private OpenVPNManagement mManagement;
|
||||
private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() {
|
||||
@Override
|
||||
public boolean protect(int fd) throws RemoteException {
|
||||
return OpenVPNService.this.protect(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userPause(boolean shouldbePaused) throws RemoteException {
|
||||
OpenVPNService.this.userPause(shouldbePaused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopVPN(boolean replaceConnection) throws RemoteException {
|
||||
return OpenVPNService.this.stopVPN(replaceConnection);
|
||||
}
|
||||
};
|
||||
private String mLastTunCfg;
|
||||
private String mRemoteGW;
|
||||
private Handler guiHandler;
|
||||
private Toast mlastToast;
|
||||
private Runnable mOpenVPNThread;
|
||||
|
||||
// From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
|
||||
public static String humanReadableByteCount(long bytes, boolean speed, Resources res) {
|
||||
if (speed) bytes = bytes * 8;
|
||||
int unit = speed ? 1000 : 1024;
|
||||
int exp = Math.max(0, Math.min((int) (Math.log(bytes) / Math.log(unit)), 3));
|
||||
float bytesUnit = (float) (bytes / Math.pow(unit, exp));
|
||||
if (speed) switch (exp) {
|
||||
case 0:
|
||||
return res.getString(R.string.bits_per_second, bytesUnit);
|
||||
case 1:
|
||||
return res.getString(R.string.kbits_per_second, bytesUnit);
|
||||
case 2:
|
||||
return res.getString(R.string.mbits_per_second, bytesUnit);
|
||||
default:
|
||||
return res.getString(R.string.gbits_per_second, bytesUnit);
|
||||
}
|
||||
else switch (exp) {
|
||||
case 0:
|
||||
return res.getString(R.string.volume_byte, bytesUnit);
|
||||
case 1:
|
||||
return res.getString(R.string.volume_kbyte, bytesUnit);
|
||||
case 2:
|
||||
return res.getString(R.string.volume_mbyte, bytesUnit);
|
||||
default:
|
||||
return res.getString(R.string.volume_gbyte, bytesUnit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity which should be opened when tapped on the permanent notification tile.
|
||||
*
|
||||
* @param activityClass The activity class to open
|
||||
*/
|
||||
public static void setNotificationActivityClass(Class<? extends Activity> activityClass) {
|
||||
mNotificationActivityClass = activityClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action != null && action.equals(START_SERVICE)) return mBinder;
|
||||
else return super.onBind(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoke() {
|
||||
VpnStatus.logError(R.string.permission_revoked);
|
||||
mManagement.stopVPN(false);
|
||||
endVpnService();
|
||||
}
|
||||
|
||||
// Similar to revoke but do not try to stop process
|
||||
public void openvpnStopped() {
|
||||
endVpnService();
|
||||
}
|
||||
|
||||
private void endVpnService() {
|
||||
synchronized (mProcessLock) {
|
||||
mProcessThread = null;
|
||||
}
|
||||
VpnStatus.removeByteCountListener(this);
|
||||
unregisterDeviceStateReceiver();
|
||||
ProfileManager.setConnectedVpnProfileDisconnected(this);
|
||||
mOpenVPNThread = null;
|
||||
if (!mStarting) {
|
||||
stopForeground(!mNotificationAlwaysVisible);
|
||||
if (!mNotificationAlwaysVisible) {
|
||||
stopSelf();
|
||||
VpnStatus.removeStateListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showNotification(final String msg, String tickerText, @NonNull String channel, long when, ConnectionStatus status) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
// Log.e("Notification", "show");
|
||||
|
||||
String NOTIFICATION_CHANNEL_ID = "ru.yourok.openvpn";
|
||||
String channelName = "My Background Service";
|
||||
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
|
||||
chan.setLightColor(Color.BLUE);
|
||||
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
assert manager != null;
|
||||
manager.createNotificationChannel(chan);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
|
||||
|
||||
int priority;
|
||||
String title;
|
||||
CharSequence ticker = null;
|
||||
|
||||
if (channel.equals(NOTIFICATION_CHANNEL_BG_ID)) priority = PRIORITY_MIN;
|
||||
else priority = PRIORITY_DEFAULT;
|
||||
|
||||
if (mProfile != null) {
|
||||
title = getString(R.string.notifcation_title, /*mProfile.mConnections[0].mServerName, mProfile.mUsername*/ getApplicationContext().getString(R.string.app_name), Build.MODEL);
|
||||
} else {
|
||||
title = getString(R.string.notifcation_title_notconnect);
|
||||
}
|
||||
|
||||
if (tickerText != null && !tickerText.equals("")) ticker = tickerText;
|
||||
|
||||
|
||||
Notification notification = notificationBuilder.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_connection_icon)
|
||||
.setContentTitle(title)
|
||||
.setContentText(msg)
|
||||
.setPriority(priority)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setTicker(ticker)
|
||||
.build();
|
||||
startForeground(2, notification);
|
||||
} else {
|
||||
|
||||
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
int icon = getIconByConnectionStatus(status);
|
||||
|
||||
|
||||
Notification.Builder nbuilder = new Notification.Builder(this);
|
||||
int priority;
|
||||
|
||||
if (channel.equals(NOTIFICATION_CHANNEL_BG_ID)) priority = PRIORITY_MIN;
|
||||
else priority = PRIORITY_DEFAULT;
|
||||
|
||||
|
||||
if (mProfile != null) {
|
||||
nbuilder.setContentTitle(getString(R.string.notifcation_title, /*mProfile.mConnections[0].mServerName, mProfile.mUsername*/ getApplicationContext().getString(R.string.app_name), Build.MODEL));
|
||||
} else {
|
||||
nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect));
|
||||
}
|
||||
|
||||
nbuilder.setContentText(msg);
|
||||
nbuilder.setOnlyAlertOnce(true);
|
||||
nbuilder.setOngoing(true);
|
||||
nbuilder.setSmallIcon(icon);
|
||||
|
||||
if (status == LEVEL_WAITING_FOR_USER_INPUT)
|
||||
nbuilder.setContentIntent(getUserInputIntent(msg));
|
||||
else nbuilder.setContentIntent(getGraphPendingIntent());
|
||||
if (when != 0) nbuilder.setWhen(when);
|
||||
|
||||
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// //noinspection NewApi
|
||||
// nbuilder.setChannelId(channel);
|
||||
// if (mProfile != null)
|
||||
// //noinspection NewApi
|
||||
// nbuilder.setShortcutId(mProfile.getUUIDString());
|
||||
// }
|
||||
|
||||
if (tickerText != null && !tickerText.equals("")) nbuilder.setTicker(tickerText);
|
||||
@SuppressWarnings("deprecation") Notification notification = nbuilder.getNotification();
|
||||
int notificationId = channel.hashCode();
|
||||
mNotificationManager.notify(notificationId, notification);
|
||||
|
||||
startForeground(2, notification);
|
||||
|
||||
|
||||
if (lastChannel != null && !channel.equals(lastChannel)) {
|
||||
// Cancel old notification
|
||||
mNotificationManager.cancel(lastChannel.hashCode());
|
||||
}
|
||||
|
||||
|
||||
// Check if running on a TV
|
||||
if (runningOnAndroidTV() && !(priority < 0)) guiHandler.post(() -> {
|
||||
if (mlastToast != null) mlastToast.cancel();
|
||||
String toastText = String.format(Locale.getDefault(), "%s - %s", mProfile.mConnections[0].mServerName, msg);
|
||||
mlastToast = Toast.makeText(getBaseContext(), toastText, Toast.LENGTH_SHORT);
|
||||
mlastToast.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void lpNotificationExtras(Notification.Builder nbuilder) {
|
||||
nbuilder.setCategory(Notification.CATEGORY_SERVICE);
|
||||
nbuilder.setLocalOnly(true);
|
||||
}
|
||||
|
||||
private boolean runningOnAndroidTV() {
|
||||
UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
|
||||
return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
|
||||
}
|
||||
|
||||
private int getIconByConnectionStatus(ConnectionStatus level) {
|
||||
/*switch (level) {
|
||||
case LEVEL_CONNECTED:
|
||||
return R.drawable.ic_stat_vpn;
|
||||
case LEVEL_AUTH_FAILED:
|
||||
case LEVEL_NONETWORK:
|
||||
case LEVEL_NOTCONNECTED:
|
||||
return R.drawable.ic_stat_vpn_offline;
|
||||
case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
|
||||
case LEVEL_WAITING_FOR_USER_INPUT:
|
||||
return R.drawable.ic_stat_vpn_outline;
|
||||
case LEVEL_CONNECTING_SERVER_REPLIED:
|
||||
return R.drawable.ic_stat_vpn_empty_halo;
|
||||
case LEVEL_VPNPAUSED:
|
||||
return android.R.drawable.ic_media_pause;
|
||||
case UNKNOWN_LEVEL:
|
||||
default:
|
||||
return R.drawable.ic_stat_vpn;
|
||||
}*/
|
||||
return R.drawable.ic_connection_icon;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private void jbNotificationExtras(int priority, Notification.Builder nbuilder) {
|
||||
try {
|
||||
if (priority != 0) {
|
||||
Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
|
||||
setpriority.invoke(nbuilder, priority);
|
||||
Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
|
||||
setUsesChronometer.invoke(nbuilder, true);
|
||||
}
|
||||
//ignore exception
|
||||
} catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
}
|
||||
|
||||
PendingIntent getUserInputIntent(String needed) {
|
||||
Intent intent = new Intent(getApplicationContext(), LaunchVPN.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
intent.putExtra("need", needed);
|
||||
Bundle b = new Bundle();
|
||||
b.putString("need", needed);
|
||||
PendingIntent pIntent = PendingIntent.getActivity(this, 12, intent, 0);
|
||||
return pIntent;
|
||||
}
|
||||
|
||||
PendingIntent getGraphPendingIntent() {
|
||||
// Let the configure Button show the Log
|
||||
// Editor : I'm not sure about this but
|
||||
// TODO : Check Later
|
||||
Class activityClass = VPNService.class;
|
||||
if (mNotificationActivityClass != null) {
|
||||
activityClass = mNotificationActivityClass;
|
||||
}
|
||||
Intent intent = new Intent(getBaseContext(), activityClass);
|
||||
intent.putExtra("PAGE", "graph");
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
return startLW;
|
||||
}
|
||||
|
||||
synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) {
|
||||
// Registers BroadcastReceiver to track network connection changes.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
filter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
mDeviceStateReceiver = new DeviceStateReceiver(magnagement);
|
||||
// Fetch initial network state
|
||||
mDeviceStateReceiver.networkStateChange(this);
|
||||
registerReceiver(mDeviceStateReceiver, filter);
|
||||
VpnStatus.addByteCountListener(mDeviceStateReceiver);
|
||||
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
addLollipopCMListener(); */
|
||||
}
|
||||
|
||||
synchronized void unregisterDeviceStateReceiver() {
|
||||
if (mDeviceStateReceiver != null) try {
|
||||
VpnStatus.removeByteCountListener(mDeviceStateReceiver);
|
||||
this.unregisterReceiver(mDeviceStateReceiver);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// I don't know why this happens:
|
||||
// java.lang.IllegalArgumentException: Receiver not registered: de.blinkt.openvpn.NetworkSateReceiver@41a61a10
|
||||
// Ignore for now ...
|
||||
iae.printStackTrace();
|
||||
}
|
||||
mDeviceStateReceiver = null;
|
||||
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
removeLollipopCMListener();*/
|
||||
}
|
||||
|
||||
public void userPause(boolean shouldBePaused) {
|
||||
if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(shouldBePaused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopVPN(boolean replaceConnection) throws RemoteException {
|
||||
if (getManagement() != null) return getManagement().stopVPN(replaceConnection);
|
||||
else return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false))
|
||||
mNotificationAlwaysVisible = true;
|
||||
VpnStatus.addStateListener(this);
|
||||
VpnStatus.addByteCountListener(this);
|
||||
guiHandler = new Handler(getMainLooper());
|
||||
if (intent != null && PAUSE_VPN.equals(intent.getAction())) {
|
||||
if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(true);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
if (intent != null && RESUME_VPN.equals(intent.getAction())) {
|
||||
if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(false);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
if (intent != null && START_SERVICE.equals(intent.getAction())) return START_NOT_STICKY;
|
||||
if (intent != null && START_SERVICE_STICKY.equals(intent.getAction())) {
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) {
|
||||
String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
|
||||
int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0);
|
||||
// Try for 10s to get current version of the profile
|
||||
mProfile = ProfileManager.get(this, profileUUID, profileVersion, 100);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
updateShortCutUsage(mProfile);
|
||||
}
|
||||
} else {
|
||||
/* The intent is null when we are set as always-on or the service has been restarted. */
|
||||
mProfile = ProfileManager.getLastConnectedProfile(this);
|
||||
VpnStatus.logInfo(R.string.service_restarted);
|
||||
/* Got no profile, just stop */
|
||||
if (mProfile == null) {
|
||||
Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on.");
|
||||
mProfile = ProfileManager.getAlwaysOnVPN(this);
|
||||
if (mProfile == null) {
|
||||
stopSelf(startId);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
/* Do the asynchronous keychain certificate stuff */
|
||||
mProfile.checkForRestart(this);
|
||||
}
|
||||
/* start the OpenVPN process itself in a background thread */
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startOpenVPN();
|
||||
}
|
||||
}).start();
|
||||
ProfileManager.setConnectedVpnProfile(this, mProfile);
|
||||
VpnStatus.setConnectedVPNProfile(mProfile.getUUIDString());
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
private void updateShortCutUsage(VpnProfile profile) {
|
||||
if (profile == null) return;
|
||||
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
|
||||
shortcutManager.reportShortcutUsed(profile.getUUIDString());
|
||||
}
|
||||
|
||||
private void startOpenVPN() {
|
||||
VpnStatus.logInfo(R.string.building_configration);
|
||||
VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START);
|
||||
try {
|
||||
mProfile.writeConfigFile(this);
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException("Error writing config file", e);
|
||||
endVpnService();
|
||||
return;
|
||||
}
|
||||
String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir;
|
||||
String tmpDir;
|
||||
try {
|
||||
tmpDir = getApplication().getCacheDir().getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
tmpDir = "/tmp";
|
||||
}
|
||||
// Write OpenVPN binary
|
||||
String[] argv = VPNLaunchHelper.buildOpenvpnArgv(this);
|
||||
// Set a flag that we are starting a new VPN
|
||||
mStarting = true;
|
||||
// Stop the previous session by interrupting the thread.
|
||||
stopOldOpenVPNProcess();
|
||||
// An old running VPN should now be exited
|
||||
mStarting = false;
|
||||
// Start a new session by creating a new thread.
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
|
||||
mOvpn3 = prefs.getBoolean("ovpn3", false);
|
||||
// if (!"ovpn3".equals(BuildConfig.FLAVOR)) mOvpn3 = false;
|
||||
// Open the Management Interface
|
||||
if (!mOvpn3) {
|
||||
// start a Thread that handles incoming messages of the managment socket
|
||||
OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, this);
|
||||
if (ovpnManagementThread.openManagementInterface(this)) {
|
||||
Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread");
|
||||
mSocketManagerThread.start();
|
||||
mManagement = ovpnManagementThread;
|
||||
VpnStatus.logInfo("started Socket Thread");
|
||||
} else {
|
||||
endVpnService();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Runnable processThread;
|
||||
if (mOvpn3) {
|
||||
OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core();
|
||||
processThread = (Runnable) mOpenVPN3;
|
||||
mManagement = mOpenVPN3;
|
||||
} else {
|
||||
//processThread = new OpenVPNThread(this, argv, nativeLibraryDirectory);
|
||||
processThread = new OpenVPNThread(this, argv, nativeLibraryDirectory, tmpDir);
|
||||
mOpenVPNThread = processThread;
|
||||
}
|
||||
synchronized (mProcessLock) {
|
||||
mProcessThread = new Thread(processThread, "OpenVPNProcessThread");
|
||||
mProcessThread.start();
|
||||
}
|
||||
new Handler(getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mDeviceStateReceiver != null) unregisterDeviceStateReceiver();
|
||||
registerDeviceStateReceiver(mManagement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stopOldOpenVPNProcess() {
|
||||
if (mManagement != null) {
|
||||
if (mOpenVPNThread != null) {
|
||||
((OpenVPNThread) mOpenVPNThread).setReplaceConnection();
|
||||
}
|
||||
if (mManagement.stopVPN(true)) {
|
||||
// an old was asked to exit, wait 1s
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
forceStopOpenVpnProcess();
|
||||
}
|
||||
|
||||
public void forceStopOpenVpnProcess() {
|
||||
synchronized (mProcessLock) {
|
||||
if (mProcessThread != null) {
|
||||
mProcessThread.interrupt();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OpenVPNManagement instantiateOpenVPN3Core() {
|
||||
try {
|
||||
Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3");
|
||||
return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class, VpnProfile.class).newInstance(this, mProfile);
|
||||
} catch (IllegalArgumentException | InstantiationException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
synchronized (mProcessLock) {
|
||||
if (mProcessThread != null) {
|
||||
mManagement.stopVPN(true);
|
||||
}
|
||||
}
|
||||
if (mDeviceStateReceiver != null) {
|
||||
this.unregisterReceiver(mDeviceStateReceiver);
|
||||
}
|
||||
// Just in case unregister for state
|
||||
VpnStatus.removeStateListener(this);
|
||||
VpnStatus.flushLog();
|
||||
}
|
||||
|
||||
private String getTunConfigString() {
|
||||
// The format of the string is not important, only that
|
||||
// two identical configurations produce the same result
|
||||
String cfg = "TUNCFG UNQIUE STRING ips:";
|
||||
if (mLocalIP != null) cfg += mLocalIP.toString();
|
||||
if (mLocalIPv6 != null) cfg += mLocalIPv6;
|
||||
cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true));
|
||||
cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false));
|
||||
cfg += "dns: " + TextUtils.join("|", mDnslist);
|
||||
cfg += "domain: " + mDomain;
|
||||
cfg += "mtu: " + mMtu;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public ParcelFileDescriptor openTun() {
|
||||
//Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024);
|
||||
Builder builder = new Builder();
|
||||
VpnStatus.logInfo(R.string.last_openvpn_tun_config);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mProfile.mAllowLocalLAN) {
|
||||
allowAllAFFamilies(builder);
|
||||
}
|
||||
if (mLocalIP == null && mLocalIPv6 == null) {
|
||||
VpnStatus.logError(getString(R.string.opentun_no_ipaddr));
|
||||
return null;
|
||||
}
|
||||
if (mLocalIP != null) {
|
||||
addLocalNetworksToRoutes();
|
||||
try {
|
||||
builder.addAddress(mLocalIP.mIp, mLocalIP.len);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (mLocalIPv6 != null) {
|
||||
String[] ipv6parts = mLocalIPv6.split("/");
|
||||
try {
|
||||
builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1]));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (String dns : mDnslist) {
|
||||
try {
|
||||
builder.addDnsServer(dns);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
String release = Build.VERSION.RELEASE;
|
||||
if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) && mMtu < 1280) {
|
||||
VpnStatus.logInfo(String.format(Locale.US, "Forcing MTU to 1280 instead of %d to workaround Android Bug #70916", mMtu));
|
||||
builder.setMtu(1280);
|
||||
} else {
|
||||
builder.setMtu(mMtu);
|
||||
}
|
||||
Collection<ipAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
|
||||
Collection<ipAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();
|
||||
if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) {
|
||||
// Check if the first DNS Server is in the VPN range
|
||||
try {
|
||||
ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true);
|
||||
boolean dnsIncluded = false;
|
||||
for (ipAddress net : positiveIPv4Routes) {
|
||||
if (net.containsNet(dnsServer)) {
|
||||
dnsIncluded = true;
|
||||
}
|
||||
}
|
||||
if (!dnsIncluded) {
|
||||
String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS resolution a route to your DNS Server (%s) has been added.", mDnslist.get(0));
|
||||
VpnStatus.logWarning(samsungwarning);
|
||||
positiveIPv4Routes.add(dnsServer);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0));
|
||||
}
|
||||
}
|
||||
ipAddress multicastRange = new ipAddress(new CIDRIP("224.0.0.0", 3), true);
|
||||
for (NetworkSpace.ipAddress route : positiveIPv4Routes) {
|
||||
try {
|
||||
if (multicastRange.containsNet(route))
|
||||
VpnStatus.logDebug(R.string.ignore_multicast_route, route.toString());
|
||||
else builder.addRoute(route.getIPv4Address(), route.networkMask);
|
||||
} catch (IllegalArgumentException ia) {
|
||||
VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) {
|
||||
try {
|
||||
builder.addRoute(route6.getIPv6Address(), route6.networkMask);
|
||||
} catch (IllegalArgumentException ia) {
|
||||
VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
if (mDomain != null) builder.addSearchDomain(mDomain);
|
||||
VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu);
|
||||
VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain);
|
||||
VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true)));
|
||||
VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)), TextUtils.join(", ", mRoutesv6.getNetworks(false)));
|
||||
VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setAllowedVpnPackages(builder);
|
||||
}
|
||||
String session = mProfile.mConnections[0].mServerName;
|
||||
if (mLocalIP != null && mLocalIPv6 != null)
|
||||
session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6);
|
||||
else if (mLocalIP != null)
|
||||
session = getString(R.string.session_ipv4string, session, mLocalIP);
|
||||
builder.setSession(session);
|
||||
// No DNS Server, log a warning
|
||||
if (mDnslist.size() == 0) VpnStatus.logInfo(R.string.warn_no_dns);
|
||||
mLastTunCfg = getTunConfigString();
|
||||
// Reset information
|
||||
mDnslist.clear();
|
||||
mRoutes.clear();
|
||||
mRoutesv6.clear();
|
||||
mLocalIP = null;
|
||||
mLocalIPv6 = null;
|
||||
mDomain = null;
|
||||
builder.setConfigureIntent(getGraphPendingIntent());
|
||||
try {
|
||||
//Debug.stopMethodTracing();
|
||||
ParcelFileDescriptor tun = builder.establish();
|
||||
if (tun == null)
|
||||
throw new NullPointerException("Android establish() method returned null (Really broken network configuration?)");
|
||||
return tun;
|
||||
} catch (Exception e) {
|
||||
VpnStatus.logError(R.string.tun_open_error);
|
||||
VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage());
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
VpnStatus.logError(R.string.tun_error_helpful);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void allowAllAFFamilies(Builder builder) {
|
||||
builder.allowFamily(OsConstants.AF_INET);
|
||||
builder.allowFamily(OsConstants.AF_INET6);
|
||||
}
|
||||
|
||||
private void addLocalNetworksToRoutes() {
|
||||
// Add local network interfaces
|
||||
String[] localRoutes = NativeUtils.getIfconfig();
|
||||
// The format of mLocalRoutes is kind of broken because I don't really like JNI
|
||||
for (int i = 0; i < localRoutes.length; i += 3) {
|
||||
String intf = localRoutes[i];
|
||||
String ipAddr = localRoutes[i + 1];
|
||||
String netMask = localRoutes[i + 2];
|
||||
if (intf == null || intf.equals("lo") || intf.startsWith("tun") || intf.startsWith("rmnet"))
|
||||
continue;
|
||||
if (ipAddr == null || netMask == null) {
|
||||
VpnStatus.logError("Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes));
|
||||
continue;
|
||||
}
|
||||
if (ipAddr.equals(mLocalIP.mIp)) continue;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mProfile.mAllowLocalLAN) {
|
||||
mRoutes.addIPSplit(new CIDRIP(ipAddr, netMask), true);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mProfile.mAllowLocalLAN)
|
||||
mRoutes.addIP(new CIDRIP(ipAddr, netMask), false);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void setAllowedVpnPackages(Builder builder) {
|
||||
boolean atLeastOneAllowedApp = false;
|
||||
for (String pkg : mProfile.mAllowedAppsVpn) {
|
||||
try {
|
||||
if (mProfile.mAllowedAppsVpnAreDisallowed) {
|
||||
builder.addDisallowedApplication(pkg);
|
||||
} else {
|
||||
builder.addAllowedApplication(pkg);
|
||||
atLeastOneAllowedApp = true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
mProfile.mAllowedAppsVpn.remove(pkg);
|
||||
VpnStatus.logInfo(R.string.app_no_longer_exists, pkg);
|
||||
}
|
||||
}
|
||||
if (!mProfile.mAllowedAppsVpnAreDisallowed && !atLeastOneAllowedApp) {
|
||||
VpnStatus.logDebug(R.string.no_allowed_app, getPackageName());
|
||||
try {
|
||||
builder.addAllowedApplication(getPackageName());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
VpnStatus.logError("This should not happen: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
if (mProfile.mAllowedAppsVpnAreDisallowed) {
|
||||
VpnStatus.logDebug(R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn));
|
||||
} else {
|
||||
VpnStatus.logDebug(R.string.allowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn));
|
||||
}
|
||||
}
|
||||
|
||||
public void addDNS(String dns) {
|
||||
mDnslist.add(dns);
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
if (mDomain == null) {
|
||||
mDomain = domain;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route that is always included, used by the v3 core
|
||||
*/
|
||||
public void addRoute(CIDRIP route) {
|
||||
mRoutes.addIP(route, true);
|
||||
}
|
||||
|
||||
public void addRoute(String dest, String mask, String gateway, String device) {
|
||||
CIDRIP route = new CIDRIP(dest, mask);
|
||||
boolean include = isAndroidTunDevice(device);
|
||||
NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false);
|
||||
if (mLocalIP == null) {
|
||||
VpnStatus.logError("Local IP address unset and received. Neither pushed server config nor local config specifies an IP addresses. Opening tun device is most likely going to fail.");
|
||||
return;
|
||||
}
|
||||
NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true);
|
||||
if (localNet.containsNet(gatewayIP)) include = true;
|
||||
if (gateway != null && (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW)))
|
||||
include = true;
|
||||
if (route.len == 32 && !mask.equals("255.255.255.255")) {
|
||||
VpnStatus.logWarning(R.string.route_not_cidr, dest, mask);
|
||||
}
|
||||
if (route.normalise())
|
||||
VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp);
|
||||
mRoutes.addIP(route, include);
|
||||
}
|
||||
|
||||
public void addRoutev6(String network, String device) {
|
||||
String[] v6parts = network.split("/");
|
||||
boolean included = isAndroidTunDevice(device);
|
||||
// Tun is opened after ROUTE6, no device name may be present
|
||||
try {
|
||||
Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0];
|
||||
int mask = Integer.parseInt(v6parts[1]);
|
||||
mRoutesv6.addIPv6(ip, mask, included);
|
||||
} catch (UnknownHostException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAndroidTunDevice(String device) {
|
||||
return device != null && (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device));
|
||||
}
|
||||
|
||||
public void setMtu(int mtu) {
|
||||
mMtu = mtu;
|
||||
}
|
||||
|
||||
public void setLocalIP(CIDRIP cdrip) {
|
||||
mLocalIP = cdrip;
|
||||
}
|
||||
|
||||
public void setLocalIP(String local, String netmask, int mtu, String mode) {
|
||||
mLocalIP = new CIDRIP(local, netmask);
|
||||
mMtu = mtu;
|
||||
mRemoteGW = null;
|
||||
long netMaskAsInt = CIDRIP.getInt(netmask);
|
||||
if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
|
||||
// get the netmask as IP
|
||||
int masklen;
|
||||
long mask;
|
||||
if ("net30".equals(mode)) {
|
||||
masklen = 30;
|
||||
mask = 0xfffffffc;
|
||||
} else {
|
||||
masklen = 31;
|
||||
mask = 0xfffffffe;
|
||||
}
|
||||
// Netmask is Ip address +/-1, assume net30/p2p with small net
|
||||
if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) {
|
||||
mLocalIP.len = masklen;
|
||||
} else {
|
||||
mLocalIP.len = 32;
|
||||
if (!"p2p".equals(mode))
|
||||
VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode);
|
||||
}
|
||||
}
|
||||
if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) {
|
||||
VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode);
|
||||
}
|
||||
/* Workaround for Lollipop, it does not route traffic to the VPNs own network mask */
|
||||
if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len);
|
||||
interfaceRoute.normalise();
|
||||
addRoute(interfaceRoute);
|
||||
}
|
||||
// Configurations are sometimes really broken...
|
||||
mRemoteGW = netmask;
|
||||
}
|
||||
|
||||
public void setLocalIPv6(String ipv6addr) {
|
||||
mLocalIPv6 = ipv6addr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(String state, String logmessage, int resid, ConnectionStatus level) {
|
||||
// If the process is not running, ignore any state,
|
||||
// Notification should be invisible in this state
|
||||
doSendBroadcast(state, level);
|
||||
if (mProcessThread == null && !mNotificationAlwaysVisible) return;
|
||||
String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID;
|
||||
// Display byte count only after being connected
|
||||
{
|
||||
if (level == LEVEL_WAITING_FOR_USER_INPUT) {
|
||||
// The user is presented a dialog of some kind, no need to inform the user
|
||||
// with a notifcation
|
||||
return;
|
||||
} else if (level == LEVEL_CONNECTED) {
|
||||
mDisplayBytecount = true;
|
||||
mConnecttime = System.currentTimeMillis();
|
||||
if (!runningOnAndroidTV()) channel = NOTIFICATION_CHANNEL_BG_ID;
|
||||
} else {
|
||||
mDisplayBytecount = false;
|
||||
}
|
||||
// Other notifications are shown,
|
||||
// This also mean we are no longer connected, ignore bytecount messages until next
|
||||
// CONNECTED
|
||||
// Does not work :(
|
||||
showNotification(VpnStatus.getLastCleanLogMessage(this), VpnStatus.getLastCleanLogMessage(this), channel, 0, level);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnectedVPN(String uuid) {
|
||||
}
|
||||
|
||||
private void doSendBroadcast(String state, ConnectionStatus level) {
|
||||
Intent vpnstatus = new Intent();
|
||||
vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS");
|
||||
vpnstatus.putExtra("status", level.toString());
|
||||
vpnstatus.putExtra("detailstatus", state);
|
||||
sendBroadcast(vpnstatus, permission.ACCESS_NETWORK_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByteCount(long in, long out, long diffIn, long diffOut) {
|
||||
if (mDisplayBytecount) {
|
||||
String netstat = String.format(getString(R.string.statusline_bytecount), humanReadableByteCount(in, false, getResources()), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, getResources()), humanReadableByteCount(out, false, getResources()), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources()));
|
||||
showNotification(netstat, getString(R.string.app_name), NOTIFICATION_CHANNEL_BG_ID, mConnecttime, LEVEL_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
Runnable r = msg.getCallback();
|
||||
if (r != null) {
|
||||
r.run();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public OpenVPNManagement getManagement() {
|
||||
return mManagement;
|
||||
}
|
||||
|
||||
public String getTunReopenStatus() {
|
||||
String currentConfiguration = getTunConfigString();
|
||||
if (currentConfiguration.equals(mLastTunCfg)) {
|
||||
return "NOACTION";
|
||||
} else {
|
||||
String release = Build.VERSION.RELEASE;
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6"))
|
||||
// There will be probably no 4.4.4 or 4.4.5 version, so don't waste effort to do parsing here
|
||||
return "OPEN_AFTER_CLOSE";
|
||||
else return "OPEN_BEFORE_CLOSE";
|
||||
}
|
||||
}
|
||||
|
||||
public void requestInputFromUser(int resid, String needed) {
|
||||
VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT);
|
||||
showNotification(getString(resid), getString(resid), NOTIFICATION_CHANNEL_NEWSTATUS_ID, 0, LEVEL_WAITING_FOR_USER_INPUT);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by arne on 08.11.16.
|
||||
*/
|
||||
public class OpenVPNStatusService extends Service implements VpnStatus.LogListener, VpnStatus.ByteCountListener, VpnStatus.StateListener {
|
||||
static final RemoteCallbackList<IStatusCallbacks> mCallbacks =
|
||||
new RemoteCallbackList<>();
|
||||
private static final OpenVPNStatusHandler mHandler = new OpenVPNStatusHandler();
|
||||
private static final int SEND_NEW_LOGITEM = 100;
|
||||
private static final int SEND_NEW_STATE = 101;
|
||||
private static final int SEND_NEW_BYTECOUNT = 102;
|
||||
private static final int SEND_NEW_CONNECTED_VPN = 103;
|
||||
static UpdateMessage mLastUpdateMessage;
|
||||
private static final IServiceStatus.Stub mBinder = new IServiceStatus.Stub() {
|
||||
@Override
|
||||
public ParcelFileDescriptor registerStatusCallback(IStatusCallbacks cb) throws RemoteException {
|
||||
final LogItem[] logbuffer = VpnStatus.getlogbuffer();
|
||||
if (mLastUpdateMessage != null)
|
||||
sendUpdate(cb, mLastUpdateMessage);
|
||||
mCallbacks.register(cb);
|
||||
try {
|
||||
final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
new Thread("pushLogs") {
|
||||
@Override
|
||||
public void run() {
|
||||
DataOutputStream fd = new DataOutputStream(new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]));
|
||||
try {
|
||||
synchronized (VpnStatus.readFileLock) {
|
||||
if (!VpnStatus.readFileLog) {
|
||||
VpnStatus.readFileLock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
try {
|
||||
for (LogItem logItem : logbuffer) {
|
||||
byte[] bytes = logItem.getMarschaledBytes();
|
||||
fd.writeShort(bytes.length);
|
||||
fd.write(bytes);
|
||||
}
|
||||
// Mark end
|
||||
fd.writeShort(0x7fff);
|
||||
fd.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
return pipe[0];
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
throw new RemoteException(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void unregisterStatusCallback(IStatusCallbacks cb) throws RemoteException {
|
||||
mCallbacks.unregister(cb);
|
||||
}
|
||||
@Override
|
||||
public String getLastConnectedVPN() throws RemoteException {
|
||||
return VpnStatus.getLastConnectedVPNProfile();
|
||||
}
|
||||
@Override
|
||||
public void setCachedPassword(String uuid, int type, String password) {
|
||||
PasswordCache.setCachedPassword(uuid, type, password);
|
||||
}
|
||||
@Override
|
||||
public TrafficHistory getTrafficHistory() throws RemoteException {
|
||||
return VpnStatus.trafficHistory;
|
||||
}
|
||||
};
|
||||
private static void sendUpdate(IStatusCallbacks broadcastItem,
|
||||
UpdateMessage um) throws RemoteException {
|
||||
broadcastItem.updateStateString(um.state, um.logmessage, um.resId, um.level);
|
||||
}
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
VpnStatus.addLogListener(this);
|
||||
VpnStatus.addByteCountListener(this);
|
||||
VpnStatus.addStateListener(this);
|
||||
mHandler.setService(this);
|
||||
}
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
VpnStatus.removeLogListener(this);
|
||||
VpnStatus.removeByteCountListener(this);
|
||||
VpnStatus.removeStateListener(this);
|
||||
mCallbacks.kill();
|
||||
}
|
||||
@Override
|
||||
public void newLog(LogItem logItem) {
|
||||
Message msg = mHandler.obtainMessage(SEND_NEW_LOGITEM, logItem);
|
||||
msg.sendToTarget();
|
||||
}
|
||||
@Override
|
||||
public void updateByteCount(long in, long out, long diffIn, long diffOut) {
|
||||
Message msg = mHandler.obtainMessage(SEND_NEW_BYTECOUNT, Pair.create(in, out));
|
||||
msg.sendToTarget();
|
||||
}
|
||||
@Override
|
||||
public void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level) {
|
||||
mLastUpdateMessage = new UpdateMessage(state, logmessage, localizedResId, level);
|
||||
Message msg = mHandler.obtainMessage(SEND_NEW_STATE, mLastUpdateMessage);
|
||||
msg.sendToTarget();
|
||||
}
|
||||
@Override
|
||||
public void setConnectedVPN(String uuid) {
|
||||
Message msg = mHandler.obtainMessage(SEND_NEW_CONNECTED_VPN, uuid);
|
||||
msg.sendToTarget();
|
||||
}
|
||||
static class UpdateMessage {
|
||||
public String state;
|
||||
public String logmessage;
|
||||
public ConnectionStatus level;
|
||||
int resId;
|
||||
UpdateMessage(String state, String logmessage, int resId, ConnectionStatus level) {
|
||||
this.state = state;
|
||||
this.resId = resId;
|
||||
this.logmessage = logmessage;
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
private static class OpenVPNStatusHandler extends Handler {
|
||||
WeakReference<OpenVPNStatusService> service = null;
|
||||
private void setService(OpenVPNStatusService statusService) {
|
||||
service = new WeakReference<>(statusService);
|
||||
}
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
RemoteCallbackList<IStatusCallbacks> callbacks;
|
||||
if (service == null || service.get() == null)
|
||||
return;
|
||||
callbacks = service.get().mCallbacks;
|
||||
// Broadcast to all clients the new value.
|
||||
final int N = callbacks.beginBroadcast();
|
||||
for (int i = 0; i < N; i++) {
|
||||
try {
|
||||
IStatusCallbacks broadcastItem = callbacks.getBroadcastItem(i);
|
||||
switch (msg.what) {
|
||||
case SEND_NEW_LOGITEM:
|
||||
broadcastItem.newLogItem((LogItem) msg.obj);
|
||||
break;
|
||||
case SEND_NEW_BYTECOUNT:
|
||||
Pair<Long, Long> inout = (Pair<Long, Long>) msg.obj;
|
||||
broadcastItem.updateByteCount(inout.first, inout.second);
|
||||
break;
|
||||
case SEND_NEW_STATE:
|
||||
sendUpdate(broadcastItem, (UpdateMessage) msg.obj);
|
||||
break;
|
||||
case SEND_NEW_CONNECTED_VPN:
|
||||
broadcastItem.connectedVPN((String) msg.obj);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// The RemoteCallbackList will take care of removing
|
||||
// the dead object for us.
|
||||
}
|
||||
}
|
||||
callbacks.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
public class OpenVPNThread implements Runnable {
|
||||
private static final String DUMP_PATH_STRING = "Dump path: ";
|
||||
@SuppressLint("SdCardPath")
|
||||
private static final String TAG = "OpenVPN";
|
||||
// 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com'
|
||||
private static final Pattern LOG_PATTERN = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)");
|
||||
public static final int M_FATAL = (1 << 4);
|
||||
public static final int M_NONFATAL = (1 << 5);
|
||||
public static final int M_WARN = (1 << 6);
|
||||
public static final int M_DEBUG = (1 << 7);
|
||||
private String[] mArgv;
|
||||
private Process mProcess;
|
||||
private String mNativeDir;
|
||||
private String mTmpDir;
|
||||
private OpenVPNService mService;
|
||||
private String mDumpPath;
|
||||
private boolean mNoProcessExitStatus = false;
|
||||
|
||||
public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir, String tmpdir) {
|
||||
mArgv = argv;
|
||||
mNativeDir = nativelibdir;
|
||||
mTmpDir = tmpdir;
|
||||
mService = service;
|
||||
}
|
||||
|
||||
public void stopProcess() {
|
||||
mProcess.destroy();
|
||||
}
|
||||
|
||||
void setReplaceConnection()
|
||||
{
|
||||
mNoProcessExitStatus=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Log.i(TAG, "Starting openvpn");
|
||||
startOpenVPNThreadArgs(mArgv);
|
||||
Log.i(TAG, "OpenVPN process exited");
|
||||
} catch (Exception e) {
|
||||
VpnStatus.logException("Starting OpenVPN Thread", e);
|
||||
Log.e(TAG, "OpenVPNThread Got " + e.toString());
|
||||
} finally {
|
||||
int exitvalue = 0;
|
||||
try {
|
||||
if (mProcess != null)
|
||||
exitvalue = mProcess.waitFor();
|
||||
} catch (IllegalThreadStateException ite) {
|
||||
VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage());
|
||||
} catch (InterruptedException ie) {
|
||||
VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage());
|
||||
}
|
||||
if (exitvalue != 0) {
|
||||
VpnStatus.logError("Process exited with exit value " + exitvalue);
|
||||
}
|
||||
|
||||
if (!mNoProcessExitStatus)
|
||||
VpnStatus.updateStateString("NOPROCESS", mService.getApplicationContext().getString(R.string.state_noprocess), R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
|
||||
|
||||
if (mDumpPath != null) {
|
||||
try {
|
||||
BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log"));
|
||||
SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.GERMAN);
|
||||
for (LogItem li : VpnStatus.getlogbuffer()) {
|
||||
String time = timeformat.format(new Date(li.getLogtime()));
|
||||
logout.write(time + " " + li.getString(mService) + "\n");
|
||||
}
|
||||
logout.close();
|
||||
VpnStatus.logError(R.string.minidump_generated);
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!mNoProcessExitStatus)
|
||||
mService.openvpnStopped();
|
||||
Log.i(TAG, "Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
private void startOpenVPNThreadArgs(String[] argv) {
|
||||
LinkedList<String> argvlist = new LinkedList<String>();
|
||||
|
||||
Collections.addAll(argvlist, argv);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(argvlist);
|
||||
// Hack O rama
|
||||
|
||||
String lbpath = genLibraryPath(argv, pb);
|
||||
|
||||
pb.environment().put("LD_LIBRARY_PATH", lbpath);
|
||||
pb.environment().put("TMPDIR", mTmpDir);
|
||||
|
||||
pb.redirectErrorStream(true);
|
||||
try {
|
||||
mProcess = pb.start();
|
||||
// Close the output, since we don't need it
|
||||
mProcess.getOutputStream().close();
|
||||
InputStream in = mProcess.getInputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
while (true) {
|
||||
String logline = br.readLine();
|
||||
if (logline == null)
|
||||
return;
|
||||
|
||||
if (logline.startsWith(DUMP_PATH_STRING))
|
||||
mDumpPath = logline.substring(DUMP_PATH_STRING.length());
|
||||
|
||||
Matcher m = LOG_PATTERN.matcher(logline);
|
||||
if (m.matches()) {
|
||||
int flags = Integer.parseInt(m.group(3), 16);
|
||||
String msg = m.group(4);
|
||||
int logLevel = flags & 0x0F;
|
||||
|
||||
VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO;
|
||||
|
||||
if ((flags & M_FATAL) != 0)
|
||||
logStatus = VpnStatus.LogLevel.ERROR;
|
||||
else if ((flags & M_NONFATAL) != 0)
|
||||
logStatus = VpnStatus.LogLevel.WARNING;
|
||||
else if ((flags & M_WARN) != 0)
|
||||
logStatus = VpnStatus.LogLevel.WARNING;
|
||||
else if ((flags & M_DEBUG) != 0)
|
||||
logStatus = VpnStatus.LogLevel.VERBOSE;
|
||||
|
||||
if (msg.startsWith("MANAGEMENT: CMD"))
|
||||
logLevel = Math.max(4, logLevel);
|
||||
|
||||
VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg);
|
||||
VpnStatus.addExtraHints(msg);
|
||||
} else {
|
||||
VpnStatus.logInfo("P:" + logline);
|
||||
}
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException("OpenVpn process was killed form java code");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
VpnStatus.logException("Error reading from output of OpenVPN process", e);
|
||||
stopProcess();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private String genLibraryPath(String[] argv, ProcessBuilder pb) {
|
||||
// Hack until I find a good way to get the real library path
|
||||
String applibpath = argv[0].replaceFirst("/cache/.*$", "/lib");
|
||||
|
||||
String lbpath = pb.environment().get("LD_LIBRARY_PATH");
|
||||
if (lbpath == null)
|
||||
lbpath = applibpath;
|
||||
else
|
||||
lbpath = applibpath + ":" + lbpath;
|
||||
|
||||
if (!applibpath.equals(mNativeDir)) {
|
||||
lbpath = mNativeDir + ":" + lbpath;
|
||||
}
|
||||
return lbpath;
|
||||
}
|
||||
}
|
|
@ -1,553 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.LocalServerSocket;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.liskovsoft.openvpn.BuildConfig;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
|
||||
public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
|
||||
private static final String TAG = "openvpn";
|
||||
private static final Vector<OpenVpnManagementThread> active = new Vector<>();
|
||||
private final Handler mResumeHandler;
|
||||
private LocalSocket mSocket;
|
||||
private VpnProfile mProfile;
|
||||
private OpenVPNService mOpenVPNService;
|
||||
private LinkedList<FileDescriptor> mFDList = new LinkedList<>();
|
||||
private LocalServerSocket mServerSocket;
|
||||
private boolean mWaitingForRelease = false;
|
||||
private long mLastHoldRelease = 0;
|
||||
private pauseReason lastPauseReason = pauseReason.noNetwork;
|
||||
private PausedStateCallback mPauseCallback;
|
||||
private boolean mShuttingDown;
|
||||
private Runnable mResumeHoldRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (shouldBeRunning()) {
|
||||
releaseHoldCmd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) {
|
||||
mProfile = profile;
|
||||
mOpenVPNService = openVpnService;
|
||||
mResumeHandler = new Handler(openVpnService.getMainLooper());
|
||||
}
|
||||
|
||||
private static boolean stopOpenVPN() {
|
||||
synchronized (active) {
|
||||
boolean sendCMD = false;
|
||||
for (OpenVpnManagementThread mt : active) {
|
||||
sendCMD = mt.managmentCommand("signal SIGINT\n");
|
||||
try {
|
||||
if (mt.mSocket != null) mt.mSocket.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore close error on already closed socket
|
||||
}
|
||||
}
|
||||
return sendCMD;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean openManagementInterface(@NonNull Context c) {
|
||||
// Could take a while to open connection
|
||||
int tries = 8;
|
||||
String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket");
|
||||
// The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning
|
||||
LocalSocket mServerSocketLocal = new LocalSocket();
|
||||
while (tries > 0 && !mServerSocketLocal.isBound()) {
|
||||
try {
|
||||
mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
} catch (IOException e) {
|
||||
// wait 300 ms before retrying
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
tries--;
|
||||
}
|
||||
try {
|
||||
mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cmd command to write to management socket
|
||||
* @return true if command have been sent
|
||||
*/
|
||||
public boolean managmentCommand(String cmd) {
|
||||
try {
|
||||
if (mSocket != null && mSocket.getOutputStream() != null) {
|
||||
mSocket.getOutputStream().write(cmd.getBytes());
|
||||
mSocket.getOutputStream().flush();
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore socket stack traces
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buffer = new byte[2048];
|
||||
// mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad
|
||||
String pendingInput = "";
|
||||
synchronized (active) {
|
||||
active.add(this);
|
||||
}
|
||||
try {
|
||||
// Wait for a client to connect
|
||||
mSocket = mServerSocket.accept();
|
||||
InputStream instream = mSocket.getInputStream();
|
||||
// Close the management socket after client connected
|
||||
try {
|
||||
mServerSocket.close();
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
// Closing one of the two sockets also closes the other
|
||||
//mServerSocketLocal.close();
|
||||
while (true) {
|
||||
int numbytesread = instream.read(buffer);
|
||||
if (numbytesread == -1) return;
|
||||
FileDescriptor[] fds = null;
|
||||
try {
|
||||
fds = mSocket.getAncillaryFileDescriptors();
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException("Error reading fds from socket", e);
|
||||
}
|
||||
if (fds != null) {
|
||||
Collections.addAll(mFDList, fds);
|
||||
}
|
||||
String input = new String(buffer, 0, numbytesread, "UTF-8");
|
||||
pendingInput += input;
|
||||
pendingInput = processInput(pendingInput);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e);
|
||||
}
|
||||
synchronized (active) {
|
||||
active.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
//! Hack O Rama 2000!
|
||||
private void protectFileDescriptor(FileDescriptor fd) {
|
||||
try {
|
||||
Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$");
|
||||
int fdint = (Integer) getInt.invoke(fd);
|
||||
// You can even get more evil by parsing toString() and extract the int from that :)
|
||||
boolean result = mOpenVPNService.protect(fdint);
|
||||
if (!result) VpnStatus.logWarning("Could not protect VPN socket");
|
||||
//ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint);
|
||||
//pfd.close();
|
||||
NativeUtils.jniclose(fdint);
|
||||
return;
|
||||
} catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) {
|
||||
VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e);
|
||||
}
|
||||
Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd);
|
||||
}
|
||||
|
||||
private String processInput(String pendingInput) {
|
||||
while (pendingInput.contains("\n")) {
|
||||
String[] tokens = pendingInput.split("\\r?\\n", 2);
|
||||
processCommand(tokens[0]);
|
||||
if (tokens.length == 1)
|
||||
// No second part, newline was at the end
|
||||
pendingInput = "";
|
||||
else pendingInput = tokens[1];
|
||||
}
|
||||
return pendingInput;
|
||||
}
|
||||
|
||||
private void processCommand(String command) {
|
||||
//Log.i(TAG, "Line from managment" + command);
|
||||
if (command.startsWith(">") && command.contains(":")) {
|
||||
String[] parts = command.split(":", 2);
|
||||
String cmd = parts[0].substring(1);
|
||||
String argument = parts[1];
|
||||
switch (cmd) {
|
||||
case "INFO":
|
||||
/* Ignore greeting from management */
|
||||
return;
|
||||
case "PASSWORD":
|
||||
processPWCommand(argument);
|
||||
break;
|
||||
case "HOLD":
|
||||
handleHold(argument);
|
||||
break;
|
||||
case "NEED-OK":
|
||||
processNeedCommand(argument);
|
||||
break;
|
||||
case "BYTECOUNT":
|
||||
processByteCount(argument);
|
||||
break;
|
||||
case "STATE":
|
||||
if (!mShuttingDown) processState(argument);
|
||||
break;
|
||||
case "PROXY":
|
||||
processProxyCMD(argument);
|
||||
break;
|
||||
case "LOG":
|
||||
processLogMessage(argument);
|
||||
break;
|
||||
case "RSA_SIGN":
|
||||
processSignCommand(argument);
|
||||
break;
|
||||
default:
|
||||
VpnStatus.logWarning("MGMT: Got unrecognized command" + command);
|
||||
Log.i(TAG, "Got unrecognized command" + command);
|
||||
break;
|
||||
}
|
||||
} else if (command.startsWith("SUCCESS:")) {
|
||||
/* Ignore this kind of message too */
|
||||
return;
|
||||
} else if (command.startsWith("PROTECTFD: ")) {
|
||||
FileDescriptor fdtoprotect = mFDList.pollFirst();
|
||||
if (fdtoprotect != null) protectFileDescriptor(fdtoprotect);
|
||||
} else {
|
||||
Log.i(TAG, "Got unrecognized line from managment" + command);
|
||||
VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command);
|
||||
}
|
||||
}
|
||||
|
||||
private void processLogMessage(String argument) {
|
||||
String[] args = argument.split(",", 4);
|
||||
// 0 unix time stamp
|
||||
// 1 log level N,I,E etc.
|
||||
/*
|
||||
(b) zero or more message flags in a single string:
|
||||
I -- informational
|
||||
F -- fatal error
|
||||
N -- non-fatal error
|
||||
W -- warning
|
||||
D -- debug, and
|
||||
*/
|
||||
// 2 log message
|
||||
Log.d("OpenVPN", argument);
|
||||
VpnStatus.LogLevel level;
|
||||
switch (args[1]) {
|
||||
case "I":
|
||||
level = VpnStatus.LogLevel.INFO;
|
||||
break;
|
||||
case "W":
|
||||
level = VpnStatus.LogLevel.WARNING;
|
||||
break;
|
||||
case "D":
|
||||
level = VpnStatus.LogLevel.VERBOSE;
|
||||
break;
|
||||
case "F":
|
||||
level = VpnStatus.LogLevel.ERROR;
|
||||
break;
|
||||
default:
|
||||
level = VpnStatus.LogLevel.INFO;
|
||||
break;
|
||||
}
|
||||
int ovpnlevel = Integer.parseInt(args[2]) & 0x0F;
|
||||
String msg = args[3];
|
||||
if (msg.startsWith("MANAGEMENT: CMD")) ovpnlevel = Math.max(4, ovpnlevel);
|
||||
VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg);
|
||||
}
|
||||
|
||||
boolean shouldBeRunning() {
|
||||
if (mPauseCallback == null) return false;
|
||||
else return mPauseCallback.shouldBeRunning();
|
||||
}
|
||||
|
||||
private void handleHold(String argument) {
|
||||
mWaitingForRelease = true;
|
||||
int waittime = Integer.parseInt(argument.split(":")[1]);
|
||||
if (shouldBeRunning()) {
|
||||
if (waittime > 1) VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
|
||||
mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000);
|
||||
if (waittime > 5) VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
|
||||
else VpnStatus.logDebug(R.string.state_waitconnectretry, String.valueOf(waittime));
|
||||
} else {
|
||||
VpnStatus.updateStatePause(lastPauseReason);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseHoldCmd() {
|
||||
mResumeHandler.removeCallbacks(mResumeHoldRunnable);
|
||||
if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) {
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
mWaitingForRelease = false;
|
||||
mLastHoldRelease = System.currentTimeMillis();
|
||||
managmentCommand("hold release\n");
|
||||
managmentCommand("bytecount " + mBytecountInterval + "\n");
|
||||
managmentCommand("state on\n");
|
||||
//managmentCommand("log on all\n");
|
||||
}
|
||||
|
||||
public void releaseHold() {
|
||||
if (mWaitingForRelease) releaseHoldCmd();
|
||||
}
|
||||
|
||||
private void processProxyCMD(String argument) {
|
||||
String[] args = argument.split(",", 3);
|
||||
SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
|
||||
if (args.length >= 2) {
|
||||
String proto = args[1];
|
||||
if (proto.equals("UDP")) {
|
||||
proxyaddr = null;
|
||||
}
|
||||
}
|
||||
if (proxyaddr instanceof InetSocketAddress) {
|
||||
InetSocketAddress isa = (InetSocketAddress) proxyaddr;
|
||||
VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort());
|
||||
String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort());
|
||||
managmentCommand(proxycmd);
|
||||
} else {
|
||||
managmentCommand("proxy NONE\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void processState(String argument) {
|
||||
String[] args = argument.split(",", 3);
|
||||
String currentstate = args[1];
|
||||
if (args[2].equals(",,")) VpnStatus.updateStateString(currentstate, "");
|
||||
else VpnStatus.updateStateString(currentstate, args[2]);
|
||||
}
|
||||
|
||||
private void processByteCount(String argument) {
|
||||
// >BYTECOUNT:{BYTES_IN},{BYTES_OUT}
|
||||
int comma = argument.indexOf(',');
|
||||
long in = Long.parseLong(argument.substring(0, comma));
|
||||
long out = Long.parseLong(argument.substring(comma + 1));
|
||||
VpnStatus.updateByteCount(in, out);
|
||||
}
|
||||
|
||||
private void processNeedCommand(String argument) {
|
||||
int p1 = argument.indexOf('\'');
|
||||
int p2 = argument.indexOf('\'', p1 + 1);
|
||||
String needed = argument.substring(p1 + 1, p2);
|
||||
String extra = argument.split(":", 2)[1];
|
||||
String status = "ok";
|
||||
switch (needed) {
|
||||
case "PROTECTFD":
|
||||
FileDescriptor fdtoprotect = mFDList.pollFirst();
|
||||
protectFileDescriptor(fdtoprotect);
|
||||
break;
|
||||
case "DNSSERVER":
|
||||
case "DNS6SERVER":
|
||||
mOpenVPNService.addDNS(extra);
|
||||
break;
|
||||
case "DNSDOMAIN":
|
||||
mOpenVPNService.setDomain(extra);
|
||||
break;
|
||||
case "ROUTE": {
|
||||
String[] routeparts = extra.split(" ");
|
||||
/*
|
||||
buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
|
||||
else
|
||||
buf_printf (&out, "%s %s %s", network, netmask, gateway);
|
||||
*/
|
||||
if (routeparts.length == 5) {
|
||||
if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]);
|
||||
mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]);
|
||||
} else if (routeparts.length >= 3) {
|
||||
mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null);
|
||||
} else {
|
||||
VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ROUTE6": {
|
||||
String[] routeparts = extra.split(" ");
|
||||
mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]);
|
||||
break;
|
||||
}
|
||||
case "IFCONFIG":
|
||||
String[] ifconfigparts = extra.split(" ");
|
||||
int mtu = Integer.parseInt(ifconfigparts[2]);
|
||||
mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]);
|
||||
break;
|
||||
case "IFCONFIG6":
|
||||
mOpenVPNService.setLocalIPv6(extra);
|
||||
break;
|
||||
case "PERSIST_TUN_ACTION":
|
||||
// check if tun cfg stayed the same
|
||||
status = mOpenVPNService.getTunReopenStatus();
|
||||
break;
|
||||
case "OPENTUN":
|
||||
if (sendTunFD(needed, extra)) return;
|
||||
else status = "cancel";
|
||||
// This not nice or anything but setFileDescriptors accepts only FilDescriptor class :(
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown needok command " + argument);
|
||||
return;
|
||||
}
|
||||
String cmd = String.format("needok '%s' %s\n", needed, status);
|
||||
managmentCommand(cmd);
|
||||
}
|
||||
|
||||
private boolean sendTunFD(String needed, String extra) {
|
||||
if (!extra.equals("tun")) {
|
||||
// We only support tun
|
||||
VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
|
||||
return false;
|
||||
}
|
||||
ParcelFileDescriptor pfd = mOpenVPNService.openTun();
|
||||
if (pfd == null) return false;
|
||||
Method setInt;
|
||||
int fdint = pfd.getFd();
|
||||
try {
|
||||
setInt = FileDescriptor.class.getDeclaredMethod("setInt$", int.class);
|
||||
FileDescriptor fdtosend = new FileDescriptor();
|
||||
setInt.invoke(fdtosend, fdint);
|
||||
FileDescriptor[] fds = {fdtosend};
|
||||
mSocket.setFileDescriptorsForSend(fds);
|
||||
// Trigger a send so we can close the fd on our side of the channel
|
||||
// The API documentation fails to mention that it will not reset the file descriptor to
|
||||
// be send and will happily send the file descriptor on every write ...
|
||||
String cmd = String.format("needok '%s' %s\n", needed, "ok");
|
||||
managmentCommand(cmd);
|
||||
// Set the FileDescriptor to null to stop this mad behavior
|
||||
mSocket.setFileDescriptorsForSend(null);
|
||||
pfd.close();
|
||||
return true;
|
||||
} catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IOException | IllegalAccessException exp) {
|
||||
VpnStatus.logException("Could not send fd over socket", exp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processPWCommand(String argument) {
|
||||
//argument has the form Need 'Private Key' password
|
||||
// or ">PASSWORD:Verification Failed: '%s' ['%s']"
|
||||
String needed;
|
||||
try {
|
||||
int p1 = argument.indexOf('\'');
|
||||
int p2 = argument.indexOf('\'', p1 + 1);
|
||||
needed = argument.substring(p1 + 1, p2);
|
||||
if (argument.startsWith("Verification Failed")) {
|
||||
proccessPWFailed(needed, argument.substring(p2 + 1));
|
||||
return;
|
||||
}
|
||||
} catch (StringIndexOutOfBoundsException sioob) {
|
||||
VpnStatus.logError("Could not parse management Password command: " + argument);
|
||||
return;
|
||||
}
|
||||
String pw = null;
|
||||
if (needed.equals("Private Key")) {
|
||||
pw = mProfile.getPasswordPrivateKey();
|
||||
} else if (needed.equals("Auth")) {
|
||||
pw = mProfile.getPasswordAuth();
|
||||
String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername));
|
||||
managmentCommand(usercmd);
|
||||
}
|
||||
if (pw != null) {
|
||||
String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw));
|
||||
managmentCommand(cmd);
|
||||
} else {
|
||||
mOpenVPNService.requestInputFromUser(R.string.password, needed);
|
||||
VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed));
|
||||
}
|
||||
}
|
||||
|
||||
private void proccessPWFailed(String needed, String args) {
|
||||
VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void networkChange(boolean samenetwork) {
|
||||
if (mWaitingForRelease) releaseHold();
|
||||
else if (samenetwork) managmentCommand("network-change\n");
|
||||
else managmentCommand("network-change\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPauseCallback(PausedStateCallback callback) {
|
||||
mPauseCallback = callback;
|
||||
}
|
||||
|
||||
public void signalusr1() {
|
||||
mResumeHandler.removeCallbacks(mResumeHoldRunnable);
|
||||
if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n");
|
||||
else
|
||||
// If signalusr1 is called update the state string
|
||||
// if there is another for stopping
|
||||
VpnStatus.updateStatePause(lastPauseReason);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
signalusr1();
|
||||
releaseHold();
|
||||
}
|
||||
|
||||
private void processSignCommand(String b64data) {
|
||||
String signed_string = mProfile.getSignedData(b64data);
|
||||
if (signed_string == null) {
|
||||
managmentCommand("rsa-sig\n");
|
||||
managmentCommand("\nEND\n");
|
||||
stopOpenVPN();
|
||||
return;
|
||||
}
|
||||
managmentCommand("rsa-sig\n");
|
||||
managmentCommand(signed_string);
|
||||
managmentCommand("\nEND\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause(pauseReason reason) {
|
||||
lastPauseReason = reason;
|
||||
signalusr1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
releaseHold();
|
||||
/* Reset the reason why we are disconnected */
|
||||
lastPauseReason = pauseReason.noNetwork;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopVPN(boolean replaceConnection) {
|
||||
boolean stopSucceed = stopOpenVPN();
|
||||
if (stopSucceed) {
|
||||
mShuttingDown = true;
|
||||
}
|
||||
return stopSucceed;
|
||||
}
|
||||
}
|
|
@ -1,299 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;/*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will Google be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, as long as the origin is not misrepresented.
|
||||
*/
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* Fixes for the output of the default PRNG having low entropy.
|
||||
* <p>
|
||||
* The fixes need to be applied via {@link #apply()} before any use of Java
|
||||
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||
* application's {@code onCreate}.
|
||||
*/
|
||||
public final class PRNGFixes {
|
||||
private static final int VERSION_CODE_JELLY_BEAN = 16;
|
||||
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
||||
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
|
||||
|
||||
/**
|
||||
* Hidden constructor to prevent instantiation.
|
||||
*/
|
||||
private PRNGFixes() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all fixes.
|
||||
*
|
||||
* @throws SecurityException if a fix is needed but could not be applied.
|
||||
*/
|
||||
public static void apply() {
|
||||
applyOpenSSLFix();
|
||||
installLinuxPRNGSecureRandom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||
* fix is not needed.
|
||||
*
|
||||
* @throws SecurityException if the fix is needed but could not be applied.
|
||||
*/
|
||||
private static void applyOpenSSLFix() throws SecurityException {
|
||||
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
|
||||
// No need to apply the fix
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
|
||||
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||
int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto").getMethod("RAND_load_file", String.class, long.class).invoke(null, "/dev/urandom", 1024);
|
||||
if (bytesRead != 1024) {
|
||||
throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
||||
* default. Does nothing if the implementation is already the default or if
|
||||
* there is not need to install the implementation.
|
||||
*
|
||||
* @throws SecurityException if the fix is needed but could not be applied.
|
||||
*/
|
||||
private static void installLinuxPRNGSecureRandom() throws SecurityException {
|
||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||
// No need to apply the fix
|
||||
return;
|
||||
}
|
||||
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||
// default, if not yet installed.
|
||||
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
|
||||
if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) || (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) {
|
||||
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
||||
}
|
||||
// Assert that new SecureRandom() and
|
||||
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||
// by the Linux PRNG-based SecureRandom implementation.
|
||||
SecureRandom rng1 = new SecureRandom();
|
||||
if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
|
||||
throw new SecurityException("new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
|
||||
}
|
||||
SecureRandom rng2;
|
||||
try {
|
||||
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SecurityException("SHA1PRNG not available", e);
|
||||
}
|
||||
if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider().getClass())) {
|
||||
throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a device- and invocation-specific seed to be mixed into the
|
||||
* Linux PRNG.
|
||||
*/
|
||||
private static byte[] generateSeed() {
|
||||
try {
|
||||
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
||||
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
|
||||
seedBufferOut.writeLong(System.currentTimeMillis());
|
||||
seedBufferOut.writeLong(System.nanoTime());
|
||||
seedBufferOut.writeInt(Process.myPid());
|
||||
seedBufferOut.writeInt(Process.myUid());
|
||||
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
||||
seedBufferOut.close();
|
||||
return seedBuffer.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to generate seed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hardware serial number of this device.
|
||||
*
|
||||
* @return serial number or {@code null} if not available.
|
||||
*/
|
||||
private static String getDeviceSerialNumber() {
|
||||
// We're using the Reflection API because Build.SERIAL is only available
|
||||
// since API Level 9 (Gingerbread, Android 2.3).
|
||||
try {
|
||||
return (String) Build.class.getField("SERIAL").get(null);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String fingerprint = Build.FINGERPRINT;
|
||||
if (fingerprint != null) {
|
||||
result.append(fingerprint);
|
||||
}
|
||||
String serial = getDeviceSerialNumber();
|
||||
if (serial != null) {
|
||||
result.append(serial);
|
||||
}
|
||||
try {
|
||||
return result.toString().getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("UTF-8 encoding not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Provider} of {@code SecureRandom} engines which pass through
|
||||
* all requests to the Linux PRNG.
|
||||
*/
|
||||
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
||||
public LinuxPRNGSecureRandomProvider() {
|
||||
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom");
|
||||
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||
// prevent them from getting the default implementation whose output
|
||||
// may have low entropy.
|
||||
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
|
||||
* ({@code /dev/urandom}).
|
||||
*/
|
||||
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
||||
/*
|
||||
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||
* this class seed themselves by mixing in the current time, PID, UID,
|
||||
* build fingerprint, and hardware serial number (where available) into
|
||||
* Linux PRNG.
|
||||
*
|
||||
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||
* serialized (on sLock) to ensure that multiple threads do not get
|
||||
* duplicated PRNG output.
|
||||
*/
|
||||
private static final File URANDOM_FILE = new File("/dev/urandom");
|
||||
private static final Object sLock = new Object();
|
||||
/**
|
||||
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
||||
* opened.
|
||||
*
|
||||
* @GuardedBy("sLock")
|
||||
*/
|
||||
private static DataInputStream sUrandomIn;
|
||||
/**
|
||||
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
||||
* opened.
|
||||
*
|
||||
* @GuardedBy("sLock")
|
||||
*/
|
||||
private static OutputStream sUrandomOut;
|
||||
/**
|
||||
* Whether this engine instance has been seeded. This is needed because
|
||||
* each instance needs to seed itself if the client does not explicitly
|
||||
* seed it.
|
||||
*/
|
||||
private boolean mSeeded;
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] bytes) {
|
||||
try {
|
||||
OutputStream out;
|
||||
synchronized (sLock) {
|
||||
out = getUrandomOutputStream();
|
||||
}
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
// On a small fraction of devices /dev/urandom is not writable.
|
||||
// Log and ignore.
|
||||
Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE);
|
||||
} finally {
|
||||
mSeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] bytes) {
|
||||
if (!mSeeded) {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
engineSetSeed(generateSeed());
|
||||
}
|
||||
try {
|
||||
DataInputStream in;
|
||||
synchronized (sLock) {
|
||||
in = getUrandomInputStream();
|
||||
}
|
||||
synchronized (in) {
|
||||
in.readFully(bytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int size) {
|
||||
byte[] seed = new byte[size];
|
||||
engineNextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
|
||||
private DataInputStream getUrandomInputStream() {
|
||||
synchronized (sLock) {
|
||||
if (sUrandomIn == null) {
|
||||
// NOTE: Consider inserting a BufferedInputStream between
|
||||
// DataInputStream and FileInputStream if you need higher
|
||||
// PRNG output performance and can live with future PRNG
|
||||
// output being pulled into this process prematurely.
|
||||
try {
|
||||
sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to open " + URANDOM_FILE + " for reading", e);
|
||||
}
|
||||
}
|
||||
return sUrandomIn;
|
||||
}
|
||||
}
|
||||
|
||||
private OutputStream getUrandomOutputStream() throws IOException {
|
||||
synchronized (sLock) {
|
||||
if (sUrandomOut == null) {
|
||||
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
||||
}
|
||||
return sUrandomOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by arne on 15.12.16.
|
||||
*/
|
||||
public class PasswordCache {
|
||||
public static final int PCKS12ORCERTPASSWORD = 2;
|
||||
public static final int AUTHPASSWORD = 3;
|
||||
private static PasswordCache mInstance;
|
||||
final private UUID mUuid;
|
||||
private String mKeyOrPkcs12Password;
|
||||
private String mAuthPassword;
|
||||
private PasswordCache(UUID uuid) {
|
||||
mUuid = uuid;
|
||||
}
|
||||
public static PasswordCache getInstance(UUID uuid) {
|
||||
if (mInstance == null || !mInstance.mUuid.equals(uuid)) {
|
||||
mInstance = new PasswordCache(uuid);
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
public static String getPKCS12orCertificatePassword(UUID uuid, boolean resetPw) {
|
||||
String pwcopy = getInstance(uuid).mKeyOrPkcs12Password;
|
||||
if (resetPw)
|
||||
getInstance(uuid).mKeyOrPkcs12Password = null;
|
||||
return pwcopy;
|
||||
}
|
||||
public static String getAuthPassword(UUID uuid, boolean resetPW) {
|
||||
String pwcopy = getInstance(uuid).mAuthPassword;
|
||||
if (resetPW)
|
||||
getInstance(uuid).mAuthPassword = null;
|
||||
return pwcopy;
|
||||
}
|
||||
public static void setCachedPassword(String uuid, int type, String password) {
|
||||
PasswordCache instance = getInstance(UUID.fromString(uuid));
|
||||
switch (type) {
|
||||
case PCKS12ORCERTPASSWORD:
|
||||
instance.mKeyOrPkcs12Password = password;
|
||||
break;
|
||||
case AUTHPASSWORD:
|
||||
instance.mAuthPassword = password;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
/**
|
||||
* Created by arne on 08.01.17.
|
||||
*/
|
||||
// Until I find a good solution
|
||||
public class Preferences {
|
||||
static SharedPreferences getSharedPreferencesMulti(String name, Context c) {
|
||||
return c.getSharedPreferences(name, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
|
||||
}
|
||||
public static SharedPreferences getDefaultSharedPreferences(Context c) {
|
||||
return c.getSharedPreferences(c.getPackageName() + "_preferences", Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
|
||||
public class ProfileManager {
|
||||
private static final String PREFS_NAME = "VPNList";
|
||||
private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile";
|
||||
private static final String TEMPORARY_PROFILE_FILENAME = "temporary-vpn-profile";
|
||||
private static ProfileManager instance;
|
||||
private static VpnProfile mLastConnectedVpn = null;
|
||||
private static VpnProfile tmpprofile = null;
|
||||
private HashMap<String, VpnProfile> profiles = new HashMap<>();
|
||||
|
||||
private ProfileManager() {
|
||||
}
|
||||
|
||||
private static VpnProfile get(String key) {
|
||||
if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) return tmpprofile;
|
||||
if (instance == null) return null;
|
||||
return instance.profiles.get(key);
|
||||
}
|
||||
|
||||
private static void checkInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new ProfileManager();
|
||||
instance.loadVPNList(context);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public static ProfileManager getInstance(Context context) {
|
||||
checkInstance(context);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void setConnectedVpnProfileDisconnected(Context c) {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
|
||||
Editor prefsedit = prefs.edit();
|
||||
prefsedit.putString(LAST_CONNECTED_PROFILE, null);
|
||||
prefsedit.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the profile that is connected (to connect if the service restarts)
|
||||
*/
|
||||
public static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
|
||||
Editor prefsedit = prefs.edit();
|
||||
prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString());
|
||||
prefsedit.apply();
|
||||
mLastConnectedVpn = connectedProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the profile that was last connected (to connect if the service restarts)
|
||||
*/
|
||||
public static VpnProfile getLastConnectedProfile(Context c) {
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
|
||||
String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null);
|
||||
if (lastConnectedProfile != null) return get(c, lastConnectedProfile);
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static void setTemporaryProfile(Context c, VpnProfile tmp) {
|
||||
ProfileManager.tmpprofile = tmp;
|
||||
saveProfile(c, tmp, true, true);
|
||||
}
|
||||
|
||||
public static boolean isTempProfile() {
|
||||
return mLastConnectedVpn != null && mLastConnectedVpn == tmpprofile;
|
||||
}
|
||||
|
||||
private static void saveProfile(Context context, VpnProfile profile, boolean updateVersion, boolean isTemporary) {
|
||||
if (updateVersion) profile.mVersion += 1;
|
||||
ObjectOutputStream vpnFile;
|
||||
String filename = profile.getUUID().toString() + ".vp";
|
||||
if (isTemporary) filename = TEMPORARY_PROFILE_FILENAME + ".vp";
|
||||
try {
|
||||
vpnFile = new ObjectOutputStream(context.openFileOutput(filename, Activity.MODE_PRIVATE));
|
||||
vpnFile.writeObject(profile);
|
||||
vpnFile.flush();
|
||||
vpnFile.close();
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException("saving VPN profile", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static VpnProfile get(Context context, String profileUUID) {
|
||||
return get(context, profileUUID, 0, 10);
|
||||
}
|
||||
|
||||
public static VpnProfile get(Context context, String profileUUID, int version, int tries) {
|
||||
checkInstance(context);
|
||||
VpnProfile profile = get(profileUUID);
|
||||
int tried = 0;
|
||||
while ((profile == null || profile.mVersion < version) && (tried++ < tries)) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
instance.loadVPNList(context);
|
||||
profile = get(profileUUID);
|
||||
int ver = profile == null ? -1 : profile.mVersion;
|
||||
}
|
||||
if (tried > 5) {
|
||||
int ver = profile == null ? -1 : profile.mVersion;
|
||||
VpnStatus.logError(String.format(Locale.US, "Used x %d tries to get current version (%d/%d) of the profile", tried, ver, version));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static VpnProfile getLastConnectedVpn() {
|
||||
return mLastConnectedVpn;
|
||||
}
|
||||
|
||||
public static VpnProfile getAlwaysOnVPN(Context context) {
|
||||
checkInstance(context);
|
||||
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
|
||||
String uuid = prefs.getString("alwaysOnVpn", null);
|
||||
return get(uuid);
|
||||
}
|
||||
|
||||
public static void updateLRU(Context c, VpnProfile profile) {
|
||||
profile.mLastUsed = System.currentTimeMillis();
|
||||
// LRU does not change the profile, no need for the service to refresh
|
||||
if (profile != tmpprofile) saveProfile(c, profile, false, false);
|
||||
}
|
||||
|
||||
public Collection<VpnProfile> getProfiles() {
|
||||
return profiles.values();
|
||||
}
|
||||
|
||||
public VpnProfile getProfileByName(String name) {
|
||||
for (VpnProfile vpnp : profiles.values()) {
|
||||
if (vpnp.getName().equals(name)) {
|
||||
return vpnp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void saveProfileList(Context context) {
|
||||
SharedPreferences sharedprefs = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
|
||||
Editor editor = sharedprefs.edit();
|
||||
editor.putStringSet("vpnlist", profiles.keySet());
|
||||
// For reasing I do not understand at all
|
||||
// Android saves my prefs file only one time
|
||||
// if I remove the debug code below :(
|
||||
int counter = sharedprefs.getInt("counter", 0);
|
||||
editor.putInt("counter", counter + 1);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public void addProfile(VpnProfile profile) {
|
||||
profiles.put(profile.getUUID().toString(), profile);
|
||||
}
|
||||
|
||||
public void saveProfile(Context context, VpnProfile profile) {
|
||||
saveProfile(context, profile, true, false);
|
||||
}
|
||||
|
||||
private void loadVPNList(Context context) {
|
||||
profiles = new HashMap<>();
|
||||
SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
|
||||
Set<String> vlist = listpref.getStringSet("vpnlist", null);
|
||||
if (vlist == null) {
|
||||
vlist = new HashSet<>();
|
||||
}
|
||||
// Always try to load the temporary profile
|
||||
vlist.add(TEMPORARY_PROFILE_FILENAME);
|
||||
for (String vpnentry : vlist) {
|
||||
try {
|
||||
ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp"));
|
||||
VpnProfile vp = ((VpnProfile) vpnfile.readObject());
|
||||
// Sanity check
|
||||
if (vp == null || vp.mName == null || vp.getUUID() == null) continue;
|
||||
vp.upgradeProfile();
|
||||
if (vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) {
|
||||
tmpprofile = vp;
|
||||
} else {
|
||||
profiles.put(vp.getUUID().toString(), vp);
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
if (!vpnentry.equals(TEMPORARY_PROFILE_FILENAME)) VpnStatus.logException("Loading VPN List", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeProfile(Context context, VpnProfile profile) {
|
||||
String vpnentry = profile.getUUID().toString();
|
||||
profiles.remove(vpnentry);
|
||||
saveProfileList(context);
|
||||
context.deleteFile(vpnentry + ".vp");
|
||||
if (mLastConnectedVpn == profile) mLastConnectedVpn = null;
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
|
||||
public class ProxyDetection {
|
||||
static SocketAddress detectProxy(VpnProfile vp) {
|
||||
// Construct a new url with https as protocol
|
||||
try {
|
||||
URL url = new URL(String.format("https://%s:%s", vp.mServerName, vp.mServerPort));
|
||||
Proxy proxy = getFirstProxy(url);
|
||||
if (proxy == null)
|
||||
return null;
|
||||
SocketAddress addr = proxy.address();
|
||||
if (addr instanceof InetSocketAddress) {
|
||||
return addr;
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
|
||||
} catch (URISyntaxException e) {
|
||||
VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static Proxy getFirstProxy(URL url) throws URISyntaxException {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
List<Proxy> proxylist = ProxySelector.getDefault().select(url.toURI());
|
||||
if (proxylist != null) {
|
||||
for (Proxy proxy : proxylist) {
|
||||
SocketAddress addr = proxy.address();
|
||||
if (addr != null) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by arne on 09.11.16.
|
||||
*/
|
||||
public class StatusListener {
|
||||
private File mCacheDir;
|
||||
private IStatusCallbacks mCallback = new IStatusCallbacks.Stub() {
|
||||
@Override
|
||||
public void newLogItem(LogItem item) throws RemoteException {
|
||||
VpnStatus.newLogItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStateString(String state, String msg, int resid, ConnectionStatus level) throws RemoteException {
|
||||
VpnStatus.updateStateString(state, msg, resid, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByteCount(long inBytes, long outBytes) throws RemoteException {
|
||||
VpnStatus.updateByteCount(inBytes, outBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectedVPN(String uuid) throws RemoteException {
|
||||
VpnStatus.setConnectedVPNProfile(uuid);
|
||||
}
|
||||
};
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
IServiceStatus serviceStatus = IServiceStatus.Stub.asInterface(service);
|
||||
try {
|
||||
/* Check if this a local service ... */
|
||||
if (service.queryLocalInterface("de.blinkt.openvpn.core.IServiceStatus") == null) {
|
||||
// Not a local service
|
||||
VpnStatus.setConnectedVPNProfile(serviceStatus.getLastConnectedVPN());
|
||||
VpnStatus.setTrafficHistory(serviceStatus.getTrafficHistory());
|
||||
ParcelFileDescriptor pfd = serviceStatus.registerStatusCallback(mCallback);
|
||||
DataInputStream fd = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd));
|
||||
short len = fd.readShort();
|
||||
byte[] buf = new byte[65336];
|
||||
while (len != 0x7fff) {
|
||||
fd.readFully(buf, 0, len);
|
||||
LogItem logitem = new LogItem(buf, len);
|
||||
VpnStatus.newLogItem(logitem, false);
|
||||
len = fd.readShort();
|
||||
}
|
||||
fd.close();
|
||||
} else {
|
||||
VpnStatus.initLogCache(mCacheDir);
|
||||
}
|
||||
} catch (RemoteException | IOException e) {
|
||||
e.printStackTrace();
|
||||
VpnStatus.logException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName arg0) {
|
||||
}
|
||||
};
|
||||
|
||||
void init(Context c) {
|
||||
Intent intent = new Intent(c, OpenVPNStatusService.class);
|
||||
intent.setAction(OpenVPNService.START_SERVICE);
|
||||
mCacheDir = c.getCacheDir();
|
||||
c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2017 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Vector;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
/**
|
||||
* Created by arne on 23.05.17.
|
||||
*/
|
||||
public class TrafficHistory implements Parcelable {
|
||||
public static final long PERIODS_TO_KEEP = 5;
|
||||
public static final int TIME_PERIOD_MINTUES = 60 * 1000;
|
||||
public static final int TIME_PERIOD_HOURS = 3600 * 1000;
|
||||
public static final Creator<TrafficHistory> CREATOR = new Creator<TrafficHistory>() {
|
||||
@Override
|
||||
public TrafficHistory createFromParcel(Parcel in) {
|
||||
return new TrafficHistory(in);
|
||||
}
|
||||
@Override
|
||||
public TrafficHistory[] newArray(int size) {
|
||||
return new TrafficHistory[size];
|
||||
}
|
||||
};
|
||||
private LinkedList<TrafficDatapoint> trafficHistorySeconds = new LinkedList<>();
|
||||
private LinkedList<TrafficDatapoint> trafficHistoryMinutes = new LinkedList<>();
|
||||
private LinkedList<TrafficDatapoint> trafficHistoryHours = new LinkedList<>();
|
||||
private TrafficDatapoint lastSecondUsedForMinute;
|
||||
private TrafficDatapoint lastMinuteUsedForHours;
|
||||
public TrafficHistory() {
|
||||
}
|
||||
protected TrafficHistory(Parcel in) {
|
||||
in.readList(trafficHistorySeconds, getClass().getClassLoader());
|
||||
in.readList(trafficHistoryMinutes, getClass().getClassLoader());
|
||||
in.readList(trafficHistoryHours, getClass().getClassLoader());
|
||||
lastSecondUsedForMinute = in.readParcelable(getClass().getClassLoader());
|
||||
lastMinuteUsedForHours = in.readParcelable(getClass().getClassLoader());
|
||||
}
|
||||
public static LinkedList<TrafficDatapoint> getDummyList() {
|
||||
LinkedList<TrafficDatapoint> list = new LinkedList<>();
|
||||
list.add(new TrafficDatapoint(0, 0, System.currentTimeMillis()));
|
||||
return list;
|
||||
}
|
||||
public LastDiff getLastDiff(TrafficDatapoint tdp) {
|
||||
TrafficDatapoint lasttdp;
|
||||
if (trafficHistorySeconds.size() == 0)
|
||||
lasttdp = new TrafficDatapoint(0, 0, System.currentTimeMillis());
|
||||
else
|
||||
lasttdp = trafficHistorySeconds.getLast();
|
||||
if (tdp == null) {
|
||||
tdp = lasttdp;
|
||||
if (trafficHistorySeconds.size() < 2)
|
||||
lasttdp = tdp;
|
||||
else {
|
||||
trafficHistorySeconds.descendingIterator().next();
|
||||
tdp = trafficHistorySeconds.descendingIterator().next();
|
||||
}
|
||||
}
|
||||
return new LastDiff(lasttdp, tdp);
|
||||
}
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeList(trafficHistorySeconds);
|
||||
dest.writeList(trafficHistoryMinutes);
|
||||
dest.writeList(trafficHistoryHours);
|
||||
dest.writeParcelable(lastSecondUsedForMinute, 0);
|
||||
dest.writeParcelable(lastMinuteUsedForHours, 0);
|
||||
}
|
||||
public LinkedList<TrafficDatapoint> getHours() {
|
||||
return trafficHistoryHours;
|
||||
}
|
||||
public LinkedList<TrafficDatapoint> getMinutes() {
|
||||
return trafficHistoryMinutes;
|
||||
}
|
||||
public LinkedList<TrafficDatapoint> getSeconds() {
|
||||
return trafficHistorySeconds;
|
||||
}
|
||||
LastDiff add(long in, long out) {
|
||||
TrafficDatapoint tdp = new TrafficDatapoint(in, out, System.currentTimeMillis());
|
||||
LastDiff diff = getLastDiff(tdp);
|
||||
addDataPoint(tdp);
|
||||
return diff;
|
||||
}
|
||||
private void addDataPoint(TrafficDatapoint tdp) {
|
||||
trafficHistorySeconds.add(tdp);
|
||||
if (lastSecondUsedForMinute == null) {
|
||||
lastSecondUsedForMinute = new TrafficDatapoint(0, 0, 0);
|
||||
lastMinuteUsedForHours = new TrafficDatapoint(0, 0, 0);
|
||||
}
|
||||
removeAndAverage(tdp, true);
|
||||
}
|
||||
private void removeAndAverage(TrafficDatapoint newTdp, boolean seconds) {
|
||||
HashSet<TrafficDatapoint> toRemove = new HashSet<>();
|
||||
Vector<TrafficDatapoint> toAverage = new Vector<>();
|
||||
long timePeriod;
|
||||
LinkedList<TrafficDatapoint> tpList, nextList;
|
||||
TrafficDatapoint lastTsPeriod;
|
||||
if (seconds) {
|
||||
timePeriod = TIME_PERIOD_MINTUES;
|
||||
tpList = trafficHistorySeconds;
|
||||
nextList = trafficHistoryMinutes;
|
||||
lastTsPeriod = lastSecondUsedForMinute;
|
||||
} else {
|
||||
timePeriod = TIME_PERIOD_HOURS;
|
||||
tpList = trafficHistoryMinutes;
|
||||
nextList = trafficHistoryHours;
|
||||
lastTsPeriod = lastMinuteUsedForHours;
|
||||
}
|
||||
if (newTdp.timestamp / timePeriod > (lastTsPeriod.timestamp / timePeriod)) {
|
||||
nextList.add(newTdp);
|
||||
if (seconds) {
|
||||
lastSecondUsedForMinute = newTdp;
|
||||
removeAndAverage(newTdp, false);
|
||||
} else
|
||||
lastMinuteUsedForHours = newTdp;
|
||||
for (TrafficDatapoint tph : tpList) {
|
||||
// List is iteratered from oldest to newest, remembert first one that we did not
|
||||
if ((newTdp.timestamp - tph.timestamp) / timePeriod >= PERIODS_TO_KEEP)
|
||||
toRemove.add(tph);
|
||||
}
|
||||
tpList.removeAll(toRemove);
|
||||
}
|
||||
}
|
||||
public static class TrafficDatapoint implements Parcelable {
|
||||
public static final Creator<TrafficDatapoint> CREATOR = new Creator<TrafficDatapoint>() {
|
||||
@Override
|
||||
public TrafficDatapoint createFromParcel(Parcel in) {
|
||||
return new TrafficDatapoint(in);
|
||||
}
|
||||
@Override
|
||||
public TrafficDatapoint[] newArray(int size) {
|
||||
return new TrafficDatapoint[size];
|
||||
}
|
||||
};
|
||||
public final long timestamp;
|
||||
public final long in;
|
||||
public final long out;
|
||||
private TrafficDatapoint(long inBytes, long outBytes, long timestamp) {
|
||||
this.in = inBytes;
|
||||
this.out = outBytes;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
private TrafficDatapoint(Parcel in) {
|
||||
timestamp = in.readLong();
|
||||
this.in = in.readLong();
|
||||
out = in.readLong();
|
||||
}
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(timestamp);
|
||||
dest.writeLong(in);
|
||||
dest.writeLong(out);
|
||||
}
|
||||
}
|
||||
static class LastDiff {
|
||||
final private TrafficDatapoint tdp;
|
||||
final private TrafficDatapoint lasttdp;
|
||||
private LastDiff(TrafficDatapoint lasttdp, TrafficDatapoint tdp) {
|
||||
this.lasttdp = lasttdp;
|
||||
this.tdp = tdp;
|
||||
}
|
||||
public long getDiffOut() {
|
||||
return max(0, tdp.out - lasttdp.out);
|
||||
}
|
||||
public long getDiffIn() {
|
||||
return max(0, tdp.in - lasttdp.in);
|
||||
}
|
||||
public long getIn() {
|
||||
return tdp.in;
|
||||
}
|
||||
public long getOut() {
|
||||
return tdp.out;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Vector;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
public class VPNLaunchHelper {
|
||||
private static final String MINIPIEVPN = "pie_openvpn";
|
||||
private static final String OVPNCONFIGFILE = "android.conf";
|
||||
|
||||
private static String writeMiniVPN(Context context) {
|
||||
String nativeAPI = NativeUtils.getNativeAPI();
|
||||
/* Q does not allow executing binaries written in temp directory anymore */
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||
return new File(context.getApplicationInfo().nativeLibraryDir, "libovpnexec.so").getPath();
|
||||
String[] abis;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
abis = getSupportedABIsLollipop();
|
||||
else
|
||||
//noinspection deprecation
|
||||
abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
|
||||
|
||||
if (!nativeAPI.equals(abis[0])) {
|
||||
VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI);
|
||||
abis = new String[]{nativeAPI};
|
||||
}
|
||||
|
||||
for (String abi : abis) {
|
||||
|
||||
File vpnExecutable = new File(context.getCacheDir(), "c_" + MINIPIEVPN + "." + abi);
|
||||
if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) {
|
||||
return vpnExecutable.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Cannot find any executable for this device's ABIs " + Arrays.toString(abis));
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private static String[] getSupportedABIsLollipop() {
|
||||
return Build.SUPPORTED_ABIS;
|
||||
}
|
||||
|
||||
|
||||
static String[] buildOpenvpnArgv(Context c) {
|
||||
Vector<String> args = new Vector<>();
|
||||
|
||||
String binaryName = writeMiniVPN(c);
|
||||
// Add fixed paramenters
|
||||
//args.add("/data/data/de.blinkt.openvpn/lib/openvpn");
|
||||
|
||||
args.add(binaryName);
|
||||
|
||||
args.add("--config");
|
||||
args.add(getConfigFilePath(c));
|
||||
|
||||
return args.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static boolean writeMiniVPNBinary(Context context, String abi, File mvpnout) {
|
||||
try {
|
||||
InputStream mvpn;
|
||||
|
||||
try {
|
||||
mvpn = context.getAssets().open(MINIPIEVPN + "." + abi);
|
||||
} catch (IOException errabi) {
|
||||
VpnStatus.logInfo("Failed getting assets for architecture " + abi);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
FileOutputStream fout = new FileOutputStream(mvpnout);
|
||||
|
||||
byte[] buf = new byte[4096];
|
||||
|
||||
int lenread = mvpn.read(buf);
|
||||
while (lenread > 0) {
|
||||
fout.write(buf, 0, lenread);
|
||||
lenread = mvpn.read(buf);
|
||||
}
|
||||
fout.close();
|
||||
|
||||
if (!mvpnout.setExecutable(true)) {
|
||||
VpnStatus.logError("Failed to make OpenVPN executable");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
VpnStatus.logException(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void startOpenVpn(VpnProfile startprofile, Context context) {
|
||||
Intent startVPN = startprofile.prepareStartService(context);
|
||||
if (startVPN != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
//noinspection NewApi
|
||||
context.startForegroundService(startVPN);
|
||||
else
|
||||
context.startService(startVPN);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getConfigFilePath(Context context) {
|
||||
return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,399 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
public class VpnStatus {
|
||||
final static Object readFileLock = new Object();
|
||||
static final int MAXLOGENTRIES = 1000;
|
||||
// keytool -printcert -jarfile de.blinkt.openvpn_85.apk
|
||||
static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
|
||||
static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
|
||||
static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
|
||||
static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
|
||||
private static final LinkedList<LogItem> logbuffer;
|
||||
public static TrafficHistory trafficHistory;
|
||||
static boolean readFileLog = false;
|
||||
private static Vector<LogListener> logListener;
|
||||
private static Vector<StateListener> stateListener;
|
||||
private static Vector<ByteCountListener> byteCountListener;
|
||||
private static String mLaststatemsg = "";
|
||||
private static String mLaststate = "NOPROCESS";
|
||||
private static int mLastStateresid = R.string.state_noprocess;
|
||||
private static HandlerThread mHandlerThread;
|
||||
private static String mLastConnectedVPNUUID;
|
||||
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
|
||||
private static LogFileHandler mLogFileHandler;
|
||||
|
||||
static {
|
||||
logbuffer = new LinkedList<>();
|
||||
logListener = new Vector<>();
|
||||
stateListener = new Vector<>();
|
||||
byteCountListener = new Vector<>();
|
||||
trafficHistory = new TrafficHistory();
|
||||
logInformation();
|
||||
}
|
||||
|
||||
public static void logException(LogLevel ll, String context, Exception e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
LogItem li;
|
||||
if (context != null) {
|
||||
li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
|
||||
} else {
|
||||
li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
|
||||
}
|
||||
newLogItem(li);
|
||||
}
|
||||
|
||||
public static void logException(Exception e) {
|
||||
logException(LogLevel.ERROR, null, e);
|
||||
}
|
||||
|
||||
public static void logException(String context, Exception e) {
|
||||
logException(LogLevel.ERROR, context, e);
|
||||
}
|
||||
|
||||
public static boolean isVPNActive() {
|
||||
return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
|
||||
}
|
||||
|
||||
public static String getLastCleanLogMessage(Context c) {
|
||||
String message = mLaststatemsg;
|
||||
switch (mLastLevel) {
|
||||
case LEVEL_CONNECTED:
|
||||
String[] parts = mLaststatemsg.split(",");
|
||||
/*
|
||||
(a) the integer unix date/time,
|
||||
(b) the state name,
|
||||
0 (c) optional descriptive string (used mostly on RECONNECTING
|
||||
and EXITING to show the reason for the disconnect),
|
||||
1 (d) optional TUN/TAP local IPv4 address
|
||||
2 (e) optional address of remote server,
|
||||
3 (f) optional port of remote server,
|
||||
4 (g) optional local address,
|
||||
5 (h) optional local port, and
|
||||
6 (i) optional TUN/TAP local IPv6 address.
|
||||
*/
|
||||
// Return only the assigned IP addresses in the UI
|
||||
if (parts.length >= 7) message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
|
||||
break;
|
||||
}
|
||||
while (message.endsWith(",")) message = message.substring(0, message.length() - 1);
|
||||
String status = mLaststate;
|
||||
if (status.equals("NOPROCESS")) return message;
|
||||
if (mLastStateresid == R.string.state_waitconnectretry) {
|
||||
return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
|
||||
}
|
||||
String prefix = c.getString(mLastStateresid);
|
||||
if (mLastStateresid == R.string.unknown_state) message = status + message;
|
||||
if (message.length() > 0) prefix += ": ";
|
||||
return prefix + message;
|
||||
}
|
||||
|
||||
public static void initLogCache(File cacheDir) {
|
||||
mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
|
||||
mHandlerThread.start();
|
||||
mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
|
||||
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
|
||||
mLogFileHandler.sendMessage(m);
|
||||
}
|
||||
|
||||
public static void flushLog() {
|
||||
if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
|
||||
}
|
||||
|
||||
public static void setConnectedVPNProfile(String uuid) {
|
||||
mLastConnectedVPNUUID = uuid;
|
||||
for (StateListener sl : stateListener)
|
||||
sl.setConnectedVPN(uuid);
|
||||
}
|
||||
|
||||
public static String getLastConnectedVPNProfile() {
|
||||
return mLastConnectedVPNUUID;
|
||||
}
|
||||
|
||||
public static void setTrafficHistory(TrafficHistory trafficHistory) {
|
||||
VpnStatus.trafficHistory = trafficHistory;
|
||||
}
|
||||
|
||||
public synchronized static void logMessage(LogLevel level, String prefix, String message) {
|
||||
newLogItem(new LogItem(level, prefix + message));
|
||||
}
|
||||
|
||||
public synchronized static void clearLog() {
|
||||
logbuffer.clear();
|
||||
logInformation();
|
||||
if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
|
||||
}
|
||||
|
||||
private static void logInformation() {
|
||||
String nativeAPI;
|
||||
try {
|
||||
nativeAPI = NativeUtils.getNativeAPI();
|
||||
} catch (UnsatisfiedLinkError ignore) {
|
||||
nativeAPI = "error";
|
||||
}
|
||||
logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
|
||||
}
|
||||
|
||||
public synchronized static void addLogListener(LogListener ll) {
|
||||
logListener.add(ll);
|
||||
}
|
||||
|
||||
public synchronized static void removeLogListener(LogListener ll) {
|
||||
logListener.remove(ll);
|
||||
}
|
||||
|
||||
public synchronized static void addByteCountListener(ByteCountListener bcl) {
|
||||
TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
|
||||
bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(), diff.getDiffOut());
|
||||
byteCountListener.add(bcl);
|
||||
}
|
||||
|
||||
public synchronized static void removeByteCountListener(ByteCountListener bcl) {
|
||||
byteCountListener.remove(bcl);
|
||||
}
|
||||
|
||||
public synchronized static void addStateListener(StateListener sl) {
|
||||
if (!stateListener.contains(sl)) {
|
||||
stateListener.add(sl);
|
||||
if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getLocalizedState(String state) {
|
||||
switch (state) {
|
||||
case "CONNECTING":
|
||||
return R.string.state_connecting;
|
||||
case "WAIT":
|
||||
return R.string.state_wait;
|
||||
case "AUTH":
|
||||
return R.string.state_auth;
|
||||
case "GET_CONFIG":
|
||||
return R.string.state_get_config;
|
||||
case "ASSIGN_IP":
|
||||
return R.string.state_assign_ip;
|
||||
case "ADD_ROUTES":
|
||||
return R.string.state_add_routes;
|
||||
case "CONNECTED":
|
||||
return R.string.state_connected;
|
||||
case "DISCONNECTED":
|
||||
return R.string.state_disconnected;
|
||||
case "RECONNECTING":
|
||||
return R.string.state_reconnecting;
|
||||
case "EXITING":
|
||||
return R.string.state_exiting;
|
||||
case "RESOLVE":
|
||||
return R.string.state_resolve;
|
||||
case "TCP_CONNECT":
|
||||
return R.string.state_tcp_connect;
|
||||
default:
|
||||
return R.string.unknown_state;
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
|
||||
switch (pauseReason) {
|
||||
case noNetwork:
|
||||
VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
|
||||
break;
|
||||
case screenOff:
|
||||
VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
|
||||
break;
|
||||
case userPause:
|
||||
VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConnectionStatus getLevel(String state) {
|
||||
String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
|
||||
String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"};
|
||||
String[] connected = {"CONNECTED"};
|
||||
String[] notconnected = {"DISCONNECTED", "EXITING"};
|
||||
for (String x : noreplyet)
|
||||
if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
|
||||
for (String x : reply)
|
||||
if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
|
||||
for (String x : connected)
|
||||
if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED;
|
||||
for (String x : notconnected)
|
||||
if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED;
|
||||
return ConnectionStatus.UNKNOWN_LEVEL;
|
||||
}
|
||||
|
||||
public synchronized static void removeStateListener(StateListener sl) {
|
||||
stateListener.remove(sl);
|
||||
}
|
||||
|
||||
synchronized public static LogItem[] getlogbuffer() {
|
||||
// The stoned way of java to return an array from a vector
|
||||
// brought to you by eclipse auto complete
|
||||
return logbuffer.toArray(new LogItem[logbuffer.size()]);
|
||||
}
|
||||
|
||||
static void updateStateString(String state, String msg) {
|
||||
int rid = getLocalizedState(state);
|
||||
ConnectionStatus level = getLevel(state);
|
||||
updateStateString(state, msg, rid, level);
|
||||
}
|
||||
|
||||
public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) {
|
||||
// Workound for OpenVPN doing AUTH and wait and being connected
|
||||
// Simply ignore these state
|
||||
if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) {
|
||||
newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
|
||||
return;
|
||||
}
|
||||
mLaststate = state;
|
||||
mLaststatemsg = msg;
|
||||
mLastStateresid = resid;
|
||||
mLastLevel = level;
|
||||
for (StateListener sl : stateListener) {
|
||||
sl.updateState(state, msg, resid, level);
|
||||
}
|
||||
newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s", state, level.toString(), msg)));
|
||||
}
|
||||
|
||||
public static void logInfo(String message) {
|
||||
newLogItem(new LogItem(LogLevel.INFO, message));
|
||||
}
|
||||
|
||||
public static void logDebug(String message) {
|
||||
newLogItem(new LogItem(LogLevel.DEBUG, message));
|
||||
}
|
||||
|
||||
public static void logInfo(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logDebug(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
|
||||
}
|
||||
|
||||
static void newLogItem(LogItem logItem) {
|
||||
newLogItem(logItem, false);
|
||||
}
|
||||
|
||||
synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
|
||||
if (cachedLine) {
|
||||
logbuffer.addFirst(logItem);
|
||||
} else {
|
||||
logbuffer.addLast(logItem);
|
||||
if (mLogFileHandler != null) {
|
||||
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
|
||||
mLogFileHandler.sendMessage(m);
|
||||
}
|
||||
}
|
||||
if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
|
||||
while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst();
|
||||
if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
|
||||
}
|
||||
//if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test"))
|
||||
// Log.d("OpenVPN", logItem.getString(null));
|
||||
for (LogListener ll : logListener) {
|
||||
ll.newLog(logItem);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logError(String msg) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, msg));
|
||||
}
|
||||
|
||||
public static void logWarning(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logWarning(String msg) {
|
||||
newLogItem(new LogItem(LogLevel.WARNING, msg));
|
||||
}
|
||||
|
||||
public static void logError(int resourceId) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, resourceId));
|
||||
}
|
||||
|
||||
public static void logError(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
|
||||
newLogItem(new LogItem(level, ovpnlevel, message));
|
||||
}
|
||||
|
||||
|
||||
public static void addExtraHints(String msg) {
|
||||
if ((msg.endsWith("md too weak") && msg.startsWith("OpenSSL: error")) || msg.contains("error:140AB18E")
|
||||
|| msg.contains("SSL_CA_MD_TOO_WEAK") || (msg.contains("ca md too weak")))
|
||||
logError("OpenSSL reported a certificate with a weak hash, please see the in app FAQ about weak hashes.");
|
||||
if ((msg.contains("digital envelope routines::unsupported")))
|
||||
logError("The encryption method of your private keys/pkcs12 might be outdated and you probably need to enable " +
|
||||
"the OpenSSL legacy provider to be able to use this profile.");
|
||||
}
|
||||
|
||||
public static synchronized void updateByteCount(long in, long out) {
|
||||
TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
|
||||
for (ByteCountListener bcl : byteCountListener) {
|
||||
bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
|
||||
}
|
||||
}
|
||||
|
||||
public enum LogLevel {
|
||||
INFO(2), ERROR(-2), WARNING(1), VERBOSE(3), DEBUG(4);
|
||||
protected int mValue;
|
||||
|
||||
LogLevel(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public static LogLevel getEnumByValue(int value) {
|
||||
switch (value) {
|
||||
case 2:
|
||||
return INFO;
|
||||
case -2:
|
||||
return ERROR;
|
||||
case 1:
|
||||
return WARNING;
|
||||
case 3:
|
||||
return VERBOSE;
|
||||
case 4:
|
||||
return DEBUG;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
|
||||
public interface LogListener {
|
||||
void newLog(LogItem logItem);
|
||||
}
|
||||
|
||||
public interface StateListener {
|
||||
void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level);
|
||||
|
||||
void setConnectedVPN(String uuid);
|
||||
}
|
||||
|
||||
public interface ByteCountListener {
|
||||
void updateByteCount(long in, long out, long diffIn, long diffOut);
|
||||
}
|
||||
}
|
|
@ -1,493 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
|
||||
import de.blinkt.openvpn.R;
|
||||
|
||||
public class VpnStatus {
|
||||
|
||||
|
||||
private static final LinkedList<LogItem> logbuffer;
|
||||
|
||||
private static Vector<LogListener> logListener;
|
||||
private static Vector<StateListener> stateListener;
|
||||
private static Vector<ByteCountListener> byteCountListener;
|
||||
|
||||
private static String mLaststatemsg = "";
|
||||
|
||||
private static String mLaststate = "NOPROCESS";
|
||||
|
||||
private static int mLastStateresid = R.string.state_noprocess;
|
||||
|
||||
private static Intent mLastIntent = null;
|
||||
|
||||
private static HandlerThread mHandlerThread;
|
||||
|
||||
private static String mLastConnectedVPNUUID;
|
||||
static boolean readFileLog =false;
|
||||
final static java.lang.Object readFileLock = new Object();
|
||||
|
||||
|
||||
public static TrafficHistory trafficHistory;
|
||||
|
||||
|
||||
public static void logException(LogLevel ll, String context, Throwable e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
LogItem li;
|
||||
if (context != null) {
|
||||
li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context);
|
||||
} else {
|
||||
li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString());
|
||||
}
|
||||
newLogItem(li);
|
||||
}
|
||||
|
||||
public static void logException(Throwable e) {
|
||||
logException(LogLevel.ERROR, null, e);
|
||||
}
|
||||
|
||||
public static void logException(String context, Throwable e) {
|
||||
logException(LogLevel.ERROR, context, e);
|
||||
}
|
||||
|
||||
static final int MAXLOGENTRIES = 1000;
|
||||
|
||||
public static boolean isVPNActive() {
|
||||
return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED);
|
||||
}
|
||||
|
||||
public static String getLastCleanLogMessage(Context c) {
|
||||
String message = mLaststatemsg;
|
||||
switch (mLastLevel) {
|
||||
case LEVEL_CONNECTED:
|
||||
String[] parts = mLaststatemsg.split(",");
|
||||
/*
|
||||
(a) the integer unix date/time,
|
||||
(b) the state name,
|
||||
0 (c) optional descriptive string (used mostly on RECONNECTING
|
||||
and EXITING to show the reason for the disconnect),
|
||||
|
||||
1 (d) optional TUN/TAP local IPv4 address
|
||||
2 (e) optional address of remote server,
|
||||
3 (f) optional port of remote server,
|
||||
4 (g) optional local address,
|
||||
5 (h) optional local port, and
|
||||
6 (i) optional TUN/TAP local IPv6 address.
|
||||
*/
|
||||
// Return only the assigned IP addresses in the UI
|
||||
if (parts.length >= 7)
|
||||
message = String.format(Locale.US, "%s %s", parts[1], parts[6]);
|
||||
break;
|
||||
}
|
||||
|
||||
while (message.endsWith(","))
|
||||
message = message.substring(0, message.length() - 1);
|
||||
|
||||
String status = mLaststate;
|
||||
if (status.equals("NOPROCESS"))
|
||||
return message;
|
||||
|
||||
if (mLastStateresid == R.string.state_waitconnectretry) {
|
||||
return c.getString(R.string.state_waitconnectretry, mLaststatemsg);
|
||||
}
|
||||
|
||||
String prefix = c.getString(mLastStateresid);
|
||||
if (mLastStateresid == R.string.unknown_state)
|
||||
message = status + message;
|
||||
if (message.length() > 0)
|
||||
prefix += ": ";
|
||||
|
||||
return prefix + message;
|
||||
|
||||
}
|
||||
|
||||
public static void initLogCache(File cacheDir) {
|
||||
mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY);
|
||||
mHandlerThread.start();
|
||||
mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper());
|
||||
|
||||
|
||||
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir);
|
||||
mLogFileHandler.sendMessage(m);
|
||||
|
||||
}
|
||||
|
||||
public static void flushLog() {
|
||||
if (mLogFileHandler!=null)
|
||||
mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK);
|
||||
}
|
||||
|
||||
public synchronized static void setConnectedVPNProfile(String uuid) {
|
||||
mLastConnectedVPNUUID = uuid;
|
||||
for (StateListener sl: stateListener)
|
||||
sl.setConnectedVPN(uuid);
|
||||
}
|
||||
|
||||
|
||||
public static String getLastConnectedVPNProfile()
|
||||
{
|
||||
return mLastConnectedVPNUUID;
|
||||
}
|
||||
|
||||
public static void setTrafficHistory(TrafficHistory trafficHistory) {
|
||||
VpnStatus.trafficHistory = trafficHistory;
|
||||
}
|
||||
|
||||
|
||||
public enum LogLevel {
|
||||
INFO(2),
|
||||
ERROR(-2),
|
||||
WARNING(1),
|
||||
VERBOSE(3),
|
||||
DEBUG(4);
|
||||
|
||||
protected int mValue;
|
||||
|
||||
LogLevel(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public static LogLevel getEnumByValue(int value) {
|
||||
switch (value) {
|
||||
case 2:
|
||||
return INFO;
|
||||
case -2:
|
||||
return ERROR;
|
||||
case 1:
|
||||
return WARNING;
|
||||
case 3:
|
||||
return VERBOSE;
|
||||
case 4:
|
||||
return DEBUG;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keytool -printcert -jarfile de.blinkt.openvpn_85.apk
|
||||
static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109};
|
||||
static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
|
||||
static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
|
||||
static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
|
||||
|
||||
|
||||
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
|
||||
|
||||
private static LogFileHandler mLogFileHandler;
|
||||
|
||||
static {
|
||||
logbuffer = new LinkedList<>();
|
||||
logListener = new Vector<>();
|
||||
stateListener = new Vector<>();
|
||||
byteCountListener = new Vector<>();
|
||||
trafficHistory = new TrafficHistory();
|
||||
|
||||
logInformation();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public interface LogListener {
|
||||
void newLog(LogItem logItem);
|
||||
}
|
||||
|
||||
public interface StateListener {
|
||||
void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level, Intent Intent);
|
||||
|
||||
void setConnectedVPN(String uuid);
|
||||
}
|
||||
|
||||
public interface ByteCountListener {
|
||||
void updateByteCount(long in, long out, long diffIn, long diffOut);
|
||||
}
|
||||
|
||||
public synchronized static void logMessage(LogLevel level, String prefix, String message) {
|
||||
newLogItem(new LogItem(level, prefix + message));
|
||||
|
||||
}
|
||||
|
||||
public synchronized static void clearLog() {
|
||||
logbuffer.clear();
|
||||
logInformation();
|
||||
if (mLogFileHandler != null)
|
||||
mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE);
|
||||
}
|
||||
|
||||
private static void logInformation() {
|
||||
String nativeAPI;
|
||||
try {
|
||||
nativeAPI = NativeUtils.getNativeAPI();
|
||||
} catch (UnsatisfiedLinkError ignore) {
|
||||
nativeAPI = "error";
|
||||
}
|
||||
|
||||
logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT,
|
||||
nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");
|
||||
}
|
||||
|
||||
public synchronized static void addLogListener(LogListener ll) {
|
||||
logListener.add(ll);
|
||||
}
|
||||
|
||||
public synchronized static void removeLogListener(LogListener ll) {
|
||||
logListener.remove(ll);
|
||||
}
|
||||
|
||||
public synchronized static void addByteCountListener(ByteCountListener bcl) {
|
||||
TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
|
||||
bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(),diff.getDiffOut());
|
||||
byteCountListener.add(bcl);
|
||||
}
|
||||
|
||||
public synchronized static void removeByteCountListener(ByteCountListener bcl) {
|
||||
byteCountListener.remove(bcl);
|
||||
}
|
||||
|
||||
|
||||
public synchronized static void addStateListener(StateListener sl) {
|
||||
if (!stateListener.contains(sl)) {
|
||||
stateListener.add(sl);
|
||||
if (mLaststate != null)
|
||||
sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel, mLastIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getLocalizedState(String state) {
|
||||
switch (state) {
|
||||
case "CONNECTING":
|
||||
return R.string.state_connecting;
|
||||
case "WAIT":
|
||||
return R.string.state_wait;
|
||||
case "AUTH":
|
||||
return R.string.state_auth;
|
||||
case "GET_CONFIG":
|
||||
return R.string.state_get_config;
|
||||
case "ASSIGN_IP":
|
||||
return R.string.state_assign_ip;
|
||||
case "ADD_ROUTES":
|
||||
return R.string.state_add_routes;
|
||||
case "CONNECTED":
|
||||
return R.string.state_connected;
|
||||
case "DISCONNECTED":
|
||||
return R.string.state_disconnected;
|
||||
case "RECONNECTING":
|
||||
return R.string.state_reconnecting;
|
||||
case "EXITING":
|
||||
return R.string.state_exiting;
|
||||
case "RESOLVE":
|
||||
return R.string.state_resolve;
|
||||
case "TCP_CONNECT":
|
||||
return R.string.state_tcp_connect;
|
||||
case "AUTH_PENDING":
|
||||
return R.string.state_auth_pending;
|
||||
default:
|
||||
return R.string.unknown_state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) {
|
||||
switch (pauseReason) {
|
||||
case noNetwork:
|
||||
VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK);
|
||||
break;
|
||||
case screenOff:
|
||||
VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED);
|
||||
break;
|
||||
case userPause:
|
||||
VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static ConnectionStatus getLevel(String state) {
|
||||
String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"};
|
||||
String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES", "AUTH_PENDING"};
|
||||
String[] connected = {"CONNECTED"};
|
||||
String[] notconnected = {"DISCONNECTED", "EXITING"};
|
||||
|
||||
for (String x : noreplyet)
|
||||
if (state.equals(x))
|
||||
return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
|
||||
|
||||
for (String x : reply)
|
||||
if (state.equals(x))
|
||||
return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED;
|
||||
|
||||
for (String x : connected)
|
||||
if (state.equals(x))
|
||||
return ConnectionStatus.LEVEL_CONNECTED;
|
||||
|
||||
for (String x : notconnected)
|
||||
if (state.equals(x))
|
||||
return ConnectionStatus.LEVEL_NOTCONNECTED;
|
||||
|
||||
return ConnectionStatus.UNKNOWN_LEVEL;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public synchronized static void removeStateListener(StateListener sl) {
|
||||
stateListener.remove(sl);
|
||||
}
|
||||
|
||||
|
||||
synchronized public static LogItem[] getlogbuffer() {
|
||||
|
||||
// The stoned way of java to return an array from a vector
|
||||
// brought to you by eclipse auto complete
|
||||
return logbuffer.toArray(new LogItem[logbuffer.size()]);
|
||||
|
||||
}
|
||||
|
||||
static void updateStateString(String state, String msg) {
|
||||
// We want to skip announcing that we are trying to get the configuration since
|
||||
// this is just polling until the user input has finished.be
|
||||
if (mLastLevel == ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT && state.equals("GET_CONFIG"))
|
||||
return;
|
||||
int rid = getLocalizedState(state);
|
||||
ConnectionStatus level = getLevel(state);
|
||||
updateStateString(state, msg, rid, level);
|
||||
}
|
||||
|
||||
public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level)
|
||||
{
|
||||
updateStateString(state, msg, resid, level, null);
|
||||
}
|
||||
|
||||
public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level, Intent intent) {
|
||||
// Workound for OpenVPN doing AUTH and wait and being connected
|
||||
// Simply ignore these state
|
||||
if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED &&
|
||||
(state.equals("WAIT") || state.equals("AUTH"))) {
|
||||
newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg)));
|
||||
return;
|
||||
}
|
||||
|
||||
mLaststate = state;
|
||||
mLaststatemsg = msg;
|
||||
mLastStateresid = resid;
|
||||
mLastLevel = level;
|
||||
mLastIntent = intent;
|
||||
|
||||
|
||||
for (StateListener sl : stateListener) {
|
||||
sl.updateState(state, msg, resid, level, intent);
|
||||
}
|
||||
//newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg)));
|
||||
}
|
||||
|
||||
public static void logInfo(String message) {
|
||||
newLogItem(new LogItem(LogLevel.INFO, message));
|
||||
}
|
||||
|
||||
public static void logDebug(String message) {
|
||||
newLogItem(new LogItem(LogLevel.DEBUG, message));
|
||||
}
|
||||
|
||||
public static void logInfo(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.INFO, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logDebug(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args));
|
||||
}
|
||||
|
||||
static void newLogItem(LogItem logItem) {
|
||||
newLogItem(logItem, false);
|
||||
}
|
||||
|
||||
|
||||
synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
|
||||
if (cachedLine) {
|
||||
logbuffer.addFirst(logItem);
|
||||
} else {
|
||||
logbuffer.addLast(logItem);
|
||||
if (mLogFileHandler != null) {
|
||||
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
|
||||
mLogFileHandler.sendMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) {
|
||||
while (logbuffer.size() > MAXLOGENTRIES)
|
||||
logbuffer.removeFirst();
|
||||
if (mLogFileHandler != null)
|
||||
mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE));
|
||||
}
|
||||
|
||||
for (LogListener ll : logListener) {
|
||||
ll.newLog(logItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void logError(String msg) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, msg));
|
||||
|
||||
}
|
||||
|
||||
public static void logWarning(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.WARNING, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logWarning(String msg) {
|
||||
newLogItem(new LogItem(LogLevel.WARNING, msg));
|
||||
}
|
||||
|
||||
|
||||
public static void logError(int resourceId) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, resourceId));
|
||||
}
|
||||
|
||||
public static void logError(int resourceId, Object... args) {
|
||||
newLogItem(new LogItem(LogLevel.ERROR, resourceId, args));
|
||||
}
|
||||
|
||||
public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) {
|
||||
/* Check for the weak md whe we have a message from OpenVPN */
|
||||
newLogItem(new LogItem(level, ovpnlevel, message));
|
||||
}
|
||||
|
||||
|
||||
public static void addExtraHints(String msg) {
|
||||
if ((msg.endsWith("md too weak") && msg.startsWith("OpenSSL: error")) || msg.contains("error:140AB18E")
|
||||
|| msg.contains("SSL_CA_MD_TOO_WEAK") || (msg.contains("ca md too weak")))
|
||||
logError("OpenSSL reported a certificate with a weak hash, please see the in app FAQ about weak hashes.");
|
||||
if ((msg.contains("digital envelope routines::unsupported")))
|
||||
logError("The encryption method of your private keys/pkcs12 might be outdated and you probably need to enable " +
|
||||
"the OpenSSL legacy provider to be able to use this profile.");
|
||||
}
|
||||
|
||||
public static synchronized void updateByteCount(long in, long out) {
|
||||
TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
|
||||
|
||||
for (ByteCountListener bcl : byteCountListener) {
|
||||
bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2016 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package de.blinkt.openvpn.core;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.liskovsoft.openvpn.R;
|
||||
|
||||
import org.spongycastle.util.io.pem.PemObject;
|
||||
import org.spongycastle.util.io.pem.PemReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import de.blinkt.openvpn.VpnProfile;
|
||||
|
||||
public class X509Utils {
|
||||
public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException {
|
||||
CertificateFactory certFact = CertificateFactory.getInstance("X.509");
|
||||
Vector<Certificate> certificates = new Vector<>();
|
||||
if (VpnProfile.isEmbedded(certfilename)) {
|
||||
int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----");
|
||||
do {
|
||||
// The java certifcate reader is ... kind of stupid
|
||||
// It does NOT ignore chars before the --BEGIN ...
|
||||
subIndex = Math.max(0, subIndex);
|
||||
InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes());
|
||||
certificates.add(certFact.generateCertificate(inStream));
|
||||
subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex + 1);
|
||||
} while (subIndex > 0);
|
||||
return certificates.toArray(new Certificate[certificates.size()]);
|
||||
} else {
|
||||
InputStream inStream = new FileInputStream(certfilename);
|
||||
return new Certificate[]{certFact.generateCertificate(inStream)};
|
||||
}
|
||||
}
|
||||
public static PemObject readPemObjectFromFile(String keyfilename) throws IOException {
|
||||
Reader inStream;
|
||||
if (VpnProfile.isEmbedded(keyfilename))
|
||||
inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename));
|
||||
else
|
||||
inStream = new FileReader(new File(keyfilename));
|
||||
PemReader pr = new PemReader(inStream);
|
||||
PemObject r = pr.readPemObject();
|
||||
pr.close();
|
||||
return r;
|
||||
}
|
||||
public static String getCertificateFriendlyName(Context c, String filename) {
|
||||
if (!TextUtils.isEmpty(filename)) {
|
||||
try {
|
||||
X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0];
|
||||
String friendlycn = getCertificateFriendlyName(cert);
|
||||
friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn;
|
||||
return friendlycn;
|
||||
} catch (Exception e) {
|
||||
VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
return c.getString(R.string.cannotparsecert);
|
||||
}
|
||||
public static String getCertificateValidityString(X509Certificate cert, Resources res) {
|
||||
try {
|
||||
cert.checkValidity();
|
||||
} catch (CertificateExpiredException ce) {
|
||||
return "EXPIRED: ";
|
||||
} catch (CertificateNotYetValidException cny) {
|
||||
return "NOT YET VALID: ";
|
||||
}
|
||||
Date certNotAfter = cert.getNotAfter();
|
||||
Date now = new Date();
|
||||
long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms
|
||||
// More than 72h left, display days
|
||||
// More than 3 months display months
|
||||
if (timeLeft > 90l * 24 * 3600 * 1000) {
|
||||
long months = getMonthsDifference(now, certNotAfter);
|
||||
return res.getQuantityString(R.plurals.months_left, (int) months, months);
|
||||
} else if (timeLeft > 72 * 3600 * 1000) {
|
||||
long days = timeLeft / (24 * 3600 * 1000);
|
||||
return res.getQuantityString(R.plurals.days_left, (int) days, days);
|
||||
} else {
|
||||
long hours = timeLeft / (3600 * 1000);
|
||||
return res.getQuantityString(R.plurals.hours_left, (int) hours, hours);
|
||||
}
|
||||
}
|
||||
public static int getMonthsDifference(Date date1, Date date2) {
|
||||
int m1 = date1.getYear() * 12 + date1.getMonth();
|
||||
int m2 = date2.getYear() * 12 + date2.getMonth();
|
||||
return m2 - m1 + 1;
|
||||
}
|
||||
public static String getCertificateFriendlyName(X509Certificate cert) {
|
||||
X500Principal principal = cert.getSubjectX500Principal();
|
||||
byte[] encodedSubject = principal.getEncoded();
|
||||
String friendlyName = null;
|
||||
/* Hack so we do not have to ship a whole Spongy/bouncycastle */
|
||||
Exception exp = null;
|
||||
try {
|
||||
@SuppressLint("PrivateApi") Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name");
|
||||
Method getInstance = X509NameClass.getMethod("getInstance", Object.class);
|
||||
Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass);
|
||||
if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1"))
|
||||
defaultSymbols.put("1.2.840.113549.1.9.1", "eMail");
|
||||
Object subjectName = getInstance.invoke(X509NameClass, encodedSubject);
|
||||
Method toString = X509NameClass.getMethod("toString", boolean.class, Hashtable.class);
|
||||
friendlyName = (String) toString.invoke(subjectName, true, defaultSymbols);
|
||||
} catch (ClassNotFoundException e) {
|
||||
exp = e;
|
||||
} catch (NoSuchMethodException e) {
|
||||
exp = e;
|
||||
} catch (InvocationTargetException e) {
|
||||
exp = e;
|
||||
} catch (IllegalAccessException e) {
|
||||
exp = e;
|
||||
} catch (NoSuchFieldException e) {
|
||||
exp = e;
|
||||
}
|
||||
if (exp != null)
|
||||
VpnStatus.logException("Getting X509 Name from certificate", exp);
|
||||
/* Fallback if the reflection method did not work */
|
||||
if (friendlyName == null)
|
||||
friendlyName = principal.getName();
|
||||
// Really evil hack to decode email address
|
||||
// See: http://code.google.com/p/android/issues/detail?id=21531
|
||||
String[] parts = friendlyName.split(",");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String part = parts[i];
|
||||
if (part.startsWith("1.2.840.113549.1.9.1=#16")) {
|
||||
parts[i] = "email=" + ia5decode(part.replace("1.2.840.113549.1.9.1=#16", ""));
|
||||
}
|
||||
}
|
||||
friendlyName = TextUtils.join(",", parts);
|
||||
return friendlyName;
|
||||
}
|
||||
public static boolean isPrintableChar(char c) {
|
||||
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
||||
return (!Character.isISOControl(c)) &&
|
||||
block != null &&
|
||||
block != Character.UnicodeBlock.SPECIALS;
|
||||
}
|
||||
private static String ia5decode(String ia5string) {
|
||||
String d = "";
|
||||
for (int i = 1; i < ia5string.length(); i = i + 2) {
|
||||
String hexstr = ia5string.substring(i - 1, i + 1);
|
||||
char c = (char) Integer.parseInt(hexstr, 16);
|
||||
if (isPrintableChar(c)) {
|
||||
d += c;
|
||||
} else if (i == 1 && (c == 0x12 || c == 0x1b)) {
|
||||
// ignore
|
||||
} else {
|
||||
d += "\\x" + hexstr;
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.encoders;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class Base64 {
|
||||
private static final Encoder encoder = new Base64Encoder();
|
||||
|
||||
/**
|
||||
* encode the input data producing a base 64 encoded byte array.
|
||||
*
|
||||
* @return a byte array containing the base 64 encoded data.
|
||||
*/
|
||||
public static byte[] encode(byte[] data) {
|
||||
int len = (data.length + 2) / 3 * 4;
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
encoder.encode(data, 0, data.length, bOut);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("exception encoding base64 string: " + e);
|
||||
}
|
||||
return bOut.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the byte data to base 64 writing it to the given output stream.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public static int encode(byte[] data, OutputStream out) throws IOException {
|
||||
return encoder.encode(data, 0, data.length, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the byte data to base 64 writing it to the given output stream.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public static int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
|
||||
return encoder.encode(data, off, length, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* decode the base 64 encoded input data. It is assumed the input data is valid.
|
||||
*
|
||||
* @return a byte array representing the decoded data.
|
||||
*/
|
||||
public static byte[] decode(byte[] data) {
|
||||
int len = data.length / 4 * 3;
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
encoder.decode(data, 0, data.length, bOut);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("exception decoding base64 string: " + e);
|
||||
}
|
||||
return bOut.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* decode the base 64 encoded String data - whitespace will be ignored.
|
||||
*
|
||||
* @return a byte array representing the decoded data.
|
||||
*/
|
||||
public static byte[] decode(String data) {
|
||||
int len = data.length() / 4 * 3;
|
||||
ByteArrayOutputStream bOut = new ByteArrayOutputStream(len);
|
||||
try {
|
||||
encoder.decode(data, bOut);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("exception decoding base64 string: " + e);
|
||||
}
|
||||
return bOut.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* decode the base 64 encoded String data writing it to the given output stream,
|
||||
* whitespace characters will be ignored.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public static int decode(String data, OutputStream out) throws IOException {
|
||||
return encoder.decode(data, out);
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.encoders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class Base64Encoder implements Encoder {
|
||||
protected final byte[] encodingTable = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'};
|
||||
/*
|
||||
* set up the decoding table.
|
||||
*/
|
||||
protected final byte[] decodingTable = new byte[128];
|
||||
protected byte padding = (byte) '=';
|
||||
|
||||
public Base64Encoder() {
|
||||
initialiseDecodingTable();
|
||||
}
|
||||
|
||||
protected void initialiseDecodingTable() {
|
||||
for (int i = 0; i < encodingTable.length; i++) {
|
||||
decodingTable[encodingTable[i]] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* encode the input data producing a base 64 output stream.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
|
||||
int modulus = length % 3;
|
||||
int dataLength = (length - modulus);
|
||||
int a1, a2, a3;
|
||||
for (int i = off; i < off + dataLength; i += 3) {
|
||||
a1 = data[i] & 0xff;
|
||||
a2 = data[i + 1] & 0xff;
|
||||
a3 = data[i + 2] & 0xff;
|
||||
out.write(encodingTable[(a1 >>> 2) & 0x3f]);
|
||||
out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
|
||||
out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
|
||||
out.write(encodingTable[a3 & 0x3f]);
|
||||
}
|
||||
/*
|
||||
* process the tail end.
|
||||
*/
|
||||
int b1, b2, b3;
|
||||
int d1, d2;
|
||||
switch (modulus) {
|
||||
case 0: /* nothing left to do */
|
||||
break;
|
||||
case 1:
|
||||
d1 = data[off + dataLength] & 0xff;
|
||||
b1 = (d1 >>> 2) & 0x3f;
|
||||
b2 = (d1 << 4) & 0x3f;
|
||||
out.write(encodingTable[b1]);
|
||||
out.write(encodingTable[b2]);
|
||||
out.write(padding);
|
||||
out.write(padding);
|
||||
break;
|
||||
case 2:
|
||||
d1 = data[off + dataLength] & 0xff;
|
||||
d2 = data[off + dataLength + 1] & 0xff;
|
||||
b1 = (d1 >>> 2) & 0x3f;
|
||||
b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
|
||||
b3 = (d2 << 2) & 0x3f;
|
||||
out.write(encodingTable[b1]);
|
||||
out.write(encodingTable[b2]);
|
||||
out.write(encodingTable[b3]);
|
||||
out.write(padding);
|
||||
break;
|
||||
}
|
||||
return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
|
||||
}
|
||||
|
||||
private boolean ignore(char c) {
|
||||
return (c == '\n' || c == '\r' || c == '\t' || c == ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* decode the base 64 encoded byte data writing it to the given output stream,
|
||||
* whitespace characters will be ignored.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
|
||||
byte b1, b2, b3, b4;
|
||||
int outLen = 0;
|
||||
int end = off + length;
|
||||
while (end > off) {
|
||||
if (!ignore((char) data[end - 1])) {
|
||||
break;
|
||||
}
|
||||
end--;
|
||||
}
|
||||
int i = off;
|
||||
int finish = end - 4;
|
||||
i = nextI(data, i, finish);
|
||||
while (i < finish) {
|
||||
b1 = decodingTable[data[i++]];
|
||||
i = nextI(data, i, finish);
|
||||
b2 = decodingTable[data[i++]];
|
||||
i = nextI(data, i, finish);
|
||||
b3 = decodingTable[data[i++]];
|
||||
i = nextI(data, i, finish);
|
||||
b4 = decodingTable[data[i++]];
|
||||
out.write((b1 << 2) | (b2 >> 4));
|
||||
out.write((b2 << 4) | (b3 >> 2));
|
||||
out.write((b3 << 6) | b4);
|
||||
outLen += 3;
|
||||
i = nextI(data, i, finish);
|
||||
}
|
||||
outLen += decodeLastBlock(out, (char) data[end - 4], (char) data[end - 3], (char) data[end - 2], (char) data[end - 1]);
|
||||
return outLen;
|
||||
}
|
||||
|
||||
private int nextI(byte[] data, int i, int finish) {
|
||||
while ((i < finish) && ignore((char) data[i])) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* decode the base 64 encoded String data writing it to the given output stream,
|
||||
* whitespace characters will be ignored.
|
||||
*
|
||||
* @return the number of bytes produced.
|
||||
*/
|
||||
public int decode(String data, OutputStream out) throws IOException {
|
||||
byte b1, b2, b3, b4;
|
||||
int length = 0;
|
||||
int end = data.length();
|
||||
while (end > 0) {
|
||||
if (!ignore(data.charAt(end - 1))) {
|
||||
break;
|
||||
}
|
||||
end--;
|
||||
}
|
||||
int i = 0;
|
||||
int finish = end - 4;
|
||||
i = nextI(data, i, finish);
|
||||
while (i < finish) {
|
||||
b1 = decodingTable[data.charAt(i++)];
|
||||
i = nextI(data, i, finish);
|
||||
b2 = decodingTable[data.charAt(i++)];
|
||||
i = nextI(data, i, finish);
|
||||
b3 = decodingTable[data.charAt(i++)];
|
||||
i = nextI(data, i, finish);
|
||||
b4 = decodingTable[data.charAt(i++)];
|
||||
out.write((b1 << 2) | (b2 >> 4));
|
||||
out.write((b2 << 4) | (b3 >> 2));
|
||||
out.write((b3 << 6) | b4);
|
||||
length += 3;
|
||||
i = nextI(data, i, finish);
|
||||
}
|
||||
length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1));
|
||||
return length;
|
||||
}
|
||||
|
||||
private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) throws IOException {
|
||||
byte b1, b2, b3, b4;
|
||||
if (c3 == padding) {
|
||||
b1 = decodingTable[c1];
|
||||
b2 = decodingTable[c2];
|
||||
out.write((b1 << 2) | (b2 >> 4));
|
||||
return 1;
|
||||
} else if (c4 == padding) {
|
||||
b1 = decodingTable[c1];
|
||||
b2 = decodingTable[c2];
|
||||
b3 = decodingTable[c3];
|
||||
out.write((b1 << 2) | (b2 >> 4));
|
||||
out.write((b2 << 4) | (b3 >> 2));
|
||||
return 2;
|
||||
} else {
|
||||
b1 = decodingTable[c1];
|
||||
b2 = decodingTable[c2];
|
||||
b3 = decodingTable[c3];
|
||||
b4 = decodingTable[c4];
|
||||
out.write((b1 << 2) | (b2 >> 4));
|
||||
out.write((b2 << 4) | (b3 >> 2));
|
||||
out.write((b3 << 6) | b4);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private int nextI(String data, int i, int finish) {
|
||||
while ((i < finish) && ignore(data.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.encoders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Encode and decode byte arrays (typically from binary to 7-bit ASCII
|
||||
* encodings).
|
||||
*/
|
||||
public interface Encoder {
|
||||
int encode(byte[] data, int off, int length, OutputStream out) throws IOException;
|
||||
|
||||
int decode(byte[] data, int off, int length, OutputStream out) throws IOException;
|
||||
|
||||
int decode(String data, OutputStream out) throws IOException;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PemGenerationException extends IOException {
|
||||
private Throwable cause;
|
||||
|
||||
public PemGenerationException(String message, Throwable cause) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public PemGenerationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
public class PemHeader {
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public PemHeader(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return getHashCode(this.name) + 31 * getHashCode(this.value);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof PemHeader)) {
|
||||
return false;
|
||||
}
|
||||
PemHeader other = (PemHeader) o;
|
||||
return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value));
|
||||
}
|
||||
|
||||
private int getHashCode(String s) {
|
||||
if (s == null) {
|
||||
return 1;
|
||||
}
|
||||
return s.hashCode();
|
||||
}
|
||||
|
||||
private boolean isEqual(String s1, String s2) {
|
||||
if (s1 == s2) {
|
||||
return true;
|
||||
}
|
||||
if (s1 == null || s2 == null) {
|
||||
return false;
|
||||
}
|
||||
return s1.equals(s2);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public class PemObject implements PemObjectGenerator {
|
||||
private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList());
|
||||
private String type;
|
||||
private List headers;
|
||||
private byte[] content;
|
||||
|
||||
/**
|
||||
* Generic constructor for object without headers.
|
||||
*
|
||||
* @param type pem object type.
|
||||
* @param content the binary content of the object.
|
||||
*/
|
||||
public PemObject(String type, byte[] content) {
|
||||
this(type, EMPTY_LIST, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic constructor for object with headers.
|
||||
*
|
||||
* @param type pem object type.
|
||||
* @param headers a list of PemHeader objects.
|
||||
* @param content the binary content of the object.
|
||||
*/
|
||||
public PemObject(String type, List headers, byte[] content) {
|
||||
this.type = type;
|
||||
this.headers = Collections.unmodifiableList(headers);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public PemObject generate() throws PemGenerationException {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
public interface PemObjectGenerator {
|
||||
PemObject generate() throws PemGenerationException;
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PemReader extends BufferedReader {
|
||||
private static final String BEGIN = "-----BEGIN ";
|
||||
private static final String END = "-----END ";
|
||||
|
||||
public PemReader(Reader reader) {
|
||||
super(reader);
|
||||
}
|
||||
|
||||
public PemObject readPemObject() throws IOException {
|
||||
String line = readLine();
|
||||
while (line != null && !line.startsWith(BEGIN)) {
|
||||
line = readLine();
|
||||
}
|
||||
if (line != null) {
|
||||
line = line.substring(BEGIN.length());
|
||||
int index = line.indexOf('-');
|
||||
String type = line.substring(0, index);
|
||||
if (index > 0) {
|
||||
return loadObject(type);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PemObject loadObject(String type) throws IOException {
|
||||
String line;
|
||||
String endMarker = END + type;
|
||||
StringBuilder buf = new StringBuilder();
|
||||
List headers = new ArrayList();
|
||||
while ((line = readLine()) != null) {
|
||||
if (line.indexOf(":") >= 0) {
|
||||
int index = line.indexOf(':');
|
||||
String hdr = line.substring(0, index);
|
||||
String value = line.substring(index + 1).trim();
|
||||
headers.add(new PemHeader(hdr, value));
|
||||
continue;
|
||||
}
|
||||
if (line.indexOf(endMarker) != -1) {
|
||||
break;
|
||||
}
|
||||
buf.append(line.trim());
|
||||
}
|
||||
if (line == null) {
|
||||
throw new IOException(endMarker + " not found");
|
||||
}
|
||||
return new PemObject(type, headers, Base64.decode(buf.toString()));
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012-2014 Arne Schwabe
|
||||
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
|
||||
*/
|
||||
package org.spongycastle.util.io.pem;
|
||||
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A generic PEM writer, based on RFC 1421
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class PemWriter extends BufferedWriter {
|
||||
private static final int LINE_LENGTH = 64;
|
||||
private final int nlLength;
|
||||
private char[] buf = new char[LINE_LENGTH];
|
||||
|
||||
/**
|
||||
* Base constructor.
|
||||
*
|
||||
* @param out output stream to use.
|
||||
*/
|
||||
public PemWriter(Writer out) {
|
||||
super(out);
|
||||
String nl = System.getProperty("line.separator");
|
||||
if (nl != null) {
|
||||
nlLength = nl.length();
|
||||
} else {
|
||||
nlLength = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes or characters required to contain the
|
||||
* passed in object if it is PEM encoded.
|
||||
*
|
||||
* @param obj pem object to be output
|
||||
* @return an estimate of the number of bytes
|
||||
*/
|
||||
public int getOutputSize(PemObject obj) {
|
||||
// BEGIN and END boundaries.
|
||||
int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4;
|
||||
if (!obj.getHeaders().isEmpty()) {
|
||||
for (Iterator it = obj.getHeaders().iterator(); it.hasNext(); ) {
|
||||
PemHeader hdr = (PemHeader) it.next();
|
||||
size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength;
|
||||
}
|
||||
size += nlLength;
|
||||
}
|
||||
// base64 encoding
|
||||
int dataLen = ((obj.getContent().length + 2) / 3) * 4;
|
||||
size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength);
|
||||
return size;
|
||||
}
|
||||
|
||||
public void writeObject(PemObjectGenerator objGen) throws IOException {
|
||||
PemObject obj = objGen.generate();
|
||||
writePreEncapsulationBoundary(obj.getType());
|
||||
if (!obj.getHeaders().isEmpty()) {
|
||||
for (Iterator it = obj.getHeaders().iterator(); it.hasNext(); ) {
|
||||
PemHeader hdr = (PemHeader) it.next();
|
||||
this.write(hdr.getName());
|
||||
this.write(": ");
|
||||
this.write(hdr.getValue());
|
||||
this.newLine();
|
||||
}
|
||||
this.newLine();
|
||||
}
|
||||
writeEncoded(obj.getContent());
|
||||
writePostEncapsulationBoundary(obj.getType());
|
||||
}
|
||||
|
||||
private void writeEncoded(byte[] bytes) throws IOException {
|
||||
bytes = Base64.encode(bytes);
|
||||
for (int i = 0; i < bytes.length; i += buf.length) {
|
||||
int index = 0;
|
||||
while (index != buf.length) {
|
||||
if ((i + index) >= bytes.length) {
|
||||
break;
|
||||
}
|
||||
buf[index] = (char) bytes[i + index];
|
||||
index++;
|
||||
}
|
||||
this.write(buf, 0, index);
|
||||
this.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void writePreEncapsulationBoundary(String type) throws IOException {
|
||||
this.write("-----BEGIN " + type + "-----");
|
||||
this.newLine();
|
||||
}
|
||||
|
||||
private void writePostEncapsulationBoundary(String type) throws IOException {
|
||||
this.write("-----END " + type + "-----");
|
||||
this.newLine();
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue