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?