commands-posix-ssh.c 13 KB

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