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