Merge tag 'accel-sh4-ui-20240503' of https://github.com/philmd/qemu into staging
[qemu/armbru.git] / qga / commands-windows-ssh.c
blob6a642e3ba8198babacf66a8123393ca0ff6b076a
1 /*
2 * QEMU Guest Agent win32-specific command implementations for SSH keys.
3 * The implementation is opinionated and expects the SSH implementation to
4 * be OpenSSH.
6 * Copyright Schweitzer Engineering Laboratories. 2024
8 * Authors:
9 * Aidan Leuck <aidan_leuck@selinc.com>
11 * This work is licensed under the terms of the GNU GPL, version 2 or later.
12 * See the COPYING file in the top-level directory.
15 #include "qemu/osdep.h"
16 #include <aclapi.h>
17 #include <qga-qapi-types.h>
19 #include "commands-common-ssh.h"
20 #include "commands-windows-ssh.h"
21 #include "guest-agent-core.h"
22 #include "limits.h"
23 #include "lmaccess.h"
24 #include "lmapibuf.h"
25 #include "lmerr.h"
26 #include "qapi/error.h"
28 #include "qga-qapi-commands.h"
29 #include "sddl.h"
30 #include "shlobj.h"
31 #include "userenv.h"
33 #define AUTHORIZED_KEY_FILE "authorized_keys"
34 #define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys"
35 #define LOCAL_SYSTEM_SID "S-1-5-18"
36 #define ADMIN_SID "S-1-5-32-544"
39 * Frees userInfo structure. This implements the g_auto cleanup
40 * for the structure.
42 void free_userInfo(PWindowsUserInfo info)
44 g_free(info->sshDirectory);
45 g_free(info->authorizedKeyFile);
46 LocalFree(info->SSID);
47 g_free(info->username);
48 g_free(info);
52 * Gets the admin SSH folder for OpenSSH. OpenSSH does not store
53 * the authorized_key file in the users home directory for security reasons and
54 * instead stores it at %PROGRAMDATA%/ssh. This function returns the path to
55 * that directory on the users machine
57 * parameters:
58 * errp -> error structure to set when an error occurs
59 * returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error
60 * occurred.
62 static char *get_admin_ssh_folder(Error **errp)
64 /* Allocate memory for the program data path */
65 g_autofree char *programDataPath = NULL;
66 char *authkeys_path = NULL;
67 PWSTR pgDataW = NULL;
68 g_autoptr(GError) gerr = NULL;
70 /* Get the KnownFolderPath on the machine. */
71 HRESULT folderResult =
72 SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW);
73 if (folderResult != S_OK) {
74 error_setg(errp, "Failed to retrieve ProgramData folder");
75 return NULL;
78 /* Convert from a wide string back to a standard character string. */
79 programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr);
80 CoTaskMemFree(pgDataW);
81 if (!programDataPath) {
82 error_setg(errp,
83 "Failed converting ProgramData folder path to UTF-16 %s",
84 gerr->message);
85 return NULL;
88 /* Build the path to the file. */
89 authkeys_path = g_build_filename(programDataPath, "ssh", NULL);
90 return authkeys_path;
94 * Gets the path to the SSH folder for the specified user. If the user is an
95 * admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is
96 * not an admin it returns %USERPROFILE%/.ssh
98 * parameters:
99 * username -> Username to get the SSH folder for
100 * isAdmin -> Whether the user is an admin or not
101 * errp -> Error structure to set any errors that occur.
102 * returns: path to the ssh folder as a string.
104 static char *get_ssh_folder(const char *username, const bool isAdmin,
105 Error **errp)
107 DWORD maxSize = MAX_PATH;
108 g_autofree char *profilesDir = g_new0(char, maxSize);
110 if (isAdmin) {
111 return get_admin_ssh_folder(errp);
114 /* If not an Admin the SSH key is in the user directory. */
115 /* Get the user profile directory on the machine. */
116 BOOL ret = GetProfilesDirectory(profilesDir, &maxSize);
117 if (!ret) {
118 error_setg_win32(errp, GetLastError(),
119 "failed to retrieve profiles directory");
120 return NULL;
123 /* Builds the filename */
124 return g_build_filename(profilesDir, username, ".ssh", NULL);
128 * Creates an entry for the user so they can access the ssh folder in their
129 * userprofile.
131 * parameters:
132 * userInfo -> Information about the current user
133 * pACL -> Pointer to an ACL structure
134 * errp -> Error structure to set any errors that occur
135 * returns -> 1 on success, 0 otherwise
137 static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
139 const int aclSize = 1;
140 PACL newACL = NULL;
141 EXPLICIT_ACCESS eAccess[1];
142 PSID userPSID = NULL;
144 /* Get a pointer to the internal SID object in Windows */
145 bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
146 if (!converted) {
147 error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
148 userInfo->username);
149 goto error;
152 /* Set the permissions for the user. */
153 eAccess[0].grfAccessPermissions = GENERIC_ALL;
154 eAccess[0].grfAccessMode = SET_ACCESS;
155 eAccess[0].grfInheritance = NO_INHERITANCE;
156 eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
157 eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
158 eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID;
160 /* Set the ACL entries */
161 DWORD setResult;
164 * If we are given a pointer that is already initialized, then we can merge
165 * the existing entries instead of overwriting them.
167 if (*pACL) {
168 setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL);
169 } else {
170 setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL);
173 if (setResult != ERROR_SUCCESS) {
174 error_setg_win32(errp, GetLastError(),
175 "failed to set ACL entries for user %s %lu",
176 userInfo->username, setResult);
177 goto error;
180 /* Free any old memory since we are going to overwrite the users pointer. */
181 LocalFree(*pACL);
182 *pACL = newACL;
184 LocalFree(userPSID);
185 return true;
186 error:
187 LocalFree(userPSID);
188 return false;
192 * Creates a base ACL for both normal users and admins to share
193 * pACL -> Pointer to an ACL structure
194 * errp -> Error structure to set any errors that occur
195 * returns: 1 on success, 0 otherwise
197 static bool create_acl_base(PACL *pACL, Error **errp)
199 PSID adminGroupPSID = NULL;
200 PSID systemPSID = NULL;
202 const int aclSize = 2;
203 EXPLICIT_ACCESS eAccess[2];
205 /* Create an entry for the system user. */
206 const char *systemSID = LOCAL_SYSTEM_SID;
207 bool converted = ConvertStringSidToSid(systemSID, &systemPSID);
208 if (!converted) {
209 error_setg_win32(errp, GetLastError(), "failed to retrieve system SID");
210 goto error;
213 /* set permissions for system user */
214 eAccess[0].grfAccessPermissions = GENERIC_ALL;
215 eAccess[0].grfAccessMode = SET_ACCESS;
216 eAccess[0].grfInheritance = NO_INHERITANCE;
217 eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
218 eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
219 eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID;
221 /* Create an entry for the admin user. */
222 const char *adminSID = ADMIN_SID;
223 converted = ConvertStringSidToSid(adminSID, &adminGroupPSID);
224 if (!converted) {
225 error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID");
226 goto error;
229 /* Set permissions for admin group. */
230 eAccess[1].grfAccessPermissions = GENERIC_ALL;
231 eAccess[1].grfAccessMode = SET_ACCESS;
232 eAccess[1].grfInheritance = NO_INHERITANCE;
233 eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
234 eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
235 eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID;
237 /* Put the entries in an ACL object. */
238 PACL pNewACL = NULL;
239 DWORD setResult;
242 *If we are given a pointer that is already initialized, then we can merge
243 *the existing entries instead of overwriting them.
245 if (*pACL) {
246 setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL);
247 } else {
248 setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL);
251 if (setResult != ERROR_SUCCESS) {
252 error_setg_win32(errp, GetLastError(),
253 "failed to set base ACL entries for system user and "
254 "admin group %lu",
255 setResult);
256 goto error;
259 LocalFree(adminGroupPSID);
260 LocalFree(systemPSID);
262 /* Free any old memory since we are going to overwrite the users pointer. */
263 LocalFree(*pACL);
265 *pACL = pNewACL;
267 return true;
269 error:
270 LocalFree(adminGroupPSID);
271 LocalFree(systemPSID);
272 return false;
276 * Sets the access control on the authorized_keys file and any ssh folders that
277 * need to be created. For administrators the required permissions on the
278 * file/folders are that only administrators and the LocalSystem account can
279 * access the folders. For normal user accounts only the specified user,
280 * LocalSystem and Administrators can have access to the key.
282 * parameters:
283 * userInfo -> pointer to structure that contains information about the user
284 * PACL -> pointer to an access control structure that will be set upon
285 * successful completion of the function.
286 * errp -> error structure that will be set upon error.
287 * returns: 1 upon success 0 upon failure.
289 static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
292 * Creates a base ACL that both admins and users will share
293 * This adds the Administrators group and the SYSTEM group
295 if (!create_acl_base(pACL, errp)) {
296 return false;
300 * If the user is not an admin give the user creating the key permission to
301 * access the file.
303 if (!userInfo->isAdmin) {
304 if (!create_acl_user(userInfo, pACL, errp)) {
305 return false;
308 return true;
311 return true;
314 * Create the SSH directory for the user and d sets appropriate permissions.
315 * In general the directory will be %PROGRAMDATA%/ssh if the user is an admin.
316 * %USERPOFILE%/.ssh if not an admin
318 * parameters:
319 * userInfo -> Contains information about the user
320 * errp -> Structure that will contain errors if the function fails.
321 * returns: zero upon failure, 1 upon success
323 static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp)
325 PACL pNewACL = NULL;
326 g_autofree PSECURITY_DESCRIPTOR pSD = NULL;
328 /* Gets the appropriate ACL for the user */
329 if (!create_acl(userInfo, &pNewACL, errp)) {
330 goto error;
333 /* Allocate memory for a security descriptor */
334 pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
335 if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) {
336 error_setg_win32(errp, GetLastError(),
337 "Failed to initialize security descriptor");
338 goto error;
341 /* Associate the security descriptor with the ACL permissions. */
342 if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) {
343 error_setg_win32(errp, GetLastError(),
344 "Failed to set security descriptor ACL");
345 goto error;
348 /* Set the security attributes on the folder */
349 SECURITY_ATTRIBUTES sAttr;
350 sAttr.bInheritHandle = FALSE;
351 sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
352 sAttr.lpSecurityDescriptor = pSD;
354 /* Create the directory with the created permissions */
355 BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr);
356 if (!created) {
357 error_setg_win32(errp, GetLastError(), "failed to create directory %s",
358 userInfo->sshDirectory);
359 goto error;
362 /* Free memory */
363 LocalFree(pNewACL);
364 return true;
365 error:
366 LocalFree(pNewACL);
367 return false;
371 * Sets permissions on the authorized_key_file that is created.
373 * parameters: userInfo -> Information about the user
374 * errp -> error structure that will contain errors upon failure
375 * returns: 1 upon success, zero upon failure.
377 static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp)
379 PACL pACL = NULL;
380 PSID userPSID;
382 /* Creates the access control structure */
383 if (!create_acl(userInfo, &pACL, errp)) {
384 goto error;
387 /* Get the PSID structure for the user based off the string SID. */
388 bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
389 if (!converted) {
390 error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
391 userInfo->username);
392 goto error;
395 /* Prevents permissions from being inherited and use the DACL provided. */
396 const SE_OBJECT_TYPE securityBitFlags =
397 DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
399 /* Set the ACL on the file. */
400 if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT,
401 securityBitFlags, userPSID, NULL, pACL,
402 NULL) != ERROR_SUCCESS) {
403 error_setg_win32(errp, GetLastError(),
404 "failed to set file security for file %s",
405 userInfo->authorizedKeyFile);
406 goto error;
409 LocalFree(pACL);
410 LocalFree(userPSID);
411 return true;
413 error:
414 LocalFree(pACL);
415 LocalFree(userPSID);
417 return false;
421 * Writes the specified keys to the authenticated keys file.
422 * parameters:
423 * userInfo: Information about the user we are writing the authkeys file to.
424 * authkeys: Array of keys to write to disk
425 * errp: Error structure that will contain any errors if they occur.
426 * returns: 1 if successful, 0 otherwise.
428 static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys,
429 Error **errp)
431 g_autofree char *contents = NULL;
432 g_autoptr(GError) err = NULL;
434 contents = g_strjoinv("\n", authkeys);
436 if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) {
437 error_setg(errp, "failed to write to '%s': %s",
438 userInfo->authorizedKeyFile, err->message);
439 return false;
442 if (!set_file_permissions(userInfo, errp)) {
443 return false;
446 return true;
450 * Retrieves information about a Windows user by their username
452 * parameters:
453 * userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it
454 * will be allocated with information about the user and need to be freed.
455 * username -> Name of the user to lookup.
456 * errp -> Contains any errors that occur.
457 * returns: 1 upon success, 0 upon failure.
459 static bool get_user_info(PWindowsUserInfo *userInfo, const char *username,
460 Error **errp)
462 DWORD infoLevel = 4;
463 LPUSER_INFO_4 uBuf = NULL;
464 g_autofree wchar_t *wideUserName = NULL;
465 g_autoptr(GError) gerr = NULL;
466 PSID psid = NULL;
469 * Converts a string to a Windows wide string since the GetNetUserInfo
470 * function requires it.
472 wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
473 if (!wideUserName) {
474 goto error;
477 /* allocate data */
478 PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1);
480 /* Set pointer so it can be cleaned up by the callee, even upon error. */
481 *userInfo = uData;
483 /* Find the information */
484 NET_API_STATUS result =
485 NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf);
486 if (result != NERR_Success) {
487 /* Give a friendlier error message if the user was not found. */
488 if (result == NERR_UserNotFound) {
489 error_setg(errp, "User %s was not found", username);
490 goto error;
493 error_setg(errp,
494 "Received unexpected error when asking for user info: Error "
495 "Code %lu",
496 result);
497 goto error;
500 /* Get information from the buffer returned by NetUserGetInfo. */
501 uData->username = g_strdup(username);
502 uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN;
503 psid = uBuf->usri4_user_sid;
505 char *sidStr = NULL;
508 * We store the string representation of the SID not SID structure in
509 * memory. Callees wanting to use the SID structure should call
510 * ConvertStringSidToSID.
512 if (!ConvertSidToStringSid(psid, &sidStr)) {
513 error_setg_win32(errp, GetLastError(),
514 "failed to get SID string for user %s", username);
515 goto error;
518 /* Store the SSID */
519 uData->SSID = sidStr;
521 /* Get the SSH folder for the user. */
522 char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp);
523 if (sshFolder == NULL) {
524 goto error;
527 /* Get the authorized key file path */
528 const char *authorizedKeyFile =
529 uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE;
530 char *authorizedKeyPath =
531 g_build_filename(sshFolder, authorizedKeyFile, NULL);
532 uData->sshDirectory = sshFolder;
533 uData->authorizedKeyFile = authorizedKeyPath;
535 /* Free */
536 NetApiBufferFree(uBuf);
537 return true;
538 error:
539 if (uBuf) {
540 NetApiBufferFree(uBuf);
543 return false;
547 * Gets the list of authorized keys for a user.
549 * parameters:
550 * username -> Username to retrieve the keys for.
551 * errp -> Error structure that will display any errors through QMP.
552 * returns: List of keys associated with the user.
554 GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username,
555 Error **errp)
557 GuestAuthorizedKeys *keys = NULL;
558 g_auto(GStrv) authKeys = NULL;
559 g_autoptr(GuestAuthorizedKeys) ret = NULL;
560 g_auto(PWindowsUserInfo) userInfo = NULL;
562 /* Gets user information */
563 if (!get_user_info(&userInfo, username, errp)) {
564 return NULL;
567 /* Reads authkeys for the user */
568 authKeys = read_authkeys(userInfo->authorizedKeyFile, errp);
569 if (authKeys == NULL) {
570 return NULL;
573 /* Set the GuestAuthorizedKey struct with keys from the file */
574 ret = g_new0(GuestAuthorizedKeys, 1);
575 for (int i = 0; authKeys[i] != NULL; i++) {
576 g_strstrip(authKeys[i]);
577 if (!authKeys[i][0] || authKeys[i][0] == '#') {
578 continue;
581 QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i]));
585 * Steal the pointer because it is up for the callee to deallocate the
586 * memory.
588 keys = g_steal_pointer(&ret);
589 return keys;
593 * Adds an ssh key for a user.
595 * parameters:
596 * username -> User to add the SSH key to
597 * strList -> Array of keys to add to the list
598 * has_reset -> Whether the keys have been reset
599 * reset -> Boolean to reset the keys (If this is set the existing list will be
600 * cleared) and the other key reset. errp -> Pointer to an error structure that
601 * will get returned over QMP if anything goes wrong.
603 void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
604 bool has_reset, bool reset, Error **errp)
606 g_auto(PWindowsUserInfo) userInfo = NULL;
607 g_auto(GStrv) authkeys = NULL;
608 strList *k;
609 size_t nkeys, nauthkeys;
611 /* Make sure the keys given are valid */
612 if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
613 return;
616 /* Gets user information */
617 if (!get_user_info(&userInfo, username, errp)) {
618 return;
621 /* Determine whether we should reset the keys */
622 reset = has_reset && reset;
623 if (!reset) {
624 /* Read existing keys into memory */
625 authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL);
628 /* Check that the SSH key directory exists for the user. */
629 if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) {
630 BOOL success = create_ssh_directory(userInfo, errp);
631 if (!success) {
632 return;
636 /* Reallocates the buffer to fit the new keys. */
637 nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
638 authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
640 /* zero out the memory for the reallocated buffer */
641 memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
643 /* Adds the keys */
644 for (k = keys; k != NULL; k = k->next) {
645 /* Check that the key doesn't already exist */
646 if (g_strv_contains((const gchar *const *)authkeys, k->value)) {
647 continue;
650 authkeys[nauthkeys++] = g_strdup(k->value);
653 /* Write the authkeys to the file. */
654 write_authkeys(userInfo, authkeys, errp);
658 * Removes an SSH key for a user
660 * parameters:
661 * username -> Username to remove the key from
662 * strList -> List of strings to remove
663 * errp -> Contains any errors that occur.
665 void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
666 Error **errp)
668 g_auto(PWindowsUserInfo) userInfo = NULL;
669 g_autofree struct passwd *p = NULL;
670 g_autofree GStrv new_keys = NULL; /* do not own the strings */
671 g_auto(GStrv) authkeys = NULL;
672 GStrv a;
673 size_t nkeys = 0;
675 /* Validates the keys passed in by the user */
676 if (!check_openssh_pub_keys(keys, NULL, errp)) {
677 return;
680 /* Gets user information */
681 if (!get_user_info(&userInfo, username, errp)) {
682 return;
685 /* Reads the authkeys for the user */
686 authkeys = read_authkeys(userInfo->authorizedKeyFile, errp);
687 if (authkeys == NULL) {
688 return;
691 /* Create a new buffer to hold the keys */
692 new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
693 for (a = authkeys; *a != NULL; a++) {
694 strList *k;
696 /* Filters out keys that are equal to ones the user specified. */
697 for (k = keys; k != NULL; k = k->next) {
698 if (g_str_equal(k->value, *a)) {
699 break;
703 if (k != NULL) {
704 continue;
707 new_keys[nkeys++] = *a;
710 /* Write the new authkeys to the file. */
711 write_authkeys(userInfo, new_keys, errp);