diff --git a/doc/xml/release.xml b/doc/xml/release.xml
index 9c0145cd9..e228900e4 100644
--- a/doc/xml/release.xml
+++ b/doc/xml/release.xml
@@ -28,6 +28,17 @@
Multi-stanza check command.
+
+
+
+
+
+
+
+
+ Accept leading tilde in paths for SFTP public/private keys.
+
+
diff --git a/src/common/user.c b/src/common/user.c
index e10030e4e..3051787e9 100644
--- a/src/common/user.c
+++ b/src/common/user.c
@@ -24,6 +24,9 @@ static struct
gid_t groupId; // Real group id of the calling process from getgid()
const String *groupName; // Group name if it exists
+#ifdef HAVE_LIBSSH2
+ const String *userHome; // User home directory
+#endif // HAVE_LIBSSH2
} userLocalData;
/**********************************************************************************************************************************/
@@ -40,6 +43,9 @@ userInitInternal(void)
userLocalData.userId = getuid();
userLocalData.userName = userNameFromId(userLocalData.userId);
+#ifdef HAVE_LIBSSH2
+ userLocalData.userHome = userHomeFromId(userLocalData.userId);
+#endif // HAVE_LIBSSH2
userLocalData.userRoot = userLocalData.userId == 0;
userLocalData.groupId = getgid();
@@ -114,6 +120,35 @@ groupNameFromId(gid_t groupId)
FUNCTION_TEST_RETURN(STRING, NULL);
}
+/**********************************************************************************************************************************/
+// Currently userHome() and userHomeFromId() are only used when building with libssh2
+#ifdef HAVE_LIBSSH2
+
+FN_EXTERN const String *
+userHome(void)
+{
+ FUNCTION_TEST_VOID();
+ FUNCTION_TEST_RETURN_CONST(STRING, userLocalData.userHome);
+}
+
+/**********************************************************************************************************************************/
+FN_EXTERN String *
+userHomeFromId(uid_t userId)
+{
+ FUNCTION_TEST_BEGIN();
+ FUNCTION_TEST_PARAM(UINT, userId);
+ FUNCTION_TEST_END();
+
+ struct passwd *userData = getpwuid(userId);
+
+ if (userData != NULL)
+ FUNCTION_TEST_RETURN(STRING, strNewZ(userData->pw_dir));
+
+ FUNCTION_TEST_RETURN(STRING, NULL);
+}
+
+#endif // HAVE_LIBSSH2
+
/**********************************************************************************************************************************/
FN_EXTERN uid_t
userId(void)
diff --git a/src/common/user.h b/src/common/user.h
index 11248668b..61dfb85e5 100644
--- a/src/common/user.h
+++ b/src/common/user.h
@@ -26,6 +26,16 @@ FN_EXTERN const String *groupName(void);
// Get the group name from a group id. Returns NULL if the group id is invalid or there is no mapping.
FN_EXTERN String *groupNameFromId(gid_t groupId);
+#ifdef HAVE_LIBSSH2
+
+// Get the home directory of the current user. Returns NULL if there is no mapping.
+FN_EXTERN const String *userHome(void);
+
+// Get the user home directory from a user id. Returns NULL if the user id is invalid or there is no mapping.
+FN_EXTERN String *userHomeFromId(uid_t userId);
+
+#endif // HAVE_LIBSSH2
+
// Get the id of the current user
FN_EXTERN uid_t userId(void);
diff --git a/src/storage/sftp/storage.c b/src/storage/sftp/storage.c
index 99610a03d..876540fd4 100644
--- a/src/storage/sftp/storage.c
+++ b/src/storage/sftp/storage.c
@@ -10,6 +10,7 @@ SFTP Storage
#include "common/io/fd.h"
#include "common/io/socket/client.h"
#include "common/log.h"
+#include "common/regExp.h"
#include "common/user.h"
#include "storage/sftp/read.h"
#include "storage/sftp/storage.intern.h"
@@ -306,6 +307,26 @@ storageSftpInfo(THIS_VOID, const String *const file, const StorageInfoLevel leve
FUNCTION_LOG_RETURN(STORAGE_INFO, result);
}
+/**********************************************************************************************************************************/
+static String *
+storageSftpExpandTildePath(const String *const tildePath)
+{
+ FUNCTION_TEST_BEGIN();
+ FUNCTION_TEST_PARAM(STRING, tildePath);
+ FUNCTION_TEST_END();
+
+ String *const result = strNew();
+
+ // Append to user home directory path substring after the tilde
+ MEM_CONTEXT_TEMP_BEGIN()
+ {
+ strCatFmt(result, "%s%s", strZ(userHome()), strZ(strSub(tildePath, (size_t)strChr(tildePath, '~') + 1)));
+ }
+ MEM_CONTEXT_TEMP_END();
+
+ FUNCTION_TEST_RETURN(STRING, result);
+}
+
/**********************************************************************************************************************************/
// Helper function to get info for a file if it exists. This logic can't live directly in storageSftpList() because there is a race
// condition where a file might exist while listing the directory but it is gone before stat() can be called. In order to get
@@ -877,13 +898,22 @@ storageSftpNew(
}
}
+ // Perform public key authorization, expand leading tilde key file paths if needed
+ String *const privKeyPath = regExpMatchOne(STRDEF("^ *~"), keyPriv) ? storageSftpExpandTildePath(keyPriv) : strDup(keyPriv);
+ String *const pubKeyPath =
+ param.keyPub != NULL && regExpMatchOne(STRDEF("^ *~"), param.keyPub) ?
+ storageSftpExpandTildePath(param.keyPub) : strDup(param.keyPub);
+
do
{
rc = libssh2_userauth_publickey_fromfile(
- this->session, strZ(user), strZNull(param.keyPub), strZ(keyPriv), strZNull(param.keyPassphrase));
+ this->session, strZ(user), strZNull(pubKeyPath), strZ(privKeyPath), strZNull(param.keyPassphrase));
}
while (storageSftpWaitFd(this, rc));
+ strFree(privKeyPath);
+ strFree(pubKeyPath);
+
if (rc != 0)
{
if (rc == LIBSSH2_ERROR_EAGAIN)
diff --git a/test/src/module/common/userTest.c b/test/src/module/common/userTest.c
index 6b450cc56..b7c90f244 100644
--- a/test/src/module/common/userTest.c
+++ b/test/src/module/common/userTest.c
@@ -30,6 +30,10 @@ testRun(void)
TEST_RESULT_UINT(groupIdFromName(STRDEF("bogus")), (uid_t)-1, "get bogus group id");
TEST_RESULT_STR(groupName(), TEST_GROUP_STR, "check name name");
TEST_RESULT_STR_Z(groupNameFromId(77777), NULL, "invalid group name by id");
+
+ TEST_RESULT_STR(userHome(), STRDEF("/home/" TEST_USER), "check user name");
+ TEST_RESULT_STR_Z(userHomeFromId(userId()), "/home/" TEST_USER, "user home by id");
+ TEST_RESULT_STR_Z(userHomeFromId(77777), NULL, "invalid user home by id");
}
FUNCTION_HARNESS_RETURN_VOID();
diff --git a/test/src/module/storage/sftpTest.c b/test/src/module/storage/sftpTest.c
index 03f02380a..da94cf7b1 100644
--- a/test/src/module/storage/sftpTest.c
+++ b/test/src/module/storage/sftpTest.c
@@ -192,7 +192,7 @@ testRun(void)
ServiceError, "requested ssh2 hostkey hash type (aes-256-cbc) not available");
// -------------------------------------------------------------------------------------------------------------------------
- TEST_TITLE("public key from file auth failure");
+ TEST_TITLE("public key from file auth failure leading - tilde key paths");
hrnLibSsh2ScriptSet((HrnLibSsh2 [])
{
@@ -212,7 +212,8 @@ testRun(void)
TEST_ERROR(
storageSftpNewP(
- TEST_PATH_STR, STRDEF("localhost"), 22, TEST_USER_STR, 1000, KEYPRIV, hashTypeSha1, .keyPub = KEYPUB,
+ TEST_PATH_STR, STRDEF("localhost"), 22, TEST_USER_STR, 1000, STRDEF("~/.ssh/id_rsa"), hashTypeSha1,
+ .keyPub = STRDEF("~/.ssh/id_rsa.pub"),
.hostFingerprint = STRDEF("3132333435363738393039383736353433323130")),
ServiceError,
"public key authentication failed: libssh2 error [-16]\n"
@@ -4577,8 +4578,8 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptRepoType, "sftp");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHost, "localhost");
hrnCfgArgRawZ(argList, cfgOptRepoSftpHostKeyHashType, "sha1");
- hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, KEYPRIV_CSTR);
- hrnCfgArgRawZ(argList, cfgOptRepoSftpPublicKeyFile, KEYPUB_CSTR);
+ hrnCfgArgRawZ(argList, cfgOptRepoSftpPrivateKeyFile, " ~/.ssh/id_rsa");
+ hrnCfgArgRawZ(argList, cfgOptRepoSftpPublicKeyFile, " ~/.ssh/id_rsa.pub");
HRN_CFG_LOAD(cfgCmdArchiveGet, argList);
const Storage *storage = NULL;