If the user runs "configure --with-java", a separate library is built with Java native interface stubs and it can be loaded with: System.loadLibrary("openconnect-wrapper"); On a native build, it should autodetect the jni.h location by looking at JAVA_HOME or the path to javac. On a cross build, --with-java=/java_home/include must be explicitly specified. Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- .gitignore | 1 + Makefile.am | 12 + configure.ac | 39 +++ jni.c | 926 +++++++++++++++++++++++++++++++++++++++++++++++++ libopenconnect.map.in | 1 + 5 files changed, 979 insertions(+) create mode 100644 jni.c diff --git a/.gitignore b/.gitignore index 31a4207..a40f78d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ version.c .*.h.dep *.o /libopenconnect.la +/libopenconnect-wrapper.la /*.lo /.libs/ /cscope.* diff --git a/Makefile.am b/Makefile.am index 2cab5d1..4675233 100644 --- a/Makefile.am +++ b/Makefile.am @@ -49,6 +49,18 @@ libopenconnect_la_LDFLAGS += -Wl, at VERSION_SCRIPT_ARG@,libopenconnect.map libopenconnect_la_DEPENDENCIES = libopenconnect.map endif +if OPENCONNECT_JNI +if JNI_STANDALONE +libopenconnect_la_SOURCES += jni.c +libopenconnect_la_CFLAGS += $(JNI_CFLAGS) -Wno-missing-declarations +else +lib_LTLIBRARIES += libopenconnect-wrapper.la +libopenconnect_wrapper_la_SOURCES = jni.c +libopenconnect_wrapper_la_CFLAGS = $(AM_CFLAGS) $(JNI_CFLAGS) -Wno-missing-declarations +libopenconnect_wrapper_la_LIBADD = libopenconnect.la +endif +endif + pkgconfig_DATA = openconnect.pc EXTRA_DIST = version.sh COPYING.LGPL $(lib_srcs_openssl) $(lib_srcs_gnutls) diff --git a/configure.ac b/configure.ac index e1c22ba..3817d21 100644 --- a/configure.ac +++ b/configure.ac @@ -545,6 +545,45 @@ AS_IF([test "x$with_liboath" != "xno"], [ liboath_pkg=no) ]) +AC_ARG_WITH([java], + AS_HELP_STRING([--with-java(=DIR)], + [Build JNI bindings using jni.h from DIR [default=no]]), + [], [with_java=no]) + +if test "$with_java" = "yes"; then + AX_JNI_INCLUDE_DIR + for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS; do + JNI_CFLAGS="$JNI_CFLAGS -I$JNI_INCLUDE_DIR" + done +elif test "$with_java" = "no"; then + JNI_CFLAGS="" +else + JNI_CFLAGS="-I$with_java" +fi + +if test "x$JNI_CFLAGS" != "x"; then + oldCFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $JNI_CFLAGS" + AC_MSG_CHECKING([jni.h usability]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include <jni.h>], + [jint foo = 0; (void)foo;])], + AC_MSG_RESULT([yes]), + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([unable to compile JNI test program])]) + CFLAGS="$oldCFLAGS" + + AC_SUBST(JNI_CFLAGS, [$JNI_CFLAGS]) +fi + +AM_CONDITIONAL(OPENCONNECT_JNI, [test "$JNI_CFLAGS" != ""]) + +AC_ARG_ENABLE([jni-standalone], + AS_HELP_STRING([--enable-jni-standalone], + [build JNI stubs directly into libopenconnect.so [default=no]]), + [jni_standalone=$enableval], + [jni_standalone=no]) +AM_CONDITIONAL(JNI_STANDALONE, [test $jni_standalone = yes]) + AC_CHECK_HEADER([if_tun.h], [AC_DEFINE([IF_TUN_HDR], ["if_tun.h"])], [AC_CHECK_HEADER([linux/if_tun.h], diff --git a/jni.c b/jni.c new file mode 100644 index 0000000..bf9d35a --- /dev/null +++ b/jni.c @@ -0,0 +1,926 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright ? 2013 Kevin Cernekee <cernekee at gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1, as published by the Free Software Foundation. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to: + * + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> + +#include <jni.h> +#include "openconnect.h" + +struct libctx { + JNIEnv *jenv; + jobject jobj; + struct openconnect_info *vpninfo; + OPENCONNECT_X509 *cert; + int cancel_fd; + int loglevel; +}; + +static void throw_excep(JNIEnv *jenv, const char *exc, int line) +{ + jclass excep; + char msg[64]; + + snprintf(msg, 64, "%s:%d", __FILE__, line); + + (*jenv)->ExceptionClear(jenv); + excep = (*jenv)->FindClass(jenv, exc); + if (excep) + (*jenv)->ThrowNew(jenv, excep, msg); +} + +#define OOM(jenv) do { throw_excep(jenv, "java/lang/OutOfMemoryError", __LINE__); } while (0) + +static struct libctx *getctx(JNIEnv *jenv, jobject jobj) +{ + jclass jcls = (*jenv)->GetObjectClass(jenv, jobj); + jfieldID jfld = (*jenv)->GetFieldID(jenv, jcls, "libctx", "J"); + if (!jfld) + return NULL; + return (void *)(unsigned long)(*jenv)->GetLongField(jenv, jobj, jfld); +} + +/* + * GetMethodID() and GetFieldID() and NewStringUTF() will automatically throw exceptions on error + */ +static jmethodID get_obj_mid(struct libctx *ctx, jobject jobj, const char *name, const char *sig) +{ + jclass jcls = (*ctx->jenv)->GetObjectClass(ctx->jenv, jobj); + jmethodID mid = (*ctx->jenv)->GetMethodID(ctx->jenv, jcls, name, sig); + return mid; +} + +static jstring dup_to_jstring(JNIEnv *jenv, const char *in) +{ + /* + * Many implementations of NewStringUTF() will return NULL on + * NULL input, but that isn't guaranteed: + * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=35979 + */ + return in ? (*jenv)->NewStringUTF(jenv, in) : NULL; +} + +static int dup_to_cstring(JNIEnv *jenv, jstring in, char **out) +{ + const char *tmp; + + if (in == NULL) { + *out = NULL; + return 0; + } + + tmp = (*jenv)->GetStringUTFChars(jenv, in, NULL); + if (!tmp) { + OOM(jenv); + return -1; + } + + *out = strdup(tmp); + (*jenv)->ReleaseStringUTFChars(jenv, in, tmp); + + if (!*out) { + OOM(jenv); + return -1; + } + return 0; +} + +static int set_int(struct libctx *ctx, jobject jobj, const char *name, int value) +{ + jclass jcls = (*ctx->jenv)->GetObjectClass(ctx->jenv, jobj); + jfieldID jfld = (*ctx->jenv)->GetFieldID(ctx->jenv, jcls, name, "I"); + + if (!jfld) + return -1; + (*ctx->jenv)->SetIntField(ctx->jenv, jobj, jfld, value); + return 0; +} + +static int set_string(struct libctx *ctx, jobject jobj, const char *name, const char *value) +{ + jclass jcls = (*ctx->jenv)->GetObjectClass(ctx->jenv, jobj); + jfieldID jfld = (*ctx->jenv)->GetFieldID(ctx->jenv, jcls, name, "Ljava/lang/String;"); + jstring jarg; + + if (!jfld) + return -1; + + jarg = dup_to_jstring(ctx->jenv, value); + if (value && !jarg) + return -1; + (*ctx->jenv)->SetObjectField(ctx->jenv, jobj, jfld, jarg); + return 0; +} + +static int add_string(struct libctx *ctx, jclass jcls, jobject jobj, + const char *name, const char *value) +{ + jmethodID mid = (*ctx->jenv)->GetMethodID(ctx->jenv, jcls, name, "(Ljava/lang/String;)V"); + jstring jarg; + + if (!value) + return 0; + + if (!mid) + return -1; + jarg = dup_to_jstring(ctx->jenv, value); + if (!jarg) + return -1; + + (*ctx->jenv)->CallVoidMethod(ctx->jenv, jobj, mid, jarg); + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jarg, NULL); + + return 0; +} + +static int validate_peer_cert_cb(void *privdata, OPENCONNECT_X509 *cert, const char *reason) +{ + struct libctx *ctx = privdata; + jstring jreason; + int ret = -1; + jmethodID mid; + + jreason = dup_to_jstring(ctx->jenv, reason); + if (!jreason) + return -1; + + ctx->cert = cert; + mid = get_obj_mid(ctx, ctx->jobj, "onValidatePeerCert", "(Ljava/lang/String;)I"); + if (mid) + ret = (*ctx->jenv)->CallIntMethod(ctx->jenv, ctx->jobj, mid, jreason); + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jreason, NULL); + + return ret; +} + +static int write_new_config_cb(void *privdata, char *buf, int buflen) +{ + struct libctx *ctx = privdata; + jmethodID mid; + jbyteArray jbuf; + int ret = -1; + + mid = get_obj_mid(ctx, ctx->jobj, "onWriteNewConfig", "([B)I"); + if (!mid) + goto out; + + jbuf = (*ctx->jenv)->NewByteArray(ctx->jenv, buflen); + if (!jbuf) + goto out; + (*ctx->jenv)->SetByteArrayRegion(ctx->jenv, jbuf, 0, buflen, (jbyte *)buf); + + ret = (*ctx->jenv)->CallIntMethod(ctx->jenv, ctx->jobj, mid, jbuf); + (*ctx->jenv)->ReleaseByteArrayElements(ctx->jenv, jbuf, NULL, 0); + +out: + return ret; +} + +static jobject new_auth_form(struct libctx *ctx, struct oc_auth_form *form) +{ + jmethodID mid; + jclass jcls; + jobject jobj = NULL; + + jcls = (*ctx->jenv)->FindClass(ctx->jenv, "org/infradead/libopenconnect/LibOpenConnect$AuthForm"); + if (jcls == NULL) + return NULL; + + mid = (*ctx->jenv)->GetMethodID(ctx->jenv, jcls, "<init>", "()V"); + if (!mid) + return NULL; + jobj = (*ctx->jenv)->NewObject(ctx->jenv, jcls, mid); + if (!jobj) + return NULL; + + if (set_string(ctx, jobj, "banner", form->banner) || + set_string(ctx, jobj, "message", form->message) || + set_string(ctx, jobj, "error", form->error) || + set_string(ctx, jobj, "authID", form->auth_id) || + set_string(ctx, jobj, "method", form->method) || + set_string(ctx, jobj, "action", form->action)) { + return NULL; + } + + return jobj; +} + +static jobject new_form_choice(struct libctx *ctx, struct oc_choice *choice) +{ + jmethodID mid; + jclass jcls; + jobject jobj = NULL; + + jcls = (*ctx->jenv)->FindClass(ctx->jenv, + "org/infradead/libopenconnect/LibOpenConnect$FormChoice"); + if (jcls == NULL) + return NULL; + + mid = (*ctx->jenv)->GetMethodID(ctx->jenv, jcls, "<init>", "()V"); + if (!mid) + return NULL; + jobj = (*ctx->jenv)->NewObject(ctx->jenv, jcls, mid); + if (!jobj) + return NULL; + + if (set_string(ctx, jobj, "name", choice->name) || + set_string(ctx, jobj, "label", choice->label) || + set_string(ctx, jobj, "authType", choice->auth_type) || + set_string(ctx, jobj, "overrideName", choice->override_name) || + set_string(ctx, jobj, "overrideLabel", choice->override_label)) { + return NULL; + } + + return jobj; +} + +static int populate_select_choices(struct libctx *ctx, jobject jopt, struct oc_form_opt_select *opt) +{ + jmethodID mid; + int i; + + mid = get_obj_mid(ctx, jopt, "addChoice", + "(Lorg/infradead/libopenconnect/LibOpenConnect$FormChoice;)V"); + if (!mid) + return -1; + + for (i = 0; i < opt->nr_choices; i++) { + jobject jformchoice = new_form_choice(ctx, &opt->choices[i]); + if (!jformchoice) + return -1; + (*ctx->jenv)->CallVoidMethod(ctx->jenv, jopt, mid, jformchoice); + } + return 0; +} + +static int add_form_option(struct libctx *ctx, jobject jform, struct oc_form_opt *opt) +{ + jmethodID addOpt; + jobject jopt; + + addOpt = get_obj_mid(ctx, jform, "addOpt", + "()Lorg/infradead/libopenconnect/LibOpenConnect$FormOpt;"); + if (!addOpt) + return -1; + + jopt = (*ctx->jenv)->CallObjectMethod(ctx->jenv, jform, addOpt); + if (jopt == NULL) + return -1; + + if (set_int(ctx, jopt, "type", opt->type) || + set_string(ctx, jopt, "name", opt->name) || + set_string(ctx, jopt, "label", opt->label)) + return -1; + + if (opt->type == OC_FORM_OPT_SELECT && + populate_select_choices(ctx, jopt, (struct oc_form_opt_select *)opt)) + return -1; + + return 0; +} + +static int process_auth_form_cb(void *privdata, struct oc_auth_form *form) +{ + struct libctx *ctx = privdata; + jobject jform; + jmethodID callback, getOptValue; + struct oc_form_opt *opt; + jint ret; + + /* create and populate new AuthForm object and option/choice lists */ + + jform = new_auth_form(ctx, form); + if (!jform) + return -1; + + getOptValue = get_obj_mid(ctx, jform, "getOptValue", "(Ljava/lang/String;)Ljava/lang/String;"); + if (!getOptValue) + return -1; + + for (opt = form->opts; opt; opt = opt->next) + if (add_form_option(ctx, jform, opt) < 0) + return -1; + + /* invoke onProcessAuthForm callback */ + + callback = get_obj_mid(ctx, ctx->jobj, "onProcessAuthForm", + "(Lorg/infradead/libopenconnect/LibOpenConnect$AuthForm;)I"); + if (!callback) + return -1; + + ret = (*ctx->jenv)->CallIntMethod(ctx->jenv, ctx->jobj, callback, jform); + + /* copy any populated form fields back into the C structs */ + + for (opt = form->opts; opt; opt = opt->next) { + jstring jname, jvalue; + + jname = dup_to_jstring(ctx->jenv, opt->name); + if (!jname) + return -1; + + jvalue = (*ctx->jenv)->CallObjectMethod(ctx->jenv, jform, getOptValue, jname); + if (jvalue) { + const char *tmp = (*ctx->jenv)->GetStringUTFChars(ctx->jenv, jvalue, NULL); + if (!tmp) { + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jname, NULL); + return -1; + } + opt->value = strdup(tmp); + if (!opt->value) + OOM(ctx->jenv); + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jvalue, tmp); + } + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jname, NULL); + } + + return ret; +} + +static void progress_cb(void *privdata, int level, const char *fmt, ...) +{ + struct libctx *ctx = privdata; + va_list ap; + char *msg; + jstring jmsg; + int ret; + jmethodID mid; + + if (level > ctx->loglevel) + return; + + va_start(ap, fmt); + ret = vasprintf(&msg, fmt, ap); + va_end(ap); + + if (ret < 0) { + OOM(ctx->jenv); + return; + } + + jmsg = dup_to_jstring(ctx->jenv, msg); + free(msg); + if (!jmsg) + return; + + mid = get_obj_mid(ctx, ctx->jobj, "onProgress", "(ILjava/lang/String;)V"); + if (mid) + (*ctx->jenv)->CallVoidMethod(ctx->jenv, ctx->jobj, mid, level, jmsg); + (*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jmsg, NULL); +} + +/* Library init/uninit */ + +JNIEXPORT jlong JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_init( + JNIEnv *jenv, jobject jobj, jstring juseragent) +{ + char *useragent; + struct libctx *ctx = calloc(1, sizeof(*ctx)); + + if (!ctx) { + OOM(jenv); + return 0; + } + + ctx->jenv = jenv; + ctx->jobj = (*jenv)->NewGlobalRef(jenv, jobj); + if (!ctx->jobj) { + free(ctx); + OOM(jenv); + return 0; + } + + useragent = (char *)(*jenv)->GetStringUTFChars(jenv, juseragent, NULL); + if (!useragent) { + (*jenv)->DeleteGlobalRef(jenv, ctx->jobj); + free(ctx); + OOM(jenv); + return 0; + } + ctx->vpninfo = openconnect_vpninfo_new(useragent, validate_peer_cert_cb, + write_new_config_cb, process_auth_form_cb, + progress_cb, ctx); + (*jenv)->ReleaseStringUTFChars(jenv, juseragent, useragent); + + ctx->cancel_fd = openconnect_setup_cancel_pipe(ctx->vpninfo); + if (ctx->cancel_fd < 0) { + openconnect_vpninfo_free(ctx->vpninfo); + free(ctx); + throw_excep(jenv, "java/lang/IOException", __LINE__); + return 0; + } + + ctx->loglevel = PRG_DEBUG; + + return (jlong)(unsigned long)ctx; +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_free( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return; + openconnect_vpninfo_free(ctx->vpninfo); + free(ctx); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_doCancel( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + char data = '.'; + + if (!ctx) + return; + if (write(ctx->cancel_fd, &data, 1) < 0) { + /* probably dead already */ + } +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_globalInit( + JNIEnv *jenv, jclass jcls) +{ + openconnect_init_ssl(); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_obtainCookie( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + int ret; + + if (!ctx) + return 0; + ctx->cert = NULL; + ret = openconnect_obtain_cookie(ctx->vpninfo); + if (ret == 0) + ctx->cert = openconnect_get_peer_cert(ctx->vpninfo); + return ret; +} + +/* special handling: caller-allocated buffer */ +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCertSHA1( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + char buf[41]; + jstring jresult = NULL; + + if (!ctx || !ctx->cert) + return NULL; + if (openconnect_get_cert_sha1(ctx->vpninfo, ctx->cert, buf)) + return NULL; + jresult = dup_to_jstring(ctx->jenv, buf); + if (!jresult) + OOM(ctx->jenv); + return jresult; +} + +/* special handling: callee-allocated, caller-freed string */ +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCertDetails( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + char *buf = NULL; + jstring jresult = NULL; + + if (!ctx || !ctx->cert) + return NULL; + buf = openconnect_get_cert_details(ctx->vpninfo, ctx->cert); + if (!buf) + return NULL; + + jresult = dup_to_jstring(ctx->jenv, buf); + if (!jresult) + OOM(ctx->jenv); + + free(buf); + return jresult; +} + +/* special handling: callee-allocated, caller-freed binary buffer */ +JNIEXPORT jbyteArray JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCertDER( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + unsigned char *buf = NULL; + int ret; + jbyteArray jresult = NULL; + + if (!ctx || !ctx->cert) + return NULL; + ret = openconnect_get_cert_DER(ctx->vpninfo, ctx->cert, &buf); + if (ret < 0) + return NULL; + + jresult = (*ctx->jenv)->NewByteArray(ctx->jenv, ret); + if (jresult) + (*ctx->jenv)->SetByteArrayRegion(ctx->jenv, jresult, 0, ret, (jbyte *) buf); + + free(buf); + return jresult; +} + +/* special handling: two string arguments */ +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setClientCert( + JNIEnv *jenv, jobject jobj, jstring jcert, jstring jsslkey) +{ + struct libctx *ctx = getctx(jenv, jobj); + char *cert = NULL, *sslkey = NULL; + + if (!ctx || + dup_to_cstring(ctx->jenv, jcert, &cert) || + dup_to_cstring(ctx->jenv, jsslkey, &sslkey)) { + free(cert); + free(sslkey); + return; + } + + openconnect_set_client_cert(ctx->vpninfo, cert, sslkey); +} + +/* special handling: two string arguments */ +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setupTunDevice( + JNIEnv *jenv, jobject jobj, jstring jvpnc_script, jstring jifname) +{ + struct libctx *ctx = getctx(jenv, jobj); + char *vpnc_script = NULL, *ifname = NULL; + + if (!ctx || + dup_to_cstring(ctx->jenv, jvpnc_script, &vpnc_script) || + dup_to_cstring(ctx->jenv, jifname, &ifname)) { + free(vpnc_script); + free(ifname); + return -ENOMEM; + } + + return openconnect_setup_tun_device(ctx->vpninfo, vpnc_script, ifname); +} + +/* class methods (general library info) */ + +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getVersion( + JNIEnv *jenv, jclass jcls) +{ + return dup_to_jstring(jenv, openconnect_get_version()); +} + +JNIEXPORT jboolean JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_hasPKCS11Support( + JNIEnv *jenv, jclass jcls) +{ + return openconnect_has_pkcs11_support(); +} + +JNIEXPORT jboolean JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_hasTSSBlobSupport( + JNIEnv *jenv, jclass jcls) +{ + return openconnect_has_tss_blob_support(); +} + +JNIEXPORT jboolean JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_hasStokenSupport( + JNIEnv *jenv, jclass jcls) +{ + return openconnect_has_stoken_support(); +} + +JNIEXPORT jboolean JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_hasOATHSupport( + JNIEnv *jenv, jclass jcls) +{ + return openconnect_has_oath_support(); +} + +/* simple cases: void or int params */ + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getPort( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_get_port(ctx->vpninfo); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_passphraseFromFSID( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_passphrase_from_fsid(ctx->vpninfo); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_clearCookie( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return; + openconnect_clear_cookie(ctx->vpninfo); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_resetSSL( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return; + openconnect_reset_ssl(ctx->vpninfo); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setCertExpiryWarning( + JNIEnv *jenv, jobject jobj, jint seconds) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return; + openconnect_set_cert_expiry_warning(ctx->vpninfo, seconds); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_makeCSTPConnection( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_make_cstp_connection(ctx->vpninfo); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setupFD( + JNIEnv *jenv, jobject jobj, jint arg) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_setup_tun_fd(ctx->vpninfo, arg); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setupDTLS( + JNIEnv *jenv, jobject jobj, jint arg) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_setup_dtls(ctx->vpninfo, arg); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_mainloop( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_mainloop(ctx->vpninfo); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setLogLevel( + JNIEnv *jenv, jobject jobj, jint arg) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return; + ctx->loglevel = arg; +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setupTunFD( + JNIEnv *jenv, jobject jobj, jint jarg) +{ + struct libctx *ctx = getctx(jenv, jobj); + + if (!ctx) + return -EINVAL; + return openconnect_setup_tun_fd(ctx->vpninfo, jarg); +} + +/* simple cases: return a const string (no need to free it) */ + +#define RETURN_STRING_START \ + struct libctx *ctx = getctx(jenv, jobj); \ + const char *buf = NULL; \ + jstring jresult = NULL; \ + if (!ctx) \ + return NULL; \ + +#define RETURN_STRING_END \ + if (!buf) \ + return NULL; \ + jresult = dup_to_jstring(ctx->jenv, buf); \ + if (!jresult) \ + OOM(ctx->jenv); \ + return jresult; + +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getHostname( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_hostname(ctx->vpninfo); + RETURN_STRING_END +} + +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getUrlpath( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_urlpath(ctx->vpninfo); + RETURN_STRING_END +} + +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getCookie( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_cookie(ctx->vpninfo); + RETURN_STRING_END +} + +JNIEXPORT jstring JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getIFName( + JNIEnv *jenv, jobject jobj) +{ + RETURN_STRING_START + buf = openconnect_get_ifname(ctx->vpninfo); + RETURN_STRING_END +} + +#define SET_STRING_START(ret) \ + struct libctx *ctx = getctx(jenv, jobj); \ + char *arg; \ + if (dup_to_cstring(ctx->jenv, jarg, &arg)) \ + return ret; + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_parseURL( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + int ret; + SET_STRING_START(-ENOMEM) + ret = openconnect_parse_url(ctx->vpninfo, arg); + return ret; +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setHTTPProxy( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + int ret; + SET_STRING_START(-ENOMEM) + ret = openconnect_set_http_proxy(ctx->vpninfo, arg); + return ret; +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setXMLSHA1( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_xmlsha1(ctx->vpninfo, arg, strlen(arg) + 1); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setHostname( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_hostname(ctx->vpninfo, arg); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setUrlpath( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_urlpath(ctx->vpninfo, arg); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setCAFile( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_cafile(ctx->vpninfo, arg); +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setReportedOS( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_reported_os(ctx->vpninfo, arg); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setTokenMode( + JNIEnv *jenv, jobject jobj, jint mode, jstring jarg) +{ + int ret; + SET_STRING_START(-EINVAL) + ret = openconnect_set_token_mode(ctx->vpninfo, mode, arg); + free(arg); + return ret; +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setCSDWrapper( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_setup_csd(ctx->vpninfo, getuid(), 1, arg); +} + +JNIEXPORT jint JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setupTunScript( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + int ret; + SET_STRING_START(-EINVAL) + ret = openconnect_setup_tun_script(ctx->vpninfo, arg); + return ret; +} + +JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setServerCertSHA1( + JNIEnv *jenv, jobject jobj, jstring jarg) +{ + SET_STRING_START() + openconnect_set_server_cert_sha1(ctx->vpninfo, arg); +} + +JNIEXPORT jobject JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getIPInfo( + JNIEnv *jenv, jobject jobj) +{ + struct libctx *ctx = getctx(jenv, jobj); + jmethodID mid; + jclass jcls; + const struct oc_ip_info *ip; + struct oc_split_include *inc; + int i; + + if (!ctx) + return NULL; + ip = openconnect_get_ip_info(ctx->vpninfo); + if (!ip) + return NULL; + + jcls = (*ctx->jenv)->FindClass(ctx->jenv, + "org/infradead/libopenconnect/LibOpenConnect$IPInfo"); + if (jcls == NULL) + return NULL; + + mid = (*ctx->jenv)->GetMethodID(ctx->jenv, jcls, "<init>", "()V"); + if (!mid) + return NULL; + jobj = (*ctx->jenv)->NewObject(ctx->jenv, jcls, mid); + if (!jobj) + return NULL; + + if (set_string(ctx, jobj, "addr", ip->addr) || + set_string(ctx, jobj, "netmask", ip->netmask) || + set_string(ctx, jobj, "addr6", ip->addr6) || + set_string(ctx, jobj, "netmask6", ip->netmask6) || + set_string(ctx, jobj, "domain", ip->domain) || + set_string(ctx, jobj, "proxyPac", ip->proxy_pac) || + set_int(ctx, jobj, "MTU", ip->mtu)) + return NULL; + + for (i = 0; i < 3; i++) { + if (ip->dns[i] && add_string(ctx, jcls, jobj, "addDNS", ip->dns[i])) + return NULL; + if (ip->nbns[i] && add_string(ctx, jcls, jobj, "addNBNS", ip->nbns[i])) + return NULL; + } + + for (inc = ip->split_dns; inc; inc = inc->next) + if (add_string(ctx, jcls, jobj, "addSplitDNS", inc->route)) + return NULL; + for (inc = ip->split_includes; inc; inc = inc->next) + if (add_string(ctx, jcls, jobj, "addSplitInclude", inc->route)) + return NULL; + for (inc = ip->split_excludes; inc; inc = inc->next) + if (add_string(ctx, jcls, jobj, "addSplitExclude", inc->route)) + return NULL; + + return jobj; +} diff --git a/libopenconnect.map.in b/libopenconnect.map.in index ef03c9b..2b50d31 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -69,6 +69,7 @@ OPENCONNECT_PRIVATE { openconnect_create_useragent; openconnect_sha1; openconnect_random; + Java_*; local: *; }; -- 1.7.9.5