aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
diff options
context:
space:
mode:
authorpacien2018-02-16 18:23:01 +0100
committerpacien2018-02-16 18:23:01 +0100
commitc359d78bcd45cb506bac51a616ef62af0845df85 (patch)
treee526bbf710d301310fdee6a0da6399fb79c71f66 /app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
parent680fe07b6ea000ee29ac28e2f48665433e7011df (diff)
downloadtincapp-c359d78bcd45cb506bac51a616ef62af0845df85.tar.gz
Refactor activities and service, locking app at daemon startup and shutdown
Diffstat (limited to 'app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt')
-rw-r--r--app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt113
1 files changed, 64 insertions, 49 deletions
diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
index ec0512a..ce41b89 100644
--- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
+++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
@@ -2,9 +2,9 @@ package org.pacien.tincapp.service
2 2
3import android.app.Service 3import android.app.Service
4import android.content.Intent 4import android.content.Intent
5import android.net.Uri
6import android.net.VpnService 5import android.net.VpnService
7import android.os.ParcelFileDescriptor 6import android.os.ParcelFileDescriptor
7import android.support.v4.content.LocalBroadcastManager
8import android.util.Log 8import android.util.Log
9import java8.util.concurrent.CompletableFuture 9import java8.util.concurrent.CompletableFuture
10import org.apache.commons.configuration2.ex.ConversionException 10import org.apache.commons.configuration2.ex.ConversionException
@@ -19,32 +19,41 @@ import org.pacien.tincapp.data.TincConfiguration
19import org.pacien.tincapp.data.VpnInterfaceConfiguration 19import org.pacien.tincapp.data.VpnInterfaceConfiguration
20import org.pacien.tincapp.extensions.Java.applyIgnoringException 20import org.pacien.tincapp.extensions.Java.applyIgnoringException
21import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg 21import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg
22import org.pacien.tincapp.intent.action.TINC_SCHEME 22import org.pacien.tincapp.intent.Actions
23import org.pacien.tincapp.utils.PemUtils 23import org.pacien.tincapp.utils.TincKeyring
24import java.io.File
25import java.io.FileNotFoundException 24import java.io.FileNotFoundException
26import java.io.IOException
27 25
28/** 26/**
29 * @author pacien 27 * @author pacien
30 */ 28 */
31class TincVpnService : VpnService() { 29class TincVpnService : VpnService() {
32
33 override fun onDestroy() { 30 override fun onDestroy() {
34 stopVpn() 31 stopVpn()
35 super.onDestroy() 32 super.onDestroy()
36 } 33 }
37 34
38 override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { 35 override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
39 if (isConnected()) stopVpn() 36 Log.i(TAG, intent.action)
40 startVpn(intent.data.schemeSpecificPart, intent.data.fragment) 37
41 return Service.START_REDELIVER_INTENT 38 when {
39 intent.action == Actions.ACTION_CONNECT && intent.scheme == Actions.TINC_SCHEME ->
40 startVpn(intent.data.schemeSpecificPart, intent.data.fragment)
41 intent.action == Actions.ACTION_DISCONNECT ->
42 stopVpn()
43 else ->
44 throw IllegalArgumentException("Invalid intent action received.")
45 }
46
47 return Service.START_NOT_STICKY
42 } 48 }
43 49
44 private fun startVpn(netName: String, passphrase: String? = null) { 50 private fun startVpn(netName: String, passphrase: String? = null): Unit = synchronized(this) {
45 if (netName.isBlank()) 51 if (netName.isBlank())
46 return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") 52 return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api")
47 53
54 if (TincKeyring.needsPassphrase(netName) && passphrase == null)
55 return reportError(resources.getString(R.string.message_passphrase_required))
56
48 if (!AppPaths.storageAvailable()) 57 if (!AppPaths.storageAvailable())
49 return reportError(resources.getString(R.string.message_storage_unavailable)) 58 return reportError(resources.getString(R.string.message_storage_unavailable))
50 59
@@ -52,6 +61,7 @@ class TincVpnService : VpnService() {
52 return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") 61 return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration")
53 62
54 Log.i(TAG, "Starting tinc daemon for network \"$netName\".") 63 Log.i(TAG, "Starting tinc daemon for network \"$netName\".")
64 if (isConnected()) stopVpn()
55 65
56 val interfaceCfg = try { 66 val interfaceCfg = try {
57 VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName))) 67 VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName)))
@@ -65,37 +75,41 @@ class TincVpnService : VpnService() {
65 Builder().setSession(netName) 75 Builder().setSession(netName)
66 .applyCfg(interfaceCfg) 76 .applyCfg(interfaceCfg)
67 .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } 77 .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) }
68 .establish() 78 .establish()!!
69 } catch (e: IllegalArgumentException) { 79 } catch (e: IllegalArgumentException) {
70 return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") 80 return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface")
71 } 81 }
72 82
73 val privateKeys = try { 83 val privateKeys = try {
74 val tincCfg = TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))) 84 TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))).let { tincCfg ->
75 85 Pair(
76 Pair( 86 TincKeyring.openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase),
77 openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase), 87 TincKeyring.openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase))
78 openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase) 88 }
79 )
80 } catch (e: FileNotFoundException) { 89 } catch (e: FileNotFoundException) {
81 Pair(null, null) 90 Pair(null, null)
82 } catch (e: PEMException) { 91 } catch (e: PEMException) {
83 return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message)) 92 return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message))
84 } 93 }
85 94
86 val daemon = Tincd.start(netName, deviceFd!!.fd, privateKeys.first?.fd, privateKeys.second?.fd) 95 val daemon = Tincd.start(netName, deviceFd.fd, privateKeys.first?.fd, privateKeys.second?.fd)
87 setState(netName, interfaceCfg, deviceFd, daemon) 96 setState(netName, interfaceCfg, deviceFd, daemon)
88 Log.i(TAG, "tinc daemon started.") 97 waitForDaemonStartup().thenRun {
98 deviceFd.close()
99 Log.i(TAG, "tinc daemon started.")
100 broadcastEvent(Actions.EVENT_CONNECTED)
101 }
89 } 102 }
90 103
91 private fun openPrivateKey(f: File?, passphrase: String?): ParcelFileDescriptor? { 104 private fun stopVpn(): Unit = synchronized(this) {
92 if (f == null || !f.exists() || passphrase == null) return null 105 Log.i(TAG, "Stopping any running tinc daemon.")
93 106 netName?.let {
94 val pipe = ParcelFileDescriptor.createPipe() 107 Tinc.stop(it).thenRun {
95 val decryptedKey = PemUtils.decrypt(PemUtils.read(f), passphrase) 108 Log.i(TAG, "All tinc daemons stopped.")
96 val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]) 109 broadcastEvent(Actions.EVENT_DISCONNECTED)
97 PemUtils.write(decryptedKey, outputStream.writer()) 110 setState(null, null, null, null)
98 return pipe[0] 111 }
112 }
99 } 113 }
100 114
101 private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { 115 private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) {
@@ -108,10 +122,18 @@ class TincVpnService : VpnService() {
108 if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null) 122 if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null)
109 } 123 }
110 124
111 companion object { 125 private fun broadcastEvent(event: String) {
126 LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(event))
127 }
112 128
113 val TAG = this::class.java.canonicalName!! 129 private fun waitForDaemonStartup() =
130 CompletableFuture
131 .runAsync { Thread.sleep(SETUP_DELAY) }
132 .thenCompose { netName?.let { Tinc.pid(it) } ?: CompletableFuture.completedFuture(0) }
114 133
134 companion object {
135 private const val SETUP_DELAY = 500L // ms
136 private val TAG = this::class.java.canonicalName!!
115 private var netName: String? = null 137 private var netName: String? = null
116 private var interfaceCfg: VpnInterfaceConfiguration? = null 138 private var interfaceCfg: VpnInterfaceConfiguration? = null
117 private var fd: ParcelFileDescriptor? = null 139 private var fd: ParcelFileDescriptor? = null
@@ -119,35 +141,28 @@ class TincVpnService : VpnService() {
119 141
120 private fun setState(netName: String?, interfaceCfg: VpnInterfaceConfiguration?, 142 private fun setState(netName: String?, interfaceCfg: VpnInterfaceConfiguration?,
121 fd: ParcelFileDescriptor?, daemon: CompletableFuture<Void>?) { 143 fd: ParcelFileDescriptor?, daemon: CompletableFuture<Void>?) {
144
122 TincVpnService.netName = netName 145 TincVpnService.netName = netName
123 TincVpnService.interfaceCfg = interfaceCfg 146 TincVpnService.interfaceCfg = interfaceCfg
124 TincVpnService.fd = fd 147 TincVpnService.fd = fd
125 TincVpnService.daemon = daemon 148 TincVpnService.daemon = daemon
126 } 149 }
127 150
128 fun startVpn(netName: String, passphrase: String? = null) {
129 App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java)
130 .setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).fragment(passphrase).build()))
131 }
132