mirror of
http://galexander.org/git/simplesshd.git
synced 2024-11-30 11:18:12 +00:00
472 lines
19 KiB
Plaintext
472 lines
19 KiB
Plaintext
BUILD INSTRUCTIONS:
|
|
|
|
|
|
To build this, you'll need to remove my key.* settings in ant.properties.
|
|
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 &&
|
|
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*
|
|
|
|
|
|
XXX - show /data/data/org.galexander.sshd/files somewhere as a suggested place to put files
|
|
XXX - find the /data/data/org.galexander.sshd/lib/ directory programatically, so it will work with whatever bizarre secure android with virtualized apps
|