1 /* 2 * This work is licensed under the terms of the GNU GPL, version 2 or later. 3 * See the COPYING file in the top-level directory. 4 */ 5 #include "qemu/osdep.h" 6 7 #include <glib-unix.h> 8 #include <glib/gstdio.h> 9 #include <locale.h> 10 #include <pwd.h> 11 12 #include "commands-common-ssh.h" 13 #include "qapi/error.h" 14 #include "qga-qapi-commands.h" 15 16 #ifdef QGA_BUILD_UNIT_TEST 17 static struct passwd * 18 test_get_passwd_entry(const gchar *user_name, GError **error) 19 { 20 struct passwd *p; 21 int ret; 22 23 if (!user_name || g_strcmp0(user_name, g_get_user_name())) { 24 g_set_error(error, G_UNIX_ERROR, 0, "Invalid user name"); 25 return NULL; 26 } 27 28 p = g_new0(struct passwd, 1); 29 p->pw_dir = (char *)g_get_home_dir(); 30 p->pw_uid = geteuid(); 31 p->pw_gid = getegid(); 32 33 ret = g_mkdir_with_parents(p->pw_dir, 0700); 34 g_assert(ret == 0); 35 36 return p; 37 } 38 39 #define g_unix_get_passwd_entry_qemu(username, err) \ 40 test_get_passwd_entry(username, err) 41 #endif 42 43 static struct passwd * 44 get_passwd_entry(const char *username, Error **errp) 45 { 46 g_autoptr(GError) err = NULL; 47 struct passwd *p; 48 49 p = g_unix_get_passwd_entry_qemu(username, &err); 50 if (p == NULL) { 51 error_setg(errp, "failed to lookup user '%s': %s", 52 username, err->message); 53 return NULL; 54 } 55 56 return p; 57 } 58 59 static bool 60 mkdir_for_user(const char *path, const struct passwd *p, 61 mode_t mode, Error **errp) 62 { 63 if (g_mkdir(path, mode) == -1) { 64 error_setg(errp, "failed to create directory '%s': %s", 65 path, g_strerror(errno)); 66 return false; 67 } 68 69 if (chown(path, p->pw_uid, p->pw_gid) == -1) { 70 error_setg(errp, "failed to set ownership of directory '%s': %s", 71 path, g_strerror(errno)); 72 return false; 73 } 74 75 if (chmod(path, mode) == -1) { 76 error_setg(errp, "failed to set permissions of directory '%s': %s", 77 path, g_strerror(errno)); 78 return false; 79 } 80 81 return true; 82 } 83 84 static bool 85 write_authkeys(const char *path, const GStrv keys, 86 const struct passwd *p, Error **errp) 87 { 88 g_autofree char *contents = NULL; 89 g_autoptr(GError) err = NULL; 90 91 contents = g_strjoinv("\n", keys); 92 if (!g_file_set_contents(path, contents, -1, &err)) { 93 error_setg(errp, "failed to write to '%s': %s", path, err->message); 94 return false; 95 } 96 97 if (chown(path, p->pw_uid, p->pw_gid) == -1) { 98 error_setg(errp, "failed to set ownership of directory '%s': %s", 99 path, g_strerror(errno)); 100 return false; 101 } 102 103 if (chmod(path, 0600) == -1) { 104 error_setg(errp, "failed to set permissions of '%s': %s", 105 path, g_strerror(errno)); 106 return false; 107 } 108 109 return true; 110 } 111 112 void 113 qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, 114 bool has_reset, bool reset, 115 Error **errp) 116 { 117 g_autofree struct passwd *p = NULL; 118 g_autofree char *ssh_path = NULL; 119 g_autofree char *authkeys_path = NULL; 120 g_auto(GStrv) authkeys = NULL; 121 strList *k; 122 size_t nkeys, nauthkeys; 123 124 reset = has_reset && reset; 125 126 if (!check_openssh_pub_keys(keys, &nkeys, errp)) { 127 return; 128 } 129 130 p = get_passwd_entry(username, errp); 131 if (p == NULL) { 132 return; 133 } 134 135 ssh_path = g_build_filename(p->pw_dir, ".ssh", NULL); 136 authkeys_path = g_build_filename(ssh_path, "authorized_keys", NULL); 137 138 if (!reset) { 139 authkeys = read_authkeys(authkeys_path, NULL); 140 } 141 if (authkeys == NULL) { 142 if (!g_file_test(ssh_path, G_FILE_TEST_IS_DIR) && 143 !mkdir_for_user(ssh_path, p, 0700, errp)) { 144 return; 145 } 146 } 147 148 nauthkeys = authkeys ? g_strv_length(authkeys) : 0; 149 authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *)); 150 memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *)); 151 152 for (k = keys; k != NULL; k = k->next) { 153 if (g_strv_contains((const gchar * const *)authkeys, k->value)) { 154 continue; 155 } 156 authkeys[nauthkeys++] = g_strdup(k->value); 157 } 158 159 write_authkeys(authkeys_path, authkeys, p, errp); 160 } 161 162 void 163 qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys, 164 Error **errp) 165 { 166 g_autofree struct passwd *p = NULL; 167 g_autofree char *authkeys_path = NULL; 168 g_autofree GStrv new_keys = NULL; /* do not own the strings */ 169 g_auto(GStrv) authkeys = NULL; 170 GStrv a; 171 size_t nkeys = 0; 172 173 if (!check_openssh_pub_keys(keys, NULL, errp)) { 174 return; 175 } 176 177 p = get_passwd_entry(username, errp); 178 if (p == NULL) { 179 return; 180 } 181 182 authkeys_path = g_build_filename(p->pw_dir, ".ssh", 183 "authorized_keys", NULL); 184 if (!g_file_test(authkeys_path, G_FILE_TEST_EXISTS)) { 185 return; 186 } 187 authkeys = read_authkeys(authkeys_path, errp); 188 if (authkeys == NULL) { 189 return; 190 } 191 192 new_keys = g_new0(char *, g_strv_length(authkeys) + 1); 193 for (a = authkeys; *a != NULL; a++) { 194 strList *k; 195 196 for (k = keys; k != NULL; k = k->next) { 197 if (g_str_equal(k->value, *a)) { 198 break; 199 } 200 } 201 if (k != NULL) { 202 continue; 203 } 204 205 new_keys[nkeys++] = *a; 206 } 207 208 write_authkeys(authkeys_path, new_keys, p, errp); 209 } 210 211 GuestAuthorizedKeys * 212 qmp_guest_ssh_get_authorized_keys(const char *username, Error **errp) 213 { 214 g_autofree struct passwd *p = NULL; 215 g_autofree char *authkeys_path = NULL; 216 g_auto(GStrv) authkeys = NULL; 217 g_autoptr(GuestAuthorizedKeys) ret = NULL; 218 int i; 219 220 p = get_passwd_entry(username, errp); 221 if (p == NULL) { 222 return NULL; 223 } 224 225 authkeys_path = g_build_filename(p->pw_dir, ".ssh", 226 "authorized_keys", NULL); 227 authkeys = read_authkeys(authkeys_path, errp); 228 if (authkeys == NULL) { 229 return NULL; 230 } 231 232 ret = g_new0(GuestAuthorizedKeys, 1); 233 for (i = 0; authkeys[i] != NULL; i++) { 234 g_strstrip(authkeys[i]); 235 if (!authkeys[i][0] || authkeys[i][0] == '#') { 236 continue; 237 } 238 239 QAPI_LIST_PREPEND(ret->keys, g_strdup(authkeys[i])); 240 } 241 242 return g_steal_pointer(&ret); 243 } 244 245 #ifdef QGA_BUILD_UNIT_TEST 246 #if GLIB_CHECK_VERSION(2, 60, 0) 247 static const strList test_key2 = { 248 .value = (char *)"algo key2 comments" 249 }; 250 251 static const strList test_key1_2 = { 252 .value = (char *)"algo key1 comments", 253 .next = (strList *)&test_key2, 254 }; 255 256 static char * 257 test_get_authorized_keys_path(void) 258 { 259 return g_build_filename(g_get_home_dir(), ".ssh", "authorized_keys", NULL); 260 } 261 262 static void 263 test_authorized_keys_set(const char *contents) 264 { 265 g_autoptr(GError) err = NULL; 266 g_autofree char *path = NULL; 267 int ret; 268 269 path = g_build_filename(g_get_home_dir(), ".ssh", NULL); 270 ret = g_mkdir_with_parents(path, 0700); 271 g_assert(ret == 0); 272 g_free(path); 273 274 path = test_get_authorized_keys_path(); 275 g_file_set_contents(path, contents, -1, &err); 276 g_assert(err == NULL); 277 } 278 279 static void 280 test_authorized_keys_equal(const char *expected) 281 { 282 g_autoptr(GError) err = NULL; 283 g_autofree char *path = NULL; 284 g_autofree char *contents = NULL; 285 286 path = test_get_authorized_keys_path(); 287 g_file_get_contents(path, &contents, NULL, &err); 288 g_assert(err == NULL); 289 290 g_assert(g_strcmp0(contents, expected) == 0); 291 } 292 293 static void 294 test_invalid_user(void) 295 { 296 Error *err = NULL; 297 298 qmp_guest_ssh_add_authorized_keys("", NULL, FALSE, FALSE, &err); 299 error_free_or_abort(&err); 300 301 qmp_guest_ssh_remove_authorized_keys("", NULL, &err); 302 error_free_or_abort(&err); 303 } 304 305 static void 306 test_invalid_key(void) 307 { 308 strList key = { 309 .value = (char *)"not a valid\nkey" 310 }; 311 Error *err = NULL; 312 313 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), &key, 314 FALSE, FALSE, &err); 315 error_free_or_abort(&err); 316 317 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), &key, &err); 318 error_free_or_abort(&err); 319 } 320 321 static void 322 test_add_keys(void) 323 { 324 Error *err = NULL; 325 326 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 327 (strList *)&test_key2, 328 FALSE, FALSE, 329 &err); 330 g_assert(err == NULL); 331 332 test_authorized_keys_equal("algo key2 comments"); 333 334 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 335 (strList *)&test_key1_2, 336 FALSE, FALSE, 337 &err); 338 g_assert(err == NULL); 339 340 /* key2 came first, and shouldn't be duplicated */ 341 test_authorized_keys_equal("algo key2 comments\n" 342 "algo key1 comments"); 343 } 344 345 static void 346 test_add_reset_keys(void) 347 { 348 Error *err = NULL; 349 350 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 351 (strList *)&test_key1_2, 352 FALSE, FALSE, 353 &err); 354 g_assert(err == NULL); 355 356 /* reset with key2 only */ 357 test_authorized_keys_equal("algo key1 comments\n" 358 "algo key2 comments"); 359 360 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 361 (strList *)&test_key2, 362 TRUE, TRUE, 363 &err); 364 g_assert(err == NULL); 365 366 test_authorized_keys_equal("algo key2 comments"); 367 368 /* empty should clear file */ 369 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 370 (strList *)NULL, 371 TRUE, TRUE, 372 &err); 373 g_assert(err == NULL); 374 375 test_authorized_keys_equal(""); 376 } 377 378 static void 379 test_remove_keys(void) 380 { 381 Error *err = NULL; 382 static const char *authkeys = 383 "algo key1 comments\n" 384 /* originally duplicated */ 385 "algo key1 comments\n" 386 "# a commented line\n" 387 "algo some-key another\n"; 388 389 test_authorized_keys_set(authkeys); 390 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), 391 (strList *)&test_key2, &err); 392 g_assert(err == NULL); 393 test_authorized_keys_equal(authkeys); 394 395 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), 396 (strList *)&test_key1_2, &err); 397 g_assert(err == NULL); 398 test_authorized_keys_equal("# a commented line\n" 399 "algo some-key another\n"); 400 } 401 402 static void 403 test_get_keys(void) 404 { 405 Error *err = NULL; 406 static const char *authkeys = 407 "algo key1 comments\n" 408 "# a commented line\n" 409 "algo some-key another\n"; 410 g_autoptr(GuestAuthorizedKeys) ret = NULL; 411 strList *k; 412 size_t len = 0; 413 414 test_authorized_keys_set(authkeys); 415 416 ret = qmp_guest_ssh_get_authorized_keys(g_get_user_name(), &err); 417 g_assert(err == NULL); 418 419 for (len = 0, k = ret->keys; k != NULL; k = k->next) { 420 g_assert(g_str_has_prefix(k->value, "algo ")); 421 len++; 422 } 423 424 g_assert(len == 2); 425 } 426 427 int main(int argc, char *argv[]) 428 { 429 setlocale(LC_ALL, ""); 430 431 g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); 432 433 g_test_add_func("/qga/ssh/invalid_user", test_invalid_user); 434 g_test_add_func("/qga/ssh/invalid_key", test_invalid_key); 435 g_test_add_func("/qga/ssh/add_keys", test_add_keys); 436 g_test_add_func("/qga/ssh/add_reset_keys", test_add_reset_keys); 437 g_test_add_func("/qga/ssh/remove_keys", test_remove_keys); 438 g_test_add_func("/qga/ssh/get_keys", test_get_keys); 439 440 return g_test_run(); 441 } 442 #else 443 int main(int argc, char *argv[]) 444 { 445 g_test_message("test skipped, needs glib >= 2.60"); 446 return 0; 447 } 448 #endif /* GLIB_2_60 */ 449 #endif /* BUILD_UNIT_TEST */ 450