remove unused openvpn module

This commit is contained in:
Yuriy Liskov 2022-11-11 21:32:08 +02:00
parent 958f212c8f
commit b0bd74c5b4
125 changed files with 3 additions and 11852 deletions

View File

@ -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
openvpn/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -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.

View File

@ -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

View File

@ -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>

View File

@ -1,3 +0,0 @@
package de.blinkt.openvpn.api;
parcelable APIVpnProfile;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -1,3 +0,0 @@
package de.blinkt.openvpn.core;
parcelable ConnectionStatus;

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -1,3 +0,0 @@
package de.blinkt.openvpn.core;
parcelable LogItem;

View File

@ -1,4 +0,0 @@
package de.blinkt.openvpn.core;
parcelable TrafficHistory;

View File

@ -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"

View File

@ -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()
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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)
}
}

View File

@ -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
// }
}

View File

@ -1,6 +0,0 @@
package com.liskovsoft.openvpn.service
interface OnVPNStatusChangeListener {
fun onProfileLoaded(profileLoaded: Boolean)
fun onVPNStatusChanged(vpnActivated: Boolean)
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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))));
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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()));
}
}

View File

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More