Skip to content

Commit 9d0fc89

Browse files
committed
tailscale: Add tailssh server
1 parent cf924dc commit 9d0fc89

9 files changed

Lines changed: 433 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
*.iml
22
.gradle
3+
.kotlin/
34
/local.properties
45
/.idea/
56
.DS_Store

app/src/main/aidl/io/nekohasekai/sfa/bg/IRootService.aidl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.nekohasekai.sfa.bg;
22

33
import android.os.ParcelFileDescriptor;
44
import io.nekohasekai.sfa.bg.INeighborTableCallback;
5+
import io.nekohasekai.sfa.bg.IRootShellSession;
56
import io.nekohasekai.sfa.bg.ParceledListSlice;
67

78
interface IRootService {
@@ -16,4 +17,8 @@ interface IRootService {
1617
void registerNeighborTableCallback(in INeighborTableCallback callback) = 4;
1718

1819
oneway void unregisterNeighborTableCallback(in INeighborTableCallback callback) = 5;
20+
21+
IRootShellSession openShellSession(String user, String command, in String[] env, String term, int rows, int cols) = 6;
22+
23+
String lookupSFTPServer() = 7;
1924
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.nekohasekai.sfa.bg;
2+
3+
import android.os.ParcelFileDescriptor;
4+
5+
interface IRootShellSession {
6+
ParcelFileDescriptor getMasterFD();
7+
void resize(int rows, int cols);
8+
void signal(int sig);
9+
int waitFor();
10+
void close();
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.nekohasekai.sfa.bg
2+
3+
import io.nekohasekai.libbox.Int32Iterator
4+
5+
class IntArrayIterator(private val array: IntArray) : Int32Iterator {
6+
private var index = 0
7+
8+
override fun len(): Int = array.size
9+
10+
override fun hasNext(): Boolean = index < array.size
11+
12+
override fun next(): Int = array[index++]
13+
}

app/src/main/java/io/nekohasekai/sfa/bg/PlatformInterfaceWrapper.kt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.nekohasekai.sfa.bg
33
import android.annotation.SuppressLint
44
import android.net.NetworkCapabilities
55
import android.os.Build
6+
import android.os.ParcelFileDescriptor
67
import android.os.Process
78
import android.system.OsConstants
89
import android.util.Log
@@ -15,12 +16,17 @@ import io.nekohasekai.libbox.NeighborEntryIterator
1516
import io.nekohasekai.libbox.NeighborUpdateListener
1617
import io.nekohasekai.libbox.NetworkInterfaceIterator
1718
import io.nekohasekai.libbox.PlatformInterface
19+
import io.nekohasekai.libbox.PlatformUser
20+
import io.nekohasekai.libbox.ShellSession
1821
import io.nekohasekai.libbox.StringIterator
1922
import io.nekohasekai.libbox.TunOptions
2023
import io.nekohasekai.libbox.WIFIState
2124
import io.nekohasekai.sfa.Application
25+
import io.nekohasekai.sfa.ktx.toList
26+
import io.nekohasekai.sfa.ktx.toStringIterator
2227
import kotlinx.coroutines.Dispatchers
2328
import kotlinx.coroutines.runBlocking
29+
import java.io.File
2430
import java.net.Inet6Address
2531
import java.net.InetSocketAddress
2632
import java.net.InterfaceAddress
@@ -205,6 +211,99 @@ interface PlatformInterfaceWrapper : PlatformInterface {
205211
}
206212
}
207213

214+
override fun usePlatformShell(): Boolean = true
215+
216+
override fun checkPlatformShell() {
217+
val available = RootClient.rootAvailable.value ?: runBlocking(Dispatchers.IO) {
218+
RootClient.checkRootAvailable()
219+
}
220+
if (!available) {
221+
error("missing root permission")
222+
}
223+
}
224+
225+
override fun openShellSession(
226+
user: PlatformUser?,
227+
command: String?,
228+
environ: StringIterator?,
229+
term: String?,
230+
rows: Int,
231+
cols: Int,
232+
): ShellSession {
233+
user!!
234+
val envList = environ?.toList().orEmpty()
235+
if (user.uid == Process.myUid()) {
236+
val resolved = ResolvedUser(user.username, user.uid, user.gid, user.homeDir)
237+
val shell = UserResolver.findShell(resolved)
238+
val shellEnv = buildBasicEnvironment(envList.toTypedArray(), shell, resolved.homeDir, term)
239+
val args = if (command.isNullOrEmpty()) {
240+
arrayOf("-" + File(shell).name)
241+
} else {
242+
arrayOf(File(shell).name, "-c", command)
243+
}
244+
val argsIter = args.asIterable().toStringIterator()
245+
val envIter = shellEnv.asIterable().toStringIterator()
246+
return if (term.isNullOrEmpty()) {
247+
Libbox.openNativePipeSession(
248+
shell,
249+
resolved.homeDir,
250+
argsIter,
251+
envIter,
252+
-1,
253+
-1,
254+
null,
255+
)
256+
} else {
257+
Libbox.openNativeShellSession(
258+
shell,
259+
resolved.homeDir,
260+
argsIter,
261+
envIter,
262+
term,
263+
rows,
264+
cols,
265+
-1,
266+
-1,
267+
null,
268+
)
269+
}
270+
}
271+
val rootSession = runBlocking(Dispatchers.IO) {
272+
RootClient.openShellSession(
273+
user.username,
274+
command,
275+
envList.toTypedArray(),
276+
term,
277+
rows,
278+
cols,
279+
)
280+
}
281+
return RootShellSessionWrapper(rootSession)
282+
}
283+
284+
override fun readSystemSSHHostKey(): io.nekohasekai.libbox.StringBox {
285+
error("not supported")
286+
}
287+
288+
override fun lookupSFTPServer(): io.nekohasekai.libbox.StringBox {
289+
val path = runBlocking(Dispatchers.IO) {
290+
RootClient.lookupSFTPServer()
291+
}
292+
val result = io.nekohasekai.libbox.StringBox()
293+
result.value = path
294+
return result
295+
}
296+
297+
override fun lookupUser(username: String?): io.nekohasekai.libbox.PlatformUser {
298+
val resolved = UserResolver.resolve(Application.packageManager, username!!)
299+
val platformUser = io.nekohasekai.libbox.PlatformUser()
300+
platformUser.username = resolved.packageName
301+
platformUser.uid = resolved.uid
302+
platformUser.gid = resolved.gid
303+
platformUser.homeDir = resolved.homeDir
304+
return platformUser
305+
}
306+
208307
override fun registerMyInterface(name: String?) {
209308
}
210309

@@ -216,6 +315,29 @@ interface PlatformInterfaceWrapper : PlatformInterface {
216315
}
217316
}
218317

318+
private class RootShellSessionWrapper(
319+
private val rootSession: IRootShellSession,
320+
) : ShellSession {
321+
private val masterPfd: ParcelFileDescriptor = rootSession.masterFD
322+
323+
override fun masterFD(): Int = masterPfd.fd
324+
325+
override fun resize(rows: Int, cols: Int) {
326+
rootSession.resize(rows, cols)
327+
}
328+
329+
override fun signal(signal: Int) {
330+
rootSession.signal(signal)
331+
}
332+
333+
override fun waitExit(): Int = rootSession.waitFor()
334+
335+
override fun close() {
336+
masterPfd.close()
337+
rootSession.close()
338+
}
339+
}
340+
219341
private class NeighborEntryArray(private val iterator: Iterator<LibboxNeighborEntry>) : NeighborEntryIterator {
220342
override fun hasNext(): Boolean = iterator.hasNext()
221343

app/src/main/java/io/nekohasekai/sfa/bg/RootClient.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,31 @@ object RootClient {
144144
}
145145
}
146146

147+
suspend fun lookupSFTPServer(): String {
148+
val svc = bindService()
149+
try {
150+
return svc.lookupSFTPServer()
151+
} catch (e: RemoteException) {
152+
throw e.rethrowAsRuntime()
153+
}
154+
}
155+
156+
suspend fun openShellSession(
157+
user: String,
158+
command: String?,
159+
env: Array<String>,
160+
term: String?,
161+
rows: Int,
162+
cols: Int,
163+
): IRootShellSession {
164+
val svc = bindService()
165+
try {
166+
return svc.openShellSession(user, command, env, term, rows, cols)
167+
} catch (e: RemoteException) {
168+
throw e.rethrowAsRuntime()
169+
}
170+
}
171+
147172
suspend fun unregisterNeighborTableCallback(callback: INeighborTableCallback) {
148173
try {
149174
service?.unregisterNeighborTableCallback(callback)

0 commit comments

Comments
 (0)