Эх сурвалжийг харах

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 жил өмнө
parent
commit
198ca65b6e
6 өөрчлөгдсөн 101 нэмэгдсэн , 7 устгасан
  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: