/COPYING
/auto-version
debug.keystore
+/local.mk
## Where to put the object files.
OUTDIR = out
-CONFIGDIR = $(OUTDIR)/config
+CONFIGDIR = $(OUTDIR)/config-$(hostcpu)
## The Java runtime, for some reason, hardcodes its default for
## `java.io.tmpdir', inviting security problems. If the user has defined a
LDFLAGS = -Wl,-z,defs
## Host toolchain.
-FLAVOURS += host
-ENV.host =
-CC.host = gcc
-CFLAGS.host = $(CFLAGS) -fPIC
-LD.host = $(CC.host)
-LDFLAGS.host = $(LDFLAGS)
-CONFIG.host =
+FLAVOURS += host-$(hostcpu)
+ENV.host-$(hostcpu) =
+CC.host-$(hostcpu) = gcc
+CFLAGS.host-$(hostcpu) = $(CFLAGS) -fPIC
+LD.host-$(hostcpu) = $(CC.host-$(hostcpu))
+LDFLAGS.host-$(hostcpu) = $(LDFLAGS)
+CONFIG.host-$(hostcpu) =
## Host JNI machinery.
$(CONFIGDIR)/jdkdir.mk:
(darwin) echo macosx ;; \
(*) echo $(hostos) ;; \
esac)
-CFLAGS.host += -I$(JDKDIR)/include -I$(JDKDIR)/include/$(JDKPLAT)
+CFLAGS.host-$(hostcpu) += -I$(JDKDIR)/include -I$(JDKDIR)/include/$(JDKPLAT)
## Android SDK location.
ANDROID_SDKDIR = /usr/local/android/sdk
CLEANFILES += $(OUTDIR)/obj.*/*.o $(OUTDIR)/obj.*/*.d
## Machinery for linking.
-JNIDIR.host = $(OUTDIR)
+JNIDIR.host-$(hostcpu) = $(OUTDIR)/lib.host-$(hostcpu)
JNIDIR.ndk = $(OUTDIR)/pkg/lib/$1
define apklib-rule
$(foreach a,$(APKLIBS), \
$(eval $(call apklib-rule,$f,$a))))
-CLEANFILES += $(OUTDIR)/pkg/lib/*/lib*.so $(OUTDIR)/lib*.so
+CLEANFILES += $(OUTDIR)/pkg/lib/*/lib*.so
+CLEANFILES += $(OUTDIR)/lib.host-$(hostcpu)/lib*.so
###--------------------------------------------------------------------------
### Android string resource generation.
CLASSES += keys:progress,tar,sys,util
CLASSES += terminal:progress,sys,util
CLASSES += R
-CLASSES += toy-activity:R
+CLASSES += app:R
+CLASSES += toy-activity:app,R
## Building class files.
$(STAMPDIR)/%.class-stamp: %.java
clean::; rm -f $(CLEANFILES)
realclean::; rm -f $(REALCLEANFILES)
-repl: $(CLASSSTAMPS) $(foreach a,$(APKLIBS),$(OUTDIR)/$a)
- $(SCALA) -cp $(CLASSDIR) -Djava.lib.path=$(OUTDIR) -Yno-load-impl-class
+repl: $(CLASSSTAMPS) $(foreach a,$(APKLIBS),$(JNIDIR.host-$(hostcpu))/$a)
+ $(SCALA) -cp $(CLASSDIR) -Yno-load-impl-class \
+ -Djava.lib.path=$(JNIDIR.host-$(hostcpu)) \
t:; : $(show)
.PHONY: t
--- /dev/null
+/* -*-scala-*-
+ *
+ * Setting up the Android environment
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package uk.org.distorted.tripe; package object app {
+
+/*----- Imports -----------------------------------------------------------*/
+
+import java.io.{File, IOException};
+
+import scala.collection.mutable.HashMap;
+
+import android.content.Context; import Context.MODE_WORLD_READABLE;
+import android.os.Build; import Build.{CPU_ABI, CPU_ABI2};
+import android.util.Log;
+
+import sys.FileImplicits._;
+
+/*----- Regular expressions for parsing the `.installed file --------------*/
+
+private final val RX_COMMENT = """(?x) ^ \s* (?: \# .* )? $""".r;
+private final val RX_KEYVAL = """(?x) ^ \s*
+ ([-\w]+)
+ (?:\s+(?!=)|\s*=\s*)
+ (|\S|\S.*\S)
+ \s* $""".r;
+
+/*----- Main code ---------------------------------------------------------*/
+
+private final val TAG = "TrIPE";
+
+var root: File = null;
+
+private def install(ctx: Context, inst: HashMap[String, String]) {
+
+ /* First, figure out which ABIs are wanted on this device. Unfortunately,
+ * the good way of doing this isn't available in our minimum API level, so
+ * we must use reflection.
+ */
+ val abis = try {
+ classOf[Build].getField("SUPPORTED_ABIS").get(null).
+ asInstanceOf[Array[String]]
+ } catch { case _: NoSuchFieldException =>
+ Array(CPU_ABI, CPU_ABI2) flatMap {
+ case null | "" => None
+ case s => Some(s)
+ }
+ }
+ Log.d(TAG, s"abis = ${abis.mkString(", ")}");
+
+ /* Clear out whatever might be there already. */
+ val bindir = root/"bin";
+ bindir.rmTree();
+ bindir.mkdir_!();
+
+ /* Now extract each of our binaries using the best available ABI. */
+ val assets = ctx.getAssets;
+ for (abi <- abis) {
+ val binsrc = s"bin/$abi";
+ for (base <- assets.list(binsrc)) {
+ val outfile = bindir/base;
+ if (!outfile.exists_!) {
+ Log.d(TAG, s"install: extract `$base' using abi `$abi'");
+ outfile.withOutput { out =>
+ closing(assets.open(s"$binsrc/$base")) { in =>
+ for ((buf, n) <- blocks(in)) out.write(buf, 0, n);
+ }
+ }
+ }
+ outfile.chmod_!(0x1ed);
+ }
+ }
+
+ /* Write out a new install file. */
+ val infofile = root/".installed";
+ val newinfofile = root/".installed.new";
+ newinfofile.withWriter { out =>
+ out.write(s"""### -*-conf-*-
+
+uuid = ${ctx.getString(R.string.auto_build_uuid)}
+""");
+ }
+ newinfofile.rename_!(infofile);
+}
+
+def setup(ctx: Context) {
+
+ /* Make our root directory and remember where it is. */
+ root = ctx.getFilesDir;
+ if (!root.isdir_!) {
+ throw new IOException("system failed to create `files' " +
+ "(but didn't tell us)");
+ }
+
+ /* Find out which build, if any, corresponds to what's there already. */
+ val inst = HashMap[String, String]();
+ try { root/".installed" withReader { in =>
+ var lno = 1;
+ for (line <- lines(in)) {
+ line match {
+ case RX_COMMENT() => ok;
+ case RX_KEYVAL(k, v) => inst(k) = v;
+ case _ => Log.w(TAG, s".installed:$lno: ignored unparseable line");
+ }
+ lno += 1;
+ }
+ } } catch {
+ case e: IOException =>
+ Log.w(TAG, s".installed: I/O error: ${e.getMessage}");
+ }
+
+ /* If this doesn't match, then we have some work to do. */
+ if (inst.getOrElse("uuid", "<nothing>") !=
+ ctx.getString(R.string.auto_build_uuid))
+ install(ctx, inst);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
+
+}
put_cstring(jni, path, pathstr);
}
+JNIEXPORT void JNIFUNC(chmod)(JNIEnv *jni, jobject cls,
+ jobject path, jint mode)
+{
+ const char *pathstr = 0;
+
+ pathstr = get_cstring(jni, path); if (!pathstr) goto end;
+ if (chmod(pathstr, mode)) {
+ except_syserror(jni, SYSERR, errno,
+ "failed st permissions on `%s'", pathstr);
+ goto end;
+ }
+end:
+ put_cstring(jni, path, pathstr);
+}
+
JNIEXPORT void JNIFUNC(mkfile)(JNIEnv *jni, jobject cls,
jobject path, jint mode)
{
private def parseConfig(file: File): HashMap[String, String] = {
/* Build the new configuration in a temporary place. */
- var m = HashMap[String, String]();
+ val m = HashMap[String, String]();
/* Read the config file into our map. */
file.withReader { in =>
+/* -*-scala-*-
+ *
+ * The database of known peers
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
+ */
+
package uk.org.distorted.tripe; package object peers {
+/*----- Imports -----------------------------------------------------------*/
+
import java.io.{BufferedReader, File, FileReader, Reader};
import java.net.{InetAddress, Inet4Address, Inet6Address,
UnknownHostException};
import scala.util.control.Breaks;
import scala.util.matching.Regex;
-val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
-val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
-val RX_ASSGN = """(?x) ^
+/*----- Handy regular expressions -----------------------------------------*/
+
+private final val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
+private final val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
+private final val RX_ASSGN = """(?x) ^
([^\s:=] (?: [^:=]* [^\s:=])?)
\s* [:=] \s*
(| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
(?: \s+ (?: [;\#].*)? )? $""".r;
-val RX_CONT = """(?x) ^ \s+
+private final val RX_CONT = """(?x) ^ \s+
(| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
(?: \s+ (?: [;\#].*)? )? $""".r;
-val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
-val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
-val RX_PARENT = """(?x) [^\s,]+""".r
+private final val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
+private final val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
+private final val RX_PARENT = """(?x) [^\s,]+""".r
+
+/*----- Name resolution ---------------------------------------------------*/
-object BulkResolver {
- val BREAK = new Breaks;
+private object BulkResolver {
+ private val BREAK = new Breaks;
}
-class BulkResolver(val nthreads: Int = 8) {
- import BulkResolver.BREAK._;
+private class BulkResolver(val nthreads: Int = 8) {
+ import BulkResolver.BREAK.{breakable, break};
+
class Host(val name: String) {
var a4, a6: Seq[InetAddress] = Seq.empty;
}
}
}
+
val ch = new Channel[Host];
val map = HashMap[String, Host]();
var preparing = true;
map(name).get(flags);
}
+/*----- The peer configuration --------------------------------------------*/
+
def fmtpath(path: Seq[String]) =
path.reverse map { i => s"`$i'" } mkString " -> ";
extends Exception {
override def getMessage(): String = s"$file:$lno: $msg";
}
+
class MissingConfigSection(val sect: String) extends Exception {
override def getMessage(): String =
s"missing configuration section `$sect'";
}
+
class MissingConfigItem(val sect: String, val key: String,
val path: Seq[(String)]) extends Exception {
override def getMessage(): String = {
else msg + s" (wanted while expanding ${fmtpath(path)})"
}
}
+
class AmbiguousConfig(val key: String,
val v0: String, val p0: Seq[String],
val v1: String, val p1: Seq[String])
override def getMessage(): String =
s"found a cycle ${fmtpath(path)} looking up key `$key'";
}
+
class NoHostAddresses(val sect: String, val key: String, val host: String)
extends Exception {
override def getMessage(): String =
s"no addresses found for `$host' (key `$key' in section `$sect')";
}
-object Config {
- sealed abstract class ConfigCacheEntry;
- case object StillLooking extends ConfigCacheEntry;
- case object NotFound extends ConfigCacheEntry;
- case class Found(value: String, path: Seq[String])
- extends ConfigCacheEntry;
-}
+private sealed abstract class ConfigCacheEntry;
+private case object StillLooking extends ConfigCacheEntry;
+private case object NotFound extends ConfigCacheEntry;
+private case class Found(value: String, path: Seq[String])
+ extends ConfigCacheEntry;
class Config { conf =>
- import Config._;
- class Section(val name: String) {
- val itemmap = HashMap[String, String]();
- val cache = HashMap[String, ConfigCacheEntry]();
+
+ class Section private(val name: String) {
+ private val itemmap = HashMap[String, String]();
+ private[this] val cache = HashMap[String, ConfigCacheEntry]();
+
override def toString: String = s"${getClass.getName}($name)";
+
def parents: Seq[Section] =
(itemmap.get("@inherit")
map { pp => (RX_PARENT.findAllIn(pp) map { conf.section _ }).toList }
getOrElse Nil);
- def get_internal(key: String, path: Seq[String] = Nil):
+ private def get_internal(key: String, path: Seq[String] = Nil):
Option[(String, Seq[String])] = {
val incpath = name +: path;
expand(key, v0, resolve, path)
}
- def expand(key: String, value: String, resolve: Boolean,
- path: Seq[String]): String = {
+ private def expand(key: String, value: String, resolve: Boolean,
+ path: Seq[String]): String = {
val v1 = RX_REF.replaceAllIn(value, { m =>
Regex.quoteReplacement(get(m.group(1), resolve, path))
});
b.result
}
}
- val sectmap = new HashMap[String, Section];
+
+ private[this] val sectmap = new HashMap[String, Section];
def sections: Iterator[Section] = sectmap.values.iterator;
def section(name: String): Section =
sectmap.getOrElse(name, throw new MissingConfigSection(name));
- val resolver = new BulkResolver;
+ private[this] val resolver = new BulkResolver;
- def parseFile(path: File): this.type = {
+ private[this] def parseFile(path: File): this.type = {
println(s";; parse ${path.getPath}");
withCleaner { clean =>
val in = new FileReader(path); clean { in.close(); }
}
this
}
+
def parse(path: File): this.type = {
if (!path.isDirectory) parseFile(path);
else for {
}
}
+/*----- That's all, folks -------------------------------------------------*/
+
}
+++ /dev/null
-package uk.org.distorted.tripe;
-
-import java.io.{Closeable, File, InputStream, OutputStream};
-import jni.Constants._;
-
-class Connection(path: File) extends Closeable {
- def this(path: String) { this(new File(path)); }
- val conn = jni.connect(path.getPath);
- override def close() { jni.close(conn, CF_CLOSEMASK); }
- lazy val input = new ConnectionInputStream(this);
- lazy val output = new ConnectionOutputStream(this);
- override protected def finalize() { super.finalize(); close(); }
-}
-
-class ConnectionInputStream(val conn: Connection) extends InputStream {
- override def read(): Int = {
- val buf = new Array[Byte](1);
- val n = read(buf, 0, 1);
- if (n < 0) -1 else buf(0)&0xff;
- }
- override def read(buf: Array[Byte]): Int =
- read(buf, 0, buf.length);
- override def read(buf: Array[Byte], start: Int, len: Int) =
- jni.recv(conn.conn, buf, start, len);
- override def close() { jni.close(conn.conn, CF_CLOSERD); }
-}
-
-class ConnectionOutputStream(val conn: Connection) extends OutputStream {
- override def write(b: Int) = {
- val buf = Array[Byte](b.toByte);
- write(buf, 0, 1);
- }
- override def write(buf: Array[Byte]) { write(buf, 0, buf.length); }
- override def write(buf: Array[Byte], start: Int, len: Int) =
- jni.send(conn.conn, buf, start, len);
- override def close() { jni.close(conn.conn, CF_CLOSEWR); }
-}
import java.io.{BufferedReader, BufferedWriter, Closeable, File,
FileDescriptor, FileInputStream, FileOutputStream,
+ FileReader, FileWriter,
InputStream, InputStreamReader,
OutputStream, OutputStreamWriter};
import java.nio.{ByteBuffer, CharBuffer};
def rmdir(path: String) { rmdir(path.toCString); }
@native protected def mkdir(path: CString, mode: Int);
def mkdir(path: String, mode: Int) { mkdir(path.toCString, mode); }
+@native protected def chmod(path: CString, mode: Int);
+def chmod(path: String, mode: Int) { chmod(path.toCString, mode); }
@native protected def mkfile(path: CString, mode: Int);
def mkfile(path: String, mode: Int) { mkfile(path.toCString, mode); }
@native protected def rename(from: CString, to: CString);
def rmdir_!() { rmdir(file.getPath); }
def mkdir_!(mode: Int) { mkdir(file.getPath, mode); }
def mkdir_!() { mkdir_!(0x1ff); }
+ def chmod_!(mode: Int) { chmod(file.getPath, mode); }
def mkfile_!(mode: Int) { mkfile(file.getPath, mode); }
def mkfile_!() { mkfile_!(0x1b6); }
def rename_!(to: File) { rename(file.getPath, to.getPath); }
/* Opening files. Again, I'm surprised this isn't here already. */
def open(): FileInputStream = new FileInputStream(file);
def openForOutput(): FileOutputStream = new FileOutputStream(file);
- def reader(): BufferedReader =
- new BufferedReader(new InputStreamReader(open()));
- def writer(): BufferedWriter =
- new BufferedWriter(new OutputStreamWriter(openForOutput()));
+ def reader(): BufferedReader = new BufferedReader(new FileReader(file));
+ def writer(): BufferedWriter = new BufferedWriter(new FileWriter(file));
def withInput[T](body: FileInputStream => T): T = {
val in = open();
try { body(in) }
val out = openForOutput();
try { body(out) } finally { out.close(); }
}
- def withReader[T](body: BufferedReader => T): T = withInput { in =>
- body(new BufferedReader(new InputStreamReader(in)))
- };
- def withWriter[T](body: BufferedWriter => T): T = withOutput { out =>
- val w = new BufferedWriter(new OutputStreamWriter(out));
- /* Do this the hard way, so that we flush the `BufferedWriter'. */
- try { body(w) } finally { w.close(); }
+ def withReader[T](body: BufferedReader => T): T = {
+ val r = reader();
+ try { body(r) }
+ finally { r.close(); }
+ }
+ def withWriter[T](body: BufferedWriter => T): T = {
+ val w = writer();
+ try { body(w) }
+ finally { w.close(); }
}
}
}
import scala.util.control.Breaks;
-object Setup {
- private final val TAG = "Setup";
- private val BREAK = new Breaks;
- import BREAK.{breakable, break};
-
- def setup(ctx: Context) {
- val bindir = ctx.getDir("bin", MODE_WORLD_READABLE);
- val assets = ctx.getAssets;
-
- val abis =
- try { classOf[Build].getField("SUPPORTED_ABIS").get(null).asInstanceOf[Array[String]] }
- catch {
- case _: NoSuchFieldException => Array(CPU_ABI, CPU_ABI2) flatMap {
- case null | "" => None
- case s => Some(s)
- }
- };
-
- Log.d(TAG, s"abis = ${abis.mkString(", ")}");
- Log.d(TAG, s"assets: ${assets.list("bin").mkString(", ")}");
-
- for (abi <- abis) {
- val binsrc = s"bin/$abi";
- for (base <- assets.list(binsrc)) {
- val prog = new File(bindir, base);
- if (!prog.exists) try {
- Log.d(TAG, s"creating $prog...");
- val in = assets.open(s"$binsrc/$base");
- Log.d(TAG, "opened source...");
- val out = new FileOutputStream(prog);
- Log.d(TAG, "opened target...");
- val buf = new Array[Byte](4096);
- breakable {
- while (true) {
- val n = in.read(buf);
- Log.d(TAG, s"read $n bytes...");
- if (n <= 0) break;
- out.write(buf, 0, n);
- }
- }
- in.close();
- out.close();
- Log.d(TAG, "set permissions...");
- if (!prog.setReadable(true, false) ||
- !prog.setExecutable(true, false))
- throw new IOException("failed to set program permissions");
- } catch {
- case exc: IOException =>
- Log.wtf(TAG, "fuck, failed to create prog", exc);
- }
- }
- }
- Log.d(TAG, "all OK");
- }
-}
object ToyActivity {
private final val TAG = "ToyActivity";
override protected def onCreate(joy: Bundle) {
super.onCreate(joy);
- Setup.setup(this);
+ app.setup(this);
setContentView(R.layout.toy);
Log.d(TAG, s"created ${this}");
}