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(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(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 static const strList test_key2 = { 247 .value = (char *)"algo key2 comments" 248 }; 249 250 static const strList test_key1_2 = { 251 .value = (char *)"algo key1 comments", 252 .next = (strList *)&test_key2, 253 }; 254 255 static char * 256 test_get_authorized_keys_path(void) 257 { 258 return g_build_filename(g_get_home_dir(), ".ssh", "authorized_keys", NULL); 259 } 260 261 static void 262 test_authorized_keys_set(const char *contents) 263 { 264 g_autoptr(GError) err = NULL; 265 g_autofree char *path = NULL; 266 int ret; 267 268 path = g_build_filename(g_get_home_dir(), ".ssh", NULL); 269 ret = g_mkdir_with_parents(path, 0700); 270 g_assert(ret == 0); 271 g_free(path); 272 273 path = test_get_authorized_keys_path(); 274 g_file_set_contents(path, contents, -1, &err); 275 g_assert(err == NULL); 276 } 277 278 static void 279 test_authorized_keys_equal(const char *expected) 280 { 281 g_autoptr(GError) err = NULL; 282 g_autofree char *path = NULL; 283 g_autofree char *contents = NULL; 284 285 path = test_get_authorized_keys_path(); 286 g_file_get_contents(path, &contents, NULL, &err); 287 g_assert(err == NULL); 288 289 g_assert(g_strcmp0(contents, expected) == 0); 290 } 291 292 static void 293 test_invalid_user(void) 294 { 295 Error *err = NULL; 296 297 qmp_guest_ssh_add_authorized_keys("", NULL, FALSE, FALSE, &err); 298 error_free_or_abort(&err); 299 300 qmp_guest_ssh_remove_authorized_keys("", NULL, &err); 301 error_free_or_abort(&err); 302 } 303 304 static void 305 test_invalid_key(void) 306 { 307 strList key = { 308 .value = (char *)"not a valid\nkey" 309 }; 310 Error *err = NULL; 311 312 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), &key, 313 FALSE, FALSE, &err); 314 error_free_or_abort(&err); 315 316 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), &key, &err); 317 error_free_or_abort(&err); 318 } 319 320 static void 321 test_add_keys(void) 322 { 323 Error *err = NULL; 324 325 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 326 (strList *)&test_key2, 327 FALSE, FALSE, 328 &err); 329 g_assert(err == NULL); 330 331 test_authorized_keys_equal("algo key2 comments"); 332 333 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 334 (strList *)&test_key1_2, 335 FALSE, FALSE, 336 &err); 337 g_assert(err == NULL); 338 339 /* key2 came first, and shouldn't be duplicated */ 340 test_authorized_keys_equal("algo key2 comments\n" 341 "algo key1 comments"); 342 } 343 344 static void 345 test_add_reset_keys(void) 346 { 347 Error *err = NULL; 348 349 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 350 (strList *)&test_key1_2, 351 FALSE, FALSE, 352 &err); 353 g_assert(err == NULL); 354 355 /* reset with key2 only */ 356 test_authorized_keys_equal("algo key1 comments\n" 357 "algo key2 comments"); 358 359 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 360 (strList *)&test_key2, 361 TRUE, TRUE, 362 &err); 363 g_assert(err == NULL); 364 365 test_authorized_keys_equal("algo key2 comments"); 366 367 /* empty should clear file */ 368 qmp_guest_ssh_add_authorized_keys(g_get_user_name(), 369 (strList *)NULL, 370 TRUE, TRUE, 371 &err); 372 g_assert(err == NULL); 373 374 test_authorized_keys_equal(""); 375 } 376 377 static void 378 test_remove_keys(void) 379 { 380 Error *err = NULL; 381 static const char *authkeys = 382 "algo key1 comments\n" 383 /* originally duplicated */ 384 "algo key1 comments\n" 385 "# a commented line\n" 386 "algo some-key another\n"; 387 388 test_authorized_keys_set(authkeys); 389 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), 390 (strList *)&test_key2, &err); 391 g_assert(err == NULL); 392 test_authorized_keys_equal(authkeys); 393 394 qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), 395 (strList *)&test_key1_2, &err); 396 g_assert(err == NULL); 397 test_authorized_keys_equal("# a commented line\n" 398 "algo some-key another\n"); 399 } 400 401 static void 402 test_get_keys(void) 403 { 404 Error *err = NULL; 405 static const char *authkeys = 406 "algo key1 comments\n" 407 "# a commented line\n" 408 "algo some-key another\n"; 409 g_autoptr(GuestAuthorizedKeys) ret = NULL; 410 strList *k; 411 size_t len = 0; 412 413 test_authorized_keys_set(authkeys); 414 415 ret = qmp_guest_ssh_get_authorized_keys(g_get_user_name(), &err); 416 g_assert(err == NULL); 417 418 for (len = 0, k = ret->keys; k != NULL; k = k->next) { 419 g_assert(g_str_has_prefix(k->value, "algo ")); 420 len++; 421 } 422 423 g_assert(len == 2); 424 } 425 426 int main(int argc, char *argv[]) 427 { 428 setlocale(LC_ALL, ""); 429 430 g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); 431 432 g_test_add_func("/qga/ssh/invalid_user", test_invalid_user); 433 g_test_add_func("/qga/ssh/invalid_key", test_invalid_key); 434 g_test_add_func("/qga/ssh/add_keys", test_add_keys); 435 g_test_add_func("/qga/ssh/add_reset_keys", test_add_reset_keys); 436 g_test_add_func("/qga/ssh/remove_keys", test_remove_keys); 437 g_test_add_func("/qga/ssh/get_keys", test_get_keys); 438 439 return g_test_run(); 440 } 441 #endif /* BUILD_UNIT_TEST */ 442