Getting a cross-compilation environment

The first step requires setting up a cross-compilation environment using the Android NDK. This is actually non-trivial, and I spent quite some time on StackOverflow and other website trying to figure out the right set of predicates. Here's the drill.

First of all, download the Android NDK from the Google webpage, and extract it somewhere.

My ~/.bashrc file is synchronized across multiple computers; however, not all of them will have the NDK installed. Therefore, I use the following handy little function:

function add_to_path_if () {
  if [ -d $1 ]; then
    export PATH=$1:$PATH
  fi
}

This allows me to add in my ~/.bashrc:

add_to_path_if "$HOME/Applications/android-ndk/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/bin/"

Beware, if the NDK got updated since I wrote this, you may want to change the path to make sure it points to the actual directory found in the NDK.

Then, setting up your environment variables to actually perform cross-compilation is a nightmare. There are zillions of options, but let me give you the answer straight away.

function android () {
  export CROSS_COMPILE=arm-linux-androideabi
  export CC=${CROSS_COMPILE}-gcc
  export CXX=${CROSS_COMPILE}-g++
  export LD=${CROSS_COMPILE}-ld
  export AR=${CROSS_COMPILE}-ar
  export RANLIB=${CROSS_COMPILE}-ranlib
  export STRIP=${CROSS_COMPILE}-strip
  export ANDROID_NDK_ROOT=$HOME/Applications/android-ndk/
  export SYSROOT=$HOME/Applications/android-ndk/platforms/android-14/arch-arm/
  export CFLAGS=--sysroot=$SYSROOT
  export CXXFLAGS=--sysroot=$SYSROOT
  export CPPFLAGS=--sysroot=$SYSROOT
  export LDFLAGS=--sysroot=$SYSROOT
  echo "Environment ready for cross-compilation."
  echo "Use --build=x86_64-unknown-linux-gnu --host=arm-linux-androideabi \
    for your configure scripts."
}

This other useful function exports the right set of environment variables so that cross-compilation can be performed. It sets the right compilers, compiler flags, linker, linker flags. Exporting CC and the like should be unnecessary, since a well-done configure script will honor the --host option but well, let's play it safe.

An alternative, possibly easier way to do this is to perform:

add_to_path_if "/path/to/android-ndk"

then to do:

function android() {
  export CC=`eval ndk-which gcc`
  export CXX=`eval ndk-which g++`
  export LD=`eval ndk-which ld`
  export AR=`eval ndk-which ar`
  export RANLIB=`eval ndk-which ranlib`
  export STRIP=`eval ndk-which strip`
  export ANDROID_NDK_ROOT=/path/to/android-ndk/
  export SYSROOT=/path/to/android-ndk/platforms/android-14/arch-arm/
  export CFLAGS=--sysroot=$SYSROOT
  export CXXFLAGS=--sysroot=$SYSROOT
  export CPPFLAGS=--sysroot=$SYSROOT
  export LDFLAGS=--sysroot=$SYSROOT
}
...

which will result in your having absolute paths instead of relative ones, which is probably safer.

Getting the Dropbear source, patching it

The Dropbear source is available online. Since I did this, the author updated the software; please get version 0.58 since that's the version on which my patch will cleanly apply.

Running ./configure --build=x86_64-unknown-linux-gnu --host=arm-linux-androideabi should, in theory, get everything right out of the box. Guess what? It doesn't.

Here's a list of all the things that need to be changed in Dropbear for cross-compilation onto Android to work.

Outdated config.guess and config.sub

Dropbear ships with an outdated config.guess and config.sub. I had to get updated versions of these files from GNU's website. (Took me a while to figure out why the configure script wasn't aware of androideabi.)

Makefile bugs

The Makefile from Dropbear doesn't include LDFLAGS at the final linking step. My patch also fixes this.

Existing patch

The SSHDroid project provides a patch for Android. It doesn't, however, apply to the latest version.

I rebased the original patch. Also, some bits of it were outdated / unclear, so I simplified them. All in all, I would say that the patch is hackish, at best, and that it should be rewritten more cleanly (something I briefly considered doing, before recalling that I had a thesis to write).

What the patch does is short-circuit the regular Unix login machinery, which doesn't work on Android, in favor of a unique password passed on the command-line, as well as a path to a unique RSA key that's also passed on the command-line.

More problems since version 0.52

The patch was for version 0.52 of Dropbear, some compilation errors had been introduced in the meanwhile, when targeting Android.

  • The Dropbear source uses, at some point, S_IWRITE, a constant that should be S_IWUSR (source).
  • The Dropbear source uses getpass, which is not implemented on Android. I removed the offending part, since the function was short-circuited on Android anyway (I made cli_auth_try always return NULL).
  • The Dropbear source uses crypt, which is not implemented on Android either. I emptied out the svr_auth_password function since, again, it was not used with the Android patch.

My final patch

Here's a patch that does all of the above together. Apply with patch -p1 < dropbear-patch.

Compiling it!

configure

./configure --build=x86_64-unknown-linux-gnu --host=arm-linux-androideabi \
  --disable-zlib --disable-largefile --disable-loginfunc \
  --disable-shadow --disable-utmp --disable-utmpx --disable-wtmp \
  --disable-wtmpx --disable-pututline --disable-pututxline --disable-lastlog

config.guess and config.sub are still wrong

Yes. Edit config.h, search for USE_DEV_PTMX and put in there #define USE_DEV_PTMX 1.

make

STATIC=1 MULTI=1 SCPPROGRESS=0 PROGRAMS="dropbear dropbearkey scp dbclient" make strip

This generate a multi-binary, that is, a binary that behaves as any one of the four PROGRAMS above depending on argv[0], that is, the symlink through which it is called.

Making life easier

Copy the dropbearmulti binary to the tablet, say, for instance, in /system/xbin. Then, create the right set of symlinks.

ln -s dropbearmulti dropbear
ln -s dropbearmulti dropbearkey
ln -s dropbearmulti ssh
ln -s dropbearmulti scp

Final setup

Use the dropbearkey utility to create your server's private key in /etc/dropbear/dropbear_rsa_host_key.

Put your client's public RSA key in, say, /sdcard/id_rsa.pub.

Finally, I advise you to create a small shell script called d that will start Dropbear with the right set of options.

#!/system/bin/sh
dropbear -A -N jonathan -R /sdcard/id_rsa.pub -F -E -p 22

The -A option bypasses the getpwnam POSIX call that gets the password for a given system user, as in a regular Unix login. It uses instead, as the only valid user for connecting to the SSH server, the username specified through the -N option. The -R option specifies the location of the public key, -F prevents the server from forking in the background so that you can Ctrl-C it, -E logs the output to stderr (syslog doesn't work on Android) and -p specifies the port.

If you wish, you can also use, instead of -R, the -C option which allows you to pass a password on the command-line. This will be the only valid password to connect to that Dropbear server.