Created
February 13, 2014 03:38
-
-
Save joebarker87/8969346 to your computer and use it in GitHub Desktop.
OSX Keychain patches to apply after HPN patches on openssh-6.3p1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Makefile.in b/Makefile.in | |
index 8fded74..5279aba 100644 | |
--- a/Makefile.in | |
+++ b/Makefile.in | |
@@ -58,6 +58,7 @@ SED=@SED@ | |
ENT=@ENT@ | |
XAUTH_PATH=@XAUTH_PATH@ | |
LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@ | |
+KEYCHAIN_LDFLAGS=@KEYCHAIN_LDFLAGS@ | |
EXEEXT=@EXEEXT@ | |
MANFMT=@MANFMT@ | |
@@ -95,6 +96,8 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ | |
sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ | |
sandbox-seccomp-filter.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@ | |
@@ -129,6 +132,7 @@ all: $(CONFIGFILES) $(MANPAGES) $(TARGETS) | |
$(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 $< | |
@@ -142,8 +146,8 @@ libssh.a: $(LIBSSH_OBJS) | |
$(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) | |
@@ -151,11 +155,11 @@ sshd$(EXEEXT): libssh.a $(LIBCOMPAT) $(SSHDOBJS) | |
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) | |
@@ -267,7 +271,7 @@ install-files: | |
$(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) | |
diff --git a/audit-bsm.c b/audit-bsm.c | |
index 6135591..4711b25 100644 | |
--- a/audit-bsm.c | |
+++ b/audit-bsm.c | |
@@ -263,7 +263,12 @@ bsm_audit_record(int typ, char *string, au_event_t event_no) | |
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; | |
} | |
diff --git a/auth-pam.c b/auth-pam.c | |
index d51318b..b74476c 100644 | |
--- a/auth-pam.c | |
+++ b/auth-pam.c | |
@@ -791,10 +791,11 @@ sshpam_query(void *ctx, char **name, char **info, | |
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; | |
diff --git a/auth.c b/auth.c | |
index 9a36f1d..1ea002c 100644 | |
--- a/auth.c | |
+++ b/auth.c | |
@@ -211,7 +211,7 @@ allowed_user(struct passwd * pw) | |
} | |
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; | |
diff --git a/authfd.c b/authfd.c | |
index 775786b..48a580a 100644 | |
--- a/authfd.c | |
+++ b/authfd.c | |
@@ -689,6 +689,29 @@ ssh_remove_all_identities(AuthenticationConnection *auth, int version) | |
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) | |
{ | |
diff --git a/authfd.h b/authfd.h | |
index 2582a27..7b786fe 100644 | |
--- a/authfd.h | |
+++ 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 | |
diff --git a/config.h.in b/config.h.in | |
index b75e501..02be253 100644 | |
--- a/config.h.in | |
+++ b/config.h.in | |
@@ -77,6 +77,18 @@ | |
/* FreeBSD strnvis does not do what we need */ | |
#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 <copyfile.h> header file. */ | |
+#undef HAVE_COPYFILE_H | |
+ | |
/* tcgetattr with ICANON may hang */ | |
#undef BROKEN_TCGETATTR_ICANON | |
diff --git a/configure.ac b/configure.ac | |
index 4a1b503..a1ef284 100644 | |
--- a/configure.ac | |
+++ b/configure.ac | |
@@ -4550,10 +4550,40 @@ AC_CHECK_MEMBER([struct utmp.ut_line], [], [ | |
#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 | |
diff --git a/groupaccess.c b/groupaccess.c | |
index 1eab10b..7934d9d 100644 | |
--- a/groupaccess.c | |
+++ b/groupaccess.c | |
@@ -34,38 +34,67 @@ | |
#include <stdlib.h> | |
#include <string.h> | |
+#ifdef __APPLE_MEMBERSHIP__ | |
+#include <membership.h> | |
+#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 @@ ga_init(const char *user, gid_t base) | |
/* | |
* 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; | |
} | |
diff --git a/groupaccess.h b/groupaccess.h | |
index 000578e..ddea117 100644 | |
--- a/groupaccess.h | |
+++ 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); | |
diff --git a/keychain.c b/keychain.c | |
new file mode 100644 | |
index 0000000..5c2d1ff | |
--- /dev/null | |
+++ 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 <stdio.h> | |
+#include <string.h> | |
+ | |
+#include "xmalloc.h" | |
+#include "key.h" | |
+#include "authfd.h" | |
+#include "authfile.h" | |
+ | |
+#if defined(__APPLE_KEYCHAIN__) | |
+ | |
+#include <CoreFoundation/CoreFoundation.h> | |
+#include <Security/Security.h> | |
+ | |
+/* 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 | |
+ | |
+} | |
diff --git a/keychain.h b/keychain.h | |
new file mode 100644 | |
index 0000000..3ab1a6b | |
--- /dev/null | |
+++ 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); | |
diff --git a/readconf.c b/readconf.c | |
index 27c1a5e..bd9f2ec 100644 | |
--- a/readconf.c | |
+++ b/readconf.c | |
@@ -137,9 +137,13 @@ typedef enum { | |
oHashKnownHosts, | |
oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, | |
oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication, | |
- oKexAlgorithms, oIPQoS, oRequestTTY, oTcpRcvBufPoll, oTcpRcvBuf, | |
- oHPNDisabled, oHPNBufferSize, oNoneSwitch, oNoneEnabled, | |
- oIgnoreUnknown, oIgnoredUnknownOption, oDeprecated, oUnsupported | |
+ oKexAlgorithms, oIPQoS, oRequestTTY, oTcpRcvBufPoll, oTcpRcvBuf, | |
+ oHPNDisabled, oHPNBufferSize, oNoneSwitch, oNoneEnabled, | |
+ oIgnoreUnknown, | |
+#ifdef __APPLE_KEYCHAIN__ | |
+ oAskPassGUI, | |
+#endif | |
+ oIgnoredUnknownOption, oDeprecated, oUnsupported | |
} OpCodes; | |
/* Textual representations of the tokens. */ | |
@@ -199,7 +203,7 @@ static struct { | |
{ "globalknownhostsfile", oGlobalKnownHostsFile }, | |
{ "globalknownhostsfile2", oDeprecated }, | |
{ "userknownhostsfile", oUserKnownHostsFile }, | |
- { "userknownhostsfile2", oDeprecated }, | |
+ { "userknownhostsfile2", oDeprecated }, | |
{ "connectionattempts", oConnectionAttempts }, | |
{ "batchmode", oBatchMode }, | |
{ "checkhostip", oCheckHostIP }, | |
@@ -257,7 +261,9 @@ static struct { | |
{ "hpndisabled", oHPNDisabled }, | |
{ "hpnbuffersize", oHPNBufferSize }, | |
{ "ignoreunknown", oIgnoreUnknown }, | |
- | |
+#ifdef __APPLE_KEYCHAIN__ | |
+ { "askpassgui", oAskPassGUI }, | |
+#endif | |
{ NULL, oBadOption } | |
}; | |
@@ -466,7 +472,7 @@ parse_flag: | |
case oForwardX11Trusted: | |
intptr = &options->forward_x11_trusted; | |
goto parse_flag; | |
- | |
+ | |
case oForwardX11Timeout: | |
intptr = &options->forward_x11_timeout; | |
goto parse_time; | |
@@ -550,7 +556,7 @@ parse_flag: | |
case oNoneEnabled: | |
intptr = &options->none_enabled; | |
goto parse_flag; | |
- | |
+ | |
/* we check to see if the command comes from the */ | |
/* command line or not. If it does then enable it */ | |
/* otherwise fail. NONE should never be a default configuration */ | |
@@ -1113,6 +1119,12 @@ parse_int: | |
charptr = &options->ignored_unknown; | |
goto parse_string; | |
+#ifdef __APPLE_KEYCHAIN__ | |
+ case oAskPassGUI: | |
+ intptr = &options->ask_pass_gui; | |
+ goto parse_flag; | |
+#endif | |
+ | |
case oDeprecated: | |
debug("%s line %d: Deprecated option \"%s\"", | |
filename, linenum, keyword); | |
@@ -1281,6 +1293,9 @@ initialize_options(Options * options) | |
options->tcp_rcv_buf_poll = -1; | |
options->tcp_rcv_buf = -1; | |
options->ignored_unknown = NULL; | |
+#ifdef __APPLE_KEYCHAIN__ | |
+ options->ask_pass_gui = -1; | |
+#endif | |
} | |
/* | |
@@ -1458,6 +1473,10 @@ fill_default_options(Options * options) | |
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 | |
/* options->local_command should not be set by default */ | |
/* options->proxy_command should not be set by default */ | |
/* options->user will be set in the main program if appropriate */ | |
diff --git a/readconf.h b/readconf.h | |
index 23c3cbb..a8a3310 100644 | |
--- a/readconf.h | |
+++ b/readconf.h | |
@@ -145,6 +145,10 @@ typedef struct { | |
int request_tty; | |
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ | |
+ | |
+#ifdef __APPLE_KEYCHAIN__ | |
+ int ask_pass_gui; | |
+#endif | |
} Options; | |
#define SSHCTL_MASTER_NO 0 | |
diff --git a/scp.1 b/scp.1 | |
index c83012c..1346041 100644 | |
--- a/scp.1 | |
+++ 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 | |
@@ -97,6 +97,8 @@ Passes the | |
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 | |
diff --git a/scp.c b/scp.c | |
index 21f22a6..132d89a 100644 | |
--- a/scp.c | |
+++ b/scp.c | |
@@ -78,6 +78,9 @@ | |
#ifdef HAVE_SYS_STAT_H | |
# include <sys/stat.h> | |
#endif | |
+#ifdef __APPLE_XSAN__ | |
+#include <sys/mount.h> | |
+#endif | |
#ifdef HAVE_POLL_H | |
#include <poll.h> | |
#else | |
@@ -114,6 +117,11 @@ | |
#include "misc.h" | |
#include "progressmeter.h" | |
+#ifdef HAVE_COPYFILE_H | |
+#include <libgen.h> | |
+#include <copyfile.h> | |
+#endif | |
+ | |
extern char *__progname; | |
#define COPY_BUFLEN 16384 | |
@@ -150,6 +158,12 @@ char *ssh_program = _PATH_SSH_PROGRAM; | |
/* 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 @@ main(int argc, char **argv) | |
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 @@ main(int argc, char **argv) | |
showprogress = 0; | |
break; | |
+#ifdef HAVE_COPYFILE | |
+ case 'E': | |
+ copy_xattr = 1; | |
+ break; | |
+#endif | |
/* Server options. */ | |
case 'd': | |
targetshouldbedirectory = 1; | |
@@ -505,7 +528,12 @@ main(int argc, char **argv) | |
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,19 +779,36 @@ source(int argc, char **argv) | |
int fd = -1, haderr, indx; | |
char *last, *name, buf[16384], encname[MAXPATHLEN]; | |
int len; | |
- | |
+#if HAVE_COPYFILE | |
+ char md_name[MAXPATHLEN]; | |
+ char *md_tmp; | |
+#endif | |
for (indx = 0; indx < argc; ++indx) { | |
name = argv[indx]; | |
statbytes = 0; | |
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; | |
@@ -846,6 +891,36 @@ next: if (fd != -1) { | |
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 | |
} | |
} | |
@@ -937,6 +1012,10 @@ sink(int argc, char **argv) | |
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; | |
@@ -1082,10 +1161,51 @@ sink(int argc, char **argv) | |
} | |
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); | |
@@ -1170,6 +1290,29 @@ bad: run_err("%s: %s", np, strerror(errno)); | |
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) { | |
@@ -1231,7 +1374,11 @@ void | |
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); | |
diff --git a/servconf.c b/servconf.c | |
index 798deef..4d0c8c9 100644 | |
--- a/servconf.c | |
+++ b/servconf.c | |
@@ -169,7 +169,7 @@ fill_default_server_options(ServerOptions *options) | |
/* Portable-specific options */ | |
if (options->use_pam == -1) | |
- options->use_pam = 0; | |
+ options->use_pam = 1; | |
/* Standard Options */ | |
if (options->protocol == SSH_PROTO_UNKNOWN) | |
@@ -252,7 +252,7 @@ fill_default_server_options(ServerOptions *options) | |
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) | |
@@ -301,9 +301,9 @@ fill_default_server_options(ServerOptions *options) | |
options->permit_tun = SSH_TUNMODE_NO; | |
if (options->zero_knowledge_password_authentication == -1) | |
options->zero_knowledge_password_authentication = 0; | |
- if (options->none_enabled == -1) | |
+ if (options->none_enabled == -1) | |
options->none_enabled = 0; | |
- if (options->hpn_disabled == -1) | |
+ if (options->hpn_disabled == -1) | |
options->hpn_disabled = 0; | |
if (options->hpn_buffer_size == -1) { | |
@@ -316,13 +316,13 @@ fill_default_server_options(ServerOptions *options) | |
/*create a socket but don't connect it */ | |
/* we use that the get the rcv socket size */ | |
sock = socket(AF_INET, SOCK_STREAM, 0); | |
- getsockopt(sock, SOL_SOCKET, SO_RCVBUF, | |
+ getsockopt(sock, SOL_SOCKET, SO_RCVBUF, | |
&socksize, &socksizelen); | |
close(sock); | |
options->hpn_buffer_size = socksize; | |
debug ("HPN Buffer Size: %d", options->hpn_buffer_size); | |
- | |
- } | |
+ | |
+ } | |
} else { | |
/* we have to do this incase the user sets both values in a contradictory */ | |
/* manner. hpn_disabled overrrides hpn_buffer_size*/ | |
@@ -677,7 +677,7 @@ match_cfg_line_group(const char *grps, int line, const char *user) | |
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) { | |
diff --git a/session.c b/session.c | |
index 2316821..d062b7d 100644 | |
--- a/session.c | |
+++ b/session.c | |
@@ -2084,8 +2084,10 @@ session_pty_req(Session *s) | |
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); | |
@@ -2331,9 +2333,11 @@ session_pty_cleanup2(Session *s) | |
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 | |
diff --git a/ssh-add.0 b/ssh-add.0 | |
index bcd1e73..551acd5 100644 | |
--- a/ssh-add.0 | |
+++ b/ssh-add.0 | |
@@ -4,7 +4,7 @@ NAME | |
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 @@ DESCRIPTION | |
-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. | |
diff --git a/ssh-add.1 b/ssh-add.1 | |
index 44846b6..058a4cb 100644 | |
--- a/ssh-add.1 | |
+++ 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 | |
@@ -118,6 +118,13 @@ Lists public key parameters of all identities currently represented | |
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 . | |
diff --git a/ssh-add.c b/ssh-add.c | |
index 5e8166f..7620730 100644 | |
--- a/ssh-add.c | |
+++ b/ssh-add.c | |
@@ -62,6 +62,7 @@ | |
#include "authfile.h" | |
#include "pathnames.h" | |
#include "misc.h" | |
+#include "keychain.h" | |
/* argv0 */ | |
extern char *__progname; | |
@@ -96,12 +97,24 @@ clear_pass(void) | |
} | |
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); | |
@@ -164,7 +177,7 @@ delete_all(AuthenticationConnection *ac) | |
} | |
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; | |
@@ -201,11 +214,16 @@ add_file(AuthenticationConnection *ac, const char *filename, int key_only) | |
/* At first, try empty passphrase */ | |
private = key_parse_private(&keyblob, filename, "", &comment); | |
+ if (keychain && private != NULL) | |
+ store_in_keychain(filename, ""); | |
if (comment == NULL) | |
comment = xstrdup(filename); | |
/* try last */ | |
- if (private == NULL && pass != NULL) | |
+ if (private == NULL && pass != NULL) { | |
private = key_parse_private(&keyblob, filename, pass, NULL); | |
+ if (keychain && private != NULL) | |
+ store_in_keychain(filename, pass); | |
+ } | |
if (private == NULL) { | |
/* clear passphrase since it did not work */ | |
clear_pass(); | |
@@ -221,8 +239,11 @@ add_file(AuthenticationConnection *ac, const char *filename, int key_only) | |
} | |
private = key_parse_private(&keyblob, filename, pass, | |
&comment); | |
- 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); | |
@@ -258,7 +279,7 @@ add_file(AuthenticationConnection *ac, const char *filename, int key_only) | |
certpath, filename); | |
key_free(cert); | |
goto out; | |
- } | |
+ } | |
/* Graft with private bits */ | |
if (key_to_certified(private, key_cert_is_legacy(cert)) != 0) { | |
@@ -376,13 +397,13 @@ lock_agent(AuthenticationConnection *ac, int lock) | |
} | |
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; | |
@@ -404,6 +425,11 @@ usage(void) | |
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 | |
@@ -414,6 +440,7 @@ main(int argc, char **argv) | |
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(); | |
@@ -430,7 +457,7 @@ main(int argc, char **argv) | |
"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; | |
@@ -469,6 +496,13 @@ main(int argc, char **argv) | |
goto done; | |
} | |
break; | |
+ case 'm': | |
+ if (add_from_keychain(ac) == -1) | |
+ ret = 1; | |
+ goto done; | |
+ case 'M': | |
+ keychain = 1; | |
+ break; | |
default: | |
usage(); | |
ret = 1; | |
@@ -500,7 +534,7 @@ main(int argc, char **argv) | |
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++; | |
@@ -509,7 +543,7 @@ main(int argc, char **argv) | |
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; | |
} | |
} | |
diff --git a/ssh-agent.c b/ssh-agent.c | |
index c3b1172..0e76e1f 100644 | |
--- a/ssh-agent.c | |
+++ b/ssh-agent.c | |
@@ -65,6 +65,9 @@ | |
#include <time.h> | |
#include <string.h> | |
#include <unistd.h> | |
+#ifdef __APPLE_LAUNCHD__ | |
+#include <launch.h> | |
+#endif | |
#include "xmalloc.h" | |
#include "ssh.h" | |
@@ -72,9 +75,11 @@ | |
#include "buffer.h" | |
#include "key.h" | |
#include "authfd.h" | |
+#include "authfile.h" | |
#include "compat.h" | |
#include "log.h" | |
#include "misc.h" | |
+#include "keychain.h" | |
#ifdef ENABLE_PKCS11 | |
#include "ssh-pkcs11.h" | |
@@ -790,6 +795,61 @@ process_remove_smartcard_key(SocketEntry *e) | |
} | |
#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 | |
@@ -882,6 +942,9 @@ process_message(SocketEntry *e) | |
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); | |
@@ -1122,7 +1185,11 @@ usage(void) | |
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; | |
@@ -1156,7 +1223,11 @@ main(int ac, char **av) | |
__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) | |
@@ -1166,6 +1237,11 @@ main(int ac, char **av) | |
case 'k': | |
k_flag++; | |
break; | |
+#ifdef __APPLE_LAUNCHD__ | |
+ case 'l': | |
+ l_flag++; | |
+ break; | |
+#endif | |
case 's': | |
if (c_flag) | |
usage(); | |
@@ -1192,7 +1268,11 @@ main(int ac, char **av) | |
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) { | |
@@ -1248,6 +1328,53 @@ main(int ac, char **av) | |
* 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 | |
sock = socket(AF_UNIX, SOCK_STREAM, 0); | |
if (sock < 0) { | |
perror("socket"); | |
@@ -1269,6 +1396,14 @@ main(int ac, char **av) | |
perror("listen"); | |
cleanup_exit(1); | |
} | |
+#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 | |
@@ -1341,6 +1476,7 @@ skip: | |
pkcs11_init(0); | |
#endif | |
new_socket(AUTH_SOCKET, sock); | |
+skip2: | |
if (ac > 0) | |
parent_alive_interval = 10; | |
idtab_init(); | |
@@ -1350,6 +1486,10 @@ skip: | |
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); | |
diff --git a/ssh-keysign.8 b/ssh-keysign.8 | |
index 5e0b2d2..f6a41f9 100644 | |
--- a/ssh-keysign.8 | |
+++ b/ssh-keysign.8 | |
@@ -71,6 +71,9 @@ accessible to others. | |
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 | |
diff --git a/sshconnect1.c b/sshconnect1.c | |
index d285e23..3bb5dcf 100644 | |
--- a/sshconnect1.c | |
+++ b/sshconnect1.c | |
@@ -47,6 +47,7 @@ | |
#include "canohost.h" | |
#include "hostfile.h" | |
#include "auth.h" | |
+#include "keychain.h" | |
/* Session id for the current session. */ | |
u_char session_id[16]; | |
@@ -260,6 +261,10 @@ try_rsa_authentication(int idx) | |
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, | |
diff --git a/sshconnect2.c b/sshconnect2.c | |
index c4adfb3..f1de09b 100644 | |
--- a/sshconnect2.c | |
+++ b/sshconnect2.c | |
@@ -72,6 +72,7 @@ | |
#include "hostfile.h" | |
#include "schnorr.h" | |
#include "jpake.h" | |
+#include "keychain.h" | |
#ifdef GSSAPI | |
#include "ssh-gss.h" | |
@@ -1378,6 +1379,10 @@ load_identity_file(char *filename, int userprovided) | |
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, | |
diff --git a/sshd.0 b/sshd.0 | |
index c48b987..6c6ac5d 100644 | |
--- a/sshd.0 | |
+++ b/sshd.0 | |
@@ -622,8 +622,8 @@ FILES | |
SEE ALSO | |
scp(1), sftp(1), ssh(1), ssh-add(1), ssh-agent(1), ssh-keygen(1), | |
- ssh-keyscan(1), chroot(2), hosts_access(5), login.conf(5), moduli(5), | |
- sshd_config(5), inetd(8), sftp-server(8) | |
+ ssh-keyscan(1), chroot(2), hosts_access(5), sshd_config(5) | |
+ sftp-server(8) | |
AUTHORS | |
OpenSSH is a derivative of the original and free ssh 1.2.12 release by | |
diff --git a/sshd.8 b/sshd.8 | |
index b0c7ab6..17148d7 100644 | |
--- a/sshd.8 | |
+++ b/sshd.8 | |
@@ -956,10 +956,7 @@ The content of this file is not sensitive; it can be world-readable. | |
.Xr ssh-keyscan 1 , | |
.Xr chroot 2 , | |
.Xr hosts_access 5 , | |
-.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 | |
diff --git a/sshd.c b/sshd.c | |
index eec1093..3481f98 100644 | |
--- a/sshd.c | |
+++ b/sshd.c | |
@@ -2121,6 +2121,12 @@ main(int ac, char **av) | |
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); | |
@@ -2128,12 +2134,6 @@ main(int ac, char **av) | |
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 | |
diff --git a/sshd_config b/sshd_config | |
index 7f10c11..2f91d86 100644 | |
--- a/sshd_config | |
+++ b/sshd_config | |
@@ -34,7 +34,7 @@ | |
# Logging | |
# obsoletes QuietMode and FascistLogging | |
-#SyslogFacility AUTH | |
+SyslogFacility AUTHPRIV | |
#LogLevel INFO | |
# Authentication: | |
@@ -67,8 +67,9 @@ AuthorizedKeysFile .ssh/authorized_keys | |
# 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 | |
@@ -93,7 +94,10 @@ AuthorizedKeysFile .ssh/authorized_keys | |
# 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 | |
diff --git a/sshd_config.0 b/sshd_config.0 | |
index 5f1df7b..ec78a1f 100644 | |
--- a/sshd_config.0 | |
+++ b/sshd_config.0 | |
@@ -507,7 +507,7 @@ DESCRIPTION | |
PasswordAuthentication | |
Specifies whether password authentication is allowed. The | |
- default is ``yes''. | |
+ default is ``no''. | |
PermitEmptyPasswords | |
When password authentication is allowed, it specifies whether the | |
@@ -709,7 +709,7 @@ DESCRIPTION | |
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 | |
diff --git a/sshd_config.5 b/sshd_config.5 | |
index 3abac6c..d0d4d45 100644 | |
--- a/sshd_config.5 | |
+++ b/sshd_config.5 | |
@@ -856,7 +856,7 @@ are refused if the number of unauthenticated connections reaches | |
.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. | |
@@ -1183,7 +1183,7 @@ is enabled, you will not be able to run | |
.Xr sshd 8 | |
as a non-root user. | |
The default is | |
-.Dq no . | |
+.Dq yes . | |
.It Cm UsePrivilegeSeparation | |
Specifies whether | |
.Xr sshd 8 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, I'm the guy who originally created the keychain patch and added the --with-keychain-support option to this recipe. I stumbled across an entry about the HPN patches on your blog (quite accidentally, during a fairly unrelated google search). First off, sorry if it was a pain to patch what I patched, I never really considered this might happen. Second, great work! You should consider doing a pull request on the openssh/dupes official repo.
You sound, uh, very busy, being a computational physicist and all, so if you are not particularly interested in spending the time, I'd happily see about merging your changes into the latest revision and do a pull request. I will only do so with your permission though. If given, I would fork it from your github and be very clear as to the author and who did all the hard work, and that I was simply doing some minor house-keeping type stuff. Of course, if you want to do it yourself, I highly encourage you too as well! I think a lot of people would appreciate those HPN performance upgrades! :)