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>
This commit is contained in:
Oswald Buddenhagen 2019-11-27 17:13:44 +01:00
parent 03b15dbdd3
commit 198ca65b6e
6 changed files with 101 additions and 7 deletions

2
NEWS
View File

@ -13,6 +13,8 @@ IMAP mailbox subscriptions are supported now.
The IMAP user query can be scripted now. The IMAP user query can be scripted now.
Added built-in support for macOS Keychain.
[1.3.0] [1.3.0]
Network timeout handling has been added. Network timeout handling has been added.

View File

@ -1,7 +1,9 @@
AC_INIT([isync], [1.4.0]) AC_INIT([isync], [1.4.0])
AC_CONFIG_HEADERS([autodefs.h]) AC_CONFIG_HEADERS([autodefs.h])
AM_INIT_AUTOMAKE
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE
AM_MAINTAINER_MODE AM_MAINTAINER_MODE
AC_PROG_CC_C99 AC_PROG_CC_C99
@ -198,6 +200,29 @@ fi
AM_CONDITIONAL(with_mdconvert, test "x$ac_cv_berkdb4" = xyes) 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_CONFIG_FILES([Makefile src/Makefile isync.spec])
AC_OUTPUT AC_OUTPUT
@ -222,4 +247,11 @@ if test "x$ac_cv_berkdb4" = xyes; then
else else
AC_MSG_RESULT([Not using Berkeley DB]) AC_MSG_RESULT([Not using Berkeley DB])
fi 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() AC_MSG_RESULT()

View File

@ -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_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 noinst_HEADERS = common.h config.h driver.h sync.h socket.h
drv_proxy.$(OBJEXT): drv_proxy.inc drv_proxy.$(OBJEXT): drv_proxy.inc

View File

@ -41,6 +41,10 @@
# include <sasl/saslutil.h> # include <sasl/saslutil.h>
#endif #endif
#ifdef HAVE_MACOS_KEYCHAIN
# include <Security/Security.h>
#endif
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
enum { SSL_None, SSL_STARTTLS, SSL_IMAPS }; enum { SSL_None, SSL_STARTTLS, SSL_IMAPS };
#endif #endif
@ -58,6 +62,9 @@ typedef struct imap_server_conf {
string_list_t *auth_mechs; string_list_t *auth_mechs;
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
char ssl_type; char ssl_type;
#endif
#ifdef HAVE_MACOS_KEYCHAIN
char use_keychain;
#endif #endif
char failed; char failed;
} imap_server_conf_t; } imap_server_conf_t;
@ -1991,6 +1998,31 @@ ensure_password( imap_server_conf_t *srvc )
if (!srvc->pass) { if (!srvc->pass) {
if (srvc->pass_cmd) { if (srvc->pass_cmd) {
srvc->pass = cred_from_cmd( "PassCmd", srvc->pass_cmd, srvc->name ); 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 { } else {
flushn(); flushn();
char prompt[80]; char prompt[80];
@ -3293,6 +3325,10 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
server->pass = nfstrdup( cfg->val ); server->pass = nfstrdup( cfg->val );
else if (!strcasecmp( "PassCmd", cfg->cmd )) else if (!strcasecmp( "PassCmd", cfg->cmd ))
server->pass_cmd = nfstrdup( cfg->val ); 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 )) { else if (!strcasecmp( "Port", cfg->cmd )) {
int port = parse_int( cfg ); int port = parse_int( cfg );
if ((unsigned)port > 0xffff) { if ((unsigned)port > 0xffff) {
@ -3462,6 +3498,13 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep )
cfg->err = 1; cfg->err = 1;
return 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 #ifdef HAVE_LIBSSL
if ((use_tlsv1 & use_tlsv11 & use_tlsv12 & use_tlsv13) != -1 || use_imaps >= 0 || require_ssl >= 0) { 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) { if (server->ssl_type >= 0 || server->sconf.ssl_versions >= 0) {

View File

@ -341,6 +341,28 @@ Prepend \fB+\fR to the command to indicate that it produces TTY output
messier output. messier output.
. .
.TP .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 \fBTunnel\fR \fIcommand\fR
Specify a command to run to establish a connection rather than opening a TCP 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 socket. This allows you to run an IMAP session over an SSH tunnel, for

View File

@ -21,11 +21,6 @@ Pass xxxxxxxx
#PassCmd "gpg --quiet --for-your-eyes-only --decrypt $HOME/imappassword.gpg" #PassCmd "gpg --quiet --for-your-eyes-only --decrypt $HOME/imappassword.gpg"
# Fetch password from pwmd (http://pwmd.sourceforge.net/): # Fetch password from pwmd (http://pwmd.sourceforge.net/):
#PassCmd "echo -ne 'GET myIsp\\tpassword' | pwmc datafile" #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 Channel work
Master :work: Master :work: