Reported by clang, but not gcc.
[5123/5678] Compiling C object qga/qemu-ga.exe.p/commands-windows-ssh.c.obj
FAILED: qga/qemu-ga.exe.p/commands-windows-ssh.c.obj
"cc" "-Iqga/qemu-ga.exe.p" "-Iqga" "-I../qga" "-I." "-Iqapi" "-Itrace" "-Iui" "-Iui/shader" "-IC:/msys64/clangarm64/include/glib-2.0" "-IC:/msys64/clangarm64/lib/glib-2.0/include" "-fdiagnostics-color=auto" "-Wall" "-Winvalid-pch" "-Werror" "-std=gnu11" "-O2" "-g" "-fstack-protector-strong" "-Wempty-body" "-Wendif-labels" "-Wexpansion-to-defined" "-Wformat-security" "-Wformat-y2k" "-Wignored-qualifiers" "-Winit-self" "-Wmissing-format-attribute" "-Wmissing-prototypes" "-Wnested-externs" "-Wold-style-definition" "-Wredundant-decls" "-Wstrict-prototypes" "-Wtype-limits" "-Wundef" "-Wvla" "-Wwrite-strings" "-Wno-gnu-variable-sized-type-not-at-end" "-Wno-initializer-overrides" "-Wno-missing-include-dirs" "-Wno-psabi" "-Wno-shift-negative-value" "-Wno-string-plus-int" "-Wno-tautological-type-limit-compare" "-Wno-typedef-redefinition" "-Wthread-safety" "-iquote" "." "-iquote" "C:/w/qemu" "-iquote" "C:/w/qemu/include" "-iquote" "C:/w/qemu/host/include/aarch64" "-iquote" "C:/w/qemu/host/include/generic" "-iquote" "C:/w/qemu/tcg/aarch64" "-D_GNU_SOURCE" "-D_FILE_OFFSET_BITS=64" "-D_LARGEFILE_SOURCE" "-fno-strict-aliasing" "-fno-common" "-fwrapv" "-fno-pie" "-ftrivial-auto-var-init=zero" "-fzero-call-used-regs=used-gpr" -MD -MQ qga/qemu-ga.exe.p/commands-windows-ssh.c.obj -MF "qga/qemu-ga.exe.p/commands-windows-ssh.c.obj.d" -o qga/qemu-ga.exe.p/commands-windows-ssh.c.obj "-c" ../qga/commands-windows-ssh.c
../qga/commands-windows-ssh.c:383:9: error: variable 'userPSID' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
  383 |     if (!create_acl(userInfo, &pACL, errp)) {
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../qga/commands-windows-ssh.c:415:15: note: uninitialized use occurs here
  415 |     LocalFree(userPSID);
      |               ^~~~~~~~
../qga/commands-windows-ssh.c:383:5: note: remove the 'if' if its condition is always false
  383 |     if (!create_acl(userInfo, &pACL, errp)) {
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  384 |         goto error;
      |         ~~~~~~~~~~~
  385 |     }
      |     ~
../qga/commands-windows-ssh.c:380:18: note: initialize the variable 'userPSID' to silence this warning
  380 |     PSID userPSID;
      |                  ^
      |                   = NULL
1 error generated.
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Reviewed-by: Konstantin Kostiuk <kkostiuk@redhat.com>
Link: https://lore.kernel.org/r/20241031040426.772604-6-pierrick.bouvier@linaro.org
Signed-off-by: Konstantin Kostiuk <kkostiuk@redhat.com>
		
	
			
		
			
				
	
	
		
			713 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			713 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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 = NULL;
 | 
						|
 | 
						|
    /* 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);
 | 
						|
}
 |