commands-posix-ssh.c 13 KB

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