qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

commands-posix-ssh.c (12997B)


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