Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- java/.gitignore | 2 + java/README | 22 ++ java/build.xml | 33 +++ java/src/com/example/LibTest.java | 201 +++++++++++++++++ .../infradead/libopenconnect/LibOpenConnect.java | 231 ++++++++++++++++++++ 5 files changed, 489 insertions(+) create mode 100644 java/.gitignore create mode 100644 java/README create mode 100644 java/build.xml create mode 100644 java/src/com/example/LibTest.java create mode 100644 java/src/org/infradead/libopenconnect/LibOpenConnect.java diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..edd9d60 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,2 @@ +build/ +dist/ diff --git a/java/README b/java/README new file mode 100644 index 0000000..1bdec00 --- /dev/null +++ b/java/README @@ -0,0 +1,22 @@ +Description: + +This directory contains a JNI interface layer for libopenconnect, and a +demo program to show how it can be used. + +Build instructions: + +From the top level, run: + + ./configure --with-java + make + cd java + ant + sudo java -Djava.library.path=../.libs -jar dist/example.jar <server_ip> + +If ocproxy[1] is installed somewhere in your $PATH, this can be run as a +non-root user and it should be pingable from across the VPN. + +Test/demo code is in src/com/example/ +OpenConnect wrapper library is in src/org/infradead/libopenconnect/ + +[1] http://repo.or.cz/w/ocproxy.git diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 0000000..acbe259 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,33 @@ +<project name="LibOpenConnect" default="dist" basedir="."> + <property name="build" location="build"/> + <property name="dist" location="dist"/> + <property name="src" location="src"/> + + <property name="jar_lib" location="${dist}/openconnect-wrapper.jar"/> + <property name="jar_app" location="${dist}/example.jar"/> + + <target name="init"> + <mkdir dir="${build}"/> + </target> + + <target name="compile" depends="init"> + <javac srcdir="${src}" destdir="${build}" + includeantruntime="false"/> + </target> + + <target name="dist" depends="compile"> + <jar jarfile="${jar_lib}" basedir="${build}" + includes="org/infradead/libopenconnect/*" /> + + <jar jarfile="${jar_app}" basedir="${build}"> + <manifest> + <attribute name="Main-Class" value="com.example.LibTest" /> + </manifest> + </jar> + </target> + + <target name="clean"> + <delete dir="${build}"/> + <delete dir="${dist}"/> + </target> +</project> diff --git a/java/src/com/example/LibTest.java b/java/src/com/example/LibTest.java new file mode 100644 index 0000000..6d7f3cb --- /dev/null +++ b/java/src/com/example/LibTest.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 com.example; + +import java.io.*; +import java.util.*; +import org.infradead.libopenconnect.LibOpenConnect; + +public final class LibTest { + + private static void die(String msg) { + System.out.println(msg); + System.exit(1); + } + + private static String getline() { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + try { + String line = br.readLine(); + return line; + } catch (IOException e) { + die("\nI/O error"); + } + return ""; + } + + private static class TestLib extends LibOpenConnect { + public int onValidatePeerCert(String msg) { + System.out.println("cert warning: " + msg); + System.out.println("cert SHA1: " + getCertSHA1()); + System.out.println("cert details: " + getCertDetails()); + + byte der[] = getCertDER(); + System.out.println("DER is " + der.length + " bytes long"); + + System.out.print("\nAccept this certificate? [n] "); + String s = getline(); + if (s.startsWith("y") || s.startsWith("Y")) { + return 0; + } else { + return -1; + } + } + + public int onWriteNewConfig(byte[] buf) { + System.out.println("new config: " + buf.length + " bytes"); + return 0; + } + + public int onProcessAuthForm(LibOpenConnect.AuthForm authForm) { + System.out.println("\nAuthForm:"); + System.out.println("+-banner: " + authForm.banner); + System.out.println("+-message: " + authForm.message); + System.out.println("+-error: " + authForm.error); + System.out.println("+-authID: " + authForm.authID); + System.out.println("+-method: " + authForm.method); + System.out.println("+-action: " + authForm.action); + + for (FormOpt fo : authForm.opts) { + System.out.println("->FormOpt: "); + System.out.println(" +-type: " + fo.type); + System.out.println(" +-name: " + fo.name); + System.out.println(" +-label: " + fo.label); + + if (fo.type == OC_FORM_OPT_SELECT) { + for (FormChoice fc : fo.choices) { + System.out.println("--->FormChoice: "); + System.out.println(" +-name: " + fc.name); + System.out.println(" +-label: " + fc.label); + System.out.println(" +-authType: " + fc.authType); + System.out.println(" +-overrideName: " + fc.overrideName); + System.out.println(" +-overrideLabel: " + fc.overrideLabel); + } + } + + if (fo.type == OC_FORM_OPT_TEXT || + fo.type == OC_FORM_OPT_PASSWORD || + fo.type == OC_FORM_OPT_SELECT) { + System.out.print("\n" + fo.label + " "); + fo.setValue(getline()); + } + } + System.out.println(""); + + return AUTH_FORM_PARSED; + } + + public void onProgress(int level, String msg) { + switch (level) { + case LibOpenConnect.PRG_TRACE: + System.out.print("TRACE: " + msg); + break; + case LibOpenConnect.PRG_DEBUG: + System.out.print("DEBUG: " + msg); + break; + case LibOpenConnect.PRG_INFO: + System.out.print("INFO: " + msg); + break; + case LibOpenConnect.PRG_ERR: + System.out.print("ERROR: " + msg); + break; + } + } + } + + private static void printList(String pfx, List<String> ss) { + System.out.print(pfx + ":"); + + if (ss.size() == 0) { + System.out.println(" <empty>"); + return; + } + for (String s : ss) { + System.out.print(" " + s); + } + System.out.println(""); + } + + private static void printIPInfo(LibOpenConnect.IPInfo ip) { + System.out.println("\nIPInfo:"); + System.out.println("+-IPv4: " + ip.addr + " / " + ip.netmask); + System.out.println("+-IPv6: " + ip.addr6 + " / " + ip.netmask6); + System.out.println("+-Domain: " + ip.domain); + System.out.println("+-proxy.pac: " + ip.proxyPac); + System.out.println("+-MTU: " + ip.MTU); + printList("+-DNS", ip.DNS); + printList("+-NBNS", ip.NBNS); + printList("+-Split DNS", ip.splitDNS); + printList("+-Split includes", ip.splitIncludes); + printList("+-Split excludes", ip.splitExcludes); + System.out.println(""); + } + + public static void main(String argv[]) { + System.loadLibrary("openconnect-wrapper"); + LibOpenConnect lib = new TestLib(); + + if (argv.length != 1) + die("usage: LibTest <server_name>"); + + System.out.println("OpenConnect version: " + lib.getVersion()); + System.out.println(" PKCS=" + lib.hasPKCS11Support() + + ", TSS=" + lib.hasTSSBlobSupport() + + ", STOKEN=" + lib.hasStokenSupport() + + ", OATH=" + lib.hasOATHSupport()); + lib.setReportedOS("win"); + lib.setLogLevel(lib.PRG_DEBUG); + //lib.setTokenMode(LibOpenConnect.OC_TOKEN_MODE_STOKEN, null); + if (new File("csd.sh").exists()) { + lib.setCSDWrapper("csd.sh"); + } + lib.parseURL(argv[0]); + + int ret = lib.obtainCookie(); + if (ret < 0) + die("obtainCookie() returned error"); + else if (ret > 0) + die("Aborted by user"); + + String cookie = lib.getCookie(); + if (cookie.length() > 40) { + System.out.println("Cookie: " + cookie.substring(0, 40) + "..."); + } else { + System.out.println("Cookie: " + cookie); + } + + if (lib.makeCSTPConnection() != 0) + die("Error establishing VPN link"); + + printIPInfo(lib.getIPInfo()); + + if (lib.setupTunDevice("/etc/vpnc/vpnc-script", null) != 0 && + lib.setupTunScript("ocproxy") != 0) + die("Error setting up tunnel"); + + if (lib.setupDTLS(60) != 0) + die("Error setting up DTLS"); + + lib.mainloop(); + } +} diff --git a/java/src/org/infradead/libopenconnect/LibOpenConnect.java b/java/src/org/infradead/libopenconnect/LibOpenConnect.java new file mode 100644 index 0000000..8911634 --- /dev/null +++ b/java/src/org/infradead/libopenconnect/LibOpenConnect.java @@ -0,0 +1,231 @@ +/* + * 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.libopenconnect; + +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; + + public static final int PRG_ERR = 0; + public static final int PRG_INFO = 1; + public static final int PRG_DEBUG = 2; + public static final int PRG_TRACE = 3; + + /* 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() { + if (libctx != 0) { + free(); + libctx = 0; + } + } + + public void cancel() { + synchronized (cancelLock) { + if (!canceled) { + doCancel(); + canceled = true; + } + } + } + + public boolean isCanceled() { + synchronized (cancelLock) { + return canceled; + } + } + + /* control operations */ + + public synchronized native int parseURL(String url); + public synchronized native int obtainCookie(); + public synchronized native void clearCookie(); + public synchronized native void resetSSL(); + public synchronized native int makeCSTPConnection(); + public synchronized native int setupTunDevice(String vpncScript, String IFName); + public synchronized native int setupTunScript(String tunScript); + public synchronized native int setupTunFD(int tunFD); + public synchronized native int setupDTLS(int attemptPeriod); + public synchronized native int mainloop(); + + /* connection settings */ + + public synchronized native void setLogLevel(int level); + 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); + public synchronized native void setServerCertSHA1(String hash); + public synchronized native void setReqMTU(int mtu); + + /* connection info */ + + public synchronized native String getHostname(); + public synchronized native String getUrlpath(); + public synchronized native int getPort(); + public synchronized native String getCookie(); + public synchronized native String getIFName(); + public synchronized native IPInfo getIPInfo(); + + /* 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 = new ArrayList<FormChoice>(); + String value; + + public void setValue(String value) { + this.value = value; + } + + /* FormOpt internals (called from JNI) */ + + 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; + }; + + 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 = new ArrayList<FormOpt>(); + + /* AuthForm internals (called from JNI) */ + + FormOpt addOpt() { + FormOpt fo = new FormOpt(); + opts.add(fo); + return fo; + } + + String getOptValue(String name) { + for (FormOpt fo : opts) { + if (fo.name.equals(name)) { + return fo.value; + } + } + return null; + } + } + + public static class IPInfo { + public String addr; + public String netmask; + public String addr6; + public String netmask6; + public ArrayList<String> DNS = new ArrayList<String>(); + public ArrayList<String> NBNS = new ArrayList<String>(); + public String domain; + public String proxyPac; + public int MTU; + + public ArrayList<String> splitDNS = new ArrayList<String>(); + public ArrayList<String> splitIncludes = new ArrayList<String>(); + public ArrayList<String> splitExcludes = new ArrayList<String>(); + + /* IPInfo internals (called from JNI) */ + + void addDNS(String arg) { DNS.add(arg); } + void addNBNS(String arg) { NBNS.add(arg); } + void addSplitDNS(String arg) { splitDNS.add(arg); } + void addSplitInclude(String arg) { splitIncludes.add(arg); } + void addSplitExclude(String arg) { splitExcludes.add(arg); } + } + + /* LibOpenConnect internals */ + + long libctx; + boolean canceled = false; + Object cancelLock = new Object(); + + static synchronized native void globalInit(); + static { + globalInit(); + } + + synchronized native long init(String useragent); + synchronized native void free(); + native void doCancel(); +} -- 1.7.9.5