[PATCH/RFC V2 22/26] JNI: Initial commit of C wrapper functions

Kevin Cernekee cernekee at gmail.com
Sun Aug 11 21:49:23 EDT 2013


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




More information about the openconnect-devel mailing list