-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathNOTES
910 lines (672 loc) · 38.8 KB
/
NOTES
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
BUILD INSTRUCTIONS:
To build this without my private keys, you'll need to replace my
ant.properties symlink with an empty file. You'll have to build a
"debug" apk instead of a "release" one. Android will not let you install
a debug apk on top of a release one, so you have to remove stock
SimpleSSHD first before installing the debug build.
Then follow these steps (roughly the "doit" script):
ndk-build -j8 &&
mv libs/armeabi/scp libs/armeabi/libscp.so &&
mv libs/armeabi/sftp-server libs/armeabi/libsftp-server.so &&
mv libs/armeabi/rsync libs/armeabi/librsync.so &&
mv libs/armeabi/buffersu libs/armeabi/libbuffersu.so &&
mv libs/x86/scp libs/x86/libscp.so &&
mv libs/x86/sftp-server libs/x86/libsftp-server.so &&
mv libs/x86/rsync libs/x86/librsync.so &&
mv libs/x86/buffersu libs/x86/libbuffersu.so &&
ant debug
The mv steps are very important, because ant will only package the
necessary binaries if they have a .so extension (even though they are
stand alone executables).
DEVELOPMENT JOURNAL:
December 6, 2014.
The idea is to make a proper ssh implementation for Android. Important
features:
* it should run happily without root (on a non-root port)
* it should be a regular android app requiring no special permissions,
and not requiring any 'magic' executable files
* should not rely on busybox
* preferably support sftp
* open source
The existing apps are either expensive, don't work, need root, or too
complicated, or a mix of all of the above. And none of them are open
source.
I figure I'll start with dropbear, which I will run through JNI instead
of putting it in its own binary (because making such a binary executable
is a bit of a hack).
So that's the plan........
December 14, 2014.
I got dropbear to compile under the Android NDK, so now it's time to work
on the Android side of it.
I need:
* a Service that can be started, stopped, and queried for whether it's
running or not
* a Thread to implement the Service's work (by calling into dropbear's
main()), which can also be stopped.
* a config UI with at least these choices:
- bool: start on boot (def: false)
- number: port number (def: 2222)
- string: path to authorized_keys file (def: /sdcard/ssh)
- string: name of default shell (def: /system/bin/sh -l)
- string: default path for HOME (def: /sdcard/ssh)
- button: start or (if it's running) stop
December 15, 2014.
Getting to the fun part. Process management...
To start sshd, it seems like I can startService(). Then in the Service's
onStartCommand(), call startForeground() so it won't be killed (return
START_STICKY too?).
The question is if dropbear's main() should run under a separate Thread,
or a separate Process. The trouble with a Thread is that it might be
hard to kill. The trouble with a process is that there is no way to
report back status (such as a failure to start sshd).
Connectbot starts a new process for its shell -- it really doesn't have a
choice because the shell binary isn't linked with Connectbot, and exec()
in a thread stinks. To stop it, it just closes stdin/stdout!!! So
zombies can (and do) linger.
I suppose dropbear could be in its own process if it had something like
stdin/stdout to communicate failure? Or it could just write error
messages to (i.e.) /sdcard/ssh/log. To stop the service, it would just
use kill().
I am curious how the main waiting-for-connections loop looks, but even if
it uses select(), I'm not sure how I would honor Thread.interrupt() or
whatever. It's not guaranteed to interrupt select(), and I'm not keen on
adding an arbitrary timeout/polling feature to it.
December 20, 2014.
So, I added a builtin scp endpoint. It was pretty straight forward,
except dropbear defaults to vfork(), which blocks the parent until the
child runs execve()!!
Anyways, I noticed that scp doesn't quote its arguments to the remote
scp. That means you can't conveniently copy a remote file with a space in
its name (it becomes two files). But the upside is that this is where
wildcards are handled -- by the shell!
So I need to either run it as a separate executable launched through the
shell, or make my own implementation of wildcards.
It is easy, using a $(BUILD_EXECUTABLE) script, to get ndk to build an
executable. But it is only packaged up if it is named "gdbserver" (and
debug apk), or "libfoo.so". The good news is that libfoo.so can be
executed in /data/data/org.galexander.sshd/lib/libfoo.so, so that is a
viable option.
Doing the expansion myself is not necessarily hard either, though. I
need a library function called glob(), which is apparently not part of
bionic. But I have the idea some cut and paste would resolve that with
very little extra work on my part.
December 21, 2014.
Well, bionic libc *does* provide fnmatch(), and even scandir() (a
shortcut for readdir). In the best case, though, that still leaves me
with a bit of a path parsing conundrum (I have to tell scandir which
directory to operate on). And also a bit of an escape character
conundrum -- \* and "*" should not act like wildcards.
Those are not insurmountable but I think I've talked myself out of it.
So then the question is, do I figure out how to ship an executable, or
do I do some hack like open a pipe to "/system/bin/sh echo filespec" and
use the shell solely for expansion?
I'm developing the idea that it's actually pretty easy to ship an
executable, I just need to find some -pre-package step where I can do
"mv scp libscp.so" and then it will ship. ndk-build will not let me make
a target with a "." in it directly.
...
Now scp, sftp-server, and rsync work as separate executables... rsync
does fail at -z because it needs it's own custom zlib...The stock
"external" one seems to lack the "old-style compress" method. There is
another commandline option for the new (deflate?) technique, but I think
the normal -z ought to work too. But that is literally the last feature!
Then just release details.
December 29, 2014.
First problem report from a user. Lollipop (Android 5.0) requires "PIE"
executables -- position independent code. I think that is a modern
equivalent to -fpic that Android is now requiring so that it can
randomize addresses to try to obscure stack smashing attacks that rely on
fixed addresses. It is epicly lame.
Anyways, the big fuck-you from Google is that Ice Cream Sandwich (Android
4.1) and earlier require fixed-position code. So one binary will not
generally work on both.
Here is a good summary:
https://code.google.com/p/android-developer-preview/issues/detail?id=888
There is something called "run_pie" which you can wrap your executables
in that lets older Android run PIE executables. It would require a
relatively small change to the exec() call to prepend it with "run_pie".
That seems like a hack.
The suggested remedy is to build two different apks! Yuck!
Anyways, it is only executables (not libraries -- they are position
independent already) that are affected. And apparently static
executables don't care one way or the other.
So that is my remedy -- static executables for the moment. I tested them
and it is only a little bit bigger -- 904kB of binaries instead of 668kB.
January 18, 2015.
Markus Ethen suggested it display the current IP address so you know
where to ssh to, in case it isn't convenient to use static dhcp or to
remember the address. That seems to be easier said than done. You can
use WifiManager, but that won't give your IP address unless you're on
wifi. That is probably "good enough", but it is certainly not ideal.
There is also java.net.NetworkInterface, which seems to return a random
ipv6 address.
Ah-hah! It is fe80::macaddr, which is a bogus "local-connection only"
ipv6 address, like 192.168, but automatically-generated without dhcp.
So if I skip that, it finds the proper ipv4 address!
Pfew! I was thinking I'd have to directly use /proc/net/dev and
SIOCGIFCONF, just like ifconfig does, but it works fine with
java.net.NetworkInterface.
June 20, 2015.
At some points, rsync is only write()ing, and assumes that the other end
will receive it all. The other end does a little bit of write()ing of
its own, and then is happy to read() all it wants. So this written stuff
may sit in a buffer somewhere indefinitely. If that happens, an
infelicity in the design of SuperSU causes everything to wedge.
Of course, this is only if you set the shell to /system/xbin/su as a way
of having root access for rsync.
Anyways, I made a new program, "buffersu", which is just a
stdin/stdout-buffering wrapper for rsync that is guaranteed to always
perform any read() that is possible at any time, no matter how many
write()s are outstanding. That seems to do the trick.
June 21, 2016.
Chris Moore reports that rsync and sftp do not like files larger than 2GB.
rsync was easy - it just needed an additional #define in rsync/config.h
to enable its builtin support for using stat64/lseek64/off64_t/etc.
Now that I'm investigating sftp, I find this surprising fact about bionic
(though the glibc man page for stat(2) tried to tell me this) - stat64
and stat are the same thing! But off64_t and lseek64 are significant.
That should make converting sftp pretty convenient. Especially since
sftp already uses "u_int64_t" instead of off_t.
p.s. Chris Moore gave me this command to test sftp, which turned out to
be useful:
curl -v --pubkey .ssh/id_rsa.pub -r 2147482624-2147484672 -k sftp://mushroom:2222/sdcard/ssh/buh -o buh-new
As for scp, it's not as clear what needs to be done. It doesn't use
lseek. But it does use off_t a bit, including on an index in a for loop
that is compared against st_size (which is 64-bit). So I'll just change
all of the off_t to off64_t and hope for the best.
sftp and rsync work! Not gonna bother testing scp on big files...
October 1, 2016.
Jared Stafford told me startForeground() improves responsiveness on
Nougat. There had been a comment suggesting startForeground(), but I
never got around to trying it because it has worked "well enough". With
Nougat, though, there is a definite tendency for SimpleSSHD to be
non-responsive. I'm not sure exactly what its cause is, but the symptom
I notice most frequently is that the first ssh connection after a while
will be delayed "a long time" - on the order of 10-30 seconds, or maybe
indefinitely sometimes. Oddly, a second connection can sometimes get
through undelayed, even before the first connection does. It is as if
the fork() of process for the new connection is where the delay is, not
in the listen() call.
That's not overall too surprising, Nougat is a lot harsher about
background processes as part of a Google push to reduce power consumption
on idle devices.
Another concern is related to a change back in July - sometimes the
system will kill the sshd process for no good reason (maybe because they
removed it from the recent apps list). The remedy I settled on was to
monitor for the sshd process dying from within the regular
Android-managed process, and restart it. I guess I didn't write down
where in the documentation I found it, but Android seems expressly
antagonistic to non-system-managed processes. If they ever get more
hard-assed about that, this whole idea goes out the window.
Anyways, I implemented startForeground(), and I am unhappy that it
requires a Notification. At API 7 (Android 2.1) those are really
primitive. For example, the PRIORITY_MIN behavior which will sometimes
hide the notification is added in API 16 (Android 4.1). So, I've got it
with this stupid old-style notification, and it really doesn't look good,
and it is always present. That is not awesome.
On the other hand, it's easy to block the notifications, and a few people
have expressed an interest in a notification.
It doesn't seem worth it to me to upgrade to a newer API just for the
better notifications... On the other hand, Google Play shows that I have
862 users:
Android 7+ : 3.13%
Android 6+ : 37.47%
Android 5+ : 67.86%
Android 4.1+: 96.62%
Android 4.0+: 98.01%
The oldest reported version is Android 2.3.
So, there are a few people on very old versions, but actually SimpleSSHD
is used on newer devices than the average app, which is the reverse of my
typical trend. So a few people would be negatively impacted, but not a
very large number. I could switch to multi-APK mode so that legacy users
are just stuck with an unsupported back-version, which is probably what
they truly want anyways..
Anyways, I'm gonna use it with startForeground() and the notifications
disabled for a while, and if I find it to be an improvement then I'll
just make it so clicking on the notification goes to the app, update the
doc, and then publish it.
...
Looking up doze mode details for improving a different app, I stumbled
across this:
https://www.bignerdranch.com/blog/diving-into-doze-mode-for-developers/
One last case which is not mentioned in the Android documentation is
an app using a foreground service. Any process using a foreground
service is exempt from Doze Mode effects, which is key for
long-running applications that you may want to run even if the user
has put their phone down for a long period of time -- like a music app,
for example.
There is, however, a bug in Android Marshmallow which requires a
foreground service to be in a separate process from all other activity
code, documented here. It is planned to be fixed in Nougat.
Not exactly confidence-inspiring. And I haven't come across a
description of how exactly doze mode screws up SimpleSSHD, either.
It might be worthwhile to do the work to find out where things actually
get wedged, it might even be a flaw in Dropbear that makes it so
unreliable with Nougat.
October 16, 2016.
I've been using a foreground service for a while, and it has not caused
me any troubles. The thing I have been most anxious about is that it
might disable doze mode entirely...but there has not been any noteworthy
overnight drain.
So, in case someone does have problems with it, I am making it an option
that defaults to enabled.
Looking through email, I haven't seen any, but I have the idea several
users have asked for a notification icon in the past. And now that that
is finally implemented, I am curious about other things people have
requested that I have not been keen on. And Jan Ondrej's requests come
to mind:
o) setting to start the service automatically when the application is
launched
o) QUIT button that stops the service and the activity at once
o) not allow wifi to power down when the activity is open
I'm really not crazy about integrating any kind of wakelock. And having
two buttons still seems silly to me. But with the notification there,
the idea that someone will micromanage whether the service is running or
not does not seem so far fetched.
So I'll go ahead and add "Start on Open" setting.
...
Looking at reviews on Google Play Store, I finally found a couple reviews
in the last couple months who would have enjoyed password login. I'm
still pretty opposed to it because it seems like the usage model would be
to enable a default password, login with it to transfer an
authorized_keys file, and then disable the default password. That is
fine, and would even be kind of convenient (I might use it), but it seems
more often than not the default password would be left enabled
indefinitely, which is not awesome. And I can't imagine more than 1% of
users ever typing a strong password into their phones.
But I've thought of a compromise. What if, if there is no
authorized_keys file, it accepts passwordless logins but with some sort
of obnoxious alert dialog sort of thing to interrupt the user? That way,
you would be able to login once to copy the authorized keys file, and the
nuissance alert would be no big deal. Then once the authorized keys
exists, no further action is necessary.
It's a pity there is no convenient way to interact between the Android
GUI thread and the sshd thread.
Ah-hah! I've got the least effort idea in my head! Under situations
that I'm not sure what they should be, it should generate a random
password and display it on the phone's screen. Probably it should
generate it iff there is no authorized_keys file at all. Since that
detection happens for each client connection, then probably all of this
logic should go in dropbear itself, and the display should come through
dropbear.err.
Then we might even want a UI way to delete authorized_keys, perhaps even
as a replacement for the current awkward UI.
November 19, 2016.
I got a user request to update for security. I looked at dropbear and
didn't see any relevant security issues, so I'm gonna hold off for a
while, but it should be on my radar.
I also got a user request for writing to external SD card. He gave
this link:
http://stackoverflow.com/questions/33162152/storage-permission-error-in-marshmallow
It gives me a really strong deja vu, I think I tried that a few months
ago when a nearly identical request came in, and it didn't work. I wish
I had kept notes for that experiment!
I think there are two separate issues. I think Android 6+ push you to
use requestPermission() to explicitly enable the WRITE_EXTERNAL_STORAGE
permission, but if targetSdkVersion is low enough then you are
grandfathered in, so we don't care. The second issue is that Android 5+
require use of a special API to access a removable SD card (which is
different from /sdcard, which is typically internal storage protected by
WRITE_EXTERNAL_STORAGE on new phones?). That second issue is what is
biting people.
I don't know any way around that. If Google doesn't back down, then I
guess the only plausible way around it would be something like an
LD_PRELOAD that intercepts open/lseek/read/write/close to external SD,
and replaces them with connection to a local daemon process that is
running in a typical Android context and is able to use the awful API.
Seems like a lot of work and complication. I might go through with it if
I happened to use external SD myself, but I'm of the personal opinion
that removable storage is obsolete now that even relatively cheap phones
like the base Moto G4 come with 16GB... 640kB ought to be enough for
anybody.
I told the most recent guy to try SuperSU. I don't have any idea if that
will really work, to be honest.
October 28, 2017.
At the beginning of October, a user notified me that rsync doesn't work
on Android 8.0 (Oreo, API 26). In the past week, Google has upgraded my
Nexus 5x to Oreo and I can confirm it.
It seems that SE Linux or something causes certain system calls to
perform the equivalent of:
fprintf(stderr, "Bad system call\n");
exit(-1);
It should return ENOSYS instead, but someone at Google is not cool enough
to be working with Unix.
The first one I discovered like this is sigprocmask(), which we can
probably do without. The next one is chmod(), which is somewhat more
useful.
The troubling thing is that I can find a file where the commandline chmod
works, but the same chmod() doesn't work in rsync.
I figured maybe my NDK was just too old ("r10d"), but I think I can't
conveniently upgrade it because Google only distributes the NDK for Linux
x86-64 now. I was using SDK 7, so I tried SDK 11, 17, 19, and got the
same result. The NDK I have also has a directory for SDK 21, but it has
typos in the header files, or they aren't compatible with its version of
gcc. But anyways, if SDK 19 doesn't work then maybe it's more than just
linking against a bad version of Bionic.
Well, I think I've got it - fchmod() works, but chmod() does not.
*shrug*
March 4, 2018.
Looked in the google play console for crashes, and I find a few. 10
people have run into UnsatisfiedLinkError, and they seem to all be using
x86. According to
https://stackoverflow.com/questions/32598756/android-ndkhow-to-exclude-x86-device-in-google-player
It's not plausible to fix this problem by blacklisting x86 devices!
Google sucks! And the justification is that there is an ARM emulation
layer, but obviously it doesn't work and why should I have to debug it??
Anyways, someday may want to try making a jni/Application.mk and setting
APP_ABI in there to support x86 too? Do I really think that will work
for scp/rsync/etc? Guh.
And this bug has only one report... The startActivity() call to open the
documentation is getting a android.content.ActivityNotFoundException. I
guess it must mean there is no browser installed or something? I seem to
be using the normal way to visit an http programatically.. Only one
report, so I guess I just don't care.
March 24, 2018.
Win Bent emailed me to let me know his x86_64 Asus ZenPad is able to run
SimpleSSHD but is unable to run rsync. So, the ARM-on-x86 emulation
layer is catching up and now works sometimes, but isn't compatible with
the hack for scp/sftp/rsync. Win was kind enough to test a build that
supports just armeabi and x86, and reported it works. So I don't need to
build support for both x86 and x86_64, apparently. Nice to have a
concrete answer to the question!
May 16, 2018.
Roland Jaeger let me know busybox's "su" doesn't appreciate being called
with "-" prefix to argv[0] for a login shell, it can't find the "-su"
applet. su wants "-" to come in argv[>=1] instead to indicate a login
shell.
I guess it's kind of a hack but I just don't see it causing much
trouble... If the string "su" is found in the shell's name, it runs
"su -" instead of "-su". *shrug*. Roland tested it and reports it works
for him.
He also notes that the default /system/bin/sh distributed with his device
doesn't run .profile, instead it runs whatever is in $ENV. Apparently
mksh is "the Android shell", and indeed that is what's on my Nexus 5x.
mksh is supposed to run /etc/mkshrc (well, /system/etc/mkshrc on Android,
I guess), and then $ENV. But on my device, it loads .profile too. So I
guess there is some diversity of configurations. *shrug*
NB - bash supports the same idea but it calls the variable $BASH_ENV, or
$ENV if --posix is set. *shrug* And whatever shell comes with
SimpleBusyBox (ash?) loads .profile by default.
Anyways, it seems the obvious way forward is to make a setting for the
environment...just one name=value per line.
...
Noticed a couple crashes in the Play store console. One is a repeat of
this Note 7 that gives ActivityNotFoundException when viewing
documentation. I decided to catch the exception and display a dialog
asking the user to contact me, because I'm curious.
The other crash is a NullPointerException due to a bug in older Android
(a 2013 Nexus 7). It isn't my fault and I don't care.
https://issuetracker.google.com/issues/36972466
There is also an ANR, I think it's my very first! Here's the message:
Input dispatching timed out (Waiting to send non-key event because the
touched window has not finished processing certain input events that
were delivered to it over 500.0ms ago. Wait queue length: 25. Wait
queue head age: 34478.4ms.)
I can't tell if it's saying it blocked for 500ms or for 34s, but either
one seems problematic.
It's 4 identical-looking failures on one day, so it might be a fluke.
It's on x86_64, though I don't think that's the problem...
There are a ton of threads so it will be a bit of a trick to try to describe:
main - waiting in epoll_wait() under a bunch of framework
MessageQueue/Looper stuff.
Thread-23 - sleeping in UpdaterThread.run() line 26
Thread-15 - SimpleSSHDService.run()'s call to native waitpid()
and just a ton of misc threads that don't seem interesting to me
So, it seems like the main thread is specifically waiting for something
to do, I have no idea why it's not responding to user input. I don't
think the blocked UpdaterThread or SimpleSSHDService thread, because
those are specifically in other threads because they are designed to
block. I'm thinking it's a fluke of some sort, maybe a DoS sort of
scenario on this one device.
Anyways, the other thing I found out at the Play store is I got warned
because I'm supposed to "target a recent SDK", which means API level 26
(Android 8.0 Oreo). I think this is dumb but apparently it only means
I have to update android:targetSdkVersion to 26. I can leave
minSdkVersion at 7, and target=android-7. I hope.
I imagine a bit of UI will move around but it should just be a matter of
shaking it out and then testing it on an older device.
Oh, I just remembered the important new Androidism - permissions. In
particular, I think it is now more complicated than just
WRITE_EXTERNAL_STORAGE. Ugh?
Using my Nexus 5x (Android 8.1), updating to a new one with
targetSdkVersion 26, everything seems to just work. But the
blue-on-white for the IP display becomes blue-on-black and it is almost
unreadable. And also, it is kind of ugly. I think if I can switch it to
a dark theme or something that will work itself out.
But then I uninstalled the app and installed it from scratch with
targetSdkVersion 26, and that does not work at all. On startup it shows
the next "null" on every line where it's supposed to show the log. And
once the service is started, the log is still null, and anything
attempting to connect says:
Connection closed by 192.168.1.23
So my guess is that it's now more complicated than just
WRITE_EXTERNAL_STORAGE. Ugh!
...
A little info from
https://medium.com/google-developers/picking-your-compilesdkversion-minsdkversion-targetsdkversion-a098a0341ebd
which told me I can put the compile target much ahead of the min target
and supposedly I'll get warned if I use new features. Anyways, maybe I
can gate the new permissions model behind a simple if? I'm not sure if
it will be enough to explicitly ask for the permission, or if the
permission is accessed differently.
It also suggests I look at the "Platform Highlights" in the API Level
table at:
https://developer.android.com/guide/topics/manifest/uses-sdk-element
That's quite a few to check out to go from SDK 11 (Honeycomb) to SDK 26
(Oreo). But I'll probably learn something...
Have to use checkSelfPermission() and requestPermission() to get
"dangerous" permissions (WRITE_EXTERNAL_STORAGE). Luckily, INTERNET and
RECEIVE_BOOT_COMPLETED are "normal" permissions and just need to be in
the AndroidManifest.xml, I guess. And I don't need to worry about
READ_EXTERNAL_STORAGE, so long as I explicitly request
WRITE_EXTERNAL_STORAGE.
May 19, 2018.
Michael Mess wrote to let me know that he can't use authorized_keys file
because the /sdcard/ssh/authorized_keys file is group-readwritable. I
know regular OpenSSH on a PC does complain if the authorized_keys file is
world-writable, so this made sense to me. He says it is a security flaw,
but really, it is not, because any app that can run code and write to
/sdcard/ssh already has all of the permissions that SimpleSSHD has
(except network, but whatever) -- it isn't escalation, just a horizontal
spreading.
He suggested moving the SSH config into app-private, which is something
I've been toying with anyways.
So after some internal debate, I decided to do two changes:
* add "Copy app-private path" to the options menu, which brings up a
dialog showing the app-private path, with an option "Copy" to put it
in the clipboard.
* make the default for new installs for "home" and "path" be the
app-private dir
I look forward to seeing if anyone complains about it.
May 20, 2018.
I tried updating the build target to a newer version than minSdkVersion,
and I got a warning, and I looked at the official google documentation,
and you can't build with anything newer than the minSdkVersion. I mean,
I knew that, so, what I'm saying is, I feel stupid for reading that
medium.com link.
June 8, 2018.
Request from Joan Marce i Igual for --iconv. That requires iconv.h,
which isn't part of Bionic. GNU libiconv will do it, but it doesn't look
particularly easy to build, and it isn't small. Someone on github has
uploaded their tree for building iconv on Android, which might save me a
bunch of effort:
https://github.com/ironsteel/iconv-android.git
Unfortunately it doesn't have much history to it, so I don't know if
it'll be worth anything or not but it should give me a sense of the build
task without forcing me to decipher autoconf.
May 25, 2019.
To update to the new SDK (26 required now, 28 required starting in
August), I have to switch to gradle and all that garbage. Every time I
update anything with the new SDKs, everything breaks, even after I've
already updated everything to gradle. So basically it is the worst
possible development environment.
Anyways, the NDK now doesn't support anything older than android-16 (JB
4.1), which is stupid but whatever. Looking on the play store, it looks
like I have 5 users on these older devices. I hope it simply refuses to
update for them, and they can continue to use the older version!
... I got it to build and install and run, and it "works". The only
issue that is currently biting me is (on using am to install an apk):
InstallStart: Requesting uid 10180 needs to declare permission
android.permission.REQUEST_INSTALL_PACKAGES
Anyways, there's a bunch of stuff remaining to do, and then I will
release it.
May 26, 2019.
I got an AVD (emulated android) running SDK 26 (Oreo 8.0) x86. It uses
kvm virtualization so it's much faster than the old emulated ARM I used
to be shackled with.
Anyways, right off, it says (in a toast):
Developer warning for package "org.galexander.sshd"
Failed to post notification on channel "null"
See log for more details
adb logcat says:
05-26 17:13:49.877 1741 1764 E NotificationService: No Channel found
for pkg=org.galexander.sshd, channelId=null, id=1, tag=null,
opPkg=org.galexander.sshd, callingUid=10085, userId=0, incomingUserId=0,
notificationUid=10085, notification=Notification(channel=null pri=0
contentView=org.galexander.sshd/0x7f030001 vibrate=null sound=null
tick defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE)
This gives a kind of deja vu -- I think from BluntMP3? I'm just adding
it to the todo list.
To access SimpleSSHD, run
adb forward tcp:2222 tcp:2222
ssh -p 2222 localhost
And it demonstrates that the sdcard access is forbidden. In fact, I can
toggle it inside the System Settings -> Applications -> SimpleSSHD ->
permissions -> Storage, and it takes effect more or less immediately.
Android development is such a constant stream of astonishing garbage.
When it requests the permission, which happens just as I would want it
to, it then pops up a "System UI has stopped" dialog. But look, it isn't
my fault:
https://stackoverflow.com/questions/49261555/system-ui-has-stopped-emulator-when-requesting-permissions
For real. There's a bug in the emulator you fix by "increasing the DPI."
I had tried some older 854x480 sort of emulated device because I didn't
want all those pixels, but I'll just use Pixel 2 (the default, and one
Google surely tests). It has a different skin where the soft buttons are
part of the skin, which is I bet what the bug is around.
So, the permissions request works now. It just asks once, and no matter
what the answer is, it never asks again. And if it is grandfathered in
because the previous version of the app used an older SDK, then it will
never ask and it'll just have the permission. And, at least on an
emulated Pixel 2, it can access /sdcard, which is "internal storage."
It does emulate an "SD card", but it can't access that.
...
Getting a channel onto the notification was a pain in the ass. All this
gradle stuff is getting increasingly undocumented. Every time they
change something so they won't ever have to change anything again, they
then immediately change everything. This leads to a mass proliferation
of immediately-obsoleted documents. It even damages resources like
stackexchange because people take their extraordinarily unique context
for granted when asking and answering questions. So something that
worked against gradle 3.3.4 will now be broken against gradle 3.3.5 but
no one will think to mention what version of gradle, or what version of
the build scripts gradle runs, or what-have-you.
Anyways, the upshot is, the way I got it to work is I opened the project
in Android Studio, and eventually I used Refactor->Migrate to AndroidX,
which turned out to have all manner of complications but here we go.
Speaking of Android Studio, the AVD Manager (Android Virtual Device, I
guess) is not accessible under Tools, even though it should be. Instead,
you have to click on the icon which is a portrait phone with a green
android head on the lower-right corner of it. Upper-right corner.
And you have to open a project before it will show you that. Jesus.
...
Testing with pie, I am able to reproduce the problem with start-on-boot.
Here's some excerpts from logcat:
05-26 21:24:26.251 1837 3408 W ActivityManager: Background start not allowed: service Intent { cmp=org.galexander.sshd/.SimpleSSHDService } to org.galexander.sshd/.SimpleSSHDService from pid=3756 uid=10085 pkg=org.galexander.sshd startFg?=false
--------- beginning of crash
05-26 21:24:26.251 3756 3756 E AndroidRuntime: FATAL EXCEPTION: main
05-26 21:24:26.251 3756 3756 E AndroidRuntime: Process: org.galexander.sshd, PID: 3756
05-26 21:24:26.251 3756 3756 E AndroidRuntime: java.lang.RuntimeException: Unable to start receiver org.galexander.sshd.BootReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=org.galexander.sshd/.SimpleSSHDService }: app is in background uid UidRecord{5aff37a u0a85 RCVR idle change:uncached procs:1 seq(0,0,0)}
...
05-26 21:24:26.251 3756 3756 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=org.galexander.sshd/.SimpleSSHDService }: app is in background uid UidRecord{5aff37a u0a85 RCVR idle change:uncached procs:1 seq(0,0,0)}
...
05-26 21:24:26.251 3756 3756 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:664)
05-26 21:24:26.251 3756 3756 E AndroidRuntime: at org.galexander.sshd.BootReceiver.onReceive(BootReceiver.java:11)
So it looks like a pretty intentional rule that you can't start a service
from a boot receiver??
And I have the same problem on my Z2 Force (Oreo 8.0) now that I've
updated the SDK.
This guy says it will allow you to startForegroundService(), so that's
what I'll try...
https://stackoverflow.com/questions/49572570/android-start-a-service-on-boot-not-working
Which seems to work! So there is a need for some sort of dance in the
Settings screen to force it to enable foreground services if
start-on-boot is selected, and then we'll be golden.
June 16, 2019.
I finished the dropbear 2019.78 merge and got it all to work again on the
x86 version 28 (Pie) emulator. It also works on x86 version 26 (Oreo)
emulator, and on my ARM Oreo phone (Moto Z2 Force). I also tried with an
emulated ARM version 16 (JB 4.1), and it works, but it generates a lot of
this spamming the log:
Failed lookup: Non-recoverable failure in name resolution.
This gives me a kind of deja vu, something to do with library versions. I
have the idea it's because I built with a newer libc than what is found
by dynamic linking in older Android. But I cannot link the main dropbear
thing with -static because it needs to be JNI (which is loaded with
dlopen()). Apparently.
Anyways, I'm not sure there's anything I can do about that other than
reduce the log noise. I'm not really interested in digging up a magic
older version of the NDK or anything like that.
But I do think the only reason it has -static on the stand-alone
executables is basically a hack to make them work with the old NDK, so I
can get rid of that...
Testing with emulated x86 Android 21 (KK 5.0)... It seems to work fine.
It resolve my IP to the string "127.0.0.1", anyways. rsync seems to
work, and start-on-boot in the background works.
Anyways, I made a naive thing to decode an IP address to a numeric
string, in case getnameinfo() fails. It is working for me in the ARM
Android 16 now. It generates a reasonable string even though it
presumably fails to use the library lookup. It starts on boot as a
background service. And rsync and scp work. Only need to test on my
Moto X now.
...
Testing start-on-boot on my Moto Z2 Force, it works, but there is an
infelicity. If I go into Settings and try to turn off foreground
service, it won't let me because start-on-boot is selected and I am using
Oreo. It is supposed to show a toast to let me know this, but the toast
doesn't appear. That's because I had previously disabled notifications
so that the foreground service doesn't have a notification.
I can make the toast appear by following these steps: re-enable
notifications (manually in Settings -> Apps & Notifications), then
re-start the service, then disable the notification (by long-tapping on
the notification itself). Then toasts work. I think probably it has
saved the fact that all notifications are disabled, because it only knew
one class of notification when it was using compatibility with the old
SDK...but now it knows two classes (a notification and a toast), and it
records that only one class is disabled. I really have no idea but I
think it sucks.
...
Testing with Moto X (ARM, Android 5.1 API 22), and it works. But it has
this infelicity...when scp or rsync is run:
WARNING: linker: /data/app/org.galexander.sshd-2/lib/arm/libscp.so: unused DT entry: type 0x6ffffffe arg 0xb64
WARNING: linker: /data/app/org.galexander.sshd-2/lib/arm/libscp.so: unused DT entry: type 0x6fffffff arg 0x2
These are DT types that are present in the .so which are not supported by
the kernel. It may be because the kernel is CyanogenMod? The types are
DT_VERNEED and DT_VERNEEDNUM. They refer to a section .gnu.version_r,
which I guess holds two 32-byte entries which are unintelligible to me.
Supposedly I can use an "ELF cleaner" to strip out these DT_VERNEED
things. But there's some ambiguity as to whether they're necessary or
useful. I think I'm gonna leave that be.
More productively, I found that it won't startForegroundService() because
that was added at Oreo!
I can't seem to get start-on-boot to work, but from some of the crap in
logcat, I have the idea CyanogenMod might not be good at broadcasting the
boot event.
Now for the interesting test...I uninstalled SimpleSSHD on the Moto X
(API 22), and am reinstalling it new. So it won't have grandfathered
permissions. It works fine! No surprises. It still doesn't start on
boot *shrug*.
Now to try the same uninstall test with Moto Z2 Force (API 26). It works
fine, and on the first start-up it does ask for permission to access
files (/sdcard). To my surprise, it saved my Settings and my
/data/user/0/org.galexander.sshd/files. That's actually kind of bad,
because the host key would be saved, and there aren't really many
settings to re-enter in the upside case. Apparently this is new behavior
since I added targetSdkVersion>=23, so users haven't had their data
backed up yet and I can change this setting with allowBackup="false" in
the manifest...
allowBackup="false" took immediate effect and had no surprises...
--- new release
XXX - see if settings looks better with SDK26, if not, hack it so that the ones with strings have their states shown
XXX - ed25519?
XXX - try /data/data/com.termux/files/usr/bin/zsh as login shell
XXX - libiconv? HAVE_ICONV_H etc