diff options
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.kt | 210 |
1 files changed, 105 insertions, 105 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 601ffbb..278d20a 100644 --- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt +++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt | |||
@@ -29,122 +29,122 @@ import java.io.IOException | |||
29 | */ | 29 | */ |
30 | class TincVpnService : VpnService() { | 30 | class TincVpnService : VpnService() { |
31 | 31 | ||
32 | override fun onDestroy() { | 32 | override fun onDestroy() { |
33 | stopVpn() | 33 | stopVpn() |
34 | super.onDestroy() | 34 | super.onDestroy() |
35 | } | ||
36 | |||
37 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | ||
38 | if (isConnected()) stopVpn() | ||
39 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) | ||
40 | return Service.START_REDELIVER_INTENT | ||
41 | } | ||
42 | |||
43 | private fun startVpn(netName: String, passphrase: String? = null) { | ||
44 | if (netName.isBlank()) | ||
45 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") | ||
46 | |||
47 | if (!AppPaths.storageAvailable()) | ||
48 | return reportError(resources.getString(R.string.message_storage_unavailable)) | ||
49 | |||
50 | if (!AppPaths.confDir(netName).exists()) | ||
51 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") | ||
52 | |||
53 | Log.i(TAG, "Starting tinc daemon for network \"$netName\".") | ||
54 | |||
55 | val interfaceCfg = try { | ||
56 | VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName))) | ||
57 | } catch (e: FileNotFoundException) { | ||
58 | return reportError(resources.getString(R.string.message_network_config_not_found_format, e.message!!), e, "configuration") | ||
59 | } catch (e: ConversionException) { | ||
60 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") | ||
35 | } | 61 | } |
36 | 62 | ||
37 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { | 63 | val deviceFd = try { |
38 | if (isConnected()) stopVpn() | 64 | Builder().setSession(netName) |
39 | startVpn(intent.data.schemeSpecificPart, intent.data.fragment) | 65 | .applyCfg(interfaceCfg) |
40 | return Service.START_REDELIVER_INTENT | 66 | .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } |
67 | .establish() | ||
68 | } catch (e: IllegalArgumentException) { | ||
69 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") | ||
41 | } | 70 | } |
42 | 71 | ||
43 | private fun startVpn(netName: String, passphrase: String? = null) { | 72 | val privateKeys = try { |
44 | if (netName.isBlank()) | 73 | val tincCfg = TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))) |
45 | return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api") | 74 | |
46 | 75 | Pair( | |
47 | if (!AppPaths.storageAvailable()) | 76 | openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase), |
48 | return reportError(resources.getString(R.string.message_storage_unavailable)) | 77 | openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase) |
49 | 78 | ) | |
50 | if (!AppPaths.confDir(netName).exists()) | 79 | } catch (e: FileNotFoundException) { |
51 | return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration") | 80 | Pair(null, null) |
52 | 81 | } catch (e: PEMException) { | |
53 | Log.i(TAG, "Starting tinc daemon for network \"$netName\".") | 82 | return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message)) |
54 | |||
55 | val interfaceCfg = try { | ||
56 | VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName))) | ||
57 | } catch (e: FileNotFoundException) { | ||
58 | return reportError(resources.getString(R.string.message_network_config_not_found_format, e.message!!), e, "configuration") | ||
59 | } catch (e: ConversionException) { | ||
60 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") | ||
61 | } | ||
62 | |||
63 | val deviceFd = try { | ||
64 | Builder().setSession(netName) | ||
65 | .applyCfg(interfaceCfg) | ||
66 | .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } | ||
67 | .establish() | ||
68 | } catch (e: IllegalArgumentException) { | ||
69 | return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface") | ||
70 | } | ||
71 | |||
72 | val privateKeys = try { | ||
73 | val tincCfg = TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))) | ||
74 | |||
75 | Pair( | ||
76 | openPrivateKey(tincCfg.ed25519PrivateKeyFile ?: AppPaths.defaultEd25519PrivateKeyFile(netName), passphrase), | ||
77 | openPrivateKey(tincCfg.privateKeyFile ?: AppPaths.defaultRsaPrivateKeyFile(netName), passphrase) | ||
78 | ) | ||
79 | } catch (e: FileNotFoundException) { | ||
80 | Pair(null, null) | ||
81 | } catch (e: PEMException) { | ||
82 | return reportError(resources.getString(R.string.message_could_not_decrypt_private_keys_format, e.message)) | ||
83 | } | ||
84 | |||
85 | Tincd.start(netName, deviceFd!!.fd, privateKeys.first?.fd, privateKeys.second?.fd) | ||
86 | setState(true, netName, interfaceCfg, deviceFd) | ||
87 | Log.i(TAG, "tinc daemon started.") | ||
88 | } | 83 | } |
89 | 84 | ||
90 | private fun openPrivateKey(f: File?, passphrase: String?): ParcelFileDescriptor? { | 85 | Tincd.start(netName, deviceFd!!.fd, privateKeys.first?.fd, privateKeys.second?.fd) |
91 | if (f == null || !f.exists() || passphrase == null) return null | 86 | setState(true, netName, interfaceCfg, deviceFd) |
92 | 87 | Log.i(TAG, "tinc daemon started.") | |
93 | val pipe = ParcelFileDescriptor.createPipe() | 88 | } |
94 | val decryptedKey = PemUtils.decrypt(PemUtils.read(f), passphrase) | 89 | |
95 | val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]) | 90 | private fun openPrivateKey(f: File?, passphrase: String?): ParcelFileDescriptor? { |
96 | PemUtils.write(decryptedKey, outputStream.writer()) | 91 | if (f == null || !f.exists() || passphrase == null) return null |
97 | return pipe[0] | 92 | |
93 | val pipe = ParcelFileDescriptor.createPipe() | ||
94 | val decryptedKey = PemUtils.decrypt(PemUtils.read(f), passphrase) | ||
95 | val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]) | ||
96 | PemUtils.write(decryptedKey, outputStream.writer()) | ||
97 | return pipe[0] | ||
98 | } | ||
99 | |||
100 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { | ||
101 | if (e != null) | ||
102 | Log.e(TAG, msg, e) | ||
103 | else | ||
104 | Log.e(TAG, msg) | ||
105 | |||
106 | App.alert(R.string.title_unable_to_start_tinc, msg, | ||
107 | if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null) | ||
108 | } | ||
109 | |||
110 | companion object { | ||
111 | |||
112 | val TAG = this::class.java.canonicalName!! | ||
113 | |||
114 | private var connected: Boolean = false | ||
115 | private var netName: String? = null | ||
116 | private var interfaceCfg: VpnInterfaceConfiguration? = null | ||
117 | private var fd: ParcelFileDescriptor? = null | ||
118 | |||
119 | private fun setState(connected: Boolean, netName: String?, interfaceCfg: VpnInterfaceConfiguration?, fd: ParcelFileDescriptor?) { | ||
120 | TincVpnService.connected = connected | ||
121 | TincVpnService.netName = netName | ||
122 | TincVpnService.interfaceCfg = interfaceCfg | ||
123 | TincVpnService.fd = fd | ||
98 | } | 124 | } |
99 | 125 | ||
100 | private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) { | 126 | fun startVpn(netName: String, passphrase: String? = null) { |
101 | if (e != null) | 127 | App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java) |
102 | Log.e(TAG, msg, e) | 128 | .setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).fragment(passphrase).build())) |
103 | else | 129 | } |
104 | Log.e(TAG, msg) | ||
105 | 130 | ||
106 | App.alert(R.string.title_unable_to_start_tinc, msg, | 131 | fun stopVpn() { |
107 | if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null) | 132 | try { |
133 | Log.i(TAG, "Stopping any running tinc daemon.") | ||
134 | if (netName != null) Tinc.stop(netName!!) | ||
135 | fd?.close() | ||
136 | Log.i(TAG, "All tinc daemons stopped.") | ||
137 | } catch (e: IOException) { | ||
138 | Log.wtf(TAG, e) | ||
139 | } finally { | ||
140 | setState(false, null, null, null) | ||
141 | } | ||
108 | } | 142 | } |
109 | 143 | ||
110 | companion object { | 144 | fun getCurrentNetName() = netName |
111 | 145 | fun getCurrentInterfaceCfg() = interfaceCfg | |
112 | val TAG = this::class.java.canonicalName!! | 146 | fun isConnected() = connected |
113 | |||
114 | private var connected: Boolean = false | ||
115 | private var netName: String? = null | ||
116 | private var interfaceCfg: VpnInterfaceConfiguration? = null | ||
117 | private var fd: ParcelFileDescriptor? = null | ||
118 | |||
119 | private fun setState(connected: Boolean, netName: String?, interfaceCfg: VpnInterfaceConfiguration?, fd: ParcelFileDescriptor?) { | ||
120 | TincVpnService.connected = connected | ||
121 | TincVpnService.netName = netName | ||
122 | TincVpnService.interfaceCfg = interfaceCfg | ||
123 | TincVpnService.fd = fd | ||
124 | } | ||
125 |