xref: /openbmc/qemu/qga/commands-posix-ssh.c (revision 8d769ec777dccbff199711aba43aa6297fe4a0e0)
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     ERRP_GUARD();
49 
50     p = g_unix_get_passwd_entry_qemu(username, &err);
51     if (p == NULL) {
52         error_setg(errp, "failed to lookup user '%s': %s",
53                    username, err->message);
54         return NULL;
55     }
56 
57     return p;
58 }
59 
60 static bool
61 mkdir_for_user(const char *path, const struct passwd *p,
62                mode_t mode, Error **errp)
63 {
64     ERRP_GUARD();
65 
66     if (g_mkdir(path, mode) == -1) {
67         error_setg(errp, "failed to create directory '%s': %s",
68                    path, g_strerror(errno));
69         return false;
70     }
71 
72     if (chown(path, p->pw_uid, p->pw_gid) == -1) {
73         error_setg(errp, "failed to set ownership of directory '%s': %s",
74                    path, g_strerror(errno));
75         return false;
76     }
77 
78     if (chmod(path, mode) == -1) {
79         error_setg(errp, "failed to set permissions of directory '%s': %s",
80                    path, g_strerror(errno));
81         return false;
82     }
83 
84     return true;
85 }
86 
87 static bool
88 check_openssh_pub_key(const char *key, Error **errp)
89 {
90     ERRP_GUARD();
91 
92     /* simple sanity-check, we may want more? */
93     if (!key || key[0] == '#' || strchr(key, '\n')) {
94         error_setg(errp, "invalid OpenSSH public key: '%s'", key);
95         return false;
96     }
97 
98     return true;
99 }
100 
101 static bool
102 check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp)
103 {
104     size_t n = 0;
105     strList *k;
106 
107     ERRP_GUARD();
108 
109     for (k = keys; k != NULL; k = k->next) {
110         if (!check_openssh_pub_key(k->value, errp)) {
111             return false;
112         }
113         n++;
114     }
115 
116     if (nkeys) {
117         *nkeys = n;
118     }
119     return true;
120 }
121 
122 static bool
123 write_authkeys(const char *path, const GStrv keys,
124                const struct passwd *p, Error **errp)
125 {
126     g_autofree char *contents = NULL;
127     g_autoptr(GError) err = NULL;
128 
129     ERRP_GUARD();
130 
131     contents = g_strjoinv("\n", keys);
132     if (!g_file_set_contents(path, contents, -1, &err)) {
133         error_setg(errp, "failed to write to '%s': %s", path, err->message);
134         return false;
135     }
136 
137     if (chown(path, p->pw_uid, p->pw_gid) == -1) {
138         error_setg(errp, "failed to set ownership of directory '%s': %s",
139                    path, g_strerror(errno));
140         return false;
141     }
142 
143     if (chmod(path, 0600) == -1) {
144         error_setg(errp, "failed to set permissions of '%s': %s",
145                    path, g_strerror(errno));
146         return false;
147     }
148 
149     return true;
150 }
151 
152 static GStrv
153 read_authkeys(const char *path, Error **errp)
154 {
155     g_autoptr(GError) err = NULL;
156     g_autofree char *contents = NULL;
157 
158     ERRP_GUARD();
159 
160     if (!g_file_get_contents(path, &contents, NULL, &err)) {
161         error_setg(errp, "failed to read '%s': %s", path, err->message);
162         return NULL;
163     }
164 
165     return g_strsplit(contents, "\n", -1);
166 
167 }
168 
169 void
170 qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
171                                   Error **errp)
172 {
173     g_autofree struct passwd *p = NULL;
174     g_autofree char *ssh_path = NULL;
175     g_autofree char *authkeys_path = NULL;
176     g_auto(GStrv) authkeys = NULL;
177     strList *k;
178     size_t nkeys, nauthkeys;
179 
180     ERRP_GUARD();
181 
182     if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
183         return;
184     }
185 
186     p = get_passwd_entry(username, errp);
187     if (p == NULL) {
188         return;
189     }
190 
191     ssh_path = g_build_filename(p->pw_dir, ".ssh", NULL);
192     authkeys_path = g_build_filename(ssh_path, "authorized_keys", NULL);
193 
194     authkeys = read_authkeys(authkeys_path, NULL);
195     if (authkeys == NULL) {
196         if (!g_file_test(ssh_path, G_FILE_TEST_IS_DIR) &&
197             !mkdir_for_user(ssh_path, p, 0700, errp)) {
198             return;
199         }
200     }
201 
202     nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
203     authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
204     memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
205 
206     for (k = keys; k != NULL; k = k->next) {
207         if (g_strv_contains((const gchar * const *)authkeys, k->value)) {
208             continue;
209         }
210         authkeys[nauthkeys++] = g_strdup(k->value);
211     }
212 
213     write_authkeys(authkeys_path, authkeys, p, errp);
214 }
215 
216 void
217 qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
218                                      Error **errp)
219 {
220     g_autofree struct passwd *p = NULL;
221     g_autofree char *authkeys_path = NULL;
222     g_autofree GStrv new_keys = NULL; /* do not own the strings */
223     g_auto(GStrv) authkeys = NULL;
224     GStrv a;
225     size_t nkeys = 0;
226 
227     ERRP_GUARD();
228 
229     if (!check_openssh_pub_keys(keys, NULL, errp)) {
230         return;
231     }
232 
233     p = get_passwd_entry(username, errp);
234     if (p == NULL) {
235         return;
236     }
237 
238     authkeys_path = g_build_filename(p->pw_dir, ".ssh",
239                                      "authorized_keys", NULL);
240     if (!g_file_test(authkeys_path, G_FILE_TEST_EXISTS)) {
241         return;
242     }
243     authkeys = read_authkeys(authkeys_path, errp);
244     if (authkeys == NULL) {
245         return;
246     }
247 
248     new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
249     for (a = authkeys; *a != NULL; a++) {
250         strList *k;
251 
252         for (k = keys; k != NULL; k = k->next) {
253             if (g_str_equal(k->value, *a)) {
254                 break;
255             }
256         }
257         if (k != NULL) {
258             continue;
259         }
260 
261         new_keys[nkeys++] = *a;
262     }
263 
264     write_authkeys(authkeys_path, new_keys, p, errp);
265 }
266 
267 
268 #ifdef QGA_BUILD_UNIT_TEST
269 #if GLIB_CHECK_VERSION(2, 60, 0)
270 static const strList test_key2 = {
271     .value = (char *)"algo key2 comments"
272 };
273 
274 static const strList test_key1_2 = {
275     .value = (char *)"algo key1 comments",
276     .next = (strList *)&test_key2,
277 };
278 
279 static char *
280 test_get_authorized_keys_path(void)
281 {
282     return g_build_filename(g_get_home_dir(), ".ssh", "authorized_keys", NULL);
283 }
284 
285 static void
286 test_authorized_keys_set(const char *contents)
287 {
288     g_autoptr(GError) err = NULL;
289     g_autofree char *path = NULL;
290     int ret;
291 
292     path = g_build_filename(g_get_home_dir(), ".ssh", NULL);
293     ret = g_mkdir_with_parents(path, 0700);
294     g_assert(ret == 0);
295     g_free(path);
296 
297     path = test_get_authorized_keys_path();
298     g_file_set_contents(path, contents, -1, &err);
299     g_assert(err == NULL);
300 }
301 
302 static void
303 test_authorized_keys_equal(const char *expected)
304 {
305     g_autoptr(GError) err = NULL;
306     g_autofree char *path = NULL;
307     g_autofree char *contents = NULL;
308 
309     path = test_get_authorized_keys_path();
310     g_file_get_contents(path, &contents, NULL, &err);
311     g_assert(err == NULL);
312 
313     g_assert(g_strcmp0(contents, expected) == 0);
314 }
315 
316 static void
317 test_invalid_user(void)
318 {
319     Error *err = NULL;
320 
321     qmp_guest_ssh_add_authorized_keys("", NULL, &err);
322     error_free_or_abort(&err);
323 
324     qmp_guest_ssh_remove_authorized_keys("", NULL, &err);
325     error_free_or_abort(&err);
326 }
327 
328 static void
329 test_invalid_key(void)
330 {
331     strList key = {
332         .value = (char *)"not a valid\nkey"
333     };
334     Error *err = NULL;
335 
336     qmp_guest_ssh_add_authorized_keys(g_get_user_name(), &key, &err);
337     error_free_or_abort(&err);
338 
339     qmp_guest_ssh_remove_authorized_keys(g_get_user_name(), &key, &err);
340     error_free_or_abort(&err);
341 }
342 
343 static void
344 test_add_keys(void)
345 {
346     Error *err = NULL;
347 
348     qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
349                                       (strList *)&test_key2, &err);
350     g_assert(err == NULL);
351 
352     test_authorized_keys_equal("algo key2 comments");
353 
354     qmp_guest_ssh_add_authorized_keys(g_get_user_name(),
355                                       (strList *)&test_key1_2, &err);
356     g_assert(err == NULL);
357 
358     /*  key2 came first, and should'nt be duplicated */
359     test_authorized_keys_equal("algo key2 comments\n"
360                                "algo key1 comments");
361 }
362 
363 static void
364 test_remove_keys(void)
365 {
366     Error *err = NULL;
367     static const char *authkeys =
368         "algo key1 comments\n"
369         /* originally duplicated */
370         "algo key1 comments\n"
371         "# a commented line\n"
372         "algo some-key another\n";
373 
374     test_authorized_keys_set(authkeys);
375     qmp_guest_ssh_remove_authorized_keys(g_get_user_name(),
376                                          (strList *)&test_key2, &err);
377     g_assert(err == NULL);
378     test_authorized_keys_equal(authkeys);
379 
380     qmp_guest_ssh_remove_authorized_keys(g_get_user_name(),
381                                          (strList *)&test_key1_2, &err);
382     g_assert(err == NULL);
383     test_authorized_keys_equal("# a commented line\n"
384                                "algo some-key another\n");
385 }
386 
387 int main(int argc, char *argv[])
388 {
389     setlocale(LC_ALL, "");
390 
391     g_test_init(&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
392 
393     g_test_add_func("/qga/ssh/invalid_user", test_invalid_user);
394     g_test_add_func("/qga/ssh/invalid_key", test_invalid_key);
395     g_test_add_func("/qga/ssh/add_keys", test_add_keys);
396     g_test_add_func("/qga/ssh/remove_keys", test_remove_keys);
397 
398     return g_test_run();
399 }
400 #else
401 int main(int argc, char *argv[])
402 {
403     g_test_message("test skipped, needs glib >= 2.60");
404     return 0;
405 }
406 #endif /* GLIB_2_60 */
407 #endif /* BUILD_UNIT_TEST */
408