You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

431 lines
12 KiB

  1. /*
  2. * virt-login-shell-helper.c: a shell to connect to a container
  3. *
  4. * Copyright (C) 2013-2014 Red Hat, Inc.
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this library. If not, see
  18. * <http://www.gnu.org/licenses/>.
  19. */
  20. #include <config.h>
  21. #include <getopt.h>
  22. #include <signal.h>
  23. #include <stdarg.h>
  24. #include <unistd.h>
  25. #include "internal.h"
  26. #include "virerror.h"
  27. #include "virconf.h"
  28. #include "virutil.h"
  29. #include "virfile.h"
  30. #include "virprocess.h"
  31. #include "configmake.h"
  32. #include "virstring.h"
  33. #include "viralloc.h"
  34. #include "vircommand.h"
  35. #include "virgettext.h"
  36. #define VIR_FROM_THIS VIR_FROM_NONE
  37. static const char *conf_file = SYSCONFDIR "/libvirt/virt-login-shell.conf";
  38. static int virLoginShellAllowedUser(virConfPtr conf,
  39. const char *name,
  40. gid_t *groups,
  41. size_t ngroups)
  42. {
  43. int ret = -1;
  44. size_t i;
  45. char *gname = NULL;
  46. char **users = NULL, **entries;
  47. if (virConfGetValueStringList(conf, "allowed_users", false, &users) < 0)
  48. goto cleanup;
  49. for (entries = users; entries && *entries; entries++) {
  50. char *entry = *entries;
  51. /*
  52. If string begins with a % this indicates a linux group.
  53. Check to see if the user is in the Linux Group.
  54. */
  55. if (entry[0] == '%') {
  56. entry++;
  57. if (!*entry)
  58. continue;
  59. for (i = 0; i < ngroups; i++) {
  60. if (!(gname = virGetGroupName(groups[i])))
  61. continue;
  62. if (g_pattern_match_simple(entry, gname)) {
  63. ret = 0;
  64. goto cleanup;
  65. }
  66. VIR_FREE(gname);
  67. }
  68. } else {
  69. if (g_pattern_match_simple(entry, name)) {
  70. ret = 0;
  71. goto cleanup;
  72. }
  73. }
  74. }
  75. virReportSystemError(EPERM,
  76. _("%s not matched against 'allowed_users' in %s"),
  77. name, conf_file);
  78. cleanup:
  79. VIR_FREE(gname);
  80. virStringListFree(users);
  81. return ret;
  82. }
  83. static int virLoginShellGetShellArgv(virConfPtr conf,
  84. char ***shargv,
  85. size_t *shargvlen)
  86. {
  87. int rv;
  88. if ((rv = virConfGetValueStringList(conf, "shell", true, shargv)) < 0)
  89. return -1;
  90. if (rv == 0) {
  91. if (VIR_ALLOC_N(*shargv, 2) < 0)
  92. return -1;
  93. (*shargv)[0] = g_strdup("/bin/sh");
  94. *shargvlen = 1;
  95. } else {
  96. *shargvlen = virStringListLength((const char *const *)shargv);
  97. }
  98. return 0;
  99. }
  100. static char *progname;
  101. /*
  102. * Print usage
  103. */
  104. static void
  105. usage(void)
  106. {
  107. fprintf(stdout,
  108. _("\n"
  109. "Usage:\n"
  110. " %s [option]\n\n"
  111. "Options:\n"
  112. " -h | --help Display program help\n"
  113. " -V | --version Display program version\n"
  114. " -c CMD Run CMD via shell\n"
  115. "\n"
  116. "libvirt login shell\n"),
  117. progname);
  118. return;
  119. }
  120. /* Display version information. */
  121. static void
  122. show_version(void)
  123. {
  124. printf("%s (%s) %s\n", progname, PACKAGE_NAME, PACKAGE_VERSION);
  125. }
  126. static void
  127. hideErrorFunc(void *opaque G_GNUC_UNUSED,
  128. virErrorPtr err G_GNUC_UNUSED)
  129. {
  130. }
  131. int
  132. main(int argc, char **argv)
  133. {
  134. g_autoptr(virConf) conf = NULL;
  135. const char *login_shell_path = conf_file;
  136. pid_t cpid = -1;
  137. int ret = EXIT_CANCELED;
  138. int status;
  139. unsigned long long uidval;
  140. unsigned long long gidval;
  141. uid_t uid;
  142. gid_t gid;
  143. char *name = NULL;
  144. char **shargv = NULL;
  145. size_t shargvlen = 0;
  146. char *shcmd = NULL;
  147. virSecurityModelPtr secmodel = NULL;
  148. virSecurityLabelPtr seclabel = NULL;
  149. virDomainPtr dom = NULL;
  150. virConnectPtr conn = NULL;
  151. char *homedir = NULL;
  152. int arg;
  153. int longindex = -1;
  154. int ngroups;
  155. gid_t *groups = NULL;
  156. ssize_t nfdlist = 0;
  157. int *fdlist = NULL;
  158. int openmax;
  159. size_t i;
  160. const char *cmdstr = NULL;
  161. char *tmp;
  162. char *term = NULL;
  163. virErrorPtr saved_err = NULL;
  164. bool autoshell = false;
  165. struct option opt[] = {
  166. {"help", no_argument, NULL, 'h'},
  167. {"version", optional_argument, NULL, 'V'},
  168. {NULL, 0, NULL, 0}
  169. };
  170. if (virInitialize() < 0) {
  171. fprintf(stderr, _("Failed to initialize libvirt error handling"));
  172. return EXIT_CANCELED;
  173. }
  174. virSetErrorFunc(NULL, hideErrorFunc);
  175. virSetErrorLogPriorityFunc(NULL);
  176. progname = argv[0];
  177. if (virGettextInitialize() < 0)
  178. return ret;
  179. if (geteuid() != 0) {
  180. fprintf(stderr, _("%s: must be run as root\n"), argv[0]);
  181. return ret;
  182. }
  183. if (getuid() != 0) {
  184. fprintf(stderr, _("%s: must not be run setuid root\n"), argv[0]);
  185. return ret;
  186. }
  187. while ((arg = getopt_long(argc, argv, "hVc:", opt, &longindex)) != -1) {
  188. switch (arg) {
  189. case 'h':
  190. usage();
  191. exit(EXIT_SUCCESS);
  192. case 'V':
  193. show_version();
  194. exit(EXIT_SUCCESS);
  195. case 'c':
  196. cmdstr = optarg;
  197. break;
  198. case '?':
  199. default:
  200. usage();
  201. exit(EXIT_CANCELED);
  202. }
  203. }
  204. if (optind != (argc - 2)) {
  205. virReportSystemError(EINVAL, _("%s expects UID and GID parameters"), progname);
  206. goto cleanup;
  207. }
  208. if (virStrToLong_ull(argv[optind], NULL, 10, &uidval) < 0 ||
  209. ((uid_t)uidval) != uidval) {
  210. virReportSystemError(EINVAL, _("%s cannot parse UID '%s'"),
  211. progname, argv[optind]);
  212. goto cleanup;
  213. }
  214. optind++;
  215. if (virStrToLong_ull(argv[optind], NULL, 10, &gidval) < 0 ||
  216. ((gid_t)gidval) != gidval) {
  217. virReportSystemError(EINVAL, _("%s cannot parse GID '%s'"),
  218. progname, argv[optind]);
  219. goto cleanup;
  220. }
  221. uid = (uid_t)uidval;
  222. gid = (gid_t)gidval;
  223. name = virGetUserName(uid);
  224. if (!name)
  225. goto cleanup;
  226. homedir = virGetUserDirectoryByUID(uid);
  227. if (!homedir)
  228. goto cleanup;
  229. if (!(conf = virConfReadFile(login_shell_path, 0)))
  230. goto cleanup;
  231. if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0)
  232. goto cleanup;
  233. if (virLoginShellAllowedUser(conf, name, groups, ngroups) < 0)
  234. goto cleanup;
  235. if (virLoginShellGetShellArgv(conf, &shargv, &shargvlen) < 0)
  236. goto cleanup;
  237. if (virConfGetValueBool(conf, "auto_shell", &autoshell) < 0)
  238. goto cleanup;
  239. conn = virConnectOpen("lxc:///system");
  240. if (!conn)
  241. goto cleanup;
  242. dom = virDomainLookupByName(conn, name);
  243. if (!dom)
  244. goto cleanup;
  245. if (!virDomainIsActive(dom) && virDomainCreate(dom) < 0) {
  246. virErrorPtr last_error;
  247. last_error = virGetLastError();
  248. if (last_error->code != VIR_ERR_OPERATION_INVALID) {
  249. virReportSystemError(last_error->code,
  250. _("Can't create %s container: %s"),
  251. name, last_error->message);
  252. goto cleanup;
  253. }
  254. }
  255. openmax = sysconf(_SC_OPEN_MAX);
  256. if (openmax < 0) {
  257. virReportSystemError(errno, "%s",
  258. _("sysconf(_SC_OPEN_MAX) failed"));
  259. goto cleanup;
  260. }
  261. if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0)
  262. goto cleanup;
  263. if (VIR_ALLOC(secmodel) < 0)
  264. goto cleanup;
  265. if (VIR_ALLOC(seclabel) < 0)
  266. goto cleanup;
  267. if (virNodeGetSecurityModel(conn, secmodel) < 0)
  268. goto cleanup;
  269. if (virDomainGetSecurityLabel(dom, seclabel) < 0)
  270. goto cleanup;
  271. if (virSetUIDGID(0, 0, NULL, 0) < 0)
  272. goto cleanup;
  273. if (virDomainLxcEnterSecurityLabel(secmodel, seclabel, NULL, 0) < 0)
  274. goto cleanup;
  275. if (virDomainLxcEnterCGroup(dom, 0) < 0)
  276. goto cleanup;
  277. if (nfdlist > 0 &&
  278. virDomainLxcEnterNamespace(dom, nfdlist, fdlist, NULL, NULL, 0) < 0)
  279. goto cleanup;
  280. if (virSetUIDGID(uid, gid, groups, ngroups) < 0)
  281. goto cleanup;
  282. if (chdir(homedir) < 0) {
  283. virReportSystemError(errno, _("Unable to chdir(%s)"), homedir);
  284. goto cleanup;
  285. }
  286. if (autoshell) {
  287. tmp = virGetUserShell(uid);
  288. if (tmp) {
  289. virStringListFree(shargv);
  290. shargvlen = 1;
  291. if (VIR_ALLOC_N(shargv[0], shargvlen + 1) < 0) {
  292. VIR_FREE(tmp);
  293. goto cleanup;
  294. }
  295. shargv[0] = tmp;
  296. shargv[1] = NULL;
  297. }
  298. }
  299. if (cmdstr) {
  300. if (VIR_REALLOC_N(shargv, shargvlen + 3) < 0)
  301. goto cleanup;
  302. shargv[shargvlen++] = g_strdup("-c");
  303. shargv[shargvlen++] = g_strdup(cmdstr);
  304. shargv[shargvlen] = NULL;
  305. }
  306. /* We need to modify the first elementin shargv
  307. * so that it has the relative filename and has
  308. * a leading '-' to indicate it is a login shell
  309. */
  310. shcmd = shargv[0];
  311. if (shcmd[0] != '/') {
  312. virReportSystemError(errno,
  313. _("Shell '%s' should have absolute path"),
  314. shcmd);
  315. goto cleanup;
  316. }
  317. tmp = strrchr(shcmd, '/');
  318. shargv[0] = g_strdup(tmp);
  319. shargv[0][0] = '-';
  320. /* We're duping the string because the clearenv()
  321. * call will shortly release the pointer we get
  322. * back from getenv() right here */
  323. term = g_strdup(getenv("TERM"));
  324. /* A fork is required to create new process in correct pid namespace. */
  325. if ((cpid = virFork()) < 0)
  326. goto cleanup;
  327. if (cpid == 0) {
  328. int tmpfd;
  329. for (i = 3; i < openmax; i++) {
  330. tmpfd = i;
  331. VIR_MASS_CLOSE(tmpfd);
  332. }
  333. clearenv();
  334. g_setenv("PATH", "/bin:/usr/bin", TRUE);
  335. g_setenv("SHELL", shcmd, TRUE);
  336. g_setenv("USER", name, TRUE);
  337. g_setenv("LOGNAME", name, TRUE);
  338. g_setenv("HOME", homedir, TRUE);
  339. if (term)
  340. g_setenv("TERM", term, TRUE);
  341. if (execv(shcmd, (char *const*) shargv) < 0) {
  342. virReportSystemError(errno, _("Unable to exec shell %s"),
  343. shcmd);
  344. virDispatchError(NULL);
  345. return errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
  346. }
  347. }
  348. /* At this point, the parent is now waiting for the child to exit,
  349. * but as that may take a long time, we release resources now. */
  350. cleanup:
  351. saved_err = virSaveLastError();
  352. if (nfdlist > 0)
  353. for (i = 0; i < nfdlist; i++)
  354. VIR_FORCE_CLOSE(fdlist[i]);
  355. VIR_FREE(fdlist);
  356. if (dom)
  357. virDomainFree(dom);
  358. if (conn)
  359. virConnectClose(conn);
  360. virStringListFree(shargv);
  361. VIR_FREE(shcmd);
  362. VIR_FREE(term);
  363. VIR_FREE(name);
  364. VIR_FREE(homedir);
  365. VIR_FREE(seclabel);
  366. VIR_FREE(secmodel);
  367. VIR_FREE(groups);
  368. if (virProcessWait(cpid, &status, true) == 0)
  369. virProcessExitWithStatus(status);
  370. if (saved_err) {
  371. virSetError(saved_err);
  372. fprintf(stderr, "%s: %s\n", argv[0], virGetLastErrorMessage());
  373. }
  374. return ret;
  375. }