commit 6833f63b0fda04c468c8b8c45fa05c6887f940dd Author: ZiCode0 Date: Sun Apr 6 20:30:38 2025 +1200 :tada: begin project :tada: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5fa895 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +dropbear/default_options_guard.h +.gradle/ +.idea/ +build/ +app/build/ +local.properties + +# ignore binary files (libraries) +obj/ +app/src/main/lib/*/*.so + +# ignore generated apk paths +app/debug/ +app/release/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0c7da1 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +1. Run `./prepare` to build libraries. +- ready lib files located in *app/src/main/lib/armeabi-v7a* +2. Run `./gradlew assembleRelease` to start building release apk. \ No newline at end of file diff --git a/_other/crash.20200109 b/_other/crash.20200109 new file mode 100644 index 0000000..4b4a905 --- /dev/null +++ b/_other/crash.20200109 @@ -0,0 +1,65 @@ +Yesterday, 7:03 PM on app version 24 +OnePlus OnePlus 7T (OnePlus7T), Android 10 +Report 1 of 3 + +android.app.RemoteServiceException: + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2038) + at android.os.Handler.dispatchMessage (Handler.java:107) + at android.os.Looper.loop (Looper.java:214) + at android.app.ActivityThread.main (ActivityThread.java:7682) + at java.lang.reflect.Method.invoke (Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:516) + at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:950) + + +2021/01/17 - confirmed this is still happening with version 26 of the +app, reported on Android 8.1 (SDK 27) and Android 10 (SDK 29). + +https://stackoverflow.com/questions/51222082/why-do-i-get-remoteserviceexception-for-creating-updating-a-notification +suggests it has something to do with the Notification passed to +startForeground(), but their "workaround" was to only modify the +Notification from the Service thread, which we already do. + +I could not reproduce it under the emulator with Android 8.1 and starting +the service is several different contexts, including using the START +broadcast after the app had been force stopped, or when the service had +started because the package had just been updated (UI not displayed yet). +I also tested launching the PendingIntent from those contexts. + +Looking at ActivityThread.java that came with my SDK and it looks like +RemoteServiceException() is just called whenever handleMessage() is +passed a msg.what == SCHEDULE_CRASH, which is sent from +ApplicationThread.scheduleCrash(), which is called (indirectly) from: + ActivityStackSupervisor.PendingActivityLaunch.sendErrorResult() + ... ultimately called when there's an exception during a + PendingIntent launch, with the string from that exception?? + BroadcastQueue.performReceiveLocked() on a RemoteException with the + string "can't deliver broadcast" + ActivityManagerShellCommand.runCrash() with "shell-induced crash" + ServiceRecord.postNotification() with "Bad notification for startForeground:" + ActiveServices.serviceForegroundCrash() with + "Context.startForegroundService() did not then call + Service.startForeground():" + NativeCrashListener.NativeCrashReporter.run() but I'm not sure it + actually makes it into the same crashApplication() or not? + RuntimeInit.wtf() not sure where this error winds up either but + +Most of those, I think, would be pretty good about putting a message +there so I'm not clear why no message is provided. + +Googling some more: + https://github.com/commons-app/apps-android-commons/issues/1877 + failed to set a NotificationChannel but I think I've already + crossed that bridge. + https://www.xspdf.com/resolution/50183960.html + the XML layout for their Notification specified a manual height for + the root element?? the outer element for mine is wrap_content, but + one of the contents is an ImageView with a manual size (60dp x + 60dp) ?? (and many others at this URL) Could maybe be a problem + with the res/drawable for the notification icon? + +Crashlytics would apparently provide more info -- I'm not clear why +Google's crash reporter isn't giving me the RemoteServiceException +message on its own though! But Crashlytics smells like bloatware to me +and from looking at the source (/usr/src/firebase-android-sdk), I didn't +immediately see how it could change the reporting of this sort of crash. diff --git a/_other/crash.20200122 b/_other/crash.20200122 new file mode 100644 index 0000000..9006357 --- /dev/null +++ b/_other/crash.20200122 @@ -0,0 +1,13 @@ +Jan 22, 9:01 AM on app version 24 +Xiaomi MiBox S (oneday), Android 8.1 +Report 1 of 1 +*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** +pid: 0, tid: 0 >>> org.galexander.sshd <<< + +backtrace: + #00 pc 000000000001a6ac /system/lib/libc.so (abort+63) + #01 pc 0000000000020c79 /system/lib/libc.so (sigprocmask+48) + + +2021/01/17 - Looking at the play console today, this crash is not in the +last 60 days which is all it will show me. So I guess it's rare. diff --git a/_other/crash.20201123 b/_other/crash.20201123 new file mode 100644 index 0000000..3517347 --- /dev/null +++ b/_other/crash.20201123 @@ -0,0 +1,23 @@ +java.lang.IllegalStateException: + at android.os.Parcel.createException (Parcel.java:1958) + at android.os.Parcel.readException (Parcel.java:1918) + at android.os.Parcel.readException (Parcel.java:1868) + at android.app.IActivityManager$Stub$Proxy.reportAssistContextExtras (IActivityManager.java:7107) + at android.app.ActivityThread.handleRequestAssistContextExtras (ActivityThread.java:3212) + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1764) + at android.os.Handler.dispatchMessage (Handler.java:106) + at android.os.Looper.loop (Looper.java:193) + at android.app.ActivityThread.main (ActivityThread.java:6669) + at java.lang.reflect.Method.invoke (Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493) + at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:858) +Caused by: android.os.RemoteException: + at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1577) + at android.app.ContextImpl.startServiceAsUser (ContextImpl.java:1549) + at com.android.server.am.ActivityManagerService.reportAssistContextExtras (ActivityManagerService.java:14185) + at android.app.IActivityManager$Stub.onTransact$reportAssistContextExtras$ (IActivityManager.java:10882) + at android.app.IActivityManager$Stub.onTransact (IActivityManager.java:1953) + + +2021/01/17 - reported exactly once on an unknown Android 9 device with +version 24 of SimpleSSHD. A mystery I will leave unless it recurs. diff --git a/_other/crash.20201225 b/_other/crash.20201225 new file mode 100644 index 0000000..b57e548 --- /dev/null +++ b/_other/crash.20201225 @@ -0,0 +1,33 @@ +java.lang.UnsatisfiedLinkError: + at java.lang.Runtime.loadLibrary0 (Runtime.java:1016) + at java.lang.System.loadLibrary (System.java:1669) + at org.galexander.sshd.SimpleSSHDService. (SimpleSSHDService.java:185) + at org.galexander.sshd.SimpleSSHDService.is_started (SimpleSSHDService.java:86) + at org.galexander.sshd.SimpleSSHD.update_startstop_prime (SimpleSSHD.java:107) + at org.galexander.sshd.SimpleSSHD.onResume (SimpleSSHD.java:49) + at android.app.Instrumentation.callActivityOnResume (Instrumentation.java:1412) + at android.app.Activity.performResume (Activity.java:7302) + at android.app.ActivityThread.performResumeActivity (ActivityThread.java:3776) + at android.app.ActivityThread.handleResumeActivity (ActivityThread.java:3816) + at android.app.servertransaction.ResumeActivityItem.execute (ResumeActivityItem.java:51) + at android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:145) + at android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:70) + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1808) + at android.os.Handler.dispatchMessage (Handler.java:106) + at android.os.Looper.loop (Looper.java:193) + at android.app.ActivityThread.main (ActivityThread.java:6669) + at java.lang.reflect.Method.invoke (Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:518) + at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:858) + + +2021/01/17 - This has been reported from two different users, both on +OnePlus3 phone running Android 9. It was reported both with version 16 +and 24 of SimpleSSHD. It seems to be from where SimpleSSHDService tries +to load simplesshd-jni, which is the core JNI for the whole thing and the +only one that is linked in the normal way. It is a normal processor +(snapdragon 820). + +Since it's just the one kind of phone, I think I'm gonna ignore it. It's +probably some problem with how libc or JNI library is linked, but I can't +imagine what that problem is. diff --git a/_other/crash.20220422 b/_other/crash.20220422 new file mode 100644 index 0000000..8b77330 --- /dev/null +++ b/_other/crash.20220422 @@ -0,0 +1,32 @@ +This crash happened 26 times to only one user, who seems to be able to +use the app since it keeps happening. A Pixel 6 Pro with Android 12L. +There are two other crashes that look the same to me, on Pixel 5 Android +12L, and on Galaxy S21+ Android 12. So it may be an Android 12 issue. + +Not sure if I've put too much blocking crap in do_startservice() or if +Android is starving us of CPU and it's impossible to satisfy the +requirement. + + +android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: + at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException (ActivityThread.java:1965) + at android.app.ActivityThread.throwRemoteServiceException (ActivityThread.java:1936) + at android.app.ActivityThread.access$2700 (ActivityThread.java:256) + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2190) + at android.os.Handler.dispatchMessage (Handler.java:106) + at android.os.Looper.loopOnce (Looper.java:201) + at android.os.Looper.loop (Looper.java:288) + at android.app.ActivityThread.main (ActivityThread.java:7870) + at java.lang.reflect.Method.invoke (Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548) + at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003) +Caused by: android.app.StackTrace: + at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1876) + at android.app.ContextImpl.startForegroundService (ContextImpl.java:1831) + at android.content.ContextWrapper.startForegroundService (ContextWrapper.java:779) + at android.content.ContextWrapper.startForegroundService (ContextWrapper.java:779) + at org.galexander.sshd.SimpleSSHDService.do_startService (SimpleSSHDService.java:226) + at org.galexander.sshd.StopReceiver.onReceive (StopReceiver.java:9) + at android.app.ActivityThread.handleReceiver (ActivityThread.java:4375) + at android.app.ActivityThread.access$1600 (ActivityThread.java:256) + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2102) diff --git a/_other/crash.20220424 b/_other/crash.20220424 new file mode 100644 index 0000000..be8ba67 --- /dev/null +++ b/_other/crash.20220424 @@ -0,0 +1,12 @@ +Happened to 6 different people one time each, on Android 9 and Android +11, on 5 different phones, all when the app was supposed to be in the +foreground. Not sure what it could mean. + +android.app.RemoteServiceException: + at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2012) + at android.os.Handler.dispatchMessage (Handler.java:106) + at android.os.Looper.loop (Looper.java:223) + at android.app.ActivityThread.main (ActivityThread.java:7868) + at java.lang.reflect.Method.invoke (Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592) + at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:981) diff --git a/_other/dump-20190804 b/_other/dump-20190804 new file mode 100644 index 0000000..067e965 --- /dev/null +++ b/_other/dump-20190804 @@ -0,0 +1,503 @@ +starting dropbear +[10627] Aug 04 18:18:56 Not backgrounding +server select in +server select out (1) +server select in +[10650] Aug 04 18:19:06 Child connection from 192.168.1.72:59362 +[10650] Aug 04 18:19:06 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59362 +server select out (1) +server select in +[10650] Aug 04 18:19:09 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +server select in +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +[10656] Aug 04 18:19:09 Child connection from 192.168.1.72:59364 +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +sigchld +sigchld return from waitpid +server select out (-1) +server select in +[10656] Aug 04 18:19:09 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59364 +server select out (1) +server select in +[10656] Aug 04 18:19:10 Exit (user): Disconnect received +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +server select out (1) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +server select in +[10659] Aug 04 18:19:10 Child connection from 192.168.1.72:59366 +sigchld +sigchld return from waitpid +server select out (-1) +server select in +[10659] Aug 04 18:19:10 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59366 +server select out (1) +server select in +[10659] Aug 04 18:19:11 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +server select in +sigchld +sigchld return from waitpid +[10662] Aug 04 18:19:11 Child connection from 192.168.1.72:59368 +[10662] Aug 04 18:19:11 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59368 +server select out (1) +server select in +[10662] Aug 04 18:19:11 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +server select in +[10665] Aug 04 18:19:12 Child connection from 192.168.1.72:59370 +sigchld +sigchld return from waitpid +server select out (-1) +server select in +[10665] Aug 04 18:19:12 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59370 +server select out (1) +server select in +[10665] Aug 04 18:19:12 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +sigchld +sigchld return from waitpid +server select in +[10669] Aug 04 18:19:12 Child connection from 192.168.1.72:59372 +[10669] Aug 04 18:19:12 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59372 +server select out (1) +server select in +[10669] Aug 04 18:19:12 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +server select in +[10672] Aug 04 18:19:12 Child connection from 192.168.1.72:59374 +sigchld +sigchld return from waitpid +server select out (-1) +server select in +[10672] Aug 04 18:19:12 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59374 +server select out (1) +server select in +[10672] Aug 04 18:19:13 Exit (user): Disconnect received +server select out (1) +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +server select in +[10675] Aug 04 18:19:13 Child connection from 192.168.1.72:59376 +sigchld +sigchld return from waitpid +server select out (-1) +server select in +[10675] Aug 04 18:19:13 Pubkey auth succeeded for 'user' with key md5 0f:fb:35:79:cf:36:50:18:c8:0a:6d:8b:7c:be:27:a8 from 192.168.1.72:59376 +server select out (1) +server select in +[10675] Aug 04 18:19:13 Exit (user): Disconnect received +Aiee, segfault! You should probably report this as a bug to the developer +sp=0000007FED323B00 +fault_address=00000007FFD3454C +pc=00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +r0=00000007FFD34554 +r1=00000007FFD3454C +r2=0000000000000001 +r3=0000000070100000 +r4=0000000070100001 +r5=0000000000000000 +r6=0000000000000001 +r7=00000000000000D8 +r8=00000007FFD34550 +r9=0000000000000001 +r10=0000000000000001 +r11=0000000000000001 +r12=0000000000000001 +r13=0000000000000001 +r14=0000000000000001 +r15=0000000000009669 +r16=0000000000004B34 +r17=0000000000000000 +r18=0000000000000001 +r19=00000078C91A3A00 +r20=0000000000000004 +r21=0000000000000000 +r22=0000000000000000 +r23=00000078C91A3AE0 +r24=00000078DE248AF0 +r25=00000078DB854640 +r26=000000797167C000 +r27=0000000000000018 +r28=0000000000000020 +r29=0000007FED323B20 +r30=00000078DB84FAA4 +stack: +00000078CEA86AF8 (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000078CEA869FC (f /data/app/org.galexander.sshd-T8u2XjV_9MO9tmjVSkhk-w==/lib/arm64/libsimplesshd-jni.so=00000078CEA4A000) +00000061A8B6DA90 (f /system/bin/app_process64=00000061A8B6B000) +00000079734A47F0 (__kernel_rt_sigreturn=00000079734A47F0) +00000078DB95C750 (_ZN10A5xContext8HwAddNopEPjj=00000078DB95C710) +00000078DB84FAA4 (_ZN9EsxCmdMgr16IssuePendingIB1sE14EsxFlushReasonii=00000078DB84F930) +00000078DB850C3C (_ZN9EsxCmdMgr5FlushE14EsxFlushReason=00000078DB84FCC8) +00000078DB80EF3C (_ZN10EsxContext7DestroyEv=00000078DB80EF08) +00000078DB81F0D8 (_ZN10EglContext17DestroyEsxContextEv=00000078DB81F090) +00000078DB81EFFC (_ZN10EglContext7DestroyEv=00000078DB81EF60) +00000078DB821CA0 (_ZN10EglDisplay25MarkContextListForDestroyEv=00000078DB821BF0) +00000078DB82151C (_ZN10EglDisplay9TerminateEi=00000078DB821418) +00000078DB82134C (_ZN14EglDisplayList7DestroyEv=00000078DB8211F8) +00000078DB822838 (_ZN10EglDisplay35DestroyStaticListsMutexesAndTlsKeysEv=00000078DB8227C8) +00000078DB6F2F20 (_Z16EsxEntryDestructv=00000078DB6F2F10) +00000078DB854780 (f /system/vendor/lib64/egl/libGLESv2_adreno.so=00000078DB642000) +sigchld +sigchld return from waitpid +server select out (-1) +server select in diff --git a/_other/proguard-rules.pro b/_other/proguard-rules.pro new file mode 100644 index 0000000..357939e --- /dev/null +++ b/_other/proguard-rules.pro @@ -0,0 +1,7 @@ +# -dontobfuscate +-optimizationpasses 5 + +# Remove Log.d messages +-assumenosideeffects class android.util.Log { + public static int d(...); +} diff --git a/_other/reimer_prochnow-manage_extensions.patch b/_other/reimer_prochnow-manage_extensions.patch new file mode 100644 index 0000000..93dec71 --- /dev/null +++ b/_other/reimer_prochnow-manage_extensions.patch @@ -0,0 +1,259 @@ +diff --git a/.gitignore b/.gitignore +new file mode 100644 +index 0000000..0c83e19 +--- /dev/null ++++ b/.gitignore +@@ -0,0 +1,7 @@ ++obj/ ++dropbear/default_options_guard.h ++.gradle/ ++.vscode/ ++app/build/ ++app/src/main/lib/ ++build/ +diff --git a/app/build.gradle b/app/build.gradle +index d045336..980d396 100644 +--- a/app/build.gradle ++++ b/app/build.gradle +@@ -1,7 +1,10 @@ + apply plugin: 'com.android.application' + +-def keyprops=new Properties() +-keyprops.load(new FileInputStream(rootProject.file("keystore.properties"))) ++def keystoreProperties = new Properties() ++def keystorePropertiesFile = rootProject.file("keystore.properties") ++if (keystorePropertiesFile.exists()) { ++ keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) ++} + + android { + compileSdkVersion 30 +@@ -14,28 +17,42 @@ android { + } + + signingConfigs { +- release { +- keyAlias keyprops['keyAlias'] +- keyPassword keyprops['keyPassword'] +- storeFile file(keyprops['storeFile']) +- storePassword keyprops['storePassword'] +- } ++ release { ++ if (keystorePropertiesFile.exists()) { ++ keyAlias keystoreProperties['keyAlias'] ++ keyPassword keystoreProperties['keyPassword'] ++ storeFile file(keystoreProperties['storeFile']) ++ storePassword keystoreProperties['storePassword'] ++ } else { ++ println("key.properties not found") ++ } ++ } + } + + defaultConfig { + applicationId "org.galexander.sshd" +- minSdkVersion 17 +- targetSdkVersion 29 ++ minSdkVersion 29 ++ targetSdkVersion 30 + +- versionCode 27 +- versionName "27" ++ versionCode 28 ++ versionName "28" + } + + buildTypes { + release { +- signingConfig signingConfigs.release +- minifyEnabled false +- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' ++ if (keystorePropertiesFile.exists()) { ++ signingConfig signingConfigs.release ++ println "Signing with key.properties" ++ } else { ++ signingConfig signingConfigs.debug ++ println "Signing with debug keys" ++ } ++ minifyEnabled false ++ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' ++ } ++ debug { ++ applicationIdSuffix ".debug" ++ debuggable true + } + } + } +diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml +index 55b171e..4360259 100644 +--- a/app/src/main/AndroidManifest.xml ++++ b/app/src/main/AndroidManifest.xml +@@ -1,8 +1,11 @@ + +- + +- ++ ++ ++ + + + +@@ -10,8 +13,7 @@ + + ++ android:usesCleartextTraffic="true"> + + +diff --git a/app/src/main/java/org/galexander/sshd/SimpleSSHD.java b/app/src/main/java/org/galexander/sshd/SimpleSSHD.java +index 3e8fc0e..dc2cbad 100644 +--- a/app/src/main/java/org/galexander/sshd/SimpleSSHD.java ++++ b/app/src/main/java/org/galexander/sshd/SimpleSSHD.java +@@ -2,6 +2,7 @@ package org.galexander.sshd; + + import android.app.Activity; + import android.app.AlertDialog; ++import android.os.Build; + import android.os.Bundle; + import android.content.Context; + import android.content.SharedPreferences; +@@ -37,6 +38,7 @@ public class SimpleSSHD extends Activity + public static String app_private = null; + private UpdaterThread updater = null; + public static boolean is_tv = false; ++ final static int APP_STORAGE_ACCESS_REQUEST_CODE = 2296; // Any value + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +@@ -319,7 +321,7 @@ public class SimpleSSHD extends Activity + } + + private void permission_startup() { +- if (android.os.Build.VERSION.SDK_INT < 23) { ++ if (Build.VERSION.SDK_INT < 23) { + return; + } + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { +@@ -328,7 +330,7 @@ public class SimpleSSHD extends Activity + if (Prefs.get_requested()) { /* already asked once */ + return; + } +- requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); ++ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1); + } + + private void toast(String s) { +@@ -336,16 +338,58 @@ public class SimpleSSHD extends Activity + } + + private void permission_menu() { +- if (android.os.Build.VERSION.SDK_INT < 23) { ++ if (Build.VERSION.SDK_INT < 23) { + toast("Your phone uses an Android version that grants external storage access by default."); + return; + } +- if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { +- toast("External storage permission already granted."); +- return; +- } +- requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); ++ requestPermission(); ++/* if (Build.VERSION.SDK_INT >= 30) { ++ Intent intent = new Intent(); ++ intent.setAction(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); ++ Uri uri = Uri.fromParts("package", this.getPackageName(), null); ++ intent.setData(uri); ++ startActivity(intent); ++ } else { ++ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { ++ toast("External storage permission already granted."); ++ return; ++ } ++ requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0); ++ }*/ + } ++ // The below method can be used for requesting a permission in android 11 or below ++ private void requestPermission() { ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { ++ try { ++ Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); ++ intent.addCategory("android.intent.category.DEFAULT"); ++ intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName()))); ++ startActivityForResult(intent, APP_STORAGE_ACCESS_REQUEST_CODE); ++ } catch (Exception e) { ++ Intent intent = new Intent(); ++ intent.setAction(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); ++ startActivityForResult(intent, APP_STORAGE_ACCESS_REQUEST_CODE); ++ } ++ } else { ++ //below android 11 ++ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); ++ } ++ } ++ ++ // Handling permission callback for Android 11 or above versions ++ protected void onActivityResult(int requestCode, int resultCode, Intent data) { ++ super.onActivityResult(requestCode, resultCode, data); ++ if (requestCode == APP_STORAGE_ACCESS_REQUEST_CODE) { ++ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { ++ if ( android.os.Environment.isExternalStorageManager()) { ++ toast("External"); ++ // perform action when allow permission success ++ } else { ++ toast("Allow permission for storage access!"); ++ } ++ } ++ } ++ } + + public void onRequestPermissionsResult(int code, String[] perms, int[] results) { + Prefs.set_requested(); /* whatever result, don't ask again */ +diff --git a/doit b/doit +index 8e9b779..ed72072 100755 +--- a/doit ++++ b/doit +@@ -9,7 +9,7 @@ instlib() { + } + + [ dropbear/default_options.h -nt dropbear/default_options_guard.h ] && dropbear/ifndef_wrapper.sh < dropbear/default_options.h > dropbear/default_options_guard.h +-~/Android/Sdk/ndk-bundle/ndk-build -j8 && ++${ANDROID_HOME}/ndk-bundle/ndk-build -j8 && + mkdir -p app/src/main/lib/armeabi-v7a && + instlib scp libscp.so && + instlib sftp-server libsftp-server.so && +diff --git a/java b/java +deleted file mode 120000 +index e19930e..0000000 +--- a/java ++++ /dev/null +@@ -1 +0,0 @@ +-app/src/main/java/org/galexander/sshd/ +\ No newline at end of file +diff --git a/keystore.properties b/keystore.properties +deleted file mode 120000 +index e4aa0b9..0000000 +--- a/keystore.properties ++++ /dev/null +@@ -1 +0,0 @@ +-/home/greg/.android/keystore.properties +\ No newline at end of file +diff --git a/local.properties b/local.properties +index 6d01e43..ddc6676 100644 +--- a/local.properties ++++ b/local.properties +@@ -5,5 +5,6 @@ + # For customization when using a Version Control System, please read the + # header note. + #Sun May 26 20:02:34 EDT 2019 +-ndk.dir=/home/greg/Android/Sdk/ndk-bundle +-sdk.dir=/home/greg/Android/Sdk ++# uses ANDROID_HOME ++#ndk.dir=/home/greg/Android/Sdk/ndk-bundle ++#sdk.dir=/home/greg/Android/Sdk diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..0f22071 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 33 + ndkVersion '21.3.6528147' + + sourceSets { + main { + jniLibs.srcDirs 'src/main/lib' + } + } + + defaultConfig { + applicationId "org.galexander.sshd" + minSdkVersion 17 + targetSdkVersion 30 + + versionCode 27 + versionName "27" + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + lintOptions { + abortOnError false + } + + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } +} +dependencies { + implementation 'com.android.support:support-compat:28.0.0' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..58f007f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/org/galexander/sshd/BootReceiver.java b/app/src/main/java/org/galexander/sshd/BootReceiver.java new file mode 100644 index 0000000..9d18263 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/BootReceiver.java @@ -0,0 +1,15 @@ +package org.galexander.sshd; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class BootReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + Prefs.init(context); + if (Prefs.get_onboot()) { + SimpleSSHDService.do_startService(context, + /*stop=*/false); + } + } +} diff --git a/app/src/main/java/org/galexander/sshd/DocActivity.java b/app/src/main/java/org/galexander/sshd/DocActivity.java new file mode 100644 index 0000000..b43ad69 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/DocActivity.java @@ -0,0 +1,26 @@ +package org.galexander.sshd; + +import android.app.Activity; +import android.os.Bundle; +import android.webkit.WebView; + +public class DocActivity extends Activity +{ + public static final String url = "http://www.galexander.org/software/simplesshd"; + private WebView wv; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.doc); + wv = (WebView)findViewById(R.id.docview); + } + + public void onResume() { + super.onResume(); + wv.loadUrl(url); + } + + public void onPause() { + super.onPause(); + } +} diff --git a/app/src/main/java/org/galexander/sshd/DocActivityTV.java b/app/src/main/java/org/galexander/sshd/DocActivityTV.java new file mode 100644 index 0000000..44e1cd1 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/DocActivityTV.java @@ -0,0 +1,5 @@ +package org.galexander.sshd; + +public class DocActivityTV extends DocActivity +{ +} diff --git a/app/src/main/java/org/galexander/sshd/Prefs.java b/app/src/main/java/org/galexander/sshd/Prefs.java new file mode 100644 index 0000000..59638b7 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/Prefs.java @@ -0,0 +1,75 @@ +package org.galexander.sshd; + +import android.content.SharedPreferences; +import android.content.Context; +import android.preference.PreferenceManager; + +public class Prefs { + private static SharedPreferences pref = null; + + public static void init(Context c) { + if(pref == null) { + pref = PreferenceManager.getDefaultSharedPreferences(c); + set_defaults(); + } + } + + public static boolean get_onboot() { + return pref.getBoolean("onboot", true); + } + public static boolean get_foreground() { + return pref.getBoolean("foreground", true); + } + public static boolean get_onopen() { + return pref.getBoolean("onopen", true); + } + public static boolean get_rsyncbuffer() { + return pref.getBoolean("rsyncbuffer", true); + } + public static int get_port() { + try { + return Integer.parseInt(pref.getString("port", "2222")); + } + catch(Exception e) { + return 2222; + } + } + public static String get_ssh_server_password() { + return pref.getString("sshserverpassword", ""); + } + public static String get_path() { + return pref.getString("path", SimpleSSHD.app_private); + } + public static String get_shell() { + return pref.getString("shell", "/system/bin/sh"); + } + public static String get_home() { + return pref.getString("home", SimpleSSHD.app_private); + } + public static String get_extra() { + return pref.getString("extra", ""); + } + public static String get_env() { + return pref.getString("env", ""); + } + public static boolean get_requested() { /* already requested perms */ + return pref.getBoolean("requested", false); + } + public static void set_requested() { + SharedPreferences.Editor edit = pref.edit(); + edit.putBoolean("requested", true); + edit.apply(); + } + + /* NB - other defaults can be filled in by either Prefs or Settings as + * needed */ + private static void set_defaults() { + boolean need_path = (pref.getString("path", null) == null); + boolean need_home = (pref.getString("home", null) == null); + if(!need_path && !need_home) return; + SharedPreferences.Editor edit = pref.edit(); + if(need_path) edit.putString("path", SimpleSSHD.app_private); + if(need_home) edit.putString("home", SimpleSSHD.app_private); + edit.apply(); + } +} diff --git a/app/src/main/java/org/galexander/sshd/Settings.java b/app/src/main/java/org/galexander/sshd/Settings.java new file mode 100644 index 0000000..5dc3a44 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/Settings.java @@ -0,0 +1,45 @@ +package org.galexander.sshd; + +import android.os.Build; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.widget.Toast; + +public class Settings extends PreferenceActivity { + private CheckBoxPreference pref_onboot, pref_foreground; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + + pref_onboot = (CheckBoxPreference)findPreference("onboot"); + pref_foreground = (CheckBoxPreference)findPreference("foreground"); + + Preference.OnPreferenceChangeListener l = + (p, v) -> { + ((CheckBoxPreference)p).setChecked((Boolean) v); + check(); + return false; + }; + pref_onboot.setOnPreferenceChangeListener(l); + pref_foreground.setOnPreferenceChangeListener(l); + } + + public void onResume() { + super.onResume(); + check(); + } + + private void check() { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) && + pref_onboot.isChecked() && + !pref_foreground.isChecked()) { + pref_foreground.setChecked(true); + Toast.makeText(this, + "Android Oreo will not start a background service at boot. Forcing foreground.", + Toast.LENGTH_LONG).show(); + } + } +} diff --git a/app/src/main/java/org/galexander/sshd/SimpleSSHD.java b/app/src/main/java/org/galexander/sshd/SimpleSSHD.java new file mode 100644 index 0000000..10e69ec --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/SimpleSSHD.java @@ -0,0 +1,334 @@ +package org.galexander.sshd; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.text.ClipboardManager; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.List; + +public class SimpleSSHD extends Activity +{ + private static final Object lock = new Object(); + private EditText log_view; + private Button startstop_view; + private TextView ip_view; + public static SimpleSSHD curr = null; + public static String app_private = null; + private UpdaterThread updater = null; + public final boolean is_tv = this instanceof SimpleSSHDTV; + + public void onCreate(Bundle savedInstanceState) { + if(is_tv) requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + app_private = getFilesDir().toString(); + Prefs.init(this); + setContentView(is_tv ? R.layout.main_tv : R.layout.main); + log_view = (EditText)findViewById(R.id.log); + startstop_view = (Button)findViewById(R.id.startstop); + ip_view = (TextView)findViewById(R.id.ip); + } + + public void onResume() { + super.onResume(); + synchronized (lock) { + curr = this; + } + permission_startup(); + update_startstop_prime(); + updater = new UpdaterThread(); + updater.start(); + ip_view.setText(get_ip(true)); + + if (Prefs.get_onopen() && !SimpleSSHDService.is_started()) { + SimpleSSHDService.do_startService(this, /*stop=*/false); + } + } + + public void onPause() { + synchronized (lock) { + curr = null; + } + updater.interrupt(); + super.onPause(); + } + + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.settings: + settings_clicked(null); + return true; + case R.id.copypriv: + copypriv_clicked(null); + return true; + case R.id.resetkeys: + resetkeys_clicked(null); + return true; + case R.id.doc: + doc_clicked(null); + return true; + case R.id.about: + about_clicked(null); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + + /* these can be called as the _clicked() variant on Android TV, or + * through the options menu on regular Android */ + public void settings_clicked(View v) { + startActivity(new Intent(this, Settings.class)); + } + public void copypriv_clicked(View v) { + copy_app_private(); + } + public void resetkeys_clicked(View v) { + reset_keys(); + } + public void doc_clicked(View v) { + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(DocActivity.url)); + startActivity(i); + } catch (Exception e) { + startActivity(new Intent(this, (is_tv + ? DocActivityTV.class : DocActivity.class))); + } + } + public void about_clicked(View v) { + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.setCancelable(true); + b.setPositiveButton("OK", (d, w) -> {}); + b.setIcon(android.R.drawable.ic_dialog_info); + b.setTitle("About"); + b.setMessage( + "SimpleSSHD version " + my_version() + + "\ndropbear 2020.81" + + "\nscp/sftp from OpenSSH 6.7p1" + + "\nrsync 3.1.1" + + "\nPGP's mod version: 20230708"); + b.show(); + } + + + private void update_startstop_prime() { + if (SimpleSSHDService.is_started()) { + startstop_view.setText(Prefs.get_onopen() ? "QUIT" : "STOP"); + startstop_view.setTextColor(is_tv ? 0xFFFF6666 : 0xFF881111); + } else { + startstop_view.setText("START"); + startstop_view.setTextColor(is_tv ? 0xFF44FF44 : 0xFF118811); + } + } + + private static void run_on_ui(Runnable r) { + synchronized(lock) { + if(curr != null) curr.runOnUiThread(r); + } + } + + public static void update_startstop() { + run_on_ui(() -> { + synchronized(lock) { + if(curr != null) curr.update_startstop_prime(); + } + }); + } + + public void startstop_clicked(View v) { + boolean already_started = SimpleSSHDService.is_started(); + SimpleSSHDService.do_startService(this, already_started); + if(already_started && Prefs.get_onopen()) finish(); + } + + private void update_log_prime() { + String[] lines = new String[50]; + int curr_line = 0; + boolean wrapped = false; + try { + File f = new File(Prefs.get_path(), "dropbear.err"); + if(f.exists()) { + BufferedReader r = new BufferedReader(new FileReader(f)); + try { + String l; + while((l = r.readLine()) != null) { + lines[curr_line++] = l; + if(curr_line >= lines.length) { + curr_line = 0; + wrapped = true; + } + } + } + finally { + r.close(); + } + } + } + catch(Exception ignored) { } + int i; + i = (wrapped ? curr_line : 0); + String output = ""; + do { + output = output + lines[i] + "\n"; + i++; + i %= lines.length; + } while (i != curr_line); + log_view.setText(output); + log_view.setSelection(output.length()); + } + + public static void update_log() { + run_on_ui(() -> { + synchronized (lock) { + if(curr != null) curr.update_log_prime(); + } + }); + } + + public static String get_ip(boolean pretty) { + String ret = ""; + int num_ips = 0; + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface intf : interfaces) { + List addrs = Collections.list(intf.getInetAddresses()); + for (InetAddress addr : addrs) { + String ip = addr.getHostAddress(); + if (!addr.isLoopbackAddress() && + !ip.startsWith("fe80")) { + int i = ip.indexOf('%'); + if (i != -1) { + ip = ip.substring(0,i); + } + if (!pretty) { + return ip; + } + if (num_ips++ >= 5) { + return ret+"..."; + } + if (num_ips > 1) { + ret += "\n"; + } + ret += "IP: " + ip; + } + } + } + } + catch (Exception ignored) { } // for now swallow exceptions + return ret; + } + + private void copy_app_private() { + new AlertDialog.Builder(this) + .setCancelable(true) + .setPositiveButton("OK", null) + .setNegativeButton("Copy", + (d, w) -> { + ClipboardManager cl = (ClipboardManager) + getSystemService( + Context.CLIPBOARD_SERVICE); + if (cl != null) { + cl.setText(app_private); + } + }) + .setIcon(android.R.drawable.ic_dialog_info) + .setTitle("App-private path") + .setMessage(app_private) + .show(); + } + + private void do_reset_keys() { + new File(Prefs.get_path(), "authorized_keys").delete(); + } + + private void reset_keys() { + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.setCancelable(true); + b.setPositiveButton("Yes", (d, w) -> do_reset_keys()); + b.setNegativeButton("No", null); + b.setIcon(android.R.drawable.ic_dialog_alert); + b.setTitle("Reset Keys"); + b.setMessage("Delete the authorized_keys file? (then you will only be able to login with single-use passwords)"); + b.show(); + } + + public String my_version() { + try { + return getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch(Exception e) { + return "UNKNOWN"; + } + } + + public boolean areStoragePermissionsGranted() { + // EITHER Android < 6 + // OR storage permissions granted on Android 6-10 + // OR all-files permissions granted on Android 11+ + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || + (Build.VERSION.SDK_INT < Build.VERSION_CODES.R && + checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) || + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()); + } + + public void requestStoragePermissions() { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R) + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); + else { + // with Android >= 11, by having this signature permission granted by user, we can access all files (both read and write, even external sd and usb drives) + Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, 0); + } + } + + private void permission_startup() { + if(!areStoragePermissionsGranted()) requestStoragePermissions(); + } + + private void toast(String s) { + Toast.makeText(this, s, Toast.LENGTH_LONG).show(); + } + + public void onRequestPermissionsResult(int code, String[] perms, int[] results) { + Prefs.set_requested(); /* whatever result, don't ask again */ + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(requestCode == 0) { + if(!areStoragePermissionsGranted()) { + toast("Storage permissions not granted, exiting..."); + finishAffinity(); + } + } + } +} diff --git a/app/src/main/java/org/galexander/sshd/SimpleSSHDService.java b/app/src/main/java/org/galexander/sshd/SimpleSSHDService.java new file mode 100644 index 0000000..1a62565 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/SimpleSSHDService.java @@ -0,0 +1,263 @@ +package org.galexander.sshd; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.content.Context; +import android.os.Build; +import android.os.IBinder; +import android.widget.RemoteViews; +import android.widget.Toast; +import android.support.v4.app.NotificationCompat; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.List; + +public class SimpleSSHDService extends Service { + /* if restarting twice within 10 seconds, give up */ + private static final int MIN_DURATION = 10000; + + private static final Object lock = new Object(); + private static int sshd_pid = 0; + private static long sshd_when = 0; + private static long sshd_duration = 0; + private static boolean foregrounded = false; + private static String libdir = null; + + public void onCreate() { + super.onCreate(); + + Prefs.init(this); + + read_pidfile(); + + stop_sshd(); /* it would be stale anyways */ + } + + public int onStartCommand(Intent intent, int flags, int startId) { + libdir = getApplicationInfo().nativeLibraryDir; + if ((intent == null) || + (!intent.getBooleanExtra("stop", false))) { + do_start(); + do_foreground(); + return START_STICKY; + } else { + stop_sshd(); + stop_service(); + SimpleSSHD.update_startstop(); + return START_NOT_STICKY; + } + } + + public IBinder onBind(Intent intent) { + return null; + } + + /* unfortunately, android doesn't reliably call this when, i.e., + * the package is upgraded... so it's really pretty useless */ + public void onDestroy() { + stop_sshd(); + stop_service(); + super.onDestroy(); + } + + private void do_foreground() { + foregrounded = Prefs.get_foreground(); + if (foregrounded) { + create_notification_channel(); + + RemoteViews rv = new RemoteViews(getPackageName(), + R.layout.notification); + rv.setImageViewResource(R.id.n_icon, R.drawable.icon); + rv.setTextViewText(R.id.n_text, + "SimpleSSHD listening on " + + SimpleSSHD.get_ip(false) + + ":" + Prefs.get_port()); + PendingIntent pi = PendingIntent.getActivity(this, 0, + new Intent(this, SimpleSSHD.class), + PendingIntent.FLAG_UPDATE_CURRENT); + Notification n = new NotificationCompat.Builder( + this, "main") + .setSmallIcon(R.drawable.notification_icon) + .setTicker("SimpleSSHD") + .setContent(rv) + .setContentIntent(pi) + .setOngoing(true) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setLocalOnly(true) + .setVisibility( + NotificationCompat.VISIBILITY_PUBLIC) + .build(); + startForeground(1, n); + } + } + + private void create_notification_channel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel nc = new NotificationChannel( + "main", "SimpleSSHD", + NotificationManager.IMPORTANCE_LOW); + nc.enableLights(false); + nc.enableVibration(false); + nc.setSound(null, null); + getSystemService(NotificationManager.class) + .createNotificationChannel(nc); + } + } + + public static boolean is_started() { + return (sshd_pid != 0); + } + + private static void stop_sshd() { + int pid; + synchronized (lock) { + pid = sshd_pid; + sshd_pid = 0; + } + if (pid != 0) { + kill(pid); + } + } + + private void stop_service() { + stopSelf(); + if (foregrounded) { + stopForeground(true); + foregrounded = false; + } + } + + private static void maybe_restart(int pid) { + boolean do_restart = false; + long now = System.currentTimeMillis(); + synchronized (lock) { + if (sshd_pid == pid) { + sshd_pid = 0; + do_restart = + ((sshd_duration == 0) || + (sshd_when == 0) || + (sshd_duration >= MIN_DURATION) || + ((now-sshd_when) >= MIN_DURATION)); + } + } + if (do_restart) { + do_start(); + } + } + + private static void do_start() { + stop_sshd(); + new File(Prefs.get_path()).mkdirs(); + final int pid = start_sshd(Prefs.get_port(), + Prefs.get_ssh_server_password(), + Prefs.get_path(), Prefs.get_shell(), + Prefs.get_home(), Prefs.get_extra(), + (Prefs.get_rsyncbuffer() ? 1 : 0), + Prefs.get_env(), libdir); + + long now = System.currentTimeMillis(); + if (pid != 0) { + synchronized (lock) { + stop_sshd(); + sshd_pid = pid; + sshd_duration = ((sshd_when != 0) + ? (now - sshd_when) : 0); + sshd_when = now; + } + new Thread(() -> { + waitpid(pid); + maybe_restart(pid); + SimpleSSHD.update_startstop(); + }).start(); + } + SimpleSSHD.update_startstop(); + } + + private static void read_pidfile() { + try { + File f = new File(Prefs.get_path(), "dropbear.pid"); + int pid = 0; + if(f.exists()) { + BufferedReader r = new BufferedReader( + new FileReader(f)); + try { + pid = Integer.parseInt(r.readLine()); + } + finally { + r.close(); + } + } + if(pid != 0) { + synchronized (lock) { + stop_sshd(); + sshd_pid = pid; + sshd_when = 0; + sshd_duration = 0; + } + } + } + catch(Exception ignored) { /* *shrug* */ } + } + + private static native int start_sshd(int port, String sshserverpassword, String path, + String shell, String home, String extra, + int rsyncbuffer, String env, String lib); + private static native void kill(int pid); + private static native int waitpid(int pid); + private static native String api_mkfifo(String path); + static { + System.loadLibrary("simplesshd-jni"); + } + + public static void do_startService(Context ctx, boolean stop) { + if (!stop) api.start(ctx, api_mkfifo(SimpleSSHD.app_private)); + Intent i = new Intent(ctx, SimpleSSHDService.class); + if (stop) { + i.putExtra("stop", true); + } + Prefs.init(ctx); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Prefs.get_foreground()) { + ctx.startForegroundService(i); + } else if (!(ctx instanceof Activity)) { + Toast.makeText(ctx, +"SimpleSSHD cannot start in background since Oreo (enable Settings -> Foreground Service).", + Toast.LENGTH_LONG).show(); + } else if (is_foreground_app(ctx)) { + ctx.startService(i); + } else { + /* The OS put us in SimpleSSHD.onResume() even + * though it's not ready to put an app activity + * in the foreground yet! Just give up. Maybe + * the OS will try again? */ + } + } else { + ctx.startService(i); + } + } + + /* There's a bug in Android 9 Pie (SDK 28) where onResume() is called + * during wake-up when the OS isn't ready to put the app in the + * foreground, so startService() fails. This should detect that state. + * This code is copy-pasted from stackoverflow, and initially comes from + * a Google bug report on the issue? */ + public static boolean is_foreground_app(Context ctx) { + ActivityManager am = (ActivityManager)ctx.getSystemService( + Context.ACTIVITY_SERVICE); + List procs = + am.getRunningAppProcesses(); + if (procs == null) { + return false; + } + // higher importance has lower number (?) + return (procs.get(0).importance <= + ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND); + } +} diff --git a/app/src/main/java/org/galexander/sshd/SimpleSSHDTV.java b/app/src/main/java/org/galexander/sshd/SimpleSSHDTV.java new file mode 100644 index 0000000..623cbe1 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/SimpleSSHDTV.java @@ -0,0 +1,3 @@ +package org.galexander.sshd; + +public class SimpleSSHDTV extends SimpleSSHD {} diff --git a/app/src/main/java/org/galexander/sshd/StartReceiver.java b/app/src/main/java/org/galexander/sshd/StartReceiver.java new file mode 100644 index 0000000..27cbdc8 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/StartReceiver.java @@ -0,0 +1,11 @@ +package org.galexander.sshd; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class StartReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + SimpleSSHDService.do_startService(context, /*stop=*/false); + } +} diff --git a/app/src/main/java/org/galexander/sshd/StopReceiver.java b/app/src/main/java/org/galexander/sshd/StopReceiver.java new file mode 100644 index 0000000..2eedd98 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/StopReceiver.java @@ -0,0 +1,11 @@ +package org.galexander.sshd; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; + +public class StopReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + SimpleSSHDService.do_startService(context, /*stop=*/true); + } +} diff --git a/app/src/main/java/org/galexander/sshd/UpdaterThread.java b/app/src/main/java/org/galexander/sshd/UpdaterThread.java new file mode 100644 index 0000000..384acc3 --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/UpdaterThread.java @@ -0,0 +1,30 @@ +package org.galexander.sshd; + +import java.io.File; + +public class UpdaterThread extends Thread { + /* poll for changes to the dropbear.err file */ + public void run() { + File f = new File(Prefs.get_path(), "dropbear.err"); + long lastmod = 0; + long lastlen = 0; + for(;;) { + if(isInterrupted()) break; + + if(SimpleSSHD.curr == null) break; + long mod = f.lastModified(); + long len = f.length(); + if((mod != lastmod) || (len != lastlen)) { + SimpleSSHD.update_log(); + lastmod = mod; + lastlen = len; + } + try{ + sleep(1000); + } + catch(InterruptedException e) { + break; + } + } + } +} diff --git a/app/src/main/java/org/galexander/sshd/api.java b/app/src/main/java/org/galexander/sshd/api.java new file mode 100644 index 0000000..13fc39b --- /dev/null +++ b/app/src/main/java/org/galexander/sshd/api.java @@ -0,0 +1,101 @@ +package org.galexander.sshd; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + + +public class api { + private static final AtomicReference curr_thread = new AtomicReference<>(null); + + /* assumes JNI api_mkfifo() has already completed */ + public static void start(final Context ctx, final String fn) { + if (fn == null) return; + if (curr_thread.get() != null) return; + Thread t = new Thread(()->thread_main(ctx, fn)); + if(curr_thread.compareAndSet(null, t)) t.start(); + } + + private static String get_line(FileInputStream fis) throws IOException { + byte[] ret = new byte[512]; + int len = 0; + while (true) { + if (len+5 > ret.length) { + ret = Arrays.copyOf(ret, len+512); + } + int c = fis.read(); + if ((c == -1) || (c == '\n') || (c == 0)) { + return new String(ret, 0, len); + } + ret[len++] = (byte)c; + } + } + + private static String get_command(String fn) { + try { + FileInputStream fis = new FileInputStream(fn); + String ret = get_line(fis); + fis.close(); + return ret; + } catch (Exception e) { + return null; + } + } + + private static List split_string(String s) { + LinkedList ret = new LinkedList(); + int ofs = 0; + final int len = s.length(); + while (ofs < len) { + while ((ofs < len) && + Character.isWhitespace(s.charAt(ofs))) { + ofs++; + } + if (s.startsWith("-- ", ofs)) { + ret.add(s.substring(ofs+3)); + break; + } else if (ofs < len) { + int start = ofs; + while ((ofs < len) && + !Character.isWhitespace(s.charAt(ofs))) { + ofs++; + } + ret.add(s.substring(start, ofs-start)); + } + } + return ret; + } + + private static void parse_command(Context ctx, String s) { + List parms = split_string(s); + Iterator pi = parms.iterator(); + String cmd = pi.next(); + + if (cmd.equals("activity")) { + String url = pi.next(); + Uri uri = (new Uri.Builder()).path(url).build(); + ctx.startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + } + + private static void thread_main(Context ctx, String fn) { + int nulls = 0; + while (nulls < 5) { /* retry limit */ + String s = get_command(fn); + if (s == null) { + nulls++; + } else { + nulls = 0; + parse_command(ctx, s); + } + } + curr_thread.compareAndSet(Thread.currentThread(), null); + } +} diff --git a/app/src/main/res/drawable/icon.png b/app/src/main/res/drawable/icon.png new file mode 100644 index 0000000..224128a Binary files /dev/null and b/app/src/main/res/drawable/icon.png differ diff --git a/app/src/main/res/drawable/notification_icon.png b/app/src/main/res/drawable/notification_icon.png new file mode 100644 index 0000000..aaec993 Binary files /dev/null and b/app/src/main/res/drawable/notification_icon.png differ diff --git a/app/src/main/res/drawable/tv_button_bg.xml b/app/src/main/res/drawable/tv_button_bg.xml new file mode 100644 index 0000000..e234b05 --- /dev/null +++ b/app/src/main/res/drawable/tv_button_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/tv_button_bg_focused.xml b/app/src/main/res/drawable/tv_button_bg_focused.xml new file mode 100644 index 0000000..ec0abd3 --- /dev/null +++ b/app/src/main/res/drawable/tv_button_bg_focused.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/tv_button_bg_normal.xml b/app/src/main/res/drawable/tv_button_bg_normal.xml new file mode 100644 index 0000000..81f7627 --- /dev/null +++ b/app/src/main/res/drawable/tv_button_bg_normal.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/tvbanner.png b/app/src/main/res/drawable/tvbanner.png new file mode 100644 index 0000000..b20f9c9 Binary files /dev/null and b/app/src/main/res/drawable/tvbanner.png differ diff --git a/app/src/main/res/layout/doc.xml b/app/src/main/res/layout/doc.xml new file mode 100644 index 0000000..d6c02b9 --- /dev/null +++ b/app/src/main/res/layout/doc.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml new file mode 100644 index 0000000..9708520 --- /dev/null +++ b/app/src/main/res/layout/main.xml @@ -0,0 +1,35 @@ + + + + +