mirror of
http://galexander.org/git/simplesshd.git
synced 2024-11-27 17:58:13 +00:00
198 lines
7.7 KiB
Plaintext
198 lines
7.7 KiB
Plaintext
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.
|
|
|
|
|
|
XXX - if you remove it from the recent apps list, does it stop the service??
|
|
XXX - support password-based logins?
|