[PATCH/RFC 3/6] JNI: Initial commit of C and Java sources

Kevin Cernekee cernekee at gmail.com
Mon Aug 5 01:25:56 EDT 2013


Signed-off-by: Kevin Cernekee <cernekee at gmail.com>
---
 jni.c                                         |  871 +++++++++++++++++++++++++
 org/infradead/openconnect/LibOpenConnect.java |  201 ++++++
 2 files changed, 1072 insertions(+)
 create mode 100644 jni.c
 create mode 100644 org/infradead/openconnect/LibOpenConnect.java

diff --git a/jni.c b/jni.c
new file mode 100644
index 0000000..94d994b
--- /dev/null
+++ b/jni.c
@@ -0,0 +1,871 @@
+/*
+ * 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 pipefd[2];
+};
+
+JNIEXPORT jlong JNICALL Java_org_infradead_openconnect_LibOpenConnect_init(
+	JNIEnv *jenv, jobject jobj, jstring juseragent);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_free(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_cancel(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_globalInit(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_parseURL(
+	JNIEnv *jenv, jobject jobj, jstring jurl);
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_obtainCookie(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertSHA1(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertDetails(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jbyteArray JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertDER(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setClientCert(
+	JNIEnv *jenv, jobject jobj, jstring jcert, jstring jsslkey);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getVersion(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasPKCS11Support(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasTSSBlobSupport(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasStokenSupport(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasOATHSupport(
+	JNIEnv *jenv, jclass jcls);
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_getPort(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_passphraseFromFSID(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_clearCookie(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_resetSSL(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCertExpiryWarning(
+	JNIEnv *jenv, jobject jobj, jint seconds);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getHostname(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getUrlpath(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCookie(
+	JNIEnv *jenv, jobject jobj);
+JNIEXPORT int JNICALL Java_org_infradead_openconnect_LibOpenConnect_setHTTPProxy(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setXMLSHA1(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setHostname(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setUrlpath(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCAFile(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setReportedOS(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_setTokenMode(
+	JNIEnv *jenv, jobject jobj, jint mode, jstring jarg);
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCSDWrapper(
+	JNIEnv *jenv, jobject jobj, jstring jarg);
+
+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)
+
+/*
+ * jobj (a reference to the LibOpenConnect object) isn't always guaranteed to
+ * stay constant across nested calls.  e.g.
+ * for obtainCookie() -> onValidatePeerCert() -> getCertSHA1(), different
+ * jobj values could be supplied to obtainCookie() and getCertSHA1().
+ *
+ * We want our callbacks to always use the jenv/jobj values supplied by the
+ * Java caller, so we save and restore the values in each native function.
+ *
+ * None of this is the slightest bit thread-safe.
+ */
+#define PUSH_CTX(err_retval...) do { \
+	ctx = getctx(jenv, jobj); \
+	if (!ctx) \
+		return err_retval; \
+	oldctx.jenv = ctx->jenv; \
+	oldctx.jobj = ctx->jobj; \
+	ctx->jenv = jenv; \
+	ctx->jobj = jobj; \
+} while (0)
+
+#define POP_CTX() do { \
+	ctx->jenv = oldctx.jenv; \
+	ctx->jobj = oldctx.jobj; \
+} 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 *)(*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_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/openconnect/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, jcls, jobj, "setBanner", form->banner) ||
+	    set_string(ctx, jcls, jobj, "setMessage", form->message) ||
+	    set_string(ctx, jcls, jobj, "setError", form->error) ||
+	    set_string(ctx, jcls, jobj, "setAuthID", form->auth_id) ||
+	    set_string(ctx, jcls, jobj, "setMethod", form->method) ||
+	    set_string(ctx, jcls, jobj, "setAction", 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/openconnect/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, jcls, jobj, "setName", choice->name) ||
+	    set_string(ctx, jcls, jobj, "setLabel", choice->label) ||
+	    set_string(ctx, jcls, jobj, "setAuthType", choice->auth_type) ||
+	    set_string(ctx, jcls, jobj, "setOverrideName", choice->override_name) ||
+	    set_string(ctx, jcls, jobj, "setOverrideLabel", 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/openconnect/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;
+	jstring jname = NULL, jlabel = NULL;
+	jobject jopt;
+	int ret = -1;
+
+	addOpt = get_obj_mid(ctx, jform, "addOpt",
+		"(ILjava/lang/String;Ljava/lang/String;)Lorg/infradead/openconnect/LibOpenConnect$FormOpt;");
+	if (!addOpt)
+		goto out;
+
+	if (opt->name) {
+		jname = dup_to_jstring(ctx->jenv, opt->name);
+		if (!jname)
+			goto out;
+	}
+
+	if (opt->label) {
+		jlabel = dup_to_jstring(ctx->jenv, opt->label);
+		if (!jlabel)
+			goto out;
+	}
+
+	jopt = (*ctx->jenv)->CallObjectMethod(ctx->jenv, jform, addOpt, opt->type, jname, jlabel);
+
+	if (opt->type == OC_FORM_OPT_SELECT &&
+	    populate_select_choices(ctx, jopt, (struct oc_form_opt_select *)opt))
+		ret = -1;
+	else
+		ret = 0;
+
+out:
+	if (jlabel)
+		(*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jlabel, NULL);
+	if (jname)
+		(*ctx->jenv)->ReleaseStringUTFChars(ctx->jenv, jname, NULL);
+	return ret;
+}
+
+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/openconnect/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;
+
+	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_openconnect_LibOpenConnect_init(
+	JNIEnv *jenv, jobject jobj, jstring juseragent)
+{
+	char *useragent;
+	struct libctx *ctx = calloc(1, sizeof(*ctx));
+
+	if (!ctx) {
+		OOM(jenv);
+		return 0;
+	}
+
+	if (pipe(ctx->pipefd) < 0) {
+		throw_excep(jenv, "java/lang/IOException", __LINE__);
+		free(ctx);
+		return 0;
+	}
+
+	useragent = (char *)(*jenv)->GetStringUTFChars(jenv, juseragent, NULL);
+	if (!useragent) {
+		close(ctx->pipefd[0]);
+		close(ctx->pipefd[1]);
+		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);
+	openconnect_set_cancel_fd(ctx->vpninfo, ctx->pipefd[0]);
+	(*jenv)->ReleaseStringUTFChars(jenv, juseragent, useragent);
+	return (jlong) ctx;
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_free(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+
+	PUSH_CTX();
+	openconnect_vpninfo_free(ctx->vpninfo);
+	close(ctx->pipefd[0]);
+	close(ctx->pipefd[1]);
+	free(ctx);
+	POP_CTX();
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_cancel(
+	JNIEnv *jenv, jobject jobj)
+{
+	/* This doesn't use PUSH_CTX so it is safe to call from another thread */
+	struct libctx *ctx = getctx(jenv, jobj);
+	char data = '.';
+
+	if (write(ctx->pipefd[1], &data, 1) < 0) {
+		throw_excep(jenv, "java/lang/IOException", __LINE__);
+	}
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_globalInit(
+	JNIEnv *jenv, jclass jcls)
+{
+	openconnect_init_ssl();
+}
+
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_parseURL(
+	JNIEnv *jenv, jobject jobj, jstring jurl)
+{
+	struct libctx *ctx, oldctx;
+	char *url;
+	int ret = -1;
+
+	PUSH_CTX(ret);
+	url = (char *)(*jenv)->GetStringUTFChars(jenv, jurl, NULL);
+	if (!url) {
+		OOM(ctx->jenv);
+	} else {
+		ret = openconnect_parse_url(ctx->vpninfo, url);
+		(*jenv)->ReleaseStringUTFChars(jenv, jurl, url);
+	}
+
+	POP_CTX();
+	return ret;
+}
+
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_obtainCookie(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	int ret;
+
+	PUSH_CTX(0);
+	ctx->cert = NULL;
+	ret = openconnect_obtain_cookie(ctx->vpninfo);
+	if (ret == 0)
+		ctx->cert = openconnect_get_peer_cert(ctx->vpninfo);
+	POP_CTX();
+	return ret;
+}
+
+/* special handling: caller-allocated buffer */
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertSHA1(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	char buf[41];
+	jstring jresult = NULL;
+
+	PUSH_CTX(NULL);
+	if (!ctx->cert)
+		goto out;
+	if (openconnect_get_cert_sha1(ctx->vpninfo, ctx->cert, buf))
+		goto out;
+	jresult = dup_to_jstring(ctx->jenv, buf);
+	if (!jresult)
+		OOM(ctx->jenv);
+
+out:
+	POP_CTX();
+	return jresult;
+}
+
+/* special handling: callee-allocated, caller-freed string */
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertDetails(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	char *buf = NULL;
+	jstring jresult = NULL;
+
+	PUSH_CTX(NULL);
+	if (!ctx->cert)
+		goto out;
+	buf = openconnect_get_cert_details(ctx->vpninfo, ctx->cert);
+	if (!buf)
+		goto out;
+
+	jresult = dup_to_jstring(ctx->jenv, buf);
+	if (!jresult)
+		OOM(ctx->jenv);
+
+out:
+	free(buf);
+	POP_CTX();
+	return jresult;
+}
+
+/* special handling: callee-allocated, caller-freed binary buffer */
+JNIEXPORT jbyteArray JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCertDER(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	unsigned char *buf = NULL;
+	int ret;
+	jbyteArray jresult = NULL;
+
+	PUSH_CTX(NULL);
+	if (!ctx->cert)
+		goto out;
+	ret = openconnect_get_cert_DER(ctx->vpninfo, ctx->cert, &buf);
+	if (ret < 0)
+		goto out;
+
+	jresult = (*ctx->jenv)->NewByteArray(ctx->jenv, ret);
+	if (!jresult)
+		goto out;
+	(*ctx->jenv)->SetByteArrayRegion(ctx->jenv, jresult, 0, ret, (jbyte *) buf);
+
+out:
+	free(buf);
+	POP_CTX();
+	return jresult;
+}
+
+/* special handling: two string arguments */
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setClientCert(
+	JNIEnv *jenv, jobject jobj, jstring jcert, jstring jsslkey)
+{
+	struct libctx *ctx, oldctx;
+	char *cert = NULL, *sslkey = NULL;
+
+	PUSH_CTX();
+	if (dup_to_cstring(ctx->jenv, jcert, &cert) ||
+	    dup_to_cstring(ctx->jenv, jsslkey, &sslkey)) {
+		free(cert);
+		free(sslkey);
+		goto out;
+	}
+
+	openconnect_set_client_cert(ctx->vpninfo, cert, sslkey);
+
+out:
+	POP_CTX();
+}
+
+/* class methods (general library info) */
+
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getVersion(
+	JNIEnv *jenv, jclass jcls)
+{
+	return dup_to_jstring(jenv, openconnect_get_version());
+}
+
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasPKCS11Support(
+	JNIEnv *jenv, jclass jcls)
+{
+	return openconnect_has_pkcs11_support();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasTSSBlobSupport(
+	JNIEnv *jenv, jclass jcls)
+{
+	return openconnect_has_tss_blob_support();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasStokenSupport(
+	JNIEnv *jenv, jclass jcls)
+{
+	return openconnect_has_stoken_support();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_infradead_openconnect_LibOpenConnect_hasOATHSupport(
+	JNIEnv *jenv, jclass jcls)
+{
+	return openconnect_has_oath_support();
+}
+
+/* simple cases: void or int params */
+
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_getPort(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	int ret;
+
+	PUSH_CTX(-EINVAL);
+	ret = openconnect_get_port(ctx->vpninfo);
+	POP_CTX();
+	return ret;
+}
+
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_LibOpenConnect_passphraseFromFSID(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+	int ret;
+
+	PUSH_CTX(-EINVAL);
+	ret = openconnect_passphrase_from_fsid(ctx->vpninfo);
+	POP_CTX();
+	return ret;
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_clearCookie(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+
+	PUSH_CTX();
+	openconnect_clear_cookie(ctx->vpninfo);
+	POP_CTX();
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_resetSSL(
+	JNIEnv *jenv, jobject jobj)
+{
+	struct libctx *ctx, oldctx;
+
+	PUSH_CTX();
+	openconnect_reset_ssl(ctx->vpninfo);
+	POP_CTX();
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCertExpiryWarning(
+	JNIEnv *jenv, jobject jobj, jint seconds)
+{
+	struct libctx *ctx, oldctx;
+
+	PUSH_CTX();
+	openconnect_set_cert_expiry_warning(ctx->vpninfo, seconds);
+	POP_CTX();
+}
+
+/* simple cases: return a const string (no need to free it) */
+
+#define RETURN_STRING_START \
+	struct libctx *ctx, oldctx; \
+	char *buf = NULL; \
+	jstring jresult = NULL; \
+	PUSH_CTX(NULL);
+
+#define RETURN_STRING_END \
+	if (!buf) \
+		goto out; \
+	jresult = dup_to_jstring(ctx->jenv, buf); \
+	if (!jresult) \
+		OOM(ctx->jenv); \
+out: \
+	POP_CTX(); \
+	return jresult;
+
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getHostname(
+	JNIEnv *jenv, jobject jobj)
+{
+	RETURN_STRING_START
+	buf = openconnect_get_hostname(ctx->vpninfo);
+	RETURN_STRING_END
+}
+
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getUrlpath(
+	JNIEnv *jenv, jobject jobj)
+{
+	RETURN_STRING_START
+	buf = openconnect_get_urlpath(ctx->vpninfo);
+	RETURN_STRING_END
+}
+
+JNIEXPORT jstring JNICALL Java_org_infradead_openconnect_LibOpenConnect_getCookie(
+	JNIEnv *jenv, jobject jobj)
+{
+	RETURN_STRING_START
+	buf = openconnect_get_cookie(ctx->vpninfo);
+	RETURN_STRING_END
+}
+
+#define SET_STRING_START(ret) \
+	struct libctx *ctx, oldctx; \
+	char *arg; \
+	PUSH_CTX(ret); \
+	if (dup_to_cstring(ctx->jenv, jarg, &arg)) \
+		return ret;
+
+#define SET_STRING_END \
+	POP_CTX();
+
+JNIEXPORT int JNICALL Java_org_infradead_openconnect_LibOpenConnect_setHTTPProxy(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	int ret;
+	SET_STRING_START(-ENOMEM)
+	ret = openconnect_set_http_proxy(ctx->vpninfo, arg);
+	SET_STRING_END
+	return ret;
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setXMLSHA1(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_set_xmlsha1(ctx->vpninfo, arg, strlen(arg) + 1);
+	SET_STRING_END
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setHostname(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_set_hostname(ctx->vpninfo, arg);
+	SET_STRING_END
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setUrlpath(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_set_urlpath(ctx->vpninfo, arg);
+	SET_STRING_END
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCAFile(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_set_cafile(ctx->vpninfo, arg);
+	SET_STRING_END
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setReportedOS(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_set_reported_os(ctx->vpninfo, arg);
+	SET_STRING_END
+}
+
+JNIEXPORT jint JNICALL Java_org_infradead_openconnect_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);
+	SET_STRING_END
+	return ret;
+}
+
+JNIEXPORT void JNICALL Java_org_infradead_openconnect_LibOpenConnect_setCSDWrapper(
+	JNIEnv *jenv, jobject jobj, jstring jarg)
+{
+	SET_STRING_START()
+	openconnect_setup_csd(ctx->vpninfo, getuid(), 1, arg);
+	SET_STRING_END
+}
diff --git a/org/infradead/openconnect/LibOpenConnect.java b/org/infradead/openconnect/LibOpenConnect.java
new file mode 100644
index 0000000..15de2f2
--- /dev/null
+++ b/org/infradead/openconnect/LibOpenConnect.java
@@ -0,0 +1,201 @@
+/*
+ * 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
+ */
+
+package org.infradead.openconnect;
+
+import java.util.ArrayList;
+
+public abstract class LibOpenConnect {
+
+	/* constants */
+
+	public static final int AUTH_FORM_ERROR = -1;
+	public static final int AUTH_FORM_PARSED = 0;
+	public static final int AUTH_FORM_CANCELLED = 1;
+
+	public static final int OC_FORM_OPT_TEXT = 1;
+	public static final int OC_FORM_OPT_PASSWORD = 2;
+	public static final int OC_FORM_OPT_SELECT = 3;
+	public static final int OC_FORM_OPT_HIDDEN = 4;
+	public static final int OC_FORM_OPT_TOKEN = 5;
+
+	public static final int OC_TOKEN_MODE_NONE = 0;
+	public static final int OC_TOKEN_MODE_STOKEN = 1;
+	public static final int OC_TOKEN_MODE_TOTP = 2;
+
+	/* required callbacks */
+
+	public abstract int onValidatePeerCert(String msg);
+	public abstract int onWriteNewConfig(byte[] buf);
+	public abstract int onProcessAuthForm(AuthForm authForm);
+	public abstract void onProgress(int level, String msg);
+
+	/* create/destroy library instances */
+
+	public LibOpenConnect() {
+		libctx = init("OpenConnect VPN Agent (Java)");
+	}
+
+	public synchronized void destroy() {
+		free();
+		libctx = 0;
+	}
+
+	/* control operations */
+
+	public synchronized native int parseURL(String url);
+	public synchronized native int obtainCookie();
+	public native void cancel();
+	public synchronized native void clearCookie();
+	public synchronized native void resetSSL();
+
+	/* connection settings */
+
+	public synchronized native int passphraseFromFSID();
+	public synchronized native void setCertExpiryWarning(int seconds);
+	public synchronized native int setHTTPProxy(String proxy);
+	public synchronized native void setXMLSHA1(String hash);
+	public synchronized native void setHostname(String hostname);
+	public synchronized native void setUrlpath(String urlpath);
+	public synchronized native void setCAFile(String caFile);
+	public synchronized native void setReportedOS(String os);
+	public synchronized native int setTokenMode(int tokenMode, String tokenString);
+	public synchronized native void setCSDWrapper(String wrapper);
+	public synchronized native void setClientCert(String cert, String sslKey);
+
+	/* connection info */
+
+	public synchronized native String getHostname();
+	public synchronized native String getUrlpath();
+	public synchronized native int getPort();
+	public synchronized native String getCookie();
+
+	/* certificate info */
+
+	public synchronized native String getCertSHA1();
+	public synchronized native String getCertDetails();
+	public synchronized native byte[] getCertDER();
+
+	/* library info */
+
+	public static native String getVersion();
+	public static native boolean hasPKCS11Support();
+	public static native boolean hasTSSBlobSupport();
+	public static native boolean hasStokenSupport();
+	public static native boolean hasOATHSupport();
+
+	/* public data structures */
+
+	public static class FormOpt {
+		public int type;
+		public String name;
+		public String label;
+		public ArrayList<FormChoice> choices;
+		String value;
+
+		public void setValue(String value) {
+			this.value = value;
+		}
+
+		/* FormOpt internals (called from JNI) */
+
+		FormOpt(int type, String name, String label) {
+			this.type = type;
+			this.name = name;
+			this.label = label;
+
+			if (type == OC_FORM_OPT_SELECT) {
+				this.choices = new ArrayList<FormChoice>();
+			}
+		}
+
+		void addChoice(FormChoice fc) {
+			this.choices.add(fc);
+		}
+	};
+
+	public static class FormChoice {
+		public String name;
+		public String label;
+		public String authType;
+		public String overrideName;
+		public String overrideLabel;
+
+		/* FormChoice internals (called from JNI) */
+
+		void setName(String arg) { this.name = arg; }
+		void setLabel(String arg) { this.label = arg; }
+		void setAuthType(String arg) { this.authType = arg; }
+		void setOverrideName(String arg) { this.overrideName = arg; }
+		void setOverrideLabel(String arg) { this.overrideLabel = arg; }
+	};
+
+	public static class AuthForm {
+		public String banner;
+		public String message;
+		public String error;
+		public String authID;
+		public String method;
+		public String action;
+		public ArrayList<FormOpt> opts;
+
+		/* AuthForm internals (called from JNI) */
+
+		void setBanner(String arg) { this.banner = arg; }
+		void setMessage(String arg) { this.message = arg; }
+		void setError(String arg) { this.error = arg; }
+		void setAuthID(String arg) { this.authID = arg; }
+		void setMethod(String arg) { this.method = arg; }
+		void setAction(String arg) { this.action = arg; }
+
+		AuthForm() {
+			opts = new ArrayList<FormOpt>();
+		}
+
+		FormOpt addOpt(int type, String name, String label) {
+			FormOpt fo = new FormOpt(type, name, label);
+			opts.add(fo);
+			return fo;
+		}
+
+		String getOptValue(String name) {
+			for (FormOpt fo : opts) {
+				if (fo.name.equals(name)) {
+					return fo.value;
+				}
+			}
+			return null;
+		}
+	}
+
+	/* LibOpenConnect internals */
+
+	long libctx;
+
+	static synchronized native void globalInit();
+	static {
+		globalInit();
+	}
+
+	synchronized native long init(String useragent);
+	synchronized native void free();
+}
-- 
1.7.9.5




More information about the openconnect-devel mailing list