[PATCH] Add Keychain support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> Man page too please.

Update man page, amend help text.

-- >8 --
Subject: [PATCH] Add Keychain support

This patch adds macOS Keychain support to fill specific password
option.
If Keychain doesn't have a password entry, it will prompt it then
save it in Keychain.

This patch is squashed commit from
https://github.com/niw/openconnect/tree/add_keychain_support

Signed-off-by: Yoshimasa Niwa <niw at niw.at>
---
 Makefile.am      |   2 +-
 configure.ac     |  16 ++++++
 main.c           | 132 ++++++++++++++++++++++++++++++++++++++++++++---
 openconnect.8.in |   6 +++
 4 files changed, 149 insertions(+), 7 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 522725eb..2e006a90 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@ AM_CPPFLAGS = -DLOCALEDIR="\"$(localedir)\""
 
 openconnect_SOURCES = xml.c main.c
 openconnect_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) $(INTL_CFLAGS) $(ICONV_CFLAGS) $(LIBPCSCLITE_CFLAGS)
-openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LIBS) $(ICONV_LIBS)
+openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LIBS) $(ICONV_LIBS) $(KEYCHAIN_LIBS)
 
 if OPENCONNECT_WIN32
 openconnect_SOURCES += openconnect.rc
diff --git a/configure.ac b/configure.ac
index 5065a298..3c4cb83a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -204,6 +204,21 @@ AC_CHECK_FUNC(__android_log_vprint, [], AC_CHECK_LIB(log, __android_log_vprint,
 AC_ENABLE_SHARED
 AC_DISABLE_STATIC
 
+keychain_support=no
+AC_ARG_ENABLE([keychain],
+	AS_HELP_STRING([--enable-keychain], [Enable Keychain support]),
+	[ENABLE_KEYCHAIN=$enableval],
+	[ENABLE_KEYCHAIN=no])
+if test "$ENABLE_KEYCHAIN" = "yes"; then
+	AC_CHECK_HEADER([CoreFoundation/CoreFoundation.h],
+		[], [AC_MSG_ERROR(Cannot find CoreFoundaation header.)])
+	AC_CHECK_HEADER([Security/Security.h],
+		[], [AC_MSG_ERROR(Cannot find Security header.)])
+	AC_DEFINE([ENABLE_KEYCHAIN], 1, [Enable Keychain support])
+	keychain_support=yes
+	AC_SUBST(KEYCHAIN_LIBS, ["-framework Foundation -framework Security"])
+fi
+
 AC_CHECK_FUNC(nl_langinfo, [AC_DEFINE(HAVE_NL_LANGINFO, 1, [Have nl_langinfo() function])], [])
 
 if test "$ac_cv_func_nl_langinfo" = "yes"; then
@@ -1042,6 +1057,7 @@ SUMMARY([Java bindings], [$with_java])
 SUMMARY([Build docs], [$build_www])
 SUMMARY([Unit tests], [$have_cwrap])
 SUMMARY([Net namespace tests], [$have_netns])
+SUMMARY([Keychain support], [$keychain_support])
 
 if test "$ssl_library" = "OpenSSL"; then
     AC_MSG_WARN([[
diff --git a/main.c b/main.c
index 2e9e3059..59c9f481 100644
--- a/main.c
+++ b/main.c
@@ -62,6 +62,11 @@
 static const char *legacy_charset;
 #endif
 
+#if ENABLE_KEYCHAIN
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#endif
+
 static int write_new_config(void *_vpninfo,
 			    const char *buf, int buflen);
 static void __attribute__ ((format(printf, 3, 4)))
@@ -85,6 +90,7 @@ static int do_passphrase_from_fsid;
 static int non_inter;
 static int cookieonly;
 static int allow_stdin_read;
+static char *keychain_opt_name = NULL;
 
 static char *token_filename;
 static char *server_cert = NULL;
@@ -171,6 +177,7 @@ enum {
 	OPT_NO_XMLPOST,
 	OPT_PIDFILE,
 	OPT_PASSWORD_ON_STDIN,
+	OPT_USE_KEYCHAIN,
 	OPT_PRINTCOOKIE,
 	OPT_RECONNECT_TIMEOUT,
 	OPT_SERVERCERT,
@@ -246,6 +253,9 @@ static const struct option long_options[] = {
 	OPTION("xmlconfig", 1, 'x'),
 	OPTION("cookie-on-stdin", 0, OPT_COOKIE_ON_STDIN),
 	OPTION("passwd-on-stdin", 0, OPT_PASSWORD_ON_STDIN),
+#if ENABLE_KEYCHAIN
+	OPTION("use-keychain", 1, OPT_USE_KEYCHAIN),
+#endif
 	OPTION("no-passwd", 0, OPT_NO_PASSWD),
 	OPTION("reconnect-timeout", 1, OPT_RECONNECT_TIMEOUT),
 	OPTION("dtls-ciphers", 1, OPT_DTLS_CIPHERS),
@@ -798,6 +808,9 @@ static void usage(void)
 	printf("      --no-passwd                 %s\n", _("Disable password/SecurID authentication"));
 	printf("      --non-inter                 %s\n", _("Do not expect user input; exit if it is required"));
 	printf("      --passwd-on-stdin           %s\n", _("Read password from standard input"));
+#if ENABLE_KEYCHAIN
+	printf("      --use-keychain=NAME         %s\n", _("Name of password form option that look up Keychain to fill"));
+#endif
 	printf("      --authgroup=GROUP           %s\n", _("Choose authentication login selection"));
 	printf("  -c, --certificate=CERT          %s\n", _("Use SSL client certificate CERT"));
 	printf("  -k, --sslkey=KEY                %s\n", _("Use SSL private key file KEY"));
@@ -1284,6 +1297,12 @@ int main(int argc, char **argv)
 			read_stdin(&password, 0, 0);
 			allow_stdin_read = 1;
 			break;
+#if ENABLE_KEYCHAIN
+		case OPT_USE_KEYCHAIN:
+			free(keychain_opt_name);
+			keychain_opt_name = dup_config_arg();
+			break;
+#endif
 		case OPT_NO_PASSWD:
 			vpninfo->nopasswd = 1;
 			break;
@@ -1946,6 +1965,83 @@ retry:
 	return 0;
 }
 
+#if ENABLE_KEYCHAIN
+static char *lookup_keychain_password(const char *user, const char *prompt, struct openconnect_info *vpninfo)
+{
+	OSStatus err = 0;
+
+	CFMutableDictionaryRef query = NULL;
+	CFStringRef account = NULL, server = NULL, path = NULL;
+	CFTypeRef data = NULL;
+	char *result = NULL;
+
+	if (verbose > PRG_ERR) {
+		fprintf(stderr, "Lookup keychain for user: %s url: https://%s%s\n";, user, vpninfo->hostname, vpninfo->urlpath);
+	}
+
+	query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+	if (!query) goto end;
+
+	account = CFStringCreateWithCString(kCFAllocatorDefault, user, kCFStringEncodingUTF8);
+	if (!account) goto end;
+	server = CFStringCreateWithCString(kCFAllocatorDefault, vpninfo->hostname, kCFStringEncodingUTF8);
+	if (!server) goto end;
+	path = CFStringCreateWithCString(kCFAllocatorDefault, vpninfo->urlpath, kCFStringEncodingUTF8);
+	if (!path) goto end;
+
+	CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
+	CFDictionaryAddValue(query, kSecAttrAccount, account);
+	CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
+	CFDictionaryAddValue(query, kSecAttrServer, server);
+	CFDictionaryAddValue(query, kSecAttrPath, path);
+	CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
+	CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
+
+	err = SecItemCopyMatching(query, &data);
+	if (err == errSecItemNotFound) {
+		if (data) CFRelease(data);
+
+		fprintf(stderr, "Item not found in Keychain\n");
+
+		result = prompt_for_input(prompt, vpninfo, 1);
+		if (!result) goto end;
+		size_t len = strlen(result);
+		if (len == 0) goto end;
+
+		data = CFDataCreate(kCFAllocatorDefault, (UInt8 *)result, len + 1);
+		if (!data) goto end;
+
+		CFDictionaryAddValue(query, kSecValueData, data);
+		CFDictionaryRemoveValue(query, kSecReturnData);
+
+		err = SecItemAdd(query, NULL);
+		if (err != errSecSuccess) {
+			if (verbose > PRG_ERR) {
+				fprintf(stderr, "Fail to add item to Keychain\n");
+			}
+		}
+		goto end;
+	}
+	if (err != errSecSuccess) goto end;
+	if (!data || CFGetTypeID(data) != CFDataGetTypeID()) goto end;
+
+	CFIndex size = CFDataGetLength(data);
+	result = malloc((size_t)size);
+	if (!result) goto end;
+
+	CFDataGetBytes(data, CFRangeMake(0, size), (UInt8 *)result);
+
+end:
+	if (query) CFRelease(query);
+	if (account) CFRelease(account);
+	if (server) CFRelease(server);
+	if (path) CFRelease(path);
+	if (data) CFRelease(data);
+
+	return result;
+}
+#endif
+
 /* Return value:
  *  < 0, on error
  *  = 0, when form was parsed and POST required
@@ -1955,8 +2051,9 @@ static int process_auth_form_cb(void *_vpninfo,
 				struct oc_auth_form *form)
 {
 	struct openconnect_info *vpninfo = _vpninfo;
-	struct oc_form_opt *opt;
+	struct oc_form_opt *opt, *prev_opt;
 	int empty = 1;
+	char *user;
 
 	if (form->banner && verbose > PRG_ERR)
 		fprintf(stderr, "%s\n", form->banner);
@@ -1981,6 +2078,18 @@ static int process_auth_form_cb(void *_vpninfo,
 		}
 	}
 
+	// Reorder `opts` to bring `user` first.
+	for (prev_opt = NULL, opt = form->opts; opt; prev_opt = opt, opt = opt->next) {
+		if ((opt->type == OC_FORM_OPT_TEXT) && !strncmp(opt->name, "user", 4)) {
+			if (prev_opt) {
+				prev_opt->next = opt->next;
+				opt->next = form->opts;
+				form->opts = opt;
+			}
+			break;
+		}
+	}
+
 	for (opt = form->opts; opt; opt = opt->next) {
 
 		if (opt->flags & OC_FORM_OPT_IGNORE)
@@ -1998,10 +2107,14 @@ static int process_auth_form_cb(void *_vpninfo,
 			empty = 0;
 
 		} else if (opt->type == OC_FORM_OPT_TEXT) {
-			if (username &&
-			    !strncmp(opt->name, "user", 4)) {
-				opt->_value = username;
-				username = NULL;
+			if (!strncmp(opt->name, "user", 4)) {
+				if (username) {
+					opt->_value = username;
+					username = NULL;
+				} else {
+					opt->_value = prompt_for_input(opt->label, vpninfo, 0);
+				}
+				user = opt->_value;
 			} else {
 				opt->_value = prompt_for_input(opt->label, vpninfo, 0);
 			}
@@ -2014,7 +2127,14 @@ static int process_auth_form_cb(void *_vpninfo,
 			if (password) {
 				opt->_value = password;
 				password = NULL;
-			} else {
+			}
+#if ENABLE_KEYCHAIN
+			else if (keychain_opt_name && user && !strcmp(opt->name, keychain_opt_name)) {
+				opt->_value = lookup_keychain_password(user, opt->label, vpninfo);
+				keychain_opt_name = NULL;
+			}
+#endif
+			else {
 				opt->_value = prompt_for_input(opt->label, vpninfo, 1);
 			}
 
diff --git a/openconnect.8.in b/openconnect.8.in
index 37a33d0c..2fe57c05 100644
--- a/openconnect.8.in
+++ b/openconnect.8.in
@@ -57,6 +57,7 @@ openconnect \- Multi-protocol VPN client, for Cisco AnyConnect VPNs and others
 .OP \-\-no\-xmlpost
 .OP \-\-non\-inter
 .OP \-\-passwd\-on\-stdin
+.OP \-\-use-keychain string
 .OP \-\-protocol proto
 .OP \-\-token\-mode mode
 .OP \-\-token\-secret {secret\fR[\fI,counter\fR]|@\fIfile\fR}
@@ -426,6 +427,11 @@ Do not expect user input; exit if it is required.
 .B \-\-passwd\-on\-stdin
 Read password from standard input
 .TP
+.B \-\-use\-keychain=NAME
+Look up Keychain to fill one of password form options.
+.I NAME
+is the name of the password form option. It may be "password".
+.TP
 .B \-\-protocol=PROTO
 Select VPN protocol
 .I PROTO
-- 
Yoshimasa Niwa




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux