Updated a patch.

 * Change `--use-keychain` behavior as I suggested in previous email.
 * Update help texts and man page
 * Add `--save-to-keychain` option to specify which fields are saved
   to Keychain.

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

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

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

Signed-off-by: Yoshimasa Niwa <n...@niw.at>
---
 Makefile.am            |   2 +-
 configure.ac           |  16 +++++
 main.c                 | 129 ++++++++++++++++++++++++++++++++++++++++-
 openconnect-internal.h |   5 ++
 openconnect.8.in       |  23 ++++++++
 5 files changed, 173 insertions(+), 2 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..195cae71 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,8 @@ static int do_passphrase_from_fsid;
 static int non_inter;
 static int cookieonly;
 static int allow_stdin_read;
+static char *keychain_account = NULL;
+static struct oc_text_list_item *keychain_saving_fields = NULL;
 
 static char *token_filename;
 static char *server_cert = NULL;
@@ -171,6 +178,8 @@ enum {
        OPT_NO_XMLPOST,
        OPT_PIDFILE,
        OPT_PASSWORD_ON_STDIN,
+       OPT_USE_KEYCHAIN,
+       OPT_SAVE_TO_KEYCHAIN,
        OPT_PRINTCOOKIE,
        OPT_RECONNECT_TIMEOUT,
        OPT_SERVERCERT,
@@ -246,6 +255,10 @@ 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),
+       OPTION("save-to-keychain", 1, OPT_SAVE_TO_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 +811,10 @@ 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=ACCOUNT      %s\n", _("Look up Keychain to 
fill password form fields"));
+       printf("      --save-to-keychain=NAME     %s\n", _("Name of password 
form field to be saved to Keychain"));
+#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 +1301,18 @@ int main(int argc, char **argv)
                        read_stdin(&password, 0, 0);
                        allow_stdin_read = 1;
                        break;
+#if ENABLE_KEYCHAIN
+               case OPT_USE_KEYCHAIN:
+                       keychain_account = keep_config_arg();
+                       break;
+               case OPT_SAVE_TO_KEYCHAIN: {
+                       struct oc_text_list_item *field = 
malloc(sizeof(*field));
+                       field->data = keep_config_arg();
+                       field->next = keychain_saving_fields;
+                       keychain_saving_fields = field;
+                       break;
+               }
+#endif
                case OPT_NO_PASSWD:
                        vpninfo->nopasswd = 1;
                        break;
@@ -1946,6 +1975,98 @@ retry:
        return 0;
 }
 
+#if ENABLE_KEYCHAIN
+static char *lookup_keychain_password(const char *acc,
+                               struct oc_form_opt *opt,
+                               struct openconnect_info *vpninfo)
+{
+       OSStatus err = 0;
+
+       CFMutableDictionaryRef query = NULL;
+       CFStringRef account = NULL, name = NULL, key = NULL, label = NULL;
+       CFTypeRef data = NULL;
+       char *result = NULL;
+
+       if (verbose > PRG_INFO)
+               fprintf(stderr, "Lookup keychain for account: %s name: %s\n", 
acc, opt->name);
+
+       query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+       if (!query) goto end;
+
+       account = CFStringCreateWithCString(kCFAllocatorDefault, acc, 
kCFStringEncodingUTF8);
+       if (!account) goto end;
+       name = CFStringCreateWithCString(kCFAllocatorDefault, opt->name, 
kCFStringEncodingUTF8);
+       if (!name) goto end;
+       key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, 
CFSTR("%@:%@"), account, name);
+       if (!key) goto end;
+
+       CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
+       CFDictionaryAddValue(query, kSecAttrService, CFSTR("openconnect"));
+       CFDictionaryAddValue(query, kSecAttrAccount, key);
+       CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitOne);
+       CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
+
+       err = SecItemCopyMatching(query, &data);
+       if (err == errSecItemNotFound) {
+               if (data) CFRelease(data);
+
+               if (verbose > PRG_ERR)
+                       fprintf(stderr, "Item not found in Keychain\n");
+
+               result = prompt_for_input(opt->label, vpninfo, 1);
+               if (!result) goto end;
+               size_t len = strlen(result);
+               if (len == 0) goto end;
+
+               for (struct oc_text_list_item *field = keychain_saving_fields; 
field; field = field->next) {
+                       if (strcmp(opt->name, field->data))
+                               continue;
+
+                       label = CFStringCreateWithFormat(kCFAllocatorDefault, 
NULL, CFSTR("openconnect: %@ (%@)"), account, name);
+                       if (!label) goto end;
+                       data = CFDataCreate(kCFAllocatorDefault, (UInt8 
*)result, len + 1);
+                       if (!data) goto end;
+
+                       CFDictionaryAddValue(query, kSecAttrLabel, label);
+                       CFDictionaryAddValue(query, kSecValueData, data);
+                       CFDictionaryRemoveValue(query, kSecReturnData);
+
+                       err = SecItemAdd(query, NULL);
+                       if (err != errSecSuccess) {
+                               if (verbose > PRG_ERR)
+                                       fprintf(stderr, "Failed to add item to 
Keychain error: %d\n", err);
+                       } else {
+                               if (verbose > PRG_INFO)
+                                       fprintf(stderr, "Item saved in 
Keychain\n");
+                       }
+                       goto end;
+               }
+               goto end;
+       } else if (err != errSecSuccess) {
+               if (verbose > PRG_ERR)
+                       fprintf(stderr, "Failed to find item in Keychain error: 
%d\n", err);
+               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 (name) CFRelease(name);
+       if (key) CFRelease(key);
+       if (label) CFRelease(label);
+       if (data) CFRelease(data);
+
+       return result;
+}
+#endif
+
 /* Return value:
  *  < 0, on error
  *  = 0, when form was parsed and POST required
@@ -2014,7 +2135,13 @@ static int process_auth_form_cb(void *_vpninfo,
                        if (password) {
                                opt->_value = password;
                                password = NULL;
-                       } else {
+                       }
+#if ENABLE_KEYCHAIN
+                       else if (keychain_account) {
+                               opt->_value = 
lookup_keychain_password(keychain_account, opt, vpninfo);
+                       }
+#endif
+                       else {
                                opt->_value = prompt_for_input(opt->label, 
vpninfo, 1);
                        }
 
diff --git a/openconnect-internal.h b/openconnect-internal.h
index 8aa8fc89..c6b8686e 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -197,6 +197,11 @@ struct oc_text_buf {
        int error;
 };
 
+struct oc_text_list_item {
+       char *data;
+       struct oc_text_list_item *next;
+};
+
 #define TLS_MASTER_KEY_SIZE 48
 
 #define RECONNECT_INTERVAL_MIN 10
diff --git a/openconnect.8.in b/openconnect.8.in
index 37a33d0c..437d374f 100644
--- a/openconnect.8.in
+++ b/openconnect.8.in
@@ -57,6 +57,8 @@ 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 \-\-save\-to\-keychain string
 .OP \-\-protocol proto
 .OP \-\-token\-mode mode
 .OP \-\-token\-secret {secret\fR[\fI,counter\fR]|@\fIfile\fR}
@@ -426,6 +428,27 @@ Do not expect user input; exit if it is required.
 .B \-\-passwd\-on\-stdin
 Read password from standard input
 .TP
+.B \-\-use\-keychain=ACCOUNT
+Look up Keychain to fill password form field.
+.I ACCOUNT
+is a base name of Keychain items. For example, if
+.I ACCOUNT
+is "companyvpn", it looks up Keychain item named "companyvpn:token" for
+"token" password form field.
+.TP
+.B \-\-save\-to\-keychain=NAME
+Name of password form field to be saved to Keychain.
+.I \-\-use\-keychain
+option is required.
+For example, if
+.I \-\-use\-keychain
+options's
+.I ACCOUNT
+is "companyvpn" and
+.I NAME
+is "token", it saves input value to Keychain item named "companyvpn:token" for
+"token" password form field.
+.TP
 .B \-\-protocol=PROTO
 Select VPN protocol
 .I PROTO
-- 
Yoshimasa Niwa


_______________________________________________
openconnect-devel mailing list
openconnect-devel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/openconnect-devel

Reply via email to