aidaleuc | 6b9296b | 2024-04-24 08:40:29 -0600 | [diff] [blame] | 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. |
| 5 | * |
| 6 | * Copyright Schweitzer Engineering Laboratories. 2024 |
| 7 | * |
| 8 | * Authors: |
| 9 | * Aidan Leuck <aidan_leuck@selinc.com> |
| 10 | * |
| 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. |
| 13 | */ |
| 14 | |
| 15 | #include "qemu/osdep.h" |
| 16 | #include <aclapi.h> |
| 17 | #include <qga-qapi-types.h> |
| 18 | |
| 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" |
| 27 | |
| 28 | #include "qga-qapi-commands.h" |
| 29 | #include "sddl.h" |
| 30 | #include "shlobj.h" |
| 31 | #include "userenv.h" |
| 32 | |
| 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" |
| 37 | |
| 38 | /* |
| 39 | * Frees userInfo structure. This implements the g_auto cleanup |
| 40 | * for the structure. |
| 41 | */ |
| 42 | void free_userInfo(PWindowsUserInfo info) |
| 43 | { |
| 44 | g_free(info->sshDirectory); |
| 45 | g_free(info->authorizedKeyFile); |
| 46 | LocalFree(info->SSID); |
| 47 | g_free(info->username); |
| 48 | g_free(info); |
| 49 | } |
| 50 | |
| 51 | /* |
| 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 |
| 56 | * |
| 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. |
| 61 | */ |
| 62 | static char *get_admin_ssh_folder(Error **errp) |
| 63 | { |
| 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; |
| 69 | |
| 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; |
| 76 | } |
| 77 | |
| 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; |
| 86 | } |
| 87 | |
| 88 | /* Build the path to the file. */ |
| 89 | authkeys_path = g_build_filename(programDataPath, "ssh", NULL); |
| 90 | return authkeys_path; |
| 91 | } |
| 92 | |
| 93 | /* |
| 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 |
| 97 | * |
| 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. |
| 103 | */ |
| 104 | static char *get_ssh_folder(const char *username, const bool isAdmin, |
| 105 | Error **errp) |
| 106 | { |
| 107 | DWORD maxSize = MAX_PATH; |
| 108 | g_autofree char *profilesDir = g_new0(char, maxSize); |
| 109 | |
| 110 | if (isAdmin) { |
| 111 | return get_admin_ssh_folder(errp); |
| 112 | } |
| 113 | |
| 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; |
| 121 | } |
| 122 | |
| 123 | /* Builds the filename */ |
| 124 | return g_build_filename(profilesDir, username, ".ssh", NULL); |
| 125 | } |
| 126 | |
| 127 | /* |
| 128 | * Creates an entry for the user so they can access the ssh folder in their |
| 129 | * userprofile. |
| 130 | * |
| 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 |
| 136 | */ |
| 137 | static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) |
| 138 | { |
| 139 | const int aclSize = 1; |
| 140 | PACL newACL = NULL; |
| 141 | EXPLICIT_ACCESS eAccess[1]; |
| 142 | PSID userPSID = NULL; |
| 143 | |
| 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; |
| 150 | } |
| 151 | |
| 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; |
| 159 | |
| 160 | /* Set the ACL entries */ |
| 161 | DWORD setResult; |
| 162 | |
| 163 | /* |
| 164 | * If we are given a pointer that is already initialized, then we can merge |
| 165 | * the existing entries instead of overwriting them. |
| 166 | */ |
| 167 | if (*pACL) { |
| 168 | setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL); |
| 169 | } else { |
| 170 | setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL); |
| 171 | } |
| 172 | |
| 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; |
| 178 | } |
| 179 | |
| 180 | /* Free any old memory since we are going to overwrite the users pointer. */ |
| 181 | LocalFree(*pACL); |
| 182 | *pACL = newACL; |
| 183 | |
| 184 | LocalFree(userPSID); |
| 185 | return true; |
| 186 | error: |
| 187 | LocalFree(userPSID); |
| 188 | return false; |
| 189 | } |
| 190 | |
| 191 | /* |
| 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 |
| 196 | */ |
| 197 | static bool create_acl_base(PACL *pACL, Error **errp) |
| 198 | { |
| 199 | PSID adminGroupPSID = NULL; |
| 200 | PSID systemPSID = NULL; |
| 201 | |
| 202 | const int aclSize = 2; |
| 203 | EXPLICIT_ACCESS eAccess[2]; |
| 204 | |
| 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; |
| 211 | } |
| 212 | |
| 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; |
| 220 | |
| 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; |
| 227 | } |
| 228 | |
| 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; |
| 236 | |
| 237 | /* Put the entries in an ACL object. */ |
| 238 | PACL pNewACL = NULL; |
| 239 | DWORD setResult; |
| 240 | |
| 241 | /* |
| 242 | *If we are given a pointer that is already initialized, then we can merge |
| 243 | *the existing entries instead of overwriting them. |
| 244 | */ |
| 245 | if (*pACL) { |
| 246 | setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL); |
| 247 | } else { |
| 248 | setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL); |
| 249 | } |
| 250 | |
| 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; |
| 257 | } |
| 258 | |
| 259 | LocalFree(adminGroupPSID); |
| 260 | LocalFree(systemPSID); |
| 261 | |
| 262 | /* Free any old memory since we are going to overwrite the users pointer. */ |
| 263 | LocalFree(*pACL); |
| 264 | |
| 265 | *pACL = pNewACL; |
| 266 | |
| 267 | return true; |
| 268 | |
| 269 | error: |
| 270 | LocalFree(adminGroupPSID); |
| 271 | LocalFree(systemPSID); |
| 272 | return false; |
| 273 | } |
| 274 | |
| 275 | /* |
| 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. |
| 281 | * |
| 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. |
| 288 | */ |
| 289 | static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp) |
| 290 | { |
| 291 | /* |
| 292 | * Creates a base ACL that both admins and users will share |
| 293 | * This adds the Administrators group and the SYSTEM group |
| 294 | */ |
| 295 | if (!create_acl_base(pACL, errp)) { |
| 296 | return false; |
| 297 | } |
| 298 | |
| 299 | /* |
| 300 | * If the user is not an admin give the user creating the key permission to |
| 301 | * access the file. |
| 302 | */ |
| 303 | if (!userInfo->isAdmin) { |
| 304 | if (!create_acl_user(userInfo, pACL, errp)) { |
| 305 | return false; |
| 306 | } |
| 307 | |
| 308 | return true; |
| 309 | } |
| 310 | |
| 311 | return true; |
| 312 | } |
| 313 | /* |
| 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 |
| 317 | * |
| 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 |
| 322 | */ |
| 323 | static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp) |
| 324 | { |
| 325 | PACL pNewACL = NULL; |
| 326 | g_autofree PSECURITY_DESCRIPTOR pSD = NULL; |
| 327 | |
| 328 | /* Gets the appropriate ACL for the user */ |
| 329 | if (!create_acl(userInfo, &pNewACL, errp)) { |
| 330 | goto error; |
| 331 | } |
| 332 | |
| 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; |
| 339 | } |
| 340 | |
| 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; |
| 346 | } |
| 347 | |
| 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; |
| 353 | |
| 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; |
| 360 | } |
| 361 | |
| 362 | /* Free memory */ |
| 363 | LocalFree(pNewACL); |
| 364 | return true; |
| 365 | error: |
| 366 | LocalFree(pNewACL); |
| 367 | return false; |
| 368 | } |
| 369 | |
| 370 | /* |
| 371 | * Sets permissions on the authorized_key_file that is created. |
| 372 | * |
| 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. |
| 376 | */ |
| 377 | static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp) |
| 378 | { |
| 379 | PACL pACL = NULL; |
| 380 | PSID userPSID; |
| 381 | |
| 382 | /* Creates the access control structure */ |
| 383 | if (!create_acl(userInfo, &pACL, errp)) { |
| 384 | goto error; |
| 385 | } |
| 386 | |
| 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; |
| 393 | } |
| 394 | |
| 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; |
| 398 | |
| 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; |
| 407 | } |
| 408 | |
| 409 | LocalFree(pACL); |
| 410 | LocalFree(userPSID); |
| 411 | return true; |
| 412 | |
| 413 | error: |
| 414 | LocalFree(pACL); |
| 415 | LocalFree(userPSID); |
| 416 | |
| 417 | return false; |
| 418 | } |
| 419 | |
| 420 | /* |
| 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. |
| 427 | */ |
| 428 | static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys, |
| 429 | Error **errp) |
| 430 | { |
| 431 | g_autofree char *contents = NULL; |
| 432 | g_autoptr(GError) err = NULL; |
| 433 | |
| 434 | contents = g_strjoinv("\n", authkeys); |
| 435 | |
| 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; |
| 440 | } |
| 441 | |
| 442 | if (!set_file_permissions(userInfo, errp)) { |
| 443 | return false; |
| 444 | } |
| 445 | |
| 446 | return true; |
| 447 | } |
| 448 | |
| 449 | /* |
| 450 | * Retrieves information about a Windows user by their username |
| 451 | * |
| 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. |
| 458 | */ |
| 459 | static bool get_user_info(PWindowsUserInfo *userInfo, const char *username, |
| 460 | Error **errp) |
| 461 | { |
| 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; |
| 467 | |
| 468 | /* |
| 469 | * Converts a string to a Windows wide string since the GetNetUserInfo |
| 470 | * function requires it. |
| 471 | */ |
| 472 | wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr); |
| 473 | if (!wideUserName) { |
| 474 | goto error; |
| 475 | } |
| 476 | |
| 477 | /* allocate data */ |
| 478 | PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1); |
| 479 | |
| 480 | /* Set pointer so it can be cleaned up by the callee, even upon error. */ |
| 481 | *userInfo = uData; |
| 482 | |
| 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; |
| 491 | } |
| 492 | |
| 493 | error_setg(errp, |
| 494 | "Received unexpected error when asking for user info: Error " |
| 495 | "Code %lu", |
| 496 | result); |
| 497 | goto error; |
| 498 | } |
| 499 | |
| 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; |
| 504 | |
| 505 | char *sidStr = NULL; |
| 506 | |
| 507 | /* |
| 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. |
| 511 | */ |
| 512 | if (!ConvertSidToStringSid(psid, &sidStr)) { |
| 513 | error_setg_win32(errp, GetLastError(), |
| 514 | "failed to get SID string for user %s", username); |
| 515 | goto error; |
| 516 | } |
| 517 | |
| 518 | /* Store the SSID */ |
| 519 | uData->SSID = sidStr; |
| 520 | |
| 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; |
| 525 | } |
| 526 | |
| 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; |
| 534 | |
| 535 | /* Free */ |
| 536 | NetApiBufferFree(uBuf); |
| 537 | return true; |
| 538 | error: |
| 539 | if (uBuf) { |
| 540 | NetApiBufferFree(uBuf); |
| 541 | } |
| 542 | |
| 543 | return false; |
| 544 | } |
| 545 | |
| 546 | /* |
| 547 | * Gets the list of authorized keys for a user. |
| 548 | * |
| 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. |
| 553 | */ |
| 554 | GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username, |
| 555 | Error **errp) |
| 556 | { |
| 557 | GuestAuthorizedKeys *keys = NULL; |
| 558 | g_auto(GStrv) authKeys = NULL; |
| 559 | g_autoptr(GuestAuthorizedKeys) ret = NULL; |
| 560 | g_auto(PWindowsUserInfo) userInfo = NULL; |
| 561 | |
| 562 | /* Gets user information */ |
| 563 | if (!get_user_info(&userInfo, username, errp)) { |
| 564 | return NULL; |
| 565 | } |
| 566 | |
| 567 | /* Reads authkeys for the user */ |
| 568 | authKeys = read_authkeys(userInfo->authorizedKeyFile, errp); |
| 569 | if (authKeys == NULL) { |
| 570 | return NULL; |
| 571 | } |
| 572 | |
| 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; |
| 579 | } |
| 580 | |
| 581 | QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i])); |
| 582 | } |
| 583 | |
| 584 | /* |
| 585 | * Steal the pointer because it is up for the callee to deallocate the |
| 586 | * memory. |
| 587 | */ |
| 588 | keys = g_steal_pointer(&ret); |
| 589 | return keys; |
| 590 | } |
| 591 | |
| 592 | /* |
| 593 | * Adds an ssh key for a user. |
| 594 | * |
| 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. |
| 602 | */ |
| 603 | void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, |
| 604 | bool has_reset, bool reset, Error **errp) |
| 605 | { |
| 606 | g_auto(PWindowsUserInfo) userInfo = NULL; |
| 607 | g_auto(GStrv) authkeys = NULL; |
| 608 | strList *k; |
| 609 | size_t nkeys, nauthkeys; |
| 610 | |
| 611 | /* Make sure the keys given are valid */ |
| 612 | if (!check_openssh_pub_keys(keys, &nkeys, errp)) { |
| 613 | return; |
| 614 | } |
| 615 | |
| 616 | /* Gets user information */ |
| 617 | if (!get_user_info(&userInfo, username, errp)) { |
| 618 | return; |
| 619 | } |
| 620 | |
| 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); |
| 626 | } |
| 627 | |
| 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; |
| 633 | } |
| 634 | } |
| 635 | |
| 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 *)); |
| 639 | |
| 640 | /* zero out the memory for the reallocated buffer */ |
| 641 | memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *)); |
| 642 | |
| 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; |
| 648 | } |
| 649 | |
| 650 | authkeys[nauthkeys++] = g_strdup(k->value); |
| 651 | } |
| 652 | |
| 653 | /* Write the authkeys to the file. */ |
| 654 | write_authkeys(userInfo, authkeys, errp); |
| 655 | } |
| 656 | |
| 657 | /* |
| 658 | * Removes an SSH key for a user |
| 659 | * |
| 660 | * parameters: |
| 661 | * username -> Username to remove the key from |
| 662 | * strList -> List of strings to remove |
| 663 | * errp -> Contains any errors that occur. |
| 664 | */ |
| 665 | void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys, |
| 666 | Error **errp) |
| 667 | { |
| 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; |
| 674 | |
| 675 | /* Validates the keys passed in by the user */ |
| 676 | if (!check_openssh_pub_keys(keys, NULL, errp)) { |
| 677 | return; |
| 678 | } |
| 679 | |
| 680 | /* Gets user information */ |
| 681 | if (!get_user_info(&userInfo, username, errp)) { |
| 682 | return; |
| 683 | } |
| 684 | |
| 685 | /* Reads the authkeys for the user */ |
| 686 | authkeys = read_authkeys(userInfo->authorizedKeyFile, errp); |
| 687 | if (authkeys == NULL) { |
| 688 | return; |
| 689 | } |
| 690 | |
| 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; |
| 695 | |
| 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; |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | if (k != NULL) { |
| 704 | continue; |
| 705 | } |
| 706 | |
| 707 | new_keys[nkeys++] = *a; |
| 708 | } |
| 709 | |
| 710 | /* Write the new authkeys to the file. */ |
| 711 | write_authkeys(userInfo, new_keys, errp); |
| 712 | } |