From 198ca65b6ea1d46e6ce8b9e9c2d4e10033c5247b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 27 Nov 2019 17:13:44 +0100 Subject: [PATCH] add option to get password from macOS Keychain this is better than using PassCmd, as it allows the keychain manager to identify the calling process and therefore use a selective whitelist. unlike in the now removed example, we use an "internet password" for the imap protocol, rather than a "generic password" - this seems more appropriate. based on a patch by Oliver Runge --- NEWS | 2 ++ configure.ac | 34 +++++++++++++++++++++++++++++++++- src/Makefile.am | 2 +- src/drv_imap.c | 43 +++++++++++++++++++++++++++++++++++++++++++ src/mbsync.1 | 22 ++++++++++++++++++++++ src/mbsyncrc.sample | 5 ----- 6 files changed, 101 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 41934a5..4133a50 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ IMAP mailbox subscriptions are supported now. The IMAP user query can be scripted now. +Added built-in support for macOS Keychain. + [1.3.0] Network timeout handling has been added. diff --git a/configure.ac b/configure.ac index 811db2c..50e5ddb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,9 @@ AC_INIT([isync], [1.4.0]) AC_CONFIG_HEADERS([autodefs.h]) -AM_INIT_AUTOMAKE +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE AM_MAINTAINER_MODE AC_PROG_CC_C99 @@ -198,6 +200,29 @@ fi AM_CONDITIONAL(with_mdconvert, test "x$ac_cv_berkdb4" = xyes) +case $target_os in +darwin*) + darwin=yes +;; +*) + darwin=no +;; +esac + +AC_ARG_WITH( + macos-keychain, + [AS_HELP_STRING([--with-macos-keychain], [Support macOS keychain])], + [have_macos_keychain=$withval], + [have_macos_keychain=$darwin]) +if test "x$have_macos_keychain" != xno; then + if test $darwin = no; then + AC_MSG_ERROR([Cannot use macOS Keychain outside macOS.]) + fi + have_macos_keychain=yes + AC_DEFINE(HAVE_MACOS_KEYCHAIN, 1, [Define to 1 if you have the macOS Keychain Services API.]) + AC_SUBST(KEYCHAIN_LIBS, ["-Wl,-framework,Security"]) +fi + AC_CONFIG_FILES([Makefile src/Makefile isync.spec]) AC_OUTPUT @@ -222,4 +247,11 @@ if test "x$ac_cv_berkdb4" = xyes; then else AC_MSG_RESULT([Not using Berkeley DB]) fi +if test $darwin = yes; then + if test "x$have_macos_keychain" = xyes; then + AC_MSG_RESULT([Using macOS Keychain]) + else + AC_MSG_RESULT([Not using macOS Keychain]) + fi +fi AC_MSG_RESULT() diff --git a/src/Makefile.am b/src/Makefile.am index 63c4e22..76dff34 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ mbsync_SOURCES = main.c sync.c config.c util.c socket.c driver.c drv_imap.c drv_maildir.c drv_proxy.c -mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS) +mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS) $(KEYCHAIN_LIBS) noinst_HEADERS = common.h config.h driver.h sync.h socket.h drv_proxy.$(OBJEXT): drv_proxy.inc diff --git a/src/drv_imap.c b/src/drv_imap.c index 3980044..5acbd79 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -41,6 +41,10 @@ # include #endif +#ifdef HAVE_MACOS_KEYCHAIN +# include +#endif + #ifdef HAVE_LIBSSL enum { SSL_None, SSL_STARTTLS, SSL_IMAPS }; #endif @@ -58,6 +62,9 @@ typedef struct imap_server_conf { string_list_t *auth_mechs; #ifdef HAVE_LIBSSL char ssl_type; +#endif +#ifdef HAVE_MACOS_KEYCHAIN + char use_keychain; #endif char failed; } imap_server_conf_t; @@ -1991,6 +1998,31 @@ ensure_password( imap_server_conf_t *srvc ) if (!srvc->pass) { if (srvc->pass_cmd) { srvc->pass = cred_from_cmd( "PassCmd", srvc->pass_cmd, srvc->name ); +#ifdef HAVE_MACOS_KEYCHAIN + } else if (srvc->use_keychain) { + void *password_data; + UInt32 password_length; + OSStatus ret = SecKeychainFindInternetPassword( + NULL, // keychainOrArray + strlen( srvc->sconf.host ), srvc->sconf.host, + 0, NULL, // securityDomain + strlen( srvc->user ), srvc->user, + 0, NULL, // path + 0, // port - we could use it, but it seems pointless + kSecProtocolTypeIMAP, + kSecAuthenticationTypeDefault, + &password_length, &password_data, + NULL ); // itemRef + if (ret != errSecSuccess) { + CFStringRef errmsg = SecCopyErrorMessageString( ret, NULL ); + error( "Looking up Keychain failed: %s\n", + CFStringGetCStringPtr( errmsg, kCFStringEncodingUTF8 ) ); + CFRelease( errmsg ); + return NULL; + } + srvc->pass = nfstrndup( password_data, password_length ); + SecKeychainItemFreeContent( NULL, password_data ); +#endif /* HAVE_MACOS_KEYCHAIN */ } else { flushn(); char prompt[80]; @@ -3293,6 +3325,10 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) server->pass = nfstrdup( cfg->val ); else if (!strcasecmp( "PassCmd", cfg->cmd )) server->pass_cmd = nfstrdup( cfg->val ); +#ifdef HAVE_MACOS_KEYCHAIN + else if (!strcasecmp( "UseKeychain", cfg->cmd )) + server->use_keychain = parse_bool( cfg ); +#endif else if (!strcasecmp( "Port", cfg->cmd )) { int port = parse_int( cfg ); if ((unsigned)port > 0xffff) { @@ -3462,6 +3498,13 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) cfg->err = 1; return 1; } +#ifdef HAVE_MACOS_KEYCHAIN + if (server->use_keychain && (server->pass || server->pass_cmd)) { + error( "%s '%s' has UseKeychain enabled despite specifying Pass/PassCmd\n", type, name ); + cfg->err = 1; + return 1; + } +#endif #ifdef HAVE_LIBSSL if ((use_tlsv1 & use_tlsv11 & use_tlsv12 & use_tlsv13) != -1 || use_imaps >= 0 || require_ssl >= 0) { if (server->ssl_type >= 0 || server->sconf.ssl_versions >= 0) { diff --git a/src/mbsync.1 b/src/mbsync.1 index 3a98d50..16572ba 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -341,6 +341,28 @@ Prepend \fB+\fR to the command to indicate that it produces TTY output messier output. . .TP +\fBUseKeychain\fR \fByes\fR|\fBno\fR +Whether to use the macOS Keychain to obtain the password. +(Default: \fBno\fR) +.IP +The neccessary keychain item can be created this way: +.RS +.IP +.nh +.B security add-internet-password \-r imap \-s +.I Host +.B \-a +.I User +.B \-w +.I password +[ +.B \-T +.I /path/to/mbsync +] +.hy +.RE +. +.TP \fBTunnel\fR \fIcommand\fR Specify a command to run to establish a connection rather than opening a TCP socket. This allows you to run an IMAP session over an SSH tunnel, for diff --git a/src/mbsyncrc.sample b/src/mbsyncrc.sample index ef842fe..a9d9b13 100644 --- a/src/mbsyncrc.sample +++ b/src/mbsyncrc.sample @@ -21,11 +21,6 @@ Pass xxxxxxxx #PassCmd "gpg --quiet --for-your-eyes-only --decrypt $HOME/imappassword.gpg" # Fetch password from pwmd (http://pwmd.sourceforge.net/): #PassCmd "echo -ne 'GET myIsp\\tpassword' | pwmc datafile" -# On Mac OS X, run "KeyChain Access" -- File->New Password Item. Fill out form using -# "Keychain Item Name" http://IMAPSERVER (note: the "http://" is a hack) -# "Account Name" USERNAME -# "Password" PASSWORD -#PassCmd "/usr/bin/security find-internet-password -w -a USERNAME -s IMAPSERVER ~/Library/Keychains/login.keychain" Channel work Master :work: