X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/3a2f1a4ba4cc824e192c9d144b317a215e79c8d6..HEAD:/jni.c?ds=sidebyside diff --git a/jni.c b/jni.c index 9f9e44d..3841f35 100644 --- a/jni.c +++ b/jni.c @@ -1,286 +1,1771 @@ +/* -*-c-*- + * + * Native-code portions of the project + * + * (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 . + */ + +/*----- Header files ------------------------------------------------------*/ + +#define _FILE_OFFSET_BITS 64 + #include +#include #include #include -#include +#include #include #include #include -#include - -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include + +#include + +//#include +#include + +#include +#include +#include +#include + +#include + +#define TUN_INTERNALS +#include #undef sun -union align { - int i; - long l; - double d; - void *p; - void (*f)(void *); - struct notexist *s; -}; +/*----- Magic class names and similar -------------------------------------*/ + +/* The name decoration is horrific. Hide it. */ +#define JNIFUNC(f) Java_uk_org_distorted_tripe_sys_package_00024_##f + +/* The little class for bundling up error codes. */ +#define ERRENTCLS "uk/org/distorted/tripe/sys/package$ErrorEntry" + +/* The `sys' package class. */ +#define SYSCLS "uk/org/distorted/tripe/sys/package" + +/* The server lock class. */ +#define LOCKCLS "uk/org/distorted/tripe/sys/package$ServerLock" + +/* The `stat' class. */ +#define STATCLS "uk/org/distorted/tripe/sys/package$FileInfo" + +/* Standard Java classes. */ +#define FDCLS "java/io/FileDescriptor" +#define STRCLS "java/lang/String" +#define RANDCLS "java/security/SecureRandom" + +/* Exception class names. */ +#define NULLERR "java/lang/NullPointerException" +#define TYPEERR "uk/org/distorted/tripe/sys/package$NativeObjectTypeException" +#define SYSERR "uk/org/distorted/tripe/sys/package$SystemError" +#define NAMEERR "uk/org/distorted/tripe/sys/package$NameResolutionException" +#define INITERR "uk/org/distorted/tripe/sys/package$InitializationException" +#define ARGERR "java/lang/IllegalArgumentException" +#define STERR "java/lang/IllegalStateException" +#define BOUNDSERR "java/lang/IndexOutOfBoundsException" + +/*----- Essential state ---------------------------------------------------*/ + +static JNIEnv *jni_tripe = 0; + +/*----- Miscellaneous utilities -------------------------------------------*/ + +static void vexcept(JNIEnv *jni, const char *clsname, + const char *msg, va_list *ap) +{ + jclass cls; + int rc; + dstr d = DSTR_INIT; + + cls = (*jni)->FindClass(jni, clsname); assert(cls); + if (!msg) + rc = (*jni)->ThrowNew(jni, cls, 0); + else { + dstr_vputf(&d, msg, ap); + rc = (*jni)->ThrowNew(jni, cls, d.buf); + assert(!rc); + dstr_destroy(&d); + } + assert(!rc); +} + +static void except(JNIEnv *jni, const char *clsname, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vexcept(jni, clsname, msg, &ap); + va_end(ap); +} + +#ifdef DEBUG +static void dump_bytes(const void *p, size_t n, size_t o) +{ + const unsigned char *q = p; + size_t i; + + if (!n) return; + for (;;) { + fprintf(stderr, ";; %08zx\n", o); + for (i = 0; i < 8; i++) + if (i < n) fprintf(stderr, "%02x ", q[i]); + else fprintf(stderr, "** "); + fprintf(stderr, ": "); + for (i = 0; i < 8; i++) + fputc(i >= n ? '*' : isprint(q[i]) ? q[i] : '.', stderr); + fputc('\n', stderr); + if (n <= 8) break; + q += 8; n -= 8; + } +} + +static void dump_byte_array(JNIEnv *jni, const char *what, jbyteArray v) +{ + jsize n; + jbyte *p; + + fprintf(stderr, ";; %s\n", what); + if (!v) { fprintf(stderr, ";; \n"); return; } + n = (*jni)->GetArrayLength(jni, v); + p = (*jni)->GetByteArrayElements(jni, v, 0); + dump_bytes(p, n, 0); + (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT); +} +#endif + +static jbyteArray wrap_cstring(JNIEnv *jni, const char *p) +{ + size_t n; + jbyteArray v; + jbyte *q; + + if (!p) return (0); + n = strlen(p) + 1; + v = (*jni)->NewByteArray(jni, n); if (!v) return (0); + q = (*jni)->GetByteArrayElements(jni, v, 0); if (!q) return (0); + memcpy(q, p, n); + (*jni)->ReleaseByteArrayElements(jni, v, q, 0); + return (v); +} + +static const char *get_cstring(JNIEnv *jni, jbyteArray v) +{ + if (!v) { except(jni, NULLERR, 0); return (0); } + return ((const char *)(*jni)->GetByteArrayElements(jni, v, 0)); +} + +static void put_cstring(JNIEnv *jni, jbyteArray v, const char *p) + { if (p) (*jni)->ReleaseByteArrayElements(jni, v, (jbyte *)p, JNI_ABORT); } + +static void vexcept_syserror(JNIEnv *jni, const char *clsname, + int err, const char *msg, va_list *ap) +{ + jclass cls; + int rc; + dstr d = DSTR_INIT; + jbyteArray msgstr; + jthrowable e; + jmethodID init; + + cls = (*jni)->FindClass(jni, clsname); assert(cls); + init = (*jni)->GetMethodID(jni, cls, "", "(I[B)V"); assert(init); + dstr_vputf(&d, msg, ap); + msgstr = wrap_cstring(jni, d.buf); assert(msgstr); + dstr_destroy(&d); + e = (*jni)->NewObject(jni, cls, init, err, msgstr); assert(e); + rc = (*jni)->Throw(jni, e); assert(!rc); +} + +static void except_syserror(JNIEnv *jni, const char *clsname, + int err, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + vexcept_syserror(jni, clsname, err, msg, &ap); + va_end(ap); +} + +static int set_nonblocking(JNIEnv *jni, int fd, int nb) +{ + int f0 = fcntl(fd, F_GETFL), f1; + if (f0 < 0) goto err; + if (nb) f1 = f0 | O_NONBLOCK; + else f1 = f0&~O_NONBLOCK; + if (fcntl(fd, F_SETFL, f1)) goto err; + return (f0 & O_NONBLOCK); +err: + except_syserror(jni, SYSERR, errno, + "failed to set descriptor nonblocking"); + return (-1); +} + +static int set_closeonexec(JNIEnv *jni, int fd) +{ + int f = fcntl(fd, F_GETFD); + if (f < 0 || fcntl(fd, F_SETFD, f | FD_CLOEXEC)) { + except_syserror(jni, SYSERR, errno, + "failed to set descriptor close-on-exec"); + return (-1); + } + return (0); +} + +/*----- Wrapping native types ---------------------------------------------*/ + +/* There's no way defined in the JNI to stash a C pointer in a Java object. + * It seems that the usual approach is to cast to `jlong', but this is + * clearly unsatisfactory. Instead, we store structures as Java byte arrays, + * with a 32-bit tag on the front. + */ struct native_type { const char *name; size_t sz; - uint32_t tag; + uint32 tag; }; -typedef jbyteArray wrapped; +typedef jbyteArray wrapper; -struct open { - wrapped obj; - jbyte *arr; +struct native_base { + uint32 tag; }; -struct base { - uint32_t tag; +static int unwrap(JNIEnv *jni, void *p, + const struct native_type *ty, wrapper w) +{ + jbyte *q; + jclass cls; + struct native_base *b = p; + jsize n; + + if (!w) { except(jni, NULLERR, 0); return (-1); } + cls = (*jni)->FindClass(jni, "[B"); assert(cls); + if (!(*jni)->IsInstanceOf(jni, w, cls)) { + except(jni, TYPEERR, + "corrupted native object wrapper: expected a byte array"); + return (-1); + } + n = (*jni)->GetArrayLength(jni, w); + if (n != ty->sz) { + except(jni, TYPEERR, + "corrupted native object wrapper: wrong size for `%s'", + ty->name); + return (-1); + } + q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1); + memcpy(b, q, ty->sz); + (*jni)->ReleaseByteArrayElements(jni, w, q, JNI_ABORT); + if (b->tag != ty->tag) { + except(jni, TYPEERR, + "corrupted native object wrapper: expected tag for `%s'", + ty->name); + return (-1); + } + return (0); +} + +static int update_wrapper(JNIEnv *jni, const struct native_type *ty, + wrapper w, const void *p) +{ + jbyte *q; + + q = (*jni)->GetByteArrayElements(jni, w, 0); if (!q) return (-1); + memcpy(q, p, ty->sz); + (*jni)->ReleaseByteArrayElements(jni, w, q, 0); + return (0); +} + +static wrapper wrap(JNIEnv *jni, const struct native_type *ty, const void *p) +{ + wrapper w; + + w = (*jni)->NewByteArray(jni, ty->sz); if (!w) return (0); + if (update_wrapper(jni, ty, w, p)) return (0); + return (w); +} + +#define INIT_NATIVE(type, p) do (p)->_base.tag = type##_type.tag; while (0) + +/*----- Crypto information ------------------------------------------------*/ + +JNIEXPORT jint JNICALL JNIFUNC(hashsz)(JNIEnv *jni, jobject cls, + jstring hnamestr) +{ + jint rc = -1; + const char *hname; + const gchash *hc; + + hname = (*jni)->GetStringUTFChars(jni, hnamestr, 0); + if (!hname) goto end; + hc = ghash_byname(hname); if (!hc) goto end; + rc = hc->hashsz; + +end: + if (hname) (*jni)->ReleaseStringUTFChars(jni, hnamestr, hname); + return (rc); +} + +/*----- System errors -----------------------------------------------------*/ + +static const struct errtab { const char *tag; int err; } errtab[] = { + /* + ;;; The errno name table is very boring to type. To make life less + ;;; awful, put the errno names in this list and evaluate the code to + ;;; get Emacs to regenerate it. + + (let ((errors '(EPERM ENOENT ESRCH EINTR EIO ENXIO E2BIG ENOEXEC EBADF + ECHILD EAGAIN ENOMEM EACCES EFAULT ENOTBLK EBUSY EEXIST + EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE EMFILE ENOTTY + ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM + ERANGE + + EDEADLK ENAMETOOLONG ENOLCK ENOSYS ENOTEMPTY ELOOP + EWOULDBLOCK ENOMSG EIDRM ECHRNG EL2NSYNC EL3HLT EL3RST + ELNRNG EUNATCH ENOCSI EL2HLT EBADE EBADR EXFULL ENOANO + EBADRQC EBADSLT EDEADLOCK EBFONT ENOSTR ENODATA ETIME + ENOSR ENONET ENOPKG EREMOTE ENOLINK EADV ESRMNT ECOMM + EPROTO EMULTIHOP EDOTDOT EBADMSG EOVERFLOW ENOTUNIQ + EBADFD EREMCHG ELIBACC ELIBBAD ELIBSCN ELIBMAX ELIBEXEC + EILSEQ ERESTART ESTRPIPE EUSERS ENOTSOCK EDESTADDRREQ + EMSGSIZE EPROTOTYPE ENOPROTOOPT EPROTONOSUPPORT + ESOCKTNOSUPPORT EOPNOTSUPP EPFNOSUPPORT EAFNOSUPPORT + EADDRINUSE EADDRNOTAVAIL ENETDOWN ENETUNREACH ENETRESET + ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN + ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED EHOSTDOWN + EHOSTUNREACH EALREADY EINPROGRESS ESTALE EUCLEAN ENOTNAM + ENAVAIL EISNAM EREMOTEIO EDQUOT ENOMEDIUM EMEDIUMTYPE + ECANCELED ENOKEY EKEYEXPIRED EKEYREVOKED EKEYREJECTED + EOWNERDEAD ENOTRECOVERABLE ERFKILL EHWPOISON))) + (save-excursion + (goto-char (point-min)) + (search-forward (concat "***" "BEGIN errtab" "***")) + (beginning-of-line 2) + (delete-region (point) + (progn + (search-forward "***END***") + (beginning-of-line) + (point))) + (dolist (err errors) + (insert (format "#ifdef %s\n { \"%s\", %s },\n#endif\n" + err err err))))) + */ + /***BEGIN errtab***/ +#ifdef EPERM + { "EPERM", EPERM }, +#endif +#ifdef ENOENT + { "ENOENT", ENOENT }, +#endif +#ifdef ESRCH + { "ESRCH", ESRCH }, +#endif +#ifdef EINTR + { "EINTR", EINTR }, +#endif +#ifdef EIO + { "EIO", EIO }, +#endif +#ifdef ENXIO + { "ENXIO", ENXIO }, +#endif +#ifdef E2BIG + { "E2BIG", E2BIG }, +#endif +#ifdef ENOEXEC + { "ENOEXEC", ENOEXEC }, +#endif +#ifdef EBADF + { "EBADF", EBADF }, +#endif +#ifdef ECHILD + { "ECHILD", ECHILD }, +#endif +#ifdef EAGAIN + { "EAGAIN", EAGAIN }, +#endif +#ifdef ENOMEM + { "ENOMEM", ENOMEM }, +#endif +#ifdef EACCES + { "EACCES", EACCES }, +#endif +#ifdef EFAULT + { "EFAULT", EFAULT }, +#endif +#ifdef ENOTBLK + { "ENOTBLK", ENOTBLK }, +#endif +#ifdef EBUSY + { "EBUSY", EBUSY }, +#endif +#ifdef EEXIST + { "EEXIST", EEXIST }, +#endif +#ifdef EXDEV + { "EXDEV", EXDEV }, +#endif +#ifdef ENODEV + { "ENODEV", ENODEV }, +#endif +#ifdef ENOTDIR + { "ENOTDIR", ENOTDIR }, +#endif +#ifdef EISDIR + { "EISDIR", EISDIR }, +#endif +#ifdef EINVAL + { "EINVAL", EINVAL }, +#endif +#ifdef ENFILE + { "ENFILE", ENFILE }, +#endif +#ifdef EMFILE + { "EMFILE", EMFILE }, +#endif +#ifdef ENOTTY + { "ENOTTY", ENOTTY }, +#endif +#ifdef ETXTBSY + { "ETXTBSY", ETXTBSY }, +#endif +#ifdef EFBIG + { "EFBIG", EFBIG }, +#endif +#ifdef ENOSPC + { "ENOSPC", ENOSPC }, +#endif +#ifdef ESPIPE + { "ESPIPE", ESPIPE }, +#endif +#ifdef EROFS + { "EROFS", EROFS }, +#endif +#ifdef EMLINK + { "EMLINK", EMLINK }, +#endif +#ifdef EPIPE + { "EPIPE", EPIPE }, +#endif +#ifdef EDOM + { "EDOM", EDOM }, +#endif +#ifdef ERANGE + { "ERANGE", ERANGE }, +#endif +#ifdef EDEADLK + { "EDEADLK", EDEADLK }, +#endif +#ifdef ENAMETOOLONG + { "ENAMETOOLONG", ENAMETOOLONG }, +#endif +#ifdef ENOLCK + { "ENOLCK", ENOLCK }, +#endif +#ifdef ENOSYS + { "ENOSYS", ENOSYS }, +#endif +#ifdef ENOTEMPTY + { "ENOTEMPTY", ENOTEMPTY }, +#endif +#ifdef ELOOP + { "ELOOP", ELOOP }, +#endif +#ifdef EWOULDBLOCK + { "EWOULDBLOCK", EWOULDBLOCK }, +#endif +#ifdef ENOMSG + { "ENOMSG", ENOMSG }, +#endif +#ifdef EIDRM + { "EIDRM", EIDRM }, +#endif +#ifdef ECHRNG + { "ECHRNG", ECHRNG }, +#endif +#ifdef EL2NSYNC + { "EL2NSYNC", EL2NSYNC }, +#endif +#ifdef EL3HLT + { "EL3HLT", EL3HLT }, +#endif +#ifdef EL3RST + { "EL3RST", EL3RST }, +#endif +#ifdef ELNRNG + { "ELNRNG", ELNRNG }, +#endif +#ifdef EUNATCH + { "EUNATCH", EUNATCH }, +#endif +#ifdef ENOCSI + { "ENOCSI", ENOCSI }, +#endif +#ifdef EL2HLT + { "EL2HLT", EL2HLT }, +#endif +#ifdef EBADE + { "EBADE", EBADE }, +#endif +#ifdef EBADR + { "EBADR", EBADR }, +#endif +#ifdef EXFULL + { "EXFULL", EXFULL }, +#endif +#ifdef ENOANO + { "ENOANO", ENOANO }, +#endif +#ifdef EBADRQC + { "EBADRQC", EBADRQC }, +#endif +#ifdef EBADSLT + { "EBADSLT", EBADSLT }, +#endif +#ifdef EDEADLOCK + { "EDEADLOCK", EDEADLOCK }, +#endif +#ifdef EBFONT + { "EBFONT", EBFONT }, +#endif +#ifdef ENOSTR + { "ENOSTR", ENOSTR }, +#endif +#ifdef ENODATA + { "ENODATA", ENODATA }, +#endif +#ifdef ETIME + { "ETIME", ETIME }, +#endif +#ifdef ENOSR + { "ENOSR", ENOSR }, +#endif +#ifdef ENONET + { "ENONET", ENONET }, +#endif +#ifdef ENOPKG + { "ENOPKG", ENOPKG }, +#endif +#ifdef EREMOTE + { "EREMOTE", EREMOTE }, +#endif +#ifdef ENOLINK + { "ENOLINK", ENOLINK }, +#endif +#ifdef EADV + { "EADV", EADV }, +#endif +#ifdef ESRMNT + { "ESRMNT", ESRMNT }, +#endif +#ifdef ECOMM + { "ECOMM", ECOMM }, +#endif +#ifdef EPROTO + { "EPROTO", EPROTO }, +#endif +#ifdef EMULTIHOP + { "EMULTIHOP", EMULTIHOP }, +#endif +#ifdef EDOTDOT + { "EDOTDOT", EDOTDOT }, +#endif +#ifdef EBADMSG + { "EBADMSG", EBADMSG }, +#endif +#ifdef EOVERFLOW + { "EOVERFLOW", EOVERFLOW }, +#endif +#ifdef ENOTUNIQ + { "ENOTUNIQ", ENOTUNIQ }, +#endif +#ifdef EBADFD + { "EBADFD", EBADFD }, +#endif +#ifdef EREMCHG + { "EREMCHG", EREMCHG }, +#endif +#ifdef ELIBACC + { "ELIBACC", ELIBACC }, +#endif +#ifdef ELIBBAD + { "ELIBBAD", ELIBBAD }, +#endif +#ifdef ELIBSCN + { "ELIBSCN", ELIBSCN }, +#endif +#ifdef ELIBMAX + { "ELIBMAX", ELIBMAX }, +#endif +#ifdef ELIBEXEC + { "ELIBEXEC", ELIBEXEC }, +#endif +#ifdef EILSEQ + { "EILSEQ", EILSEQ }, +#endif +#ifdef ERESTART + { "ERESTART", ERESTART }, +#endif +#ifdef ESTRPIPE + { "ESTRPIPE", ESTRPIPE }, +#endif +#ifdef EUSERS + { "EUSERS", EUSERS }, +#endif +#ifdef ENOTSOCK + { "ENOTSOCK", ENOTSOCK }, +#endif +#ifdef EDESTADDRREQ + { "EDESTADDRREQ", EDESTADDRREQ }, +#endif +#ifdef EMSGSIZE + { "EMSGSIZE", EMSGSIZE }, +#endif +#ifdef EPROTOTYPE + { "EPROTOTYPE", EPROTOTYPE }, +#endif +#ifdef ENOPROTOOPT + { "ENOPROTOOPT", ENOPROTOOPT }, +#endif +#ifdef EPROTONOSUPPORT + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, +#endif +#ifdef ESOCKTNOSUPPORT + { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT }, +#endif +#ifdef EOPNOTSUPP + { "EOPNOTSUPP", EOPNOTSUPP }, +#endif +#ifdef EPFNOSUPPORT + { "EPFNOSUPPORT", EPFNOSUPPORT }, +#endif +#ifdef EAFNOSUPPORT + { "EAFNOSUPPORT", EAFNOSUPPORT }, +#endif +#ifdef EADDRINUSE + { "EADDRINUSE", EADDRINUSE }, +#endif +#ifdef EADDRNOTAVAIL + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, +#endif +#ifdef ENETDOWN + { "ENETDOWN", ENETDOWN }, +#endif +#ifdef ENETUNREACH + { "ENETUNREACH", ENETUNREACH }, +#endif +#ifdef ENETRESET + { "ENETRESET", ENETRESET }, +#endif +#ifdef ECONNABORTED + { "ECONNABORTED", ECONNABORTED }, +#endif +#ifdef ECONNRESET + { "ECONNRESET", ECONNRESET }, +#endif +#ifdef ENOBUFS + { "ENOBUFS", ENOBUFS }, +#endif +#ifdef EISCONN + { "EISCONN", EISCONN }, +#endif +#ifdef ENOTCONN + { "ENOTCONN", ENOTCONN }, +#endif +#ifdef ESHUTDOWN + { "ESHUTDOWN", ESHUTDOWN }, +#endif +#ifdef ETOOMANYREFS + { "ETOOMANYREFS", ETOOMANYREFS }, +#endif +#ifdef ETIMEDOUT + { "ETIMEDOUT", ETIMEDOUT }, +#endif +#ifdef ECONNREFUSED + { "ECONNREFUSED", ECONNREFUSED }, +#endif +#ifdef EHOSTDOWN + { "EHOSTDOWN", EHOSTDOWN }, +#endif +#ifdef EHOSTUNREACH + { "EHOSTUNREACH", EHOSTUNREACH }, +#endif +#ifdef EALREADY + { "EALREADY", EALREADY }, +#endif +#ifdef EINPROGRESS + { "EINPROGRESS", EINPROGRESS }, +#endif +#ifdef ESTALE + { "ESTALE", ESTALE }, +#endif +#ifdef EUCLEAN + { "EUCLEAN", EUCLEAN }, +#endif +#ifdef ENOTNAM + { "ENOTNAM", ENOTNAM }, +#endif +#ifdef ENAVAIL + { "ENAVAIL", ENAVAIL }, +#endif +#ifdef EISNAM + { "EISNAM", EISNAM }, +#endif +#ifdef EREMOTEIO + { "EREMOTEIO", EREMOTEIO }, +#endif +#ifdef EDQUOT + { "EDQUOT", EDQUOT }, +#endif +#ifdef ENOMEDIUM + { "ENOMEDIUM", ENOMEDIUM }, +#endif +#ifdef EMEDIUMTYPE + { "EMEDIUMTYPE", EMEDIUMTYPE }, +#endif +#ifdef ECANCELED + { "ECANCELED", ECANCELED }, +#endif +#ifdef ENOKEY + { "ENOKEY", ENOKEY }, +#endif +#ifdef EKEYEXPIRED + { "EKEYEXPIRED", EKEYEXPIRED }, +#endif +#ifdef EKEYREVOKED + { "EKEYREVOKED", EKEYREVOKED }, +#endif +#ifdef EKEYREJECTED + { "EKEYREJECTED", EKEYREJECTED }, +#endif +#ifdef EOWNERDEAD + { "EOWNERDEAD", EOWNERDEAD }, +#endif +#ifdef ENOTRECOVERABLE + { "ENOTRECOVERABLE", ENOTRECOVERABLE }, +#endif +#ifdef ERFKILL + { "ERFKILL", ERFKILL }, +#endif +#ifdef EHWPOISON + { "EHWPOISON", EHWPOISON }, +#endif + /***END***/ }; -static void except(JNIEnv *jni, const char *clsname, const char *msg) +JNIEXPORT jobject JNIFUNC(errtab)(JNIEnv *jni, jobject cls) +{ + size_t i; + jclass eltcls; + jarray v; + jmethodID init; + jobject e; + + eltcls = + (*jni)->FindClass(jni, ERRENTCLS); + assert(eltcls); + v = (*jni)->NewObjectArray(jni, N(errtab), eltcls, 0); if (!v) return (0); + init = (*jni)->GetMethodID(jni, eltcls, "", + "(L"STRCLS";I)V"); + assert(init); + + for (i = 0; i < N(errtab); i++) { + e = (*jni)->NewObject(jni, eltcls, init, + (*jni)->NewStringUTF(jni, errtab[i].tag), + errtab[i].err); + (*jni)->SetObjectArrayElement(jni, v, i, e); + } + return (v); +} + +JNIEXPORT jobject JNIFUNC(strerror)(JNIEnv *jni, jobject cls, jint err) + { return (wrap_cstring(jni, strerror(err))); } + +/*----- Messing with file descriptors -------------------------------------*/ + +static void fdguts(JNIEnv *jni, jclass *cls, jfieldID *fid) +{ + *cls = (*jni)->FindClass(jni, FDCLS); assert(cls); + *fid = (*jni)->GetFieldID(jni, *cls, "fd", "I"); // OpenJDK + if (!*fid) *fid = (*jni)->GetFieldID(jni, *cls, "descriptor", "I"); // Android + assert(*fid); +} + +static int fdint(JNIEnv *jni, jobject jfd) +{ + jclass cls; + jfieldID fid; + + fdguts(jni, &cls, &fid); + return ((*jni)->GetIntField(jni, jfd, fid)); +} + +static jobject newfd(JNIEnv *jni, int fd) { + jobject jfd; jclass cls; + jmethodID init; + jfieldID fid; + + fdguts(jni, &cls, &fid); + init = (*jni)->GetMethodID(jni, cls, "", "()V"); assert(init); + jfd = (*jni)->NewObject(jni, cls, init); + (*jni)->SetIntField(jni, jfd, fid, fd); + return (jfd); +} + +JNIEXPORT jint JNIFUNC(fdint)(JNIEnv *jni, jobject cls, jobject jfd) + { return (fdint(jni, jfd)); } + +JNIEXPORT jobject JNIFUNC(newfd)(JNIEnv *jni, jobject cls, jint fd) + { return (newfd(jni, fd)); } + +JNIEXPORT jboolean JNIFUNC(isatty)(JNIEnv *jni, jobject cls, jobject jfd) + { return (isatty(fdint(jni, jfd))); } + +/*----- Low-level file operations -----------------------------------------*/ + +/* Java has these already, as methods on `java.io.File' objects. Alas, these + * methods are useless at reporting errors: they tend to return a `boolean' + * success/fail indicator, and throw away any more detailed information. + * There's better functionality in `java.nio.file.Files', but that only turns + * up in Android API 26 (in 7.0 Nougat). There's `android.system.Os', which + * has a bunch of POSIX-shaped functions -- but they're only in Android API + * 21 (in 5.0 Lollipop), and there's nothing in the support library to help. + * + * So the other option is to implement them ourselves. + */ + +JNIEXPORT void JNIFUNC(unlink)(JNIEnv *jni, jobject cls, jobject path) +{ + const char *pathstr = 0; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + if (unlink(pathstr)) { + except_syserror(jni, SYSERR, errno, + "failed to delete file `%s'", pathstr); + goto end; + } +end: + put_cstring(jni, path, pathstr); +} + +JNIEXPORT void JNIFUNC(rmdir)(JNIEnv *jni, jobject cls, jobject path) +{ + const char *pathstr = 0; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + if (rmdir(pathstr)) { + except_syserror(jni, SYSERR, errno, + "failed to delete directory `%s'", pathstr); + goto end; + } +end: + put_cstring(jni, path, pathstr); +} + +JNIEXPORT void JNIFUNC(mkdir)(JNIEnv *jni, jobject cls, + jobject path, jint mode) +{ + const char *pathstr = 0; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + if (mkdir(pathstr, mode)) { + except_syserror(jni, SYSERR, errno, + "failed to create directory `%s'", pathstr); + goto end; + } +end: + 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) +{ + const char *pathstr = 0; + int fd = -1; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + fd = open(pathstr, O_WRONLY | O_CREAT | O_EXCL, mode); + if (fd < 0) { + except_syserror(jni, SYSERR, errno, + "failed to create fresh file `%s'", pathstr); + goto end; + } +end: + if (fd != -1) close(fd); + put_cstring(jni, path, pathstr); +} + +JNIEXPORT void JNIFUNC(rename)(JNIEnv *jni, jobject cls, + jobject from, jobject to) +{ + const char *fromstr = 0, *tostr = 0; + + fromstr = get_cstring(jni, from); if (!fromstr) goto end; + tostr = get_cstring(jni, to); if (!tostr) goto end; + if (rename(fromstr, tostr)) { + except_syserror(jni, SYSERR, errno, + "failed to rename `%s' as `%s'", fromstr, tostr); + goto end; + } +end: + put_cstring(jni, from, fromstr); + put_cstring(jni, to, tostr); +} + +#define LKF_EXCL 0x1000u +#define LKF_WAIT 0x2000u +struct lockf { + struct native_base _base; + int fd; +}; +static struct native_type lockf_type = + { "lock", sizeof(struct lockf), 0xb2648926}; +JNIEXPORT wrapper JNIFUNC(lock)(JNIEnv *jni, jobject cls, + jobject path, jint flags) +{ + const char *pathstr = 0; + int fd = -1; + struct flock l; + struct lockf lk; + struct stat st0, st1; + int f; + wrapper r = 0; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + +again: + fd = open(pathstr, O_RDWR | O_CREAT, flags&07777); if (fd < 0) goto err; + if (fstat(fd, &st0)) goto err; + f = fcntl(fd, F_GETFD); if (f < 0) goto err; + if (fcntl(fd, F_SETFD, f | FD_CLOEXEC)) goto err; + l.l_type = (flags&LKF_EXCL) ? F_WRLCK : F_RDLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + if (fcntl(fd, (flags&LKF_WAIT) ? F_SETLKW : F_SETLK, &l)) goto err; + if (stat(pathstr, &st1)) + { if (errno == ENOENT) goto again; else goto err; } + if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino) + { close(fd); fd = -1; goto again; } + + INIT_NATIVE(lockf, &lk); lk.fd = fd; fd = -1; + r = wrap(jni, &lockf_type, &lk); + goto end; + +err: + except_syserror(jni, SYSERR, errno, "failed to lock file `%s'", pathstr); +end: + if (fd != -1) close(fd); + put_cstring(jni, path, pathstr); + return (r); +} + +JNIEXPORT void JNIFUNC(unlock)(JNIEnv *jni, jobject cls, wrapper wlk) +{ + struct lockf lk; + struct flock l; int rc; - cls = (*jni)->FindClass(jni, clsname); assert(cls); - rc = (*jni)->ThrowNew(jni, cls, msg); assert(!rc); + if (unwrap(jni, &lk, &lockf_type, wlk)) goto end; + if (lk.fd == -1) goto end; + l.l_type = F_UNLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + if (fcntl(lk.fd, F_SETLK, &l)) goto end; + close(lk.fd); lk.fd = -1; + rc = update_wrapper(jni, &lockf_type, wlk, &lk); assert(!rc); +end:; } -static void except_errno(JNIEnv *jni, const char *clsname, int err) - { except(jni, clsname, strerror(err)); } +static jlong xlttimespec(const struct timespec *ts) + { return (1000*(jlong)ts->tv_sec + ts->tv_nsec/1000000); } -static void *open_struct_unchecked(JNIEnv *jni, wrapped obj, struct open *op) +static jobject xltstat(JNIEnv *jni, const struct stat *st) { - jboolean copyp; - uintptr_t p, q; + jclass cls; + jmethodID init; + jint modehack; + + modehack = st->st_mode&07777; + if (S_ISFIFO(st->st_mode)) modehack |= 0010000; + else if (S_ISCHR(st->st_mode)) modehack |= 0020000; + else if (S_ISDIR(st->st_mode)) modehack |= 0040000; + else if (S_ISBLK(st->st_mode)) modehack |= 0060000; + else if (S_ISREG(st->st_mode)) modehack |= 0100000; + else if (S_ISLNK(st->st_mode)) modehack |= 0120000; + else if (S_ISSOCK(st->st_mode)) modehack |= 0140000; - op->obj = obj; - op->arr = (*jni)->GetByteArrayElements(jni, obj, ©p); - if (!op->arr) return (0); - p = (uintptr_t)op->arr; - q = p + sizeof(union align) - 1; - q -= q%sizeof(union align); - fprintf(stderr, ";; offset = %"PRIuPTR"\n", q - p); - return (op->arr + (q - p)); + cls = (*jni)->FindClass(jni, STATCLS); assert(cls); + init = (*jni)->GetMethodID(jni, cls, "", "(IIJIIIIIIJIJJJJ)V"); + assert(init); + return ((*jni)->NewObject(jni, cls, init, + (jint)major(st->st_dev), (jint)minor(st->st_dev), + (jlong)st->st_ino, + modehack, + (jint)st->st_nlink, + (jint)st->st_uid, (jint)st->st_gid, + (jint)major(st->st_rdev), (jint)minor(st->st_rdev), + (jlong)st->st_size, + (jint)st->st_blksize, (jlong)st->st_blocks, + xlttimespec(&st->st_atim), + xlttimespec(&st->st_mtim), + xlttimespec(&st->st_ctim))); } -static void *open_struct(JNIEnv *jni, wrapped obj, - const struct native_type *ty, struct open *op) +JNIEXPORT jobject JNIFUNC(stat)(JNIEnv *jni, jobject cls, jobject path) { - struct base *p; - jsize n; + jobject r = 0; + const char *pathstr = 0; + struct stat st; - if (!obj) { except(jni, "java/lang/NullPointerException", 0); return (0); } - n = (*jni)->GetArrayLength(jni, obj); - if ((*jni)->ExceptionOccurred(jni)) return (0); - p = open_struct_unchecked(jni, obj, op); - if (!p) return (0); - if (n < ty->sz + sizeof(union align) - 1 || p->tag != ty->tag) - { - (*jni)->ReleaseByteArrayElements(jni, obj, op->arr, JNI_ABORT); - except(jni, "uk/org/distorted/tripe/JNI$NativeObjectTypeException", 0); - return (0); + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + if (stat(pathstr, &st)) { + except_syserror(jni, SYSERR, errno, + "failed to read information about `%s'", pathstr); + goto end; + } + r = xltstat(jni, &st); +end: + put_cstring(jni, path, pathstr); + return (r); +} + +JNIEXPORT jobject JNIFUNC(lstat)(JNIEnv *jni, jobject cls, jobject path) +{ + jobject r = 0; + const char *pathstr = 0; + struct stat st; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + if (lstat(pathstr, &st)) { + except_syserror(jni, SYSERR, errno, + "failed to read information about `%s'", pathstr); + goto end; } - return (p); + r = xltstat(jni, &st); +end: + put_cstring(jni, path, pathstr); + return (r); } -static wrapped close_struct(JNIEnv *jni, struct open *op) +struct dir { + struct native_base _base; + DIR *d; +}; +static const struct native_type dir_type = + { "dir", sizeof(struct dir), 0x0f5ca477 }; + +JNIEXPORT jobject JNIFUNC(opendir)(JNIEnv *jni, jobject cls, jobject path) { - (*jni)->ReleaseByteArrayElements(jni, op->obj, op->arr, 0); - return (op->obj); + const char *pathstr = 0; + struct dir dir; + wrapper r = 0; + + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + INIT_NATIVE(dir, &dir); + dir.d = opendir(pathstr); + if (!dir.d) { + except_syserror(jni, SYSERR, errno, + "failed to open directory `%s'", pathstr); + goto end; + } + r = wrap(jni, &dir_type, &dir); +end: + put_cstring(jni, path, pathstr); + return (r); +} + +JNIEXPORT jbyteArray JNIFUNC(readdir)(JNIEnv *jni, jobject cls, + jobject path, jobject wdir) +{ + const char *pathstr = 0; + struct dir dir; + struct dirent *d; + jbyteArray r = 0; + + if (unwrap(jni, &dir, &dir_type, wdir)) goto end; + if (!dir.d) { except(jni, ARGERR, "directory has been closed"); goto end; } + errno = 0; d = readdir(dir.d); + if (errno) { + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + except_syserror(jni, SYSERR, errno, + "failed to read directory `%s'", pathstr); + goto end; + } + if (d) r = wrap_cstring(jni, d->d_name); +end: + put_cstring(jni, path, pathstr); + return (r); } -static void *alloc_struct(JNIEnv *jni, const struct native_type *ty, - struct open *op) +JNIEXPORT void JNIFUNC(closedir)(JNIEnv *jni, jobject cls, + jobject path, jobject wdir) { - wrapped obj; - struct base *p; + const char *pathstr = 0; + struct dir dir; - obj = (*jni)->NewByteArray(jni, ty->sz + sizeof(union align) - 1); - if (!obj) return (0); - p = open_struct_unchecked(jni, obj, op); - if (!p) { (*jni)->DeleteLocalRef(jni, obj); return (0); } - p->tag = ty->tag; - return (p); + if (unwrap(jni, &dir, &dir_type, wdir)) goto end; + if (!dir.d) goto end; + if (closedir(dir.d)) { + pathstr = get_cstring(jni, path); if (!pathstr) goto end; + except_syserror(jni, SYSERR, errno, + "failed to close directory `%s'", pathstr); + goto end; + } + dir.d = 0; + if (update_wrapper(jni, &dir_type, wdir, &dir)) goto end; +end: + put_cstring(jni, path, pathstr); } -JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_test - (JNIEnv *jni, jobject cls) - { printf("Hello from C!\n"); } +/*----- Triggers ----------------------------------------------------------*/ + +/* A trigger is a gadget for waking up a thread which is blocking on I/O, + * and it's used to implement interruptability. + * + * Really, a trigger is a pipe. A `blocking' I/O operation secretly uses + * select(2) to block on the descriptor of interest /and/ the read side of + * the trigger pipe. To wake up a thread that's blocked, we just write a + * byte (nobody cares /which/ byte) to the write end. + */ -struct toy { - struct base _base; - const char *p; +struct trigger { + struct native_base _base; + int rfd, wfd; }; -static const struct native_type toy_type = - { "toy", sizeof(struct toy), 0x58008918 }; +static const struct native_type trigger_type = + { "trigger", sizeof(struct trigger), 0x65ffd8b4 }; + +JNIEXPORT wrapper JNICALL JNIFUNC(make_1trigger)(JNIEnv *jni, jobject cls) +{ + struct trigger trig; + int fd[2]; + int i; + wrapper ret = 0; + + fd[0] = fd[1] = -1; + if (pipe(fd)) { + except_syserror(jni, SYSERR, errno, "failed to create pipe"); + goto end; + } + for (i = 0; i < 2; i++) { + if (set_nonblocking(jni, fd[i], 1) < 0 || set_closeonexec(jni, fd[i])) + goto end; + } + + INIT_NATIVE(trigger, &trig); + trig.rfd = fd[0]; fd[0] = -1; + trig.wfd = fd[1]; fd[1] = -1; + ret = wrap(jni, &trigger_type, &trig); -JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_make - (JNIEnv *jni, jobject cls) +end: + for (i = 0; i < 2; i++) + if (fd[i] != -1) close(fd[i]); + return (ret); +} + +JNIEXPORT void JNICALL JNIFUNC(destroy_1trigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) { - struct open op_toy; - struct toy *toy; + struct trigger trig; - toy = alloc_struct(jni, &toy_type, &op_toy); - if (!toy) return (0); - toy->p = "A working thing"; - return (close_struct(jni, &op_toy)); + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + if (trig.rfd != -1) { close(trig.rfd); trig.rfd = -1; } + if (trig.wfd != -1) { close(trig.wfd); trig.wfd = -1; } + update_wrapper(jni, &trigger_type, wtrig, &trig); } -JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_check - (JNIEnv *jni, jobject cls, wrapped wtoy) +JNIEXPORT void JNICALL JNIFUNC(reset_1trigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) { - struct toy *toy; - struct open op_toy; + struct trigger trig; + char buf[64]; + ssize_t n; - toy = open_struct(jni, wtoy, &toy_type, &op_toy); - if (!toy) return; - printf("Toy says: %s\n", toy->p); - close_struct(jni, &op_toy); + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + for (;;) { + n = read(trig.rfd, buf, sizeof(buf)); + if (n > 0) continue; + assert(n < 0); + if (errno == EAGAIN || errno == EWOULDBLOCK) break; + else { + except_syserror(jni, SYSERR, errno, "failed to reset trigger"); + break; + } + } } -struct conn { - struct base _base; - int fd; - unsigned f; -#define CF_CLOSERD 1u -#define CF_CLOSEWR 2u -#define CF_CLOSEMASK (CF_CLOSERD | CF_CLOSEWR) +JNIEXPORT void JNICALL JNIFUNC(trigger)(JNIEnv *jni, jobject cls, + wrapper wtrig) +{ + struct trigger trig; + ssize_t n; + char c = 0; + + if (unwrap(jni, &trig, &trigger_type, wtrig)) return; + n = write(trig.wfd, &c, 1); + if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) + except_syserror(jni, SYSERR, errno, "failed to pull trigger"); +} + +/*----- A tunnel supplied by Java -----------------------------------------*/ + +struct tunnel { + const tunnel_ops *ops; + sel_file f; + struct peer *p; }; -static const struct native_type conn_type - = { "conn", sizeof(struct conn), 0xed030167 }; -JNIEXPORT wrapped JNICALL Java_uk_org_distorted_tripe_JNI_connect - (JNIEnv *jni, jobject cls) +static const struct tunnel_ops tun_java; + +static int t_init(void) { return (0); } + +static void t_read(int fd, unsigned mode, void *v) { - struct conn *conn; - struct open op; - struct sockaddr_un sun; + tunnel *t = v; + ssize_t n; + buf b; + + n = read(fd, buf_i, sizeof(buf_i)); + if (n < 0) { + a_warn("TUN", "%s", p_ifname(t->p), "java", + "read-error", "?ERRNO", A_END); + return; + } + IF_TRACING(T_TUNNEL, { + trace(T_TUNNEL, "tun-java: packet arrived"); + trace_block(T_PACKET, "tunnel: packet contents", buf_i, n); + }) + buf_init(&b, buf_i, n); + p_tun(t->p, &b); +} + +static tunnel *t_create(peer *p, int fd, char **ifn) +{ + JNIEnv *jni = jni_tripe; + tunnel *t = 0; + const char *name = p_name(p); + jbyteArray jname; + size_t n = strlen(p_name(p)); + jclass cls, metacls; + jstring jclsname, jexcmsg; + const char *clsname, *excmsg; + jmethodID mid; + jthrowable exc; + + assert(jni); + + jname = wrap_cstring(jni, name); + cls = (*jni)->FindClass(jni, SYSCLS); assert(cls); + mid = (*jni)->GetStaticMethodID(jni, cls, "getTunnelFd", "([B)I"); + assert(mid); + fd = (*jni)->CallStaticIntMethod(jni, cls, mid, jname); + + exc = (*jni)->ExceptionOccurred(jni); + if (exc) { + cls = (*jni)->GetObjectClass(jni, exc); + metacls = (*jni)->GetObjectClass(jni, cls); + mid = (*jni)->GetMethodID(jni, metacls, + "getName", "()L"STRCLS";"); + assert(mid); + jclsname = (*jni)->CallObjectMethod(jni, cls, mid); + clsname = (*jni)->GetStringUTFChars(jni, jclsname, 0); + mid = (*jni)->GetMethodID(jni, cls, + "getMessage", "()L"STRCLS";"); + jexcmsg = (*jni)->CallObjectMethod(jni, exc, mid); + excmsg = (*jni)->GetStringUTFChars(jni, jexcmsg, 0); + a_warn("TUN", "-", "java", "get-tunnel-fd-failed", + "%s", clsname, "%s", excmsg, A_END); + (*jni)->ReleaseStringUTFChars(jni, jclsname, clsname); + (*jni)->ReleaseStringUTFChars(jni, jexcmsg, excmsg); + (*jni)->ExceptionClear(jni); + goto end; + } + + t = CREATE(tunnel); + t->ops = &tun_java; + t->p = p; + sel_initfile(&sel, &t->f, fd, SEL_READ, t_read, t); + + if (!*ifn) { + *ifn = xmalloc(n + 5); + sprintf(*ifn, "vpn-%s", name); + } + +end: + return (t); +} + +static void t_inject(tunnel *t, buf *b) +{ + IF_TRACING(T_TUNNEL, { + trace(T_TUNNEL, "tun-java: inject decrypted packet"); + trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b)); + }) + DISCARD(write(t->f.fd, BBASE(b), BLEN(b))); +} + +static void t_destroy(tunnel *t) + { sel_rmfile(&t->f); close(t->f.fd); DESTROY(t); } + +static const struct tunnel_ops tun_java = { + "java", 0, + /* init */ t_init, + /* create */ t_create, + /* setifname */ 0, + /* inject */ t_inject, + /* destroy */ t_destroy +}; + + +JNIEXPORT jint JNICALL JNIFUNC(open_1tun)(JNIEnv *jni, jobject cls) +{ + int ret = -1; int fd = -1; + struct ifreq iff; - conn = alloc_struct(jni, &conn_type, &op); - if (!conn) goto err; + if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { + except_syserror(jni, SYSERR, errno, "failed to open tunnel device"); + goto end; + } - fd = socket(SOCK_STREAM, PF_UNIX, 0); - if (!fd) goto err_except; + if (set_nonblocking(jni, fd, 1) || set_closeonexec(jni, fd)) goto end; - sun.sun_family = AF_UNIX; - strcpy(sun.sun_path, "/tmp/mdw/sk"); - if (connect(fd, (struct sockaddr *)&sun, sizeof(sun))) goto err_except; + memset(&iff, 0, sizeof(iff)); + iff.ifr_name[0] = 0; + iff.ifr_flags = IFF_TUN | IFF_NO_PI; + if (ioctl(fd, TUNSETIFF, &iff) < 0) { + except_syserror(jni, SYSERR, errno, "failed to configure tunnel device"); + goto end; + } - conn->fd = fd; - return (close_struct(jni, &op)); + ret = fd; fd = -1; -err_except: - except_errno(jni, "java/io/IOException", errno); -err: - if (fd) close(fd); +end: + if (fd != -1) close(fd); + return (ret); +} + +/*----- A custom noise source ---------------------------------------------*/ + +static void javanoise(rand_pool *r) +{ + JNIEnv *jni = jni_tripe; + jclass cls; + jmethodID mid; + jbyteArray v; + jbyte *p; + jsize n; + + noise_devrandom(r); + + assert(jni); + cls = (*jni)->FindClass(jni, RANDCLS); assert(cls); + mid = (*jni)->GetStaticMethodID(jni, cls, "getSeed", "(I)[B"); assert(mid); + v = (*jni)->CallStaticObjectMethod(jni, cls, mid, 32); + if (v) { + n = (*jni)->GetArrayLength(jni, v); + p = (*jni)->GetByteArrayElements(jni, v, 0); + rand_add(r, p, n, n); + (*jni)->ReleaseByteArrayElements(jni, v, p, JNI_ABORT); + } + if ((*jni)->ExceptionOccurred(jni)) { + (*jni)->ExceptionDescribe(jni); + (*jni)->ExceptionClear(jni); + } +} + +static const rand_source javasource = { javanoise, noise_timer }; + +/*----- Embedding the TrIPE server ----------------------------------------*/ + +static void lock_tripe(JNIEnv *jni) +{ + jclass cls = (*jni)->FindClass(jni, LOCKCLS); assert(cls); + (*jni)->MonitorEnter(jni, cls); +} + +static void unlock_tripe(JNIEnv *jni) +{ + jclass cls = (*jni)->FindClass(jni, LOCKCLS); assert(cls); + (*jni)->MonitorExit(jni, cls); +} + +#define STATES(_) \ + _(INIT) \ + _(RESOLVE) \ + _(KEYS) \ + _(BIND) \ + _(READY) \ + _(RUNNING) + +enum { +#define DEFTAG(st) st, + STATES(DEFTAG) +#undef DEFTAG + MAXSTATE +}; + +static const char *statetab[] = { +#define DEFNAME(st) #st, + STATES(DEFNAME) +#undef DEFNAME +}; + +static unsigned state = INIT; +static int clientsk = -1; + +static const char *statename(unsigned st) +{ + if (st >= MAXSTATE) return (""); + else return (statetab[st]); +} + +static int ensure_state(JNIEnv *jni, unsigned want) +{ + unsigned cur; + + lock_tripe(jni); + cur = state; + unlock_tripe(jni); + + if (cur != want) { + except(jni, STERR, "server is in state %s (%u), not %s (%u)", + statename(cur), cur, statename(want), want); + return (-1); + } return (0); } -JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_send - (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf, - jint start, jint len) +JNIEXPORT void JNICALL JNIFUNC(base_1init)(JNIEnv *jni, jobject cls) +{ + int fd[2]; + int i; + + for (i = 0; i < N(fd); i++) fd[i] = -1; + + lock_tripe(jni); + jni_tripe = jni; + if (ensure_state(jni, INIT)) goto end; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)) { + except_syserror(jni, SYSERR, errno, "failed to create socket pair"); + goto end; + } + + clientsk = fd[0]; fd[0] = -1; + + rand_noisesrc(RAND_GLOBAL, &javasource); + rand_seed(RAND_GLOBAL, MAXHASHSZ); + lp_init(); + a_create(fd[1], fd[1], AF_NOTE | AF_WARN | AF_TRACE); fd[1] = -1; + a_switcherr(); + p_addtun(&tun_java); p_setdflttun(&tun_java); + p_init(); + kx_init(); + + state++; + +end: + for (i = 0; i < N(fd); i++) if (fd[i] != -1) close(fd[i]); + jni_tripe = 0; + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(setup_1resolver)(JNIEnv *jni, jobject cls) +{ + lock_tripe(jni); + if (ensure_state(jni, RESOLVE)) goto end; + + if (a_init()) + { except(jni, INITERR, "failed to initialize resolver"); return; } + + state++; + +end: + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(load_1keys)(JNIEnv *jni, jobject cls, + jobject privstr, jobject pubstr, + jobject tagstr) +{ + const char *priv = 0, *pub = 0, *tag = 0; + + lock_tripe(jni); + if (ensure_state(jni, KEYS)) return; + + priv = get_cstring(jni, privstr); if (!priv) goto end; + pub = get_cstring(jni, pubstr); if (!pub) goto end; + tag = get_cstring(jni, tagstr); if (!tag) goto end; + + if (km_init(priv, pub, tag)) + { except(jni, INITERR, "failed to load initial keys"); goto end; } + + state++; + +end: + put_cstring(jni, privstr, priv); + put_cstring(jni, pubstr, pub); + put_cstring(jni, tagstr, tag); + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(unload_1keys)(JNIEnv *jni, jobject cls) +{ + lock_tripe(jni); + if (ensure_state(jni, KEYS + 1)) goto end; + + km_clear(); + + state--; + +end: + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(bind)(JNIEnv *jni, jobject cls, + jbyteArray hoststr, jbyteArray svcstr) +{ + const char *host = 0, *svc = 0; + struct addrinfo hint, *ai = 0; + int err; + + lock_tripe(jni); + if (ensure_state(jni, BIND)) goto end; + + if (hoststr) { host = get_cstring(jni, hoststr); if (!host) goto end; } + svc = get_cstring(jni, svcstr); if (!svc) goto end; + + hint.ai_socktype = SOCK_DGRAM; + hint.ai_family = AF_UNSPEC; + hint.ai_protocol = IPPROTO_UDP; + hint.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + err = getaddrinfo(host, svc, &hint, &ai); + if (err) { + except(jni, NAMEERR, "failed to resolve %c%s%c, port `%s': %s", + host ? '`' : '<', host ? host : "nil", host ? '\'' : '>', + svc, gai_strerror(err)); + goto end; + } + + if (p_bind(ai)) + { except(jni, INITERR, "failed to bind master socket"); goto end; } + + state++; + +end: + if (ai) freeaddrinfo(ai); + put_cstring(jni, hoststr, host); + put_cstring(jni, svcstr, svc); + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(unbind)(JNIEnv *jni, jobject cls) +{ + lock_tripe(jni); + if (ensure_state(jni, BIND + 1)) goto end; + + p_unbind(); + + state--; + +end: + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(mark)(JNIEnv *jni, jobject cls, jint seq) +{ + lock_tripe(jni); + a_notify("MARK", "%d", seq, A_END); + unlock_tripe(jni); +} + +JNIEXPORT void JNICALL JNIFUNC(run)(JNIEnv *jni, jobject cls) +{ + lock_tripe(jni); + if (ensure_state(jni, READY)) goto end; + assert(!jni_tripe); + jni_tripe = jni; + state = RUNNING; + unlock_tripe(jni); + + lp_run(); + + lock_tripe(jni); + jni_tripe = 0; + state = READY; + +end: + unlock_tripe(jni); +} + +static int check_buffer_bounds(JNIEnv *jni, const char *what, + jbyteArray buf, jint start, jint len) { - struct conn *conn = 0; - struct open op; - jboolean copyp; jsize bufsz; + jclass cls; + + cls = (*jni)->FindClass(jni, "[B"); assert(cls); + if (!(*jni)->IsInstanceOf(jni, buf, cls)) { + except(jni, ARGERR, + "expected a byte array"); + return (-1); + } + bufsz = (*jni)->GetArrayLength(jni, buf); + if (start > bufsz) { + except(jni, BOUNDSERR, + "bad %s buffer bounds: start %d > buffer size %d", start, bufsz); + return (-1); + } + if (len > bufsz - start) { + except(jni, BOUNDSERR, + "bad %s buffer bounds: length %d > remaining buffer size %d", + len, bufsz - start); + return (-1); + } + return (0); +} + +JNIEXPORT void JNICALL JNIFUNC(send)(JNIEnv *jni, jobject cls, + jbyteArray buf, + jint start, jint len, + wrapper wtrig) +{ + struct trigger trig; + int rc, maxfd; ssize_t n; + fd_set rfds, wfds; jbyte *p = 0; - conn = open_struct(jni, wconn, &conn_type, &op); - if (!conn) goto end; + if (ensure_state(jni, RUNNING)) goto end; - bufsz = (*jni)->GetArrayLength(jni, buf); - if ((*jni)->ExceptionOccurred(jni)) goto end; - if (bufsz < start || bufsz - start < len) { - except(jni, "java/lang/IndexOutOfBoundsException", - "bad send-buffer bounds"); - goto end; - } + if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end; + if (check_buffer_bounds(jni, "send", buf, start, len)) goto end; - p = (*jni)->GetByteArrayElements(jni, buf, ©p); + p = (*jni)->GetByteArrayElements(jni, buf, 0); if (!p) goto end; + maxfd = trig.rfd; + if (maxfd < clientsk) maxfd = clientsk; while (len) { - n = send(conn->fd, p + start, len, 0); - if (n < 0) { - except_errno(jni, "java/io/IOException", errno); - goto end; + FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); + FD_ZERO(&wfds); FD_SET(clientsk, &wfds); + rc = select(maxfd + 1, &rfds, &wfds, 0, 0); if (rc < 0) goto err; + if (FD_ISSET(trig.rfd, &rfds)) break; + if (FD_ISSET(clientsk, &wfds)) { + n = send(clientsk, p + start, len, 0); + if (n >= 0) { start += n; len -= n; } + else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err; } - start += n; len -= n; } + goto end; +err: + except_syserror(jni, SYSERR, errno, "failed to send on connection"); end: if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, JNI_ABORT); - if (conn) close_struct(jni, &op); return; } -JNIEXPORT jint JNICALL Java_uk_org_distorted_tripe_JNI_recv - (JNIEnv *jni, jobject cls, wrapped wconn, jbyteArray buf, - jint start, jint len) +JNIEXPORT jint JNICALL JNIFUNC(recv)(JNIEnv *jni, jobject cls, + jbyteArray buf, + jint start, jint len, + wrapper wtrig) { - struct conn *conn = 0; - struct open op; - jboolean copyp; - jsize bufsz; + struct trigger trig; + int maxfd; + fd_set rfds; jbyte *p = 0; jint rc = -1; - conn = open_struct(jni, wconn, &conn_type, &op); - if (!conn) goto end; - - bufsz = (*jni)->GetArrayLength(jni, buf); - if ((*jni)->ExceptionOccurred(jni)) goto end; - if (bufsz < start || bufsz - start < len) { - except(jni, "java/lang/IndexOutOfBoundsException", - "bad receive-buffer bounds"); + lock_tripe(jni); + if (clientsk == -1) { + except(jni, STERR, "client connection not established"); + unlock_tripe(jni); goto end; } + unlock_tripe(jni); + + if (unwrap(jni, &trig, &trigger_type, wtrig)) goto end; + if (check_buffer_bounds(jni, "send", buf, start, len)) goto end; - p = (*jni)->GetByteArrayElements(jni, buf, ©p); + p = (*jni)->GetByteArrayElements(jni, buf, 0); if (!p) goto end; - rc = recv(conn->fd, p + start, len, 0); - if (rc < 0) { - except_errno(jni, "java/io/IOException", errno); - goto end; + maxfd = trig.rfd; + if (maxfd < clientsk) maxfd = clientsk; + for (;;) { + FD_ZERO(&rfds); FD_SET(trig.rfd, &rfds); FD_SET(clientsk, &rfds); + rc = select(maxfd + 1, &rfds, 0, 0, 0); if (rc < 0) goto err; + if (FD_ISSET(trig.rfd, &rfds)) { + break; + } + if (FD_ISSET(clientsk, &rfds)) { + rc = recv(clientsk, p + start, len, 0); + if (rc >= 0) break; + else if (errno != EAGAIN && errno != EWOULDBLOCK) goto err; + } } if (!rc) rc = -1; + goto end; +err: + except_syserror(jni, SYSERR, errno, "failed to read from connection"); end: if (p) (*jni)->ReleaseByteArrayElements(jni, buf, p, 0); - if (conn) close_struct(jni, &op); return (rc); } -JNIEXPORT void JNICALL Java_uk_org_distorted_tripe_JNI_close - (JNIEnv *jni, jobject cls, wrapped wconn, jint how) -{ - struct conn *conn = 0; - struct open op; - - conn = open_struct(jni, wconn, &conn_type, &op); - if (!conn || conn->fd == -1) goto end; - - how &= CF_CLOSEMASK&~conn->f; - conn->f |= how; -fprintf(stderr, ";; closing %u\n", how); - if ((conn->f&CF_CLOSEMASK) == CF_CLOSEMASK) { - close(conn->fd); - conn->fd = -1; - } else { - if (how&CF_CLOSERD) shutdown(conn->fd, SHUT_RD); - if (how&CF_CLOSEWR) shutdown(conn->fd, SHUT_WR); - } - -end: - if (conn) close_struct(jni, &op); - return; -} +/*----- That's all, folks -------------------------------------------------*/