Prechádzať zdrojové kódy

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 <oliver.runge@gmail.com>
Oswald Buddenhagen 5 rokov pred
rodič
commit
198ca65b6e
6 zmenil súbory, kde vykonal 101 pridanie a 7 odobranie
  1. 2 0
      NEWS
  2. 33 1
      configure.ac
  3. 1 1
      src/Makefile.am
  4. 43 0
      src/drv_imap.c
  5. 22 0
      src/mbsync.1
  6. 0 5
      src/mbsyncrc.sample

+ 2 - 0
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.

+ 33 - 1
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()

+ 1 - 1
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

+ 43 - 0
src/drv_imap.c

@@ -41,6 +41,10 @@
 # include <sasl/saslutil.h>
 #endif
 
+#ifdef HAVE_MACOS_KEYCHAIN
+# include <Security/Security.h>
+#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) {

+ 22 - 0
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

+ 0 - 5
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: