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