> 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 <n...@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


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

Reply via email to