--- a/Makefile.in.old +++ b/Makefile.in @@ -59,6 +59,7 @@ ENT=@ENT@ XAUTH_PATH=@XAUTH_PATH@ LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@ +KEYCHAIN_LDFLAGS=@KEYCHAIN_LDFLAGS@ EXEEXT=@EXEEXT@ MANFMT=@MANFMT@ @@ -108,6 +109,8 @@ sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ sandbox-seccomp-filter.o sandbox-capsicum.o +KEYCHAINOBJS=keychain.o + MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5 MANTYPE = @MANTYPE@ @@ -143,6 +146,7 @@ $(LIBSSH_OBJS): Makefile.in config.h $(SSHOBJS): Makefile.in config.h $(SSHDOBJS): Makefile.in config.h +$(KEYCHAINOBJS): Makefile.in config.h .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ @@ -156,8 +160,8 @@ $(AR) rv $@ $(LIBSSH_OBJS) $(RANLIB) $@ -ssh$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHOBJS) - $(LD) -o $@ $(SSHOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHLIBS) $(LIBS) $(GSSLIBS) +ssh$(EXEEXT): $(LIBCOMPAT) libssh.a $(SSHOBJS) $(KEYCHAINOBJS) + $(LD) -o $@ $(SSHOBJS) $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(SSHLIBS) $(LIBS) $(GSSLIBS) sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(SSHDOBJS) $(LD) -o $@ $(SSHDOBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(SSHDLIBS) $(LIBS) $(GSSLIBS) $(K5LIBS) @@ -165,11 +169,11 @@ scp$(EXEEXT): $(LIBCOMPAT) libssh.a scp.o progressmeter.o $(LD) -o $@ scp.o progressmeter.o bufaux.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) -ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o - $(LD) -o $@ ssh-add.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) +ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o $(KEYCHAINOBJS) + $(LD) -o $@ ssh-add.o $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS) -ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o - $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) +ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o $(KEYCHAINOBJS) + $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(KEYCHAINOBJS) $(LDFLAGS) $(KEYCHAIN_LDFLAGS) -lssh -lopenbsd-compat $(LIBS) ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o $(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) @@ -293,7 +297,7 @@ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keygen$(EXEEXT) $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) $(INSTALL) -m 0755 $(STRIP_OPT) ssh-keyscan$(EXEEXT) $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) $(INSTALL) -m 0755 $(STRIP_OPT) sshd$(EXEEXT) $(DESTDIR)$(sbindir)/sshd$(EXEEXT) - $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT) + $(INSTALL) -m 0711 $(STRIP_OPT) ssh-keysign$(EXEEXT) $(DESTDIR)$(SSH_KEYSIGN)$(EXEEXT) $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT) $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT) $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) --- a/audit-bsm.c.old +++ b/audit-bsm.c @@ -263,7 +263,12 @@ pid_t pid = getpid(); AuditInfoTermID tid = ssh_bsm_tid; - if (the_authctxt != NULL && the_authctxt->valid) { + if (the_authctxt == NULL) { + error("BSM audit: audit record internal error (NULL ctxt)"); + abort(); + } + + if (the_authctxt->valid) { uid = the_authctxt->pw->pw_uid; gid = the_authctxt->pw->pw_gid; } --- a/auth-pam.c.old +++ b/auth-pam.c @@ -793,10 +793,11 @@ free(msg); return (0); } - error("PAM: %s for %s%.100s from %.100s", msg, + error("PAM: %s for %s%.100s from %.100s via %s", msg, sshpam_authctxt->valid ? "" : "illegal user ", sshpam_authctxt->user, - get_remote_name_or_ip(utmp_len, options.use_dns)); + get_remote_name_or_ip(utmp_len, options.use_dns), + get_local_ipaddr(packet_get_connection_in())); /* FALLTHROUGH */ default: *num = 0; --- a/auth.c.old +++ b/auth.c @@ -211,7 +211,7 @@ } if (options.num_deny_groups > 0 || options.num_allow_groups > 0) { /* Get the user's group access list (primary and supplementary) */ - if (ga_init(pw->pw_name, pw->pw_gid) == 0) { + if (ga_init(pw) == 0) { logit("User %.100s from %.100s not allowed because " "not in any group", pw->pw_name, hostname); return 0; --- a/authfd.c.old +++ b/authfd.c @@ -650,6 +650,29 @@ return decode_reply(type); } +/* + * Adds identities using passphrases stored in the keychain. This call is not + * meant to be used by normal applications. + */ + +int +ssh_add_from_keychain(AuthenticationConnection *auth) +{ + Buffer msg; + int type; + + buffer_init(&msg); + buffer_put_char(&msg, SSH_AGENTC_ADD_FROM_KEYCHAIN); + + if (ssh_request_reply(auth, &msg, &msg) == 0) { + buffer_free(&msg); + return 0; + } + type = buffer_get_char(&msg); + buffer_free(&msg); + return decode_reply(type); +} + int decode_reply(int type) { --- a/authfd.h.old +++ b/authfd.h @@ -49,6 +49,9 @@ #define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 #define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 +/* keychain */ +#define SSH_AGENTC_ADD_FROM_KEYCHAIN 27 + #define SSH_AGENT_CONSTRAIN_LIFETIME 1 #define SSH_AGENT_CONSTRAIN_CONFIRM 2 --- a/config.h.in.old +++ b/config.h.in @@ -81,6 +81,18 @@ /* FreeBSD strnvis argument order is swapped compared to OpenBSD */ #undef BROKEN_STRNVIS +/* platform uses an in-memory credentials cache */ +#undef USE_CCAPI + +/* platform has a Security Authorization Session API */ +#undef USE_SECURITY_SESSION_API + +/* Define to 1 if you have the `copyfile' function. */ +#undef HAVE_COPYFILE + +/* Define to 1 if you have the header file. */ +#undef HAVE_COPYFILE_H + /* tcgetattr with ICANON may hang */ #undef BROKEN_TCGETATTR_ICANON --- a/configure.ac.old +++ b/configure.ac @@ -4766,10 +4766,40 @@ #endif ]) +dnl Keychain support +AC_ARG_WITH(keychain, + [ --with-keychain=apple Use Mac OS X Keychain], + [ + case "$withval" in + apple|no) + KEYCHAIN=$withval + ;; + *) + AC_MSG_ERROR(invalid keychain type: $withval) + ;; + esac + ] +) +if test ! -z "$KEYCHAIN" -a "$KEYCHAIN" != "no"; then + case "$KEYCHAIN" in + apple) + AC_CHECK_HEADERS(Security/Security.h, [ + CPPFLAGS="$CPPFLAGS -D__APPLE_KEYCHAIN__" + KEYCHAIN_LDFLAGS="-framework Security -framework CoreFoundation" + AC_SUBST(KEYCHAIN_LDFLAGS) + ], + AC_MSG_WARN([Security framework not found. Disabling Mac OS X Keychain support.])) + ;; + esac +fi + dnl Adding -Werror to CFLAGS early prevents configure tests from running. dnl Add now. CFLAGS="$CFLAGS $werror_flags" +AC_CHECK_FUNCS(copyfile) +AC_CHECK_HEADERS(copyfile.h) + if test "x$ac_cv_func_getaddrinfo" != "xyes" ; then TEST_SSH_IPV6=no else --- a/groupaccess.c.old +++ b/groupaccess.c @@ -34,38 +34,67 @@ #include #include +#ifdef __APPLE_MEMBERSHIP__ +#include +#endif + #include "xmalloc.h" #include "groupaccess.h" #include "match.h" #include "log.h" +#ifdef __APPLE_MEMBERSHIP__ +// SPI for 5235093 +int32_t getgrouplist_2(const char *, gid_t, gid_t **); +int32_t getgroupcount(const char *, gid_t); +#endif + static int ngroups; static char **groups_byname; +#ifdef __APPLE_MEMBERSHIP__ +uuid_t u_uuid; +#endif /* * Initialize group access list for user with primary (base) and * supplementary groups. Return the number of groups in the list. */ int -ga_init(const char *user, gid_t base) +ga_init(struct passwd *pw) { - gid_t *groups_bygid; + gid_t *groups_bygid = NULL; int i, j; struct group *gr; +#ifdef __APPLE_MEMBERSHIP__ + if (0 != mbr_uid_to_uuid(pw->pw_uid, u_uuid)) + return 0; +#endif + if (ngroups > 0) ga_free(); +#ifndef __APPLE_MEMBERSHIP__ ngroups = NGROUPS_MAX; #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) ngroups = MAX(NGROUPS_MAX, sysconf(_SC_NGROUPS_MAX)); -#endif - +#endif groups_bygid = xcalloc(ngroups, sizeof(*groups_bygid)); +#else + if (-1 == (ngroups = getgrouplist_2(pw->pw_name, pw->pw_gid, + &groups_bygid))) { + logit("getgrouplist_2 failed"); + return 0; + } +#endif groups_byname = xcalloc(ngroups, sizeof(*groups_byname)); - - if (getgrouplist(user, base, groups_bygid, &ngroups) == -1) - logit("getgrouplist: groups list too small"); +#ifndef __APPLE_MEMBERSHIP__ + if (getgrouplist(pw->pw_name, pw->pw_gid, groups_bygid, &ngroups) == -1) { + logit("getgrouplist: groups list too small"); + free(groups_bygid); + return 0; + } +#endif for (i = 0, j = 0; i < ngroups; i++) if ((gr = getgrgid(groups_bygid[i])) != NULL) groups_byname[j++] = xstrdup(gr->gr_name); @@ -76,16 +105,32 @@ /* * Return 1 if one of user's groups is contained in groups. * Return 0 otherwise. Use match_pattern() for string comparison. + * Use mbr_check_membership() for membership checking on Mac OS X. */ int ga_match(char * const *groups, int n) { +#ifdef __APPLE_MEMBERSHIP__ + int i, ismember = 0; + uuid_t g_uuid; + struct group *grp; + + for (i = 0; i < n; i++) { + if ((grp = getgrnam(groups[i])) == NULL || + (mbr_gid_to_uuid(grp->gr_gid, g_uuid) != 0) || + (mbr_check_membership(u_uuid, g_uuid, &ismember) != 0)) + return 0; + if (ismember) + return 1; + } +#else int i, j; for (i = 0; i < ngroups; i++) for (j = 0; j < n; j++) if (match_pattern(groups_byname[i], groups[j])) return 1; +#endif return 0; } --- a/groupaccess.h.old +++ b/groupaccess.h @@ -27,7 +27,7 @@ #ifndef GROUPACCESS_H #define GROUPACCESS_H -int ga_init(const char *, gid_t); +int ga_init(struct passwd *); int ga_match(char * const *, int); int ga_match_pattern_list(const char *); void ga_free(void); --- a/keychain.c.old 1970-01-01 01:00:00.000000000 +0100 +++ b/keychain.c @@ -0,0 +1,694 @@ +/* + * Copyright (c) 2007 Apple Inc. All rights reserved. + * + * @APPLE_BSD_LICENSE_HEADER_START@ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @APPLE_BSD_LICENSE_HEADER_END@ + */ + +#include "includes.h" + +#include +#include + +#include "xmalloc.h" +#include "key.h" +#include "authfd.h" +#include "authfile.h" + +#if defined(__APPLE_KEYCHAIN__) + +#include +#include + +/* Our Security/SecPassword.h is not yet API, so I will define the constants that I am using here. */ +int kSecPasswordGet = 1<<0; // Get password from keychain or user +int kSecPasswordSet = 1<<1; // Set password (passed in if kSecPasswordGet not set, otherwise from user) +int kSecPasswordFail = 1<<2; // Wrong password (ignore item in keychain and flag error) +OSStatus SecGenericPasswordCreate(SecKeychainAttributeList *searchAttrList, SecKeychainAttributeList *itemAttrList, SecPasswordRef *itemRef); +OSStatus SecPasswordAction(SecPasswordRef itemRef, CFTypeRef message, UInt32 flags, UInt32 *length, const void **data); +OSStatus SecPasswordSetInitialAccess(SecPasswordRef itemRef, SecAccessRef accessRef); + +#endif + +/* + * Platform-specific helper functions. + */ + +#if defined(__APPLE_KEYCHAIN__) + +static int get_boolean_preference(const char *key, int default_value, + int foreground) +{ + int value = default_value; + CFStringRef keyRef = NULL; + CFPropertyListRef valueRef = NULL; + + keyRef = CFStringCreateWithCString(NULL, key, kCFStringEncodingUTF8); + if (keyRef != NULL) + valueRef = CFPreferencesCopyAppValue(keyRef, + CFSTR("org.openbsd.openssh")); + if (valueRef != NULL) + if (CFGetTypeID(valueRef) == CFBooleanGetTypeID()) + value = CFBooleanGetValue(valueRef); + else if (foreground) + fprintf(stderr, "Ignoring nonboolean %s preference.\n", key); + + if (keyRef) + CFRelease(keyRef); + if (valueRef) + CFRelease(valueRef); + + return value; +} + +#endif + +/* + * Store the passphrase for a given identity in the keychain. + */ +void +store_in_keychain(const char *filename, const char *passphrase) +{ + +#if defined(__APPLE_KEYCHAIN__) + + /* + * store_in_keychain + * Mac OS X implementation + */ + + CFStringRef cfstr_relative_filename = NULL; + CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL; + CFStringRef cfstr_filename = NULL; + CFDataRef cfdata_filename = NULL; + CFIndex filename_len; + UInt8 *label = NULL; + UInt8 *utf8_filename; + OSStatus rv; + SecKeychainItemRef itemRef = NULL; + SecTrustedApplicationRef apps[] = {NULL, NULL, NULL}; + CFArrayRef trustedlist = NULL; + SecAccessRef initialAccess = NULL; + + /* Bail out if KeychainIntegration preference is -bool NO */ + if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) { + fprintf(stderr, "Keychain integration is disabled.\n"); + goto err; + } + + /* Interpret filename with the correct encoding. */ + if ((cfstr_relative_filename = + CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL) + { + fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n"); + goto err; + } + if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL, + cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) { + fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n"); + goto err; + } + if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) == + NULL) { + fprintf(stderr, "CFURLCopyAbsoluteURL failed\n"); + goto err; + } + if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename, + kCFURLPOSIXPathStyle)) == NULL) { + fprintf(stderr, "CFURLCopyFileSystemPath failed\n"); + goto err; + } + if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL, + cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) { + fprintf(stderr, "CFStringCreateExternalRepresentation failed\n"); + goto err; + } + filename_len = CFDataGetLength(cfdata_filename); + if ((label = xmalloc(filename_len + 5)) == NULL) { + fprintf(stderr, "xmalloc failed\n"); + goto err; + } + memcpy(label, "SSH: ", 5); + utf8_filename = label + 5; + CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len), + utf8_filename); + + /* Check if we already have this passphrase. */ + rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len, + (char *)utf8_filename, NULL, NULL, &itemRef); + if (rv == errSecItemNotFound) { + /* Add a new keychain item. */ + SecKeychainAttribute attrs[] = { + {kSecLabelItemAttr, filename_len + 5, label}, + {kSecServiceItemAttr, 3, "SSH"}, + {kSecAccountItemAttr, filename_len, utf8_filename} + }; + SecKeychainAttributeList attrList = + {sizeof(attrs) / sizeof(attrs[0]), attrs}; + if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent", + &apps[0]) != noErr || + SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add", + &apps[1]) != noErr || + SecTrustedApplicationCreateFromPath("/usr/bin/ssh", + &apps[2]) != noErr) { + fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n"); + goto err; + } + if ((trustedlist = CFArrayCreate(NULL, (const void **)apps, + sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) == + NULL) { + fprintf(stderr, "CFArrayCreate failed\n"); + goto err; + } + if (SecAccessCreate(cfstr_filename, trustedlist, + &initialAccess) != noErr) { + fprintf(stderr, "SecAccessCreate failed\n"); + goto err; + } + if (SecKeychainItemCreateFromContent( + kSecGenericPasswordItemClass, &attrList, strlen(passphrase), + passphrase, NULL, initialAccess, NULL) == noErr) + fprintf(stderr, "Passphrase stored in keychain: %s\n", filename); + else + fprintf(stderr, "Could not create keychain item\n"); + } else if (rv == noErr) { + /* Update an existing keychain item. */ + if (SecKeychainItemModifyAttributesAndData(itemRef, NULL, + strlen(passphrase), passphrase) == noErr) + fprintf(stderr, "Passphrase updated in keychain: %s\n", filename); + else + fprintf(stderr, "Could not modify keychain item\n"); + } else + fprintf(stderr, "Could not access keychain\n"); + +err: /* Clean up. */ + if (cfstr_relative_filename) + CFRelease(cfstr_relative_filename); + if (cfurl_relative_filename) + CFRelease(cfurl_relative_filename); + if (cfurl_filename) + CFRelease(cfurl_filename); + if (cfstr_filename) + CFRelease(cfstr_filename); + if (cfdata_filename) + CFRelease(cfdata_filename); + if (label) + free(label); + if (itemRef) + CFRelease(itemRef); + if (apps[0]) + CFRelease(apps[0]); + if (apps[1]) + CFRelease(apps[1]); + if (apps[2]) + CFRelease(apps[2]); + if (trustedlist) + CFRelease(trustedlist); + if (initialAccess) + CFRelease(initialAccess); + +#else + + /* + * store_in_keychain + * no keychain implementation + */ + + fprintf(stderr, "Keychain is not available on this system\n"); + +#endif + +} + +/* + * Remove the passphrase for a given identity from the keychain. + */ +void +remove_from_keychain(const char *filename) +{ + +#if defined(__APPLE_KEYCHAIN__) + + /* + * remove_from_keychain + * Mac OS X implementation + */ + + CFStringRef cfstr_relative_filename = NULL; + CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL; + CFStringRef cfstr_filename = NULL; + CFDataRef cfdata_filename = NULL; + CFIndex filename_len; + const UInt8 *utf8_filename; + OSStatus rv; + SecKeychainItemRef itemRef = NULL; + + /* Bail out if KeychainIntegration preference is -bool NO */ + if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) { + fprintf(stderr, "Keychain integration is disabled.\n"); + goto err; + } + + /* Interpret filename with the correct encoding. */ + if ((cfstr_relative_filename = + CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL) + { + fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n"); + goto err; + } + if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL, + cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) { + fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n"); + goto err; + } + if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) == + NULL) { + fprintf(stderr, "CFURLCopyAbsoluteURL failed\n"); + goto err; + } + if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename, + kCFURLPOSIXPathStyle)) == NULL) { + fprintf(stderr, "CFURLCopyFileSystemPath failed\n"); + goto err; + } + if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL, + cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) { + fprintf(stderr, "CFStringCreateExternalRepresentation failed\n"); + goto err; + } + filename_len = CFDataGetLength(cfdata_filename); + utf8_filename = CFDataGetBytePtr(cfdata_filename); + + /* Check if we already have this passphrase. */ + rv = SecKeychainFindGenericPassword(NULL, 3, "SSH", filename_len, + (const char *)utf8_filename, NULL, NULL, &itemRef); + if (rv == noErr) { + /* Remove the passphrase from the keychain. */ + if (SecKeychainItemDelete(itemRef) == noErr) + fprintf(stderr, "Passphrase removed from keychain: %s\n", filename); + else + fprintf(stderr, "Could not remove keychain item\n"); + } else if (rv != errSecItemNotFound) + fprintf(stderr, "Could not access keychain\n"); + +err: /* Clean up. */ + if (cfstr_relative_filename) + CFRelease(cfstr_relative_filename); + if (cfurl_relative_filename) + CFRelease(cfurl_relative_filename); + if (cfurl_filename) + CFRelease(cfurl_filename); + if (cfstr_filename) + CFRelease(cfstr_filename); + if (cfdata_filename) + CFRelease(cfdata_filename); + if (itemRef) + CFRelease(itemRef); + +#else + + /* + * remove_from_keychain + * no keychain implementation + */ + + fprintf(stderr, "Keychain is not available on this system\n"); + +#endif + +} + +/* + * Add identities to ssh-agent using passphrases stored in the keychain. + * Returns zero on success and nonzero on failure. + * add_identity is a callback into ssh-agent. It takes a filename and a + * passphrase, and attempts to add the identity to the agent. It returns + * zero on success and nonzero on failure. + */ +int +add_identities_using_keychain(int (*add_identity)(const char *, const char *)) +{ + +#if defined(__APPLE_KEYCHAIN__) + + /* + * add_identities_using_keychain + * Mac OS X implementation + */ + + OSStatus rv; + SecKeychainSearchRef searchRef; + SecKeychainItemRef itemRef; + UInt32 length; + void *data; + CFIndex maxsize; + + /* Bail out if KeychainIntegration preference is -bool NO */ + if (get_boolean_preference("KeychainIntegration", 1, 0) == 0) + return 0; + + /* Search for SSH passphrases in the keychain */ + SecKeychainAttribute attrs[] = { + {kSecServiceItemAttr, 3, "SSH"} + }; + SecKeychainAttributeList attrList = + {sizeof(attrs) / sizeof(attrs[0]), attrs}; + if ((rv = SecKeychainSearchCreateFromAttributes(NULL, + kSecGenericPasswordItemClass, &attrList, &searchRef)) != noErr) + return 0; + + /* Iterate through the search results. */ + while ((rv = SecKeychainSearchCopyNext(searchRef, &itemRef)) == noErr) { + UInt32 tag = kSecAccountItemAttr; + UInt32 format = kSecFormatUnknown; + SecKeychainAttributeInfo info = {1, &tag, &format}; + SecKeychainAttributeList *itemAttrList = NULL; + CFStringRef cfstr_filename = NULL; + char *filename = NULL; + char *passphrase = NULL; + + /* Retrieve filename and passphrase. */ + if ((rv = SecKeychainItemCopyAttributesAndData(itemRef, &info, + NULL, &itemAttrList, &length, &data)) != noErr) + goto err; + if (itemAttrList->count != 1) + goto err; + cfstr_filename = CFStringCreateWithBytes(NULL, + itemAttrList->attr->data, itemAttrList->attr->length, + kCFStringEncodingUTF8, true); + maxsize = CFStringGetMaximumSizeOfFileSystemRepresentation( + cfstr_filename); + if ((filename = xmalloc(maxsize)) == NULL) + goto err; + if (CFStringGetFileSystemRepresentation(cfstr_filename, + filename, maxsize) == false) + goto err; + if ((passphrase = xmalloc(length + 1)) == NULL) + goto err; + memcpy(passphrase, data, length); + passphrase[length] = '\0'; + + /* Add the identity. */ + add_identity(filename, passphrase); + +err: /* Clean up. */ + if (itemRef) + CFRelease(itemRef); + if (cfstr_filename) + CFRelease(cfstr_filename); + if (filename) + free(filename); + if (passphrase) + free(passphrase); + if (itemAttrList) + SecKeychainItemFreeAttributesAndData(itemAttrList, + data); + } + + CFRelease(searchRef); + + return 0; + +#else + + /* + * add_identities_using_keychain + * no implementation + */ + + return 1; + +#endif + +} + +/* + * Prompt the user for a key's passphrase. The user will be offered the option + * of storing the passphrase in their keychain. Returns the passphrase + * (which the caller is responsible for freeing), or NULL if this function + * fails or is not implemented. If this function is not implemented, ssh will + * fall back on the standard read_passphrase function, and the user will need + * to use ssh-add -K to add their keys to the keychain. + */ +char * +keychain_read_passphrase(const char *filename, int oAskPassGUI) +{ + +#if defined(__APPLE_KEYCHAIN__) + + /* + * keychain_read_passphrase + * Mac OS X implementation + */ + + CFStringRef cfstr_relative_filename = NULL; + CFURLRef cfurl_relative_filename = NULL, cfurl_filename = NULL; + CFStringRef cfstr_filename = NULL; + CFDataRef cfdata_filename = NULL; + CFIndex filename_len; + UInt8 *label = NULL; + UInt8 *utf8_filename; + SecPasswordRef passRef = NULL; + SecTrustedApplicationRef apps[] = {NULL, NULL, NULL}; + CFArrayRef trustedlist = NULL; + SecAccessRef initialAccess = NULL; + CFURLRef path = NULL; + CFStringRef pathFinal = NULL; + CFURLRef bundle_url = NULL; + CFBundleRef bundle = NULL; + CFStringRef promptTemplate = NULL, prompt = NULL; + UInt32 length; + const void *data; + AuthenticationConnection *ac = NULL; + char *result = NULL; + + /* Bail out if KeychainIntegration preference is -bool NO */ + if (get_boolean_preference("KeychainIntegration", 1, 1) == 0) + goto err; + + /* Bail out if the user set AskPassGUI preference to -bool NO */ + if (get_boolean_preference("AskPassGUI", 1, 1) == 0 || oAskPassGUI == 0) + goto err; + + /* Bail out if we can't communicate with ssh-agent */ + if ((ac = ssh_get_authentication_connection()) == NULL) + goto err; + + /* Interpret filename with the correct encoding. */ + if ((cfstr_relative_filename = + CFStringCreateWithFileSystemRepresentation(NULL, filename)) == NULL) + { + fprintf(stderr, "CFStringCreateWithFileSystemRepresentation failed\n"); + goto err; + } + if ((cfurl_relative_filename = CFURLCreateWithFileSystemPath(NULL, + cfstr_relative_filename, kCFURLPOSIXPathStyle, false)) == NULL) { + fprintf(stderr, "CFURLCreateWithFileSystemPath failed\n"); + goto err; + } + if ((cfurl_filename = CFURLCopyAbsoluteURL(cfurl_relative_filename)) == + NULL) { + fprintf(stderr, "CFURLCopyAbsoluteURL failed\n"); + goto err; + } + if ((cfstr_filename = CFURLCopyFileSystemPath(cfurl_filename, + kCFURLPOSIXPathStyle)) == NULL) { + fprintf(stderr, "CFURLCopyFileSystemPath failed\n"); + goto err; + } + if ((cfdata_filename = CFStringCreateExternalRepresentation(NULL, + cfstr_filename, kCFStringEncodingUTF8, 0)) == NULL) { + fprintf(stderr, "CFStringCreateExternalRepresentation failed\n"); + goto err; + } + filename_len = CFDataGetLength(cfdata_filename); + if ((label = xmalloc(filename_len + 5)) == NULL) { + fprintf(stderr, "xmalloc failed\n"); + goto err; + } + memcpy(label, "SSH: ", 5); + utf8_filename = label + 5; + CFDataGetBytes(cfdata_filename, CFRangeMake(0, filename_len), + utf8_filename); + + /* Build a SecPasswordRef. */ + SecKeychainAttribute searchAttrs[] = { + {kSecServiceItemAttr, 3, "SSH"}, + {kSecAccountItemAttr, filename_len, utf8_filename} + }; + SecKeychainAttributeList searchAttrList = + {sizeof(searchAttrs) / sizeof(searchAttrs[0]), searchAttrs}; + SecKeychainAttribute attrs[] = { + {kSecLabelItemAttr, filename_len + 5, label}, + {kSecServiceItemAttr, 3, "SSH"}, + {kSecAccountItemAttr, filename_len, utf8_filename} + }; + SecKeychainAttributeList attrList = + {sizeof(attrs) / sizeof(attrs[0]), attrs}; + if (SecGenericPasswordCreate(&searchAttrList, &attrList, &passRef) != + noErr) { + fprintf(stderr, "SecGenericPasswordCreate failed\n"); + goto err; + } + if (SecTrustedApplicationCreateFromPath("/usr/bin/ssh-agent", &apps[0]) + != noErr || + SecTrustedApplicationCreateFromPath("/usr/bin/ssh-add", &apps[1]) + != noErr || + SecTrustedApplicationCreateFromPath("/usr/bin/ssh", &apps[2]) + != noErr) { + fprintf(stderr, "SecTrustedApplicationCreateFromPath failed\n"); + goto err; + } + if ((trustedlist = CFArrayCreate(NULL, (const void **)apps, + sizeof(apps) / sizeof(apps[0]), &kCFTypeArrayCallBacks)) == NULL) { + fprintf(stderr, "CFArrayCreate failed\n"); + goto err; + } + if (SecAccessCreate(cfstr_filename, trustedlist, &initialAccess) + != noErr) { + fprintf(stderr, "SecAccessCreate failed\n"); + goto err; + } + if (SecPasswordSetInitialAccess(passRef, initialAccess) != noErr) { + fprintf(stderr, "SecPasswordSetInitialAccess failed\n"); + goto err; + } + + /* Request the passphrase from the user. */ + if ((path = CFURLCreateFromFileSystemRepresentation(NULL, + (UInt8 *)filename, strlen(filename), false)) == NULL) { + fprintf(stderr, "CFURLCreateFromFileSystemRepresentation failed\n"); + goto err; + } + if ((pathFinal = CFURLCopyLastPathComponent(path)) == NULL) { + fprintf(stderr, "CFURLCopyLastPathComponent failed\n"); + goto err; + } + if (!((bundle_url = CFURLCreateWithFileSystemPath(NULL, + CFSTR("/System/Library/CoreServices/"), kCFURLPOSIXPathStyle, true)) + != NULL && (bundle = CFBundleCreate(NULL, bundle_url)) != NULL && + (promptTemplate = CFCopyLocalizedStringFromTableInBundle( + CFSTR("Enter your password for the SSH key \"%@\"."), + CFSTR("OpenSSH"), bundle, "Text of the dialog asking the user for" + "their passphrase. The %@ will be replaced with the filename of a" + "specific key.")) != NULL) && + (promptTemplate = CFStringCreateCopy(NULL, + CFSTR("Enter your password for the SSH key \"%@\"."))) == NULL) { + fprintf(stderr, "CFStringCreateCopy failed\n"); + goto err; + } + if ((prompt = CFStringCreateWithFormat(NULL, NULL, promptTemplate, + pathFinal)) == NULL) { + fprintf(stderr, "CFStringCreateWithFormat failed\n"); + goto err; + } + switch (SecPasswordAction(passRef, prompt, + kSecPasswordGet|kSecPasswordFail, &length, &data)) { + case noErr: + result = xmalloc(length + 1); + memcpy(result, data, length); + result[length] = '\0'; + + /* Save password in keychain if requested. */ + if (noErr != SecPasswordAction(passRef, CFSTR(""), kSecPasswordSet, &length, &data)) + fprintf(stderr, "Saving password to keychain failed\n"); + + /* Add password to agent. */ + char *comment = NULL; + Key *private = key_load_private(filename, result, &comment); + if (NULL == private) + break; + if (ssh_add_identity_constrained(ac, private, comment, 0, 0)) + fprintf(stderr, "Identity added: %s (%s)\n", filename, comment); + else + fprintf(stderr, "Could not add identity: %s\n", filename); + free(comment); + key_free(private); + break; + case errAuthorizationCanceled: + result = xmalloc(1); + *result = '\0'; + break; + default: + goto err; + } + +err: /* Clean up. */ + if (cfstr_relative_filename) + CFRelease(cfstr_relative_filename); + if (cfurl_relative_filename) + CFRelease(cfurl_relative_filename); + if (cfurl_filename) + CFRelease(cfurl_filename); + if (cfstr_filename) + CFRelease(cfstr_filename); + if (cfdata_filename) + CFRelease(cfdata_filename); + if (label) + free(label); + if (passRef) + CFRelease(passRef); + if (apps[0]) + CFRelease(apps[0]); + if (apps[1]) + CFRelease(apps[1]); + if (apps[2]) + CFRelease(apps[2]); + if (trustedlist) + CFRelease(trustedlist); + if (initialAccess) + CFRelease(initialAccess); + if (path) + CFRelease(path); + if (pathFinal) + CFRelease(pathFinal); + if (bundle_url) + CFRelease(bundle_url); + if (bundle) + CFRelease(bundle); + if (promptTemplate) + CFRelease(promptTemplate); + if (prompt) + CFRelease(prompt); + if (ac) + ssh_close_authentication_connection(ac); + + return result; + +#else + + /* + * keychain_read_passphrase + * no implementation + */ + + return NULL; + +#endif + +} --- a/keychain.h.old 1970-01-01 01:00:00.000000000 +0100 +++ b/keychain.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2007 Apple Inc. All rights reserved. + * + * @APPLE_BSD_LICENSE_HEADER_START@ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @APPLE_BSD_LICENSE_HEADER_END@ + */ + +/* + * KEYCHAIN indicates that keychain functionality is present. + * KEYCHAIN_* indicates the implementation to use, and implies KEYCHAIN. + */ +#if defined(__APPLE_KEYCHAIN__) +#define KEYCHAIN +#endif + +void store_in_keychain(const char *filename, const char *passphrase); +void remove_from_keychain(const char *filename); +int add_identities_using_keychain( + int (*add_identity)(const char *, const char *)); +char *keychain_read_passphrase(const char *filename, int oAskPassGUI); --- a/readconf.c.old +++ b/readconf.c @@ -150,6 +150,9 @@ oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, +#ifdef __APPLE_KEYCHAIN__ + oAskPassGUI, +#endif oStreamLocalBindMask, oStreamLocalBindUnlink, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -266,6 +269,9 @@ { "streamlocalbindmask", oStreamLocalBindMask }, { "streamlocalbindunlink", oStreamLocalBindUnlink }, { "ignoreunknown", oIgnoreUnknown }, +#ifdef __APPLE_KEYCHAIN__ + { "askpassgui", oAskPassGUI }, +#endif { NULL, oBadOption } }; @@ -1358,6 +1364,12 @@ charptr = &options->ignored_unknown; goto parse_string; +#ifdef __APPLE_KEYCHAIN__ + case oAskPassGUI: + intptr = &options->ask_pass_gui; + goto parse_flag; +#endif + case oProxyUseFdpass: intptr = &options->proxy_use_fdpass; goto parse_flag; @@ -1604,6 +1616,9 @@ options->request_tty = -1; options->proxy_use_fdpass = -1; options->ignored_unknown = NULL; +#ifdef __APPLE_KEYCHAIN__ + options->ask_pass_gui = -1; +#endif options->num_canonical_domains = 0; options->num_permitted_cnames = 0; options->canonicalize_max_dots = -1; @@ -1778,6 +1793,10 @@ options->ip_qos_bulk = IPTOS_THROUGHPUT; if (options->request_tty == -1) options->request_tty = REQUEST_TTY_AUTO; +#ifdef __APPLE_KEYCHAIN__ + if (options->ask_pass_gui == -1) + options->ask_pass_gui = 1; +#endif if (options->proxy_use_fdpass == -1) options->proxy_use_fdpass = 0; if (options->canonicalize_max_dots == -1) --- a/readconf.h.old +++ b/readconf.h @@ -145,6 +145,10 @@ struct allowed_cname permitted_cnames[MAX_CANON_DOMAINS]; char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ + +#ifdef __APPLE_KEYCHAIN__ + int ask_pass_gui; +#endif } Options; #define SSH_CANONICALISE_NO 0 --- a/scp.1.old +++ b/scp.1 @@ -19,7 +19,7 @@ .Sh SYNOPSIS .Nm scp .Bk -words -.Op Fl 12346BCpqrv +.Op Fl 12346BCEpqrv .Op Fl c Ar cipher .Op Fl F Ar ssh_config .Op Fl i Ar identity_file @@ -95,6 +95,8 @@ flag to .Xr ssh 1 to enable compression. +.It Fl E +Preserves extended attributes, resource forks, and ACLs. Requires both ends to be running Mac OS X 10.4 or later. .It Fl c Ar cipher Selects the cipher to use for encrypting the data transfer. This option is directly passed to --- a/scp.c.old +++ b/scp.c @@ -78,6 +78,9 @@ #ifdef HAVE_SYS_STAT_H # include #endif +#ifdef __APPLE_XSAN__ +#include +#endif #ifdef HAVE_POLL_H #include #else @@ -114,6 +117,11 @@ #include "misc.h" #include "progressmeter.h" +#ifdef HAVE_COPYFILE_H +#include +#include +#endif + extern char *__progname; #define COPY_BUFLEN 16384 @@ -150,6 +158,12 @@ /* This is used to store the pid of ssh_program */ pid_t do_cmd_pid = -1; +#ifdef HAVE_COPYFILE +int copy_xattr = 0; +int md_flag = 0; +#endif + + static void killchild(int signo) { @@ -395,7 +409,11 @@ addargs(&args, "-oClearAllForwardings=yes"); fflag = tflag = 0; +#if HAVE_COPYFILE + while ((ch = getopt(argc, argv, "dfl:prtvBCEc:i:P:q12346S:o:F:")) != -1) +#else while ((ch = getopt(argc, argv, "dfl:prtvBCc:i:P:q12346S:o:F:")) != -1) +#endif switch (ch) { /* User-visible flags. */ case '1': @@ -456,6 +474,11 @@ showprogress = 0; break; +#ifdef HAVE_COPYFILE + case 'E': + copy_xattr = 1; + break; +#endif /* Server options. */ case 'd': targetshouldbedirectory = 1; @@ -505,7 +528,12 @@ remin = remout = -1; do_cmd_pid = -1; /* Command to be executed on remote system using "ssh". */ +#if HAVE_COPYFILE + (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s%s", + copy_xattr ? " -E" : "", +#else (void) snprintf(cmd, sizeof cmd, "scp%s%s%s%s", +#endif verbose_mode ? " -v" : "", iamrecursive ? " -r" : "", pflag ? " -p" : "", targetshouldbedirectory ? " -d" : ""); @@ -751,6 +779,10 @@ int fd = -1, haderr, indx; char *last, *name, buf[2048], encname[MAXPATHLEN]; int len; +#if HAVE_COPYFILE + char md_name[MAXPATHLEN]; + char *md_tmp; +#endif for (indx = 0; indx < argc; ++indx) { name = argv[indx]; @@ -758,12 +790,26 @@ len = strlen(name); while (len > 1 && name[len-1] == '/') name[--len] = '\0'; +#if HAVE_COPYFILE +md_next: + statbytes = 0; + if (md_flag) { + fd = open(md_tmp, O_RDONLY, 0); + unlink(md_tmp); + free(md_tmp); + if (fd < 0) + goto syserr; + } else { +#endif if ((fd = open(name, O_RDONLY|O_NONBLOCK, 0)) < 0) goto syserr; if (strchr(name, '\n') != NULL) { strnvis(encname, name, sizeof(encname), VIS_NL); name = encname; } +#if HAVE_COPYFILE + } +#endif if (fstat(fd, &stb) < 0) { syserr: run_err("%s: %s", name, strerror(errno)); goto next; @@ -850,6 +896,36 @@ else run_err("%s: %s", name, strerror(haderr)); (void) response(); +#ifdef HAVE_COPYFILE + if (copy_xattr && md_flag == 0) + { + if (!copyfile(name, NULL, 0, + COPYFILE_ACL | COPYFILE_XATTR | COPYFILE_CHECK)) + continue; + + /* + * this file will hold the actual metadata + * to be transferred + */ + md_tmp = strdup("/tmp/scp.md.XXXXXX"); + md_tmp = mktemp(md_tmp); + + if(copyfile(name, md_tmp, 0, + COPYFILE_ACL | COPYFILE_XATTR | COPYFILE_PACK) == 0) + { + /* + * this is the fake name to display + */ + snprintf(md_name, sizeof md_name, "%s/._%s", dirname(name), basename(name)); + name = md_name; + md_flag = 1; + if (verbose_mode) + fprintf(stderr, "copyfile(%s, %s, PACK)\n", name, md_tmp); + goto md_next; + } + } else + md_flag = 0; +#endif } } @@ -941,6 +1017,10 @@ if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode)) targisdir = 1; for (first = 1;; first = 0) { +#if HAVE_COPYFILE + char md_src[MAXPATHLEN]; + char md_dst[MAXPATHLEN]; +#endif cp = buf; if (atomicio(read, remin, cp, 1) != 1) return; @@ -1086,10 +1166,51 @@ } omode = mode; mode |= S_IWUSR; + +#if HAVE_COPYFILE + if (copy_xattr && !strncmp(basename(curfile), "._", 2)) + { + int mdfd; + if (targisdir) + { + snprintf(md_src, sizeof md_src, "%s.XXXXXX", np); + snprintf(md_dst, sizeof md_dst, "%s/%s", + dirname(np), basename(np) + 2); + if((mdfd = mkstemp(md_src)) < 0) + continue; + } + else + { + snprintf(md_src, sizeof md_src, "%s/._%s.XXXXXX", + dirname(np), basename(np)); + snprintf(md_dst, sizeof md_dst, "%s", np); + if((mdfd = mkstemp(md_src)) < 0) + continue; + } + if (mdfd >= 0) + close(mdfd); + np = md_src; + } +#endif if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) { bad: run_err("%s: %s", np, strerror(errno)); continue; } +#ifdef __APPLE_XSAN__ + { + /* + * Pre-allocate blocks for the destination file. + */ + fstore_t fst; + + fst.fst_flags = 0; + fst.fst_posmode = F_PEOFPOSMODE; + fst.fst_offset = 0; + fst.fst_length = size; + + (void) fcntl(ofd, F_PREALLOCATE, &fst); + } +#endif /* __APPLE_XSAN__ */ (void) atomicio(vwrite, remout, "", 1); if ((bp = allocbuf(&buffer, ofd, COPY_BUFLEN)) == NULL) { (void) close(ofd); @@ -1174,6 +1295,29 @@ wrerrno = errno; } (void) response(); +#ifdef HAVE_COPYFILE + if (copy_xattr && strncmp(basename(np), "._", 2) == 0) + { + if (verbose_mode) + fprintf(stderr, "copyfile(%s, %s, UNPACK)\n", md_src, md_dst); + if(!copyfile(md_src, md_dst, 0, + COPYFILE_ACL | COPYFILE_XATTR | COPYFILE_UNPACK) < 0) + { + snprintf(md_dst, sizeof md_dst, "%s/._%s", + dirname(md_dst), basename(md_dst)); + rename(md_src, md_dst); + } else + unlink(md_src); + if (setimes && wrerr == NO) { + setimes = 0; + if (utimes(md_dst, tv) < 0) { + run_err("%s: set times: %s", + np, strerror(errno)); + wrerr = DISPLAYED; + } + } + } else +#endif if (setimes && wrerr == NO) { setimes = 0; if (utimes(np, tv) < 0) { @@ -1235,7 +1379,11 @@ usage(void) { (void) fprintf(stderr, +#if HAVE_COPYFILE + "usage: scp [-12346BCEpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" +#else "usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]\n" +#endif " [-l limit] [-o ssh_option] [-P port] [-S program]\n" " [[user@]host1:]file1 ... [[user@]host2:]file2\n"); exit(1); --- a/servconf.c.old +++ b/servconf.c @@ -253,7 +253,7 @@ if (options->gss_cleanup_creds == -1) options->gss_cleanup_creds = 1; if (options->password_authentication == -1) - options->password_authentication = 1; + options->password_authentication = 0; if (options->kbd_interactive_authentication == -1) options->kbd_interactive_authentication = 0; if (options->challenge_response_authentication == -1) @@ -639,7 +639,7 @@ if ((pw = getpwnam(user)) == NULL) { debug("Can't match group at line %d because user %.100s does " "not exist", line, user); - } else if (ga_init(pw->pw_name, pw->pw_gid) == 0) { + } else if (ga_init(pw) == 0) { debug("Can't Match group because user %.100s not in any group " "at line %d", user, line); } else if (ga_match_pattern_list(grps) != 1) { --- a/session.c.old +++ b/session.c @@ -2113,8 +2113,10 @@ n_bytes = packet_remaining(); tty_parse_modes(s->ttyfd, &n_bytes); +#ifndef __APPLE_PRIVPTY__ if (!use_privsep) pty_setowner(s->pw, s->tty); +#endif /* Set window size from the packet. */ pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel); @@ -2354,9 +2356,11 @@ if (s->pid != 0) record_logout(s->pid, s->tty, s->pw->pw_name); +#ifndef __APPLE_PRIVPTY__ /* Release the pseudo-tty. */ if (getuid() == 0) pty_release(s->tty); +#endif /* * Close the server side of the socket pairs. We must do this after --- a/ssh-add.0.old +++ b/ssh-add.0 @@ -4,7 +4,7 @@ ssh-add - adds private key identities to the authentication agent SYNOPSIS - ssh-add [-cDdkLlXx] [-t life] [file ...] + ssh-add [-cDdkKLlXx] [-t life] [file ...] ssh-add -s pkcs11 ssh-add -e pkcs11 @@ -55,6 +55,13 @@ -l Lists fingerprints of all identities currently represented by the agent. + -m Add identities to the agent using any passphrases stored in your + Mac OS X keychain. + + -M When adding identities, each passphrase will also be stored in + your Mac OS X keychain. When removing identities with -d, each + passphrase will be removed from your Mac OS X keychain. + -s pkcs11 Add keys provided by the PKCS#11 shared library pkcs11. --- a/ssh-add.1.old +++ b/ssh-add.1 @@ -43,7 +43,7 @@ .Nd adds private key identities to the authentication agent .Sh SYNOPSIS .Nm ssh-add -.Op Fl cDdkLlXx +.Op Fl cDdkLlMmXx .Op Fl t Ar life .Op Ar .Nm ssh-add @@ -119,6 +119,13 @@ by the agent. .It Fl l Lists fingerprints of all identities currently represented by the agent. +.It Fl m +Add identities to the agent using any passphrases stored in your Mac OS +X keychain. +.It Fl M +When adding identities, each passphrase will also be stored in your Mac OS +Xkeychain. When removing identities with -d, each passphrase will be removed +from your Mac OS X keychain. .It Fl s Ar pkcs11 Add keys provided by the PKCS#11 shared library .Ar pkcs11 . --- a/ssh-add.c.old +++ b/ssh-add.c @@ -63,6 +63,7 @@ #include "pathnames.h" #include "misc.h" #include "ssherr.h" +#include "keychain.h" /* argv0 */ extern char *__progname; @@ -98,12 +99,24 @@ } static int -delete_file(AuthenticationConnection *ac, const char *filename, int key_only) +add_from_keychain(AuthenticationConnection *ac) +{ + if (ssh_add_from_keychain(ac) == 0) + return -1; + + fprintf(stderr, "Added keychain identities.\n"); + return 0; +} + +static int +delete_file(AuthenticationConnection *ac, int keychain, const char *filename, int key_only) { Key *public = NULL, *cert = NULL; char *certpath = NULL, *comment = NULL; int ret = -1; + if (keychain) + remove_from_keychain(filename); public = key_load_public(filename, &comment); if (public == NULL) { printf("Bad key file %s\n", filename); @@ -166,7 +179,7 @@ } static int -add_file(AuthenticationConnection *ac, const char *filename, int key_only) +add_file(AuthenticationConnection *ac, int keychain, const char *filename, int key_only) { Key *private, *cert; char *comment = NULL; @@ -205,12 +218,16 @@ if ((r = sshkey_parse_private_fileblob(&keyblob, "", filename, &private, &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) fatal("Cannot parse %s: %s", filename, ssh_err(r)); + if (keychain && private != NULL) + store_in_keychain(filename, ""); /* try last */ if (private == NULL && pass != NULL) { if ((r = sshkey_parse_private_fileblob(&keyblob, pass, filename, &private, &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) fatal("Cannot parse %s: %s", filename, ssh_err(r)); + if (keychain && private != NULL) + store_in_keychain(filename, pass); } if (comment == NULL) comment = xstrdup(filename); @@ -232,8 +249,11 @@ r != SSH_ERR_KEY_WRONG_PASSPHRASE) fatal("Cannot parse %s: %s", filename, ssh_err(r)); - if (private != NULL) + if (private != NULL) { + if (keychain) + store_in_keychain(filename, pass); break; + } clear_pass(); snprintf(msg, sizeof msg, "Bad passphrase, try again for %.200s: ", comment); @@ -390,13 +410,13 @@ } static int -do_file(AuthenticationConnection *ac, int deleting, int key_only, char *file) +do_file(AuthenticationConnection *ac, int deleting, int keychain, int key_only, char *file) { if (deleting) { - if (delete_file(ac, file, key_only) == -1) + if (delete_file(ac, keychain, file, key_only) == -1) return -1; } else { - if (add_file(ac, file, key_only) == -1) + if (add_file(ac, keychain, file, key_only) == -1) return -1; } return 0; @@ -418,6 +438,11 @@ fprintf(stderr, " -X Unlock agent.\n"); fprintf(stderr, " -s pkcs11 Add keys from PKCS#11 provider.\n"); fprintf(stderr, " -e pkcs11 Remove keys provided by PKCS#11 provider.\n"); +#ifdef KEYCHAIN + fprintf(stderr, " -m Add all identities stored in your Mac OS X keychain.\n"); + fprintf(stderr, " -M Store passphrases in your Mac OS X keychain.\n"); + fprintf(stderr, " With -d, remove passphrases from your Mac OS X keychain.\n"); +#endif } int @@ -428,6 +453,7 @@ AuthenticationConnection *ac = NULL; char *pkcs11provider = NULL; int i, ch, deleting = 0, ret = 0, key_only = 0; + int keychain = 0; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); @@ -446,7 +472,7 @@ "Could not open a connection to your authentication agent.\n"); exit(2); } - while ((ch = getopt(argc, argv, "klLcdDxXe:s:t:")) != -1) { + while ((ch = getopt(argc, argv, "kKlLcdDxXmMe:s:t:")) != -1) { switch (ch) { case 'k': key_only = 1; @@ -485,6 +511,13 @@ goto done; } break; + case 'm': + if (add_from_keychain(ac) == -1) + ret = 1; + goto done; + case 'M': + keychain = 1; + break; default: usage(); ret = 1; @@ -516,7 +549,7 @@ default_files[i]); if (stat(buf, &st) < 0) continue; - if (do_file(ac, deleting, key_only, buf) == -1) + if (do_file(ac, deleting, keychain, key_only, buf) == -1) ret = 1; else count++; @@ -525,7 +558,7 @@ ret = 1; } else { for (i = 0; i < argc; i++) { - if (do_file(ac, deleting, key_only, argv[i]) == -1) + if (do_file(ac, deleting, keychain, key_only, argv[i]) == -1) ret = 1; } } --- a/ssh-agent.c.old +++ b/ssh-agent.c @@ -66,6 +66,9 @@ #include #include #include +#ifdef __APPLE_LAUNCHD__ +#include +#endif #include "xmalloc.h" #include "ssh.h" @@ -73,10 +76,12 @@ #include "buffer.h" #include "key.h" #include "authfd.h" +#include "authfile.h" #include "compat.h" #include "log.h" #include "misc.h" #include "digest.h" +#include "keychain.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" @@ -701,6 +706,61 @@ } #endif /* ENABLE_PKCS11 */ +static int +add_identity_callback(const char *filename, const char *passphrase) +{ + Key *k; + int version; + Idtab *tab; + + if ((k = key_load_private(filename, passphrase, NULL)) == NULL) + return 1; + switch (k->type) { + case KEY_RSA: + case KEY_RSA1: + if (RSA_blinding_on(k->rsa, NULL) != 1) { + key_free(k); + return 1; + } + break; + } + version = k->type == KEY_RSA1 ? 1 : 2; + tab = idtab_lookup(version); + if (lookup_identity(k, version) == NULL) { + Identity *id = xmalloc(sizeof(Identity)); + id->key = k; + id->comment = xstrdup(filename); + if (id->comment == NULL) { + key_free(k); + return 1; + } + id->death = 0; + id->confirm = 0; + TAILQ_INSERT_TAIL(&tab->idlist, id, next); + tab->nentries++; + } else { + key_free(k); + return 1; + } + + return 0; +} + +static void +process_add_from_keychain(SocketEntry *e) +{ + int result; + + result = add_identities_using_keychain(&add_identity_callback); + + /* e will be NULL when ssh-agent adds keys on its own at startup */ + if (e) { + buffer_put_int(&e->output, 1); + buffer_put_char(&e->output, + result ? SSH_AGENT_FAILURE : SSH_AGENT_SUCCESS); + } +} + /* dispatch incoming messages */ static void @@ -795,6 +855,9 @@ process_remove_smartcard_key(e); break; #endif /* ENABLE_PKCS11 */ + case SSH_AGENTC_ADD_FROM_KEYCHAIN: + process_add_from_keychain(e); + break; default: /* Unknown message. Respond with failure. */ error("Unknown message %d", type); @@ -1034,7 +1097,11 @@ int main(int ac, char **av) { +#ifdef __APPLE_LAUNCHD__ + int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0, l_flag = 0; +#else int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0; +#endif int sock, fd, ch, result, saved_errno; u_int nalloc; char *shell, *format, *pidstr, *agentsocket = NULL; @@ -1069,7 +1136,11 @@ __progname = ssh_get_progname(av[0]); seed_rng(); +#ifdef __APPLE_LAUNCHD__ + while ((ch = getopt(ac, av, "cdklsa:t:")) != -1) { +#else while ((ch = getopt(ac, av, "cdksa:t:")) != -1) { +#endif switch (ch) { case 'c': if (s_flag) @@ -1079,6 +1150,11 @@ case 'k': k_flag++; break; +#ifdef __APPLE_LAUNCHD__ + case 'l': + l_flag++; + break; +#endif case 's': if (c_flag) usage(); @@ -1105,7 +1181,11 @@ ac -= optind; av += optind; +#ifdef __APPPLE_LAUNCHD__ + if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || l_flag)) +#else if (ac > 0 && (c_flag || k_flag || s_flag || d_flag)) +#endif usage(); if (ac == 0 && !c_flag && !s_flag) { @@ -1161,6 +1241,53 @@ * Create socket early so it will exist before command gets run from * the parent. */ +#ifdef __APPLE_LAUNCHD__ + if (l_flag) { + launch_data_t resp, msg, tmp; + size_t listeners_i; + + msg = launch_data_new_string(LAUNCH_KEY_CHECKIN); + + resp = launch_msg(msg); + + if (NULL == resp) { + perror("launch_msg"); + exit(1); + } + launch_data_free(msg); + switch (launch_data_get_type(resp)) { + case LAUNCH_DATA_ERRNO: + errno = launch_data_get_errno(resp); + perror("launch_msg response"); + exit(1); + case LAUNCH_DATA_DICTIONARY: + break; + default: + fprintf(stderr, "launch_msg unknown response"); + exit(1); + } + tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS); + + if (NULL == tmp) { + fprintf(stderr, "no sockets\n"); + exit(1); + } + + tmp = launch_data_dict_lookup(tmp, "Listeners"); + + if (NULL == tmp) { + fprintf(stderr, "no known listeners\n"); + exit(1); + } + + for (listeners_i = 0; listeners_i < launch_data_array_get_count(tmp); listeners_i++) { + launch_data_t obj_at_ind = launch_data_array_get_index(tmp, listeners_i); + new_socket(AUTH_SOCKET, launch_data_get_fd(obj_at_ind)); + } + + launch_data_free(resp); + } else { +#endif prev_mask = umask(0177); sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0); if (sock < 0) { @@ -1169,6 +1296,14 @@ cleanup_exit(1); } umask(prev_mask); +#ifdef __APPLE_LAUNCHD__ + } +#endif + +#ifdef __APPLE_LAUNCHD__ + if (l_flag) + goto skip2; +#endif /* * Fork, and have the parent execute the command, if any, or present @@ -1243,6 +1378,7 @@ pkcs11_init(0); #endif new_socket(AUTH_SOCKET, sock); +skip2: if (ac > 0) parent_alive_interval = 10; idtab_init(); @@ -1252,6 +1388,10 @@ signal(SIGTERM, cleanup_handler); nalloc = 0; +#ifdef KEYCHAIN + process_add_from_keychain(NULL); +#endif + while (1) { prepare_select(&readsetp, &writesetp, &max_fd, &nalloc, &tvp); result = select(max_fd + 1, readsetp, writesetp, NULL, tvp); --- a/ssh-keysign.8.old +++ b/ssh-keysign.8 @@ -72,6 +72,9 @@ Since they are readable only by root, .Nm must be set-uid root if host-based authentication is used. +Note that +.Nm +is not set-uid by default on Mac OS X. .Pp .It Pa /etc/ssh/ssh_host_dsa_key-cert.pub .It Pa /etc/ssh/ssh_host_ecdsa_key-cert.pub --- a/sshconnect1.c.old +++ b/sshconnect1.c @@ -47,6 +47,7 @@ #include "hostfile.h" #include "auth.h" #include "digest.h" +#include "keychain.h" /* Session id for the current session. */ u_char session_id[16]; @@ -262,6 +263,10 @@ snprintf(buf, sizeof(buf), "Enter passphrase for RSA key '%.100s': ", comment); for (i = 0; i < options.number_of_password_prompts; i++) { +#ifdef __APPLE_KEYCHAIN__ + passphrase = keychain_read_passphrase(comment, options.ask_pass_gui); + if (passphrase == NULL) +#endif passphrase = read_passphrase(buf, 0); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_RSA1, --- a/sshconnect2.c.old +++ b/sshconnect2.c @@ -70,6 +70,7 @@ #include "pathnames.h" #include "uidswap.h" #include "hostfile.h" +#include "keychain.h" #ifdef GSSAPI #include "ssh-gss.h" @@ -1122,6 +1123,10 @@ snprintf(prompt, sizeof prompt, "Enter passphrase for key '%.100s': ", filename); for (i = 0; i < options.number_of_password_prompts; i++) { +#ifdef __APPLE_KEYCHAIN__ + passphrase = keychain_read_passphrase(filename, options.ask_pass_gui); + if (passphrase == NULL) +#endif passphrase = read_passphrase(prompt, 0); if (strcmp(passphrase, "") != 0) { private = key_load_private_type(KEY_UNSPEC, --- a/sshd.0.old +++ b/sshd.0 @@ -621,8 +621,7 @@ SEE ALSO scp(1), sftp(1), ssh(1), ssh-add(1), ssh-agent(1), ssh-keygen(1), - ssh-keyscan(1), chroot(2), login.conf(5), moduli(5), sshd_config(5), - inetd(8), sftp-server(8) + ssh-keyscan(1), chroot(2), sshd_config(5), sftp-server(8) AUTHORS OpenSSH is a derivative of the original and free ssh 1.2.12 release by --- a/sshd.8.old +++ b/sshd.8 @@ -954,10 +954,7 @@ .Xr ssh-keygen 1 , .Xr ssh-keyscan 1 , .Xr chroot 2 , -.Xr login.conf 5 , -.Xr moduli 5 , .Xr sshd_config 5 , -.Xr inetd 8 , .Xr sftp-server 8 .Sh AUTHORS OpenSSH is a derivative of the original and free --- a/sshd.c.old +++ b/sshd.c @@ -2144,6 +2144,12 @@ audit_event(SSH_AUTH_SUCCESS); #endif +#ifdef USE_PAM + if (options.use_pam) { + do_pam_setcred(1); + do_pam_session(); + } +#endif #ifdef GSSAPI if (options.gss_authentication) { temporarily_use_uid(authctxt->pw); @@ -2151,12 +2157,6 @@ restore_uid(); } #endif -#ifdef USE_PAM - if (options.use_pam) { - do_pam_setcred(1); - do_pam_session(); - } -#endif /* * In privilege separation, we fork another child and prepare --- a/sshd_config.old +++ b/sshd_config @@ -35,7 +35,7 @@ # Logging # obsoletes QuietMode and FascistLogging -#SyslogFacility AUTH +SyslogFacility AUTHPRIV #LogLevel INFO # Authentication: @@ -68,8 +68,9 @@ # Don't read the user's ~/.rhosts and ~/.shosts files #IgnoreRhosts yes -# To disable tunneled clear text passwords, change to no here! -#PasswordAuthentication yes +# To disable tunneled clear text passwords, change to no here! Also, +# remember to set the UsePAM setting to 'no'. +#PasswordAuthentication no #PermitEmptyPasswords no # Change to no to disable s/key passwords @@ -94,7 +95,10 @@ # If you just want the PAM account and session checks to run without # PAM authentication, then enable this but set PasswordAuthentication # and ChallengeResponseAuthentication to 'no'. -#UsePAM no +# Also, PAM will deny null passwords by default. If you need to allow +# null passwords, add the " nullok" option to the end of the +# securityserver.so line in /etc/pam.d/sshd. +#UsePAM yes #AllowAgentForwarding yes #AllowTcpForwarding yes --- a/sshd_config.0.old +++ b/sshd_config.0 @@ -571,7 +571,7 @@ PasswordAuthentication Specifies whether password authentication is allowed. The - default is ``yes''. + default is ``no''. PermitEmptyPasswords When password authentication is allowed, it specifies whether the @@ -802,7 +802,7 @@ either PasswordAuthentication or ChallengeResponseAuthentication. If UsePAM is enabled, you will not be able to run sshd(8) as a - non-root user. The default is ``no''. + non-root user. The default is ``yes''. UsePrivilegeSeparation Specifies whether sshd(8) separates privileges by creating an --- a/sshd_config.5.old +++ b/sshd_config.5 @@ -977,7 +977,7 @@ .It Cm PasswordAuthentication Specifies whether password authentication is allowed. The default is -.Dq yes . +.Dq no . .It Cm PermitEmptyPasswords When password authentication is allowed, it specifies whether the server allows login to accounts with empty password strings. @@ -1343,7 +1343,7 @@ .Xr sshd 8 as a non-root user. The default is -.Dq no . +.Dq yes . .It Cm UsePrivilegeSeparation Specifies whether .Xr sshd 8