Browse Source
Signed-off-by: Aidan Leuck <aidan_leuck@selinc.com> Tested-by: Dehan Meng <demeng@redhat.com> Reviewed-by: Konstantin Kostiuk <kkostiuk@redhat.com> Link: https://lore.kernel.org/r/20240424144029.30665-3-aidan_leuck@selinc.com Signed-off-by: Konstantin Kostiuk <kkostiuk@redhat.com>pull/266/head
committed by
Konstantin Kostiuk
4 changed files with 749 additions and 11 deletions
@ -0,0 +1,712 @@ |
|||
/*
|
|||
* QEMU Guest Agent win32-specific command implementations for SSH keys. |
|||
* The implementation is opinionated and expects the SSH implementation to |
|||
* be OpenSSH. |
|||
* |
|||
* Copyright Schweitzer Engineering Laboratories. 2024 |
|||
* |
|||
* Authors: |
|||
* Aidan Leuck <aidan_leuck@selinc.com> |
|||
* |
|||
* This work is licensed under the terms of the GNU GPL, version 2 or later. |
|||
* See the COPYING file in the top-level directory. |
|||
*/ |
|||
|
|||
#include "qemu/osdep.h" |
|||
#include <aclapi.h> |
|||
#include <qga-qapi-types.h> |
|||
|
|||
#include "commands-common-ssh.h" |
|||
#include "commands-windows-ssh.h" |
|||
#include "guest-agent-core.h" |
|||
#include "limits.h" |
|||
#include "lmaccess.h" |
|||
#include "lmapibuf.h" |
|||
#include "lmerr.h" |
|||
#include "qapi/error.h" |
|||
|
|||
#include "qga-qapi-commands.h" |
|||
#include "sddl.h" |
|||
#include "shlobj.h" |
|||
#include "userenv.h" |
|||
|
|||
#define AUTHORIZED_KEY_FILE "authorized_keys" |
|||
#define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys" |
|||
#define LOCAL_SYSTEM_SID "S-1-5-18" |
|||
#define ADMIN_SID "S-1-5-32-544" |
|||
|
|||
/*
|
|||
* Frees userInfo structure. This implements the g_auto cleanup |
|||
* for the structure. |
|||
*/ |
|||
void free_userInfo(PWindowsUserInfo info) |
|||
{ |
|||
g_free(info->sshDirectory); |
|||
g_free(info->authorizedKeyFile); |
|||
LocalFree(info->SSID); |
|||
g_free(info->username); |
|||
g_free(info); |
|||
} |
|||
|
|||
/*
|
|||
* Gets the admin SSH folder for OpenSSH. OpenSSH does not store |
|||
* the authorized_key file in the users home directory for security reasons and |
|||
* instead stores it at %PROGRAMDATA%/ssh. This function returns the path to |
|||
* that directory on the users machine |
|||
* |
|||
* parameters: |
|||
* errp -> error structure to set when an error occurs |
|||
* returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error |
|||
* occurred. |
|||
*/ |
|||
static char *get_admin_ssh_folder(Error **errp) |
|||
{ |
|||
/* Allocate memory for the program data path */ |
|||
g_autofree char *programDataPath = NULL; |
|||
char *authkeys_path = NULL; |
|||
PWSTR pgDataW = NULL; |
|||
g_autoptr(GError) gerr = NULL; |
|||
|
|||
/* Get the KnownFolderPath on the machine. */ |
|||
HRESULT folderResult = |
|||
SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW); |
|||
if (folderResult != S_OK) { |
|||
error_setg(errp, "Failed to retrieve ProgramData folder"); |
|||
return NULL; |
|||
} |
|||
|
|||
/* Convert from a wide string back to a standard character string. */ |
|||
programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr); |
|||
CoTaskMemFree(pgDataW); |
|||
if (!programDataPath) { |
|||
error_setg(errp, |
|||
"Failed converting ProgramData folder path to UTF-16 %s", |
|||
gerr->message); |
|||
return NULL; |
|||
} |
|||
|
|||
/* Build the path to the file. */ |
|||
authkeys_path = g_build_filename(programDataPath, "ssh", NULL); |
|||
return authkeys_path; |
|||
} |
|||
|
|||
/*
|
|||
* Gets the path to the SSH folder for the specified user. If the user is an |
|||
* admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is |
|||
* not an admin it returns %USERPROFILE%/.ssh |
|||
* |
|||
* parameters: |
|||
* username -> Username to get the SSH folder for |
|||
* isAdmin -> Whether the user is an admin or not |
|||
* errp -> Error structure to set any errors that occur. |
|||
* returns: path to the ssh folder as a string. |
|||
*/ |
|||
static char *get_ssh_folder(const char *username, const bool isAdmin, |
|||
Error **errp) |
|||
{ |
|||
DWORD maxSize = MAX_PATH; |
|||
g_autofree char *profilesDir = g_new0(char, maxSize); |
|||
|
|||
if (isAdmin) { |
|||
return get_admin_ssh_folder(errp); |
|||
} |
|||
|
|||
/* If not an Admin the SSH key is in the user directory. */ |
|||
/* Get the user profile directory on the machine. */ |
|||
BOOL ret = GetProfilesDirectory(profilesDir, &maxSize); |
|||
if (!ret) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"failed to retrieve profiles directory"); |
|||
return NULL; |
|||
} |
|||
|
|||
/* Builds the filename */ |
|||
return g_build_filename(profilesDir, username, ".ssh", NULL); |
|||
} |
|||
|
|||
/*
|
|||
* Creates an entry for the user so they can access the ssh folder in their |
|||
* userprofile. |
|||
* |
|||
* parameters: |
|||
* userInfo -> Information about the current user |
|||
* pACL -> Pointer to an ACL structure |
|||
* errp -> Error structure to set any errors that occur |
|||
* returns -> 1 on success, 0 otherwise |
|||
*/ |
|||
static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) |
|||
{ |
|||
const int aclSize = 1; |
|||
PACL newACL = NULL; |
|||
EXPLICIT_ACCESS eAccess[1]; |
|||
PSID userPSID = NULL; |
|||
|
|||
/* Get a pointer to the internal SID object in Windows */ |
|||
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID); |
|||
if (!converted) { |
|||
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID", |
|||
userInfo->username); |
|||
goto error; |
|||
} |
|||
|
|||
/* Set the permissions for the user. */ |
|||
eAccess[0].grfAccessPermissions = GENERIC_ALL; |
|||
eAccess[0].grfAccessMode = SET_ACCESS; |
|||
eAccess[0].grfInheritance = NO_INHERITANCE; |
|||
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; |
|||
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER; |
|||
eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID; |
|||
|
|||
/* Set the ACL entries */ |
|||
DWORD setResult; |
|||
|
|||
/*
|
|||
* If we are given a pointer that is already initialized, then we can merge |
|||
* the existing entries instead of overwriting them. |
|||
*/ |
|||
if (*pACL) { |
|||
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL); |
|||
} else { |
|||
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL); |
|||
} |
|||
|
|||
if (setResult != ERROR_SUCCESS) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"failed to set ACL entries for user %s %lu", |
|||
userInfo->username, setResult); |
|||
goto error; |
|||
} |
|||
|
|||
/* Free any old memory since we are going to overwrite the users pointer. */ |
|||
LocalFree(*pACL); |
|||
*pACL = newACL; |
|||
|
|||
LocalFree(userPSID); |
|||
return true; |
|||
error: |
|||
LocalFree(userPSID); |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Creates a base ACL for both normal users and admins to share |
|||
* pACL -> Pointer to an ACL structure |
|||
* errp -> Error structure to set any errors that occur |
|||
* returns: 1 on success, 0 otherwise |
|||
*/ |
|||
static bool create_acl_base(PACL *pACL, Error **errp) |
|||
{ |
|||
PSID adminGroupPSID = NULL; |
|||
PSID systemPSID = NULL; |
|||
|
|||
const int aclSize = 2; |
|||
EXPLICIT_ACCESS eAccess[2]; |
|||
|
|||
/* Create an entry for the system user. */ |
|||
const char *systemSID = LOCAL_SYSTEM_SID; |
|||
bool converted = ConvertStringSidToSid(systemSID, &systemPSID); |
|||
if (!converted) { |
|||
error_setg_win32(errp, GetLastError(), "failed to retrieve system SID"); |
|||
goto error; |
|||
} |
|||
|
|||
/* set permissions for system user */ |
|||
eAccess[0].grfAccessPermissions = GENERIC_ALL; |
|||
eAccess[0].grfAccessMode = SET_ACCESS; |
|||
eAccess[0].grfInheritance = NO_INHERITANCE; |
|||
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; |
|||
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER; |
|||
eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID; |
|||
|
|||
/* Create an entry for the admin user. */ |
|||
const char *adminSID = ADMIN_SID; |
|||
converted = ConvertStringSidToSid(adminSID, &adminGroupPSID); |
|||
if (!converted) { |
|||
error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID"); |
|||
goto error; |
|||
} |
|||
|
|||
/* Set permissions for admin group. */ |
|||
eAccess[1].grfAccessPermissions = GENERIC_ALL; |
|||
eAccess[1].grfAccessMode = SET_ACCESS; |
|||
eAccess[1].grfInheritance = NO_INHERITANCE; |
|||
eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; |
|||
eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; |
|||
eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID; |
|||
|
|||
/* Put the entries in an ACL object. */ |
|||
PACL pNewACL = NULL; |
|||
DWORD setResult; |
|||
|
|||
/*
|
|||
*If we are given a pointer that is already initialized, then we can merge |
|||
*the existing entries instead of overwriting them. |
|||
*/ |
|||
if (*pACL) { |
|||
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL); |
|||
} else { |
|||
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL); |
|||
} |
|||
|
|||
if (setResult != ERROR_SUCCESS) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"failed to set base ACL entries for system user and " |
|||
"admin group %lu", |
|||
setResult); |
|||
goto error; |
|||
} |
|||
|
|||
LocalFree(adminGroupPSID); |
|||
LocalFree(systemPSID); |
|||
|
|||
/* Free any old memory since we are going to overwrite the users pointer. */ |
|||
LocalFree(*pACL); |
|||
|
|||
*pACL = pNewACL; |
|||
|
|||
return true; |
|||
|
|||
error: |
|||
LocalFree(adminGroupPSID); |
|||
LocalFree(systemPSID); |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Sets the access control on the authorized_keys file and any ssh folders that |
|||
* need to be created. For administrators the required permissions on the |
|||
* file/folders are that only administrators and the LocalSystem account can |
|||
* access the folders. For normal user accounts only the specified user, |
|||
* LocalSystem and Administrators can have access to the key. |
|||
* |
|||
* parameters: |
|||
* userInfo -> pointer to structure that contains information about the user |
|||
* PACL -> pointer to an access control structure that will be set upon |
|||
* successful completion of the function. |
|||
* errp -> error structure that will be set upon error. |
|||
* returns: 1 upon success 0 upon failure. |
|||
*/ |
|||
static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) |
|||
{ |
|||
/*
|
|||
* Creates a base ACL that both admins and users will share |
|||
* This adds the Administrators group and the SYSTEM group |
|||
*/ |
|||
if (!create_acl_base(pACL, errp)) { |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* If the user is not an admin give the user creating the key permission to |
|||
* access the file. |
|||
*/ |
|||
if (!userInfo->isAdmin) { |
|||
if (!create_acl_user(userInfo, pACL, errp)) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
/*
|
|||
* Create the SSH directory for the user and d sets appropriate permissions. |
|||
* In general the directory will be %PROGRAMDATA%/ssh if the user is an admin. |
|||
* %USERPOFILE%/.ssh if not an admin |
|||
* |
|||
* parameters: |
|||
* userInfo -> Contains information about the user |
|||
* errp -> Structure that will contain errors if the function fails. |
|||
* returns: zero upon failure, 1 upon success |
|||
*/ |
|||
static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp) |
|||
{ |
|||
PACL pNewACL = NULL; |
|||
g_autofree PSECURITY_DESCRIPTOR pSD = NULL; |
|||
|
|||
/* Gets the appropriate ACL for the user */ |
|||
if (!create_acl(userInfo, &pNewACL, errp)) { |
|||
goto error; |
|||
} |
|||
|
|||
/* Allocate memory for a security descriptor */ |
|||
pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH); |
|||
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"Failed to initialize security descriptor"); |
|||
goto error; |
|||
} |
|||
|
|||
/* Associate the security descriptor with the ACL permissions. */ |
|||
if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"Failed to set security descriptor ACL"); |
|||
goto error; |
|||
} |
|||
|
|||
/* Set the security attributes on the folder */ |
|||
SECURITY_ATTRIBUTES sAttr; |
|||
sAttr.bInheritHandle = FALSE; |
|||
sAttr.nLength = sizeof(SECURITY_ATTRIBUTES); |
|||
sAttr.lpSecurityDescriptor = pSD; |
|||
|
|||
/* Create the directory with the created permissions */ |
|||
BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr); |
|||
if (!created) { |
|||
error_setg_win32(errp, GetLastError(), "failed to create directory %s", |
|||
userInfo->sshDirectory); |
|||
goto error; |
|||
} |
|||
|
|||
/* Free memory */ |
|||
LocalFree(pNewACL); |
|||
return true; |
|||
error: |
|||
LocalFree(pNewACL); |
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Sets permissions on the authorized_key_file that is created. |
|||
* |
|||
* parameters: userInfo -> Information about the user |
|||
* errp -> error structure that will contain errors upon failure |
|||
* returns: 1 upon success, zero upon failure. |
|||
*/ |
|||
static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp) |
|||
{ |
|||
PACL pACL = NULL; |
|||
PSID userPSID; |
|||
|
|||
/* Creates the access control structure */ |
|||
if (!create_acl(userInfo, &pACL, errp)) { |
|||
goto error; |
|||
} |
|||
|
|||
/* Get the PSID structure for the user based off the string SID. */ |
|||
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID); |
|||
if (!converted) { |
|||
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID", |
|||
userInfo->username); |
|||
goto error; |
|||
} |
|||
|
|||
/* Prevents permissions from being inherited and use the DACL provided. */ |
|||
const SE_OBJECT_TYPE securityBitFlags = |
|||
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; |
|||
|
|||
/* Set the ACL on the file. */ |
|||
if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT, |
|||
securityBitFlags, userPSID, NULL, pACL, |
|||
NULL) != ERROR_SUCCESS) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"failed to set file security for file %s", |
|||
userInfo->authorizedKeyFile); |
|||
goto error; |
|||
} |
|||
|
|||
LocalFree(pACL); |
|||
LocalFree(userPSID); |
|||
return true; |
|||
|
|||
error: |
|||
LocalFree(pACL); |
|||
LocalFree(userPSID); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Writes the specified keys to the authenticated keys file. |
|||
* parameters: |
|||
* userInfo: Information about the user we are writing the authkeys file to. |
|||
* authkeys: Array of keys to write to disk |
|||
* errp: Error structure that will contain any errors if they occur. |
|||
* returns: 1 if successful, 0 otherwise. |
|||
*/ |
|||
static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys, |
|||
Error **errp) |
|||
{ |
|||
g_autofree char *contents = NULL; |
|||
g_autoptr(GError) err = NULL; |
|||
|
|||
contents = g_strjoinv("\n", authkeys); |
|||
|
|||
if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) { |
|||
error_setg(errp, "failed to write to '%s': %s", |
|||
userInfo->authorizedKeyFile, err->message); |
|||
return false; |
|||
} |
|||
|
|||
if (!set_file_permissions(userInfo, errp)) { |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/*
|
|||
* Retrieves information about a Windows user by their username |
|||
* |
|||
* parameters: |
|||
* userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it |
|||
* will be allocated with information about the user and need to be freed. |
|||
* username -> Name of the user to lookup. |
|||
* errp -> Contains any errors that occur. |
|||
* returns: 1 upon success, 0 upon failure. |
|||
*/ |
|||
static bool get_user_info(PWindowsUserInfo *userInfo, const char *username, |
|||
Error **errp) |
|||
{ |
|||
DWORD infoLevel = 4; |
|||
LPUSER_INFO_4 uBuf = NULL; |
|||
g_autofree wchar_t *wideUserName = NULL; |
|||
g_autoptr(GError) gerr = NULL; |
|||
PSID psid = NULL; |
|||
|
|||
/*
|
|||
* Converts a string to a Windows wide string since the GetNetUserInfo |
|||
* function requires it. |
|||
*/ |
|||
wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr); |
|||
if (!wideUserName) { |
|||
goto error; |
|||
} |
|||
|
|||
/* allocate data */ |
|||
PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1); |
|||
|
|||
/* Set pointer so it can be cleaned up by the callee, even upon error. */ |
|||
*userInfo = uData; |
|||
|
|||
/* Find the information */ |
|||
NET_API_STATUS result = |
|||
NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf); |
|||
if (result != NERR_Success) { |
|||
/* Give a friendlier error message if the user was not found. */ |
|||
if (result == NERR_UserNotFound) { |
|||
error_setg(errp, "User %s was not found", username); |
|||
goto error; |
|||
} |
|||
|
|||
error_setg(errp, |
|||
"Received unexpected error when asking for user info: Error " |
|||
"Code %lu", |
|||
result); |
|||
goto error; |
|||
} |
|||
|
|||
/* Get information from the buffer returned by NetUserGetInfo. */ |
|||
uData->username = g_strdup(username); |
|||
uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN; |
|||
psid = uBuf->usri4_user_sid; |
|||
|
|||
char *sidStr = NULL; |
|||
|
|||
/*
|
|||
* We store the string representation of the SID not SID structure in |
|||
* memory. Callees wanting to use the SID structure should call |
|||
* ConvertStringSidToSID. |
|||
*/ |
|||
if (!ConvertSidToStringSid(psid, &sidStr)) { |
|||
error_setg_win32(errp, GetLastError(), |
|||
"failed to get SID string for user %s", username); |
|||
goto error; |
|||
} |
|||
|
|||
/* Store the SSID */ |
|||
uData->SSID = sidStr; |
|||
|
|||
/* Get the SSH folder for the user. */ |
|||
char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp); |
|||
if (sshFolder == NULL) { |
|||
goto error; |
|||
} |
|||
|
|||
/* Get the authorized key file path */ |
|||
const char *authorizedKeyFile = |
|||
uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE; |
|||
char *authorizedKeyPath = |
|||
g_build_filename(sshFolder, authorizedKeyFile, NULL); |
|||
uData->sshDirectory = sshFolder; |
|||
uData->authorizedKeyFile = authorizedKeyPath; |
|||
|
|||
/* Free */ |
|||
NetApiBufferFree(uBuf); |
|||
return true; |
|||
error: |
|||
if (uBuf) { |
|||
NetApiBufferFree(uBuf); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/*
|
|||
* Gets the list of authorized keys for a user. |
|||
* |
|||
* parameters: |
|||
* username -> Username to retrieve the keys for. |
|||
* errp -> Error structure that will display any errors through QMP. |
|||
* returns: List of keys associated with the user. |
|||
*/ |
|||
GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username, |
|||
Error **errp) |
|||
{ |
|||
GuestAuthorizedKeys *keys = NULL; |
|||
g_auto(GStrv) authKeys = NULL; |
|||
g_autoptr(GuestAuthorizedKeys) ret = NULL; |
|||
g_auto(PWindowsUserInfo) userInfo = NULL; |
|||
|
|||
/* Gets user information */ |
|||
if (!get_user_info(&userInfo, username, errp)) { |
|||
return NULL; |
|||
} |
|||
|
|||
/* Reads authkeys for the user */ |
|||
authKeys = read_authkeys(userInfo->authorizedKeyFile, errp); |
|||
if (authKeys == NULL) { |
|||
return NULL; |
|||
} |
|||
|
|||
/* Set the GuestAuthorizedKey struct with keys from the file */ |
|||
ret = g_new0(GuestAuthorizedKeys, 1); |
|||
for (int i = 0; authKeys[i] != NULL; i++) { |
|||
g_strstrip(authKeys[i]); |
|||
if (!authKeys[i][0] || authKeys[i][0] == '#') { |
|||
continue; |
|||
} |
|||
|
|||
QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i])); |
|||
} |
|||
|
|||
/*
|
|||
* Steal the pointer because it is up for the callee to deallocate the |
|||
* memory. |
|||
*/ |
|||
keys = g_steal_pointer(&ret); |
|||
return keys; |
|||
} |
|||
|
|||
/*
|
|||
* Adds an ssh key for a user. |
|||
* |
|||
* parameters: |
|||
* username -> User to add the SSH key to |
|||
* strList -> Array of keys to add to the list |
|||
* has_reset -> Whether the keys have been reset |
|||
* reset -> Boolean to reset the keys (If this is set the existing list will be |
|||
* cleared) and the other key reset. errp -> Pointer to an error structure that |
|||
* will get returned over QMP if anything goes wrong. |
|||
*/ |
|||
void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, |
|||
bool has_reset, bool reset, Error **errp) |
|||
{ |
|||
g_auto(PWindowsUserInfo) userInfo = NULL; |
|||
g_auto(GStrv) authkeys = NULL; |
|||
strList *k; |
|||
size_t nkeys, nauthkeys; |
|||
|
|||
/* Make sure the keys given are valid */ |
|||
if (!check_openssh_pub_keys(keys, &nkeys, errp)) { |
|||
return; |
|||
} |
|||
|
|||
/* Gets user information */ |
|||
if (!get_user_info(&userInfo, username, errp)) { |
|||
return; |
|||
} |
|||
|
|||
/* Determine whether we should reset the keys */ |
|||
reset = has_reset && reset; |
|||
if (!reset) { |
|||
/* Read existing keys into memory */ |
|||
authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL); |
|||
} |
|||
|
|||
/* Check that the SSH key directory exists for the user. */ |
|||
if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) { |
|||
BOOL success = create_ssh_directory(userInfo, errp); |
|||
if (!success) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/* Reallocates the buffer to fit the new keys. */ |
|||
nauthkeys = authkeys ? g_strv_length(authkeys) : 0; |
|||
authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *)); |
|||
|
|||
/* zero out the memory for the reallocated buffer */ |
|||
memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *)); |
|||
|
|||
/* Adds the keys */ |
|||
for (k = keys; k != NULL; k = k->next) { |
|||
/* Check that the key doesn't already exist */ |
|||
if (g_strv_contains((const gchar *const *)authkeys, k->value)) { |
|||
continue; |
|||
} |
|||
|
|||
authkeys[nauthkeys++] = g_strdup(k->value); |
|||
} |
|||
|
|||
/* Write the authkeys to the file. */ |
|||
write_authkeys(userInfo, authkeys, errp); |
|||
} |
|||
|
|||
/*
|
|||
* Removes an SSH key for a user |
|||
* |
|||
* parameters: |
|||
* username -> Username to remove the key from |
|||
* strList -> List of strings to remove |
|||
* errp -> Contains any errors that occur. |
|||
*/ |
|||
void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys, |
|||
Error **errp) |
|||
{ |
|||
g_auto(PWindowsUserInfo) userInfo = NULL; |
|||
g_autofree struct passwd *p = NULL; |
|||
g_autofree GStrv new_keys = NULL; /* do not own the strings */ |
|||
g_auto(GStrv) authkeys = NULL; |
|||
GStrv a; |
|||
size_t nkeys = 0; |
|||
|
|||
/* Validates the keys passed in by the user */ |
|||
if (!check_openssh_pub_keys(keys, NULL, errp)) { |
|||
return; |
|||
} |
|||
|
|||
/* Gets user information */ |
|||
if (!get_user_info(&userInfo, username, errp)) { |
|||
return; |
|||
} |
|||
|
|||
/* Reads the authkeys for the user */ |
|||
authkeys = read_authkeys(userInfo->authorizedKeyFile, errp); |
|||
if (authkeys == NULL) { |
|||
return; |
|||
} |
|||
|
|||
/* Create a new buffer to hold the keys */ |
|||
new_keys = g_new0(char *, g_strv_length(authkeys) + 1); |
|||
for (a = authkeys; *a != NULL; a++) { |
|||
strList *k; |
|||
|
|||
/* Filters out keys that are equal to ones the user specified. */ |
|||
for (k = keys; k != NULL; k = k->next) { |
|||
if (g_str_equal(k->value, *a)) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (k != NULL) { |
|||
continue; |
|||
} |
|||
|
|||
new_keys[nkeys++] = *a; |
|||
} |
|||
|
|||
/* Write the new authkeys to the file. */ |
|||
write_authkeys(userInfo, new_keys, errp); |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/*
|
|||
* Header file for commands-windows-ssh.c |
|||
* |
|||
* Copyright Schweitzer Engineering Laboratories. 2024 |
|||
* |
|||
* Authors: |
|||
* Aidan Leuck <aidan_leuck@selinc.com> |
|||
* |
|||
* This work is licensed under the terms of the GNU GPL, version 2 or later. |
|||
* See the COPYING file in the top-level directory. |
|||
*/ |
|||
|
|||
#include <glib/gstrfuncs.h> |
|||
#include <stdbool.h> |
|||
typedef struct WindowsUserInfo { |
|||
char *sshDirectory; |
|||
char *authorizedKeyFile; |
|||
char *username; |
|||
char *SSID; |
|||
bool isAdmin; |
|||
} WindowsUserInfo; |
|||
|
|||
typedef WindowsUserInfo *PWindowsUserInfo; |
|||
|
|||
void free_userInfo(PWindowsUserInfo info); |
|||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(PWindowsUserInfo, free_userInfo, NULL); |
|||
Loading…
Reference in new issue