--- /dev/null
+/*
+ * virt-login-shell.c: a shell to connect to a container
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Daniel Walsh <dwalsh@redhat.com>
+ */
+#include <config.h>
+
+#include <stdarg.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <fnmatch.h>
+
+#include "internal.h"
+#include "virerror.h"
+#include "virconf.h"
+#include "virutil.h"
+#include "virfile.h"
+#include "virprocess.h"
+#include "configmake.h"
+#include "virstring.h"
+#include "viralloc.h"
+#include "vircommand.h"
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+static ssize_t nfdlist = 0;
+static int *fdlist = NULL;
+static const char *conf_file = SYSCONFDIR "/libvirt/virt-login-shell.conf";
+
+static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom)
+{
+ size_t i;
+
+ for (i = 0; i < nfdlist; i++)
+ VIR_FORCE_CLOSE(fdlist[i]);
+ VIR_FREE(fdlist);
+ nfdlist = 0;
+ if (dom)
+ virDomainFree(dom);
+ if (conn)
+ virConnectClose(conn);
+}
+
+static int virLoginShellAllowedUser(virConfPtr conf,
+ const char *name,
+ gid_t *groups)
+{
+ virConfValuePtr p;
+ int ret = -1;
+ char *ptr = NULL;
+ size_t i;
+ char *gname = NULL;
+
+ p = virConfGetValue(conf, "allowed_users");
+ if (p && p->type == VIR_CONF_LIST) {
+ virConfValuePtr pp;
+
+ /* Calc length and check items */
+ for (pp = p->list; pp; pp = pp->next) {
+ if (pp->type != VIR_CONF_STRING) {
+ virReportSystemError(EINVAL, "%s", _("shell must be a list of strings"));
+ goto cleanup;
+ } else {
+ /*
+ If string begins with a % this indicates a linux group.
+ Check to see if the user is in the Linux Group.
+ */
+ if (pp->str[0] == '%') {
+ ptr = &pp->str[1];
+ if (!ptr)
+ continue;
+ for (i = 0; groups[i]; i++) {
+ if (!(gname = virGetGroupName(groups[i])))
+ continue;
+ if (fnmatch(ptr, gname, 0) == 0) {
+ ret = 0;
+ goto cleanup;
+ }
+ VIR_FREE(gname);
+ }
+ VIR_FREE(groups);
+ continue;
+ }
+ if (fnmatch(pp->str, name, 0) == 0) {
+ ret = 0;
+ goto cleanup;
+ }
+ }
+ }
+ }
+ virReportSystemError(EPERM, _("%s not listed as an allowed_users in %s"), name, conf_file);
+cleanup:
+ VIR_FREE(gname);
+ VIR_FREE(groups);
+ return ret;
+}
+
+static char **virLoginShellGetShellArgv(virConfPtr conf)
+{
+ size_t i;
+ char **shargv=NULL;
+ virConfValuePtr p;
+
+ p = virConfGetValue(conf, "shell");
+ if (!p)
+ return virStringSplit("/bin/sh -l", " ", 3);
+
+ if (p && p->type == VIR_CONF_LIST) {
+ size_t len;
+ virConfValuePtr pp;
+
+ /* Calc length and check items */
+ for (len = 0, pp = p->list; pp; len++, pp = pp->next) {
+ if (pp->type != VIR_CONF_STRING) {
+ virReportSystemError(EINVAL, "%s", _("shell must be a list of strings"));
+ goto error;
+ }
+ }
+
+ if (VIR_ALLOC_N(shargv, len + 1) < 0)
+ goto error;
+ for (i = 0, pp = p->list; pp; i++, pp = pp->next) {
+ if (VIR_STRDUP(shargv[i], pp->str) < 0)
+ goto error;
+ }
+ shargv[len] = NULL;
+ }
+ return shargv;
+error:
+ virStringFreeList(shargv);
+ return NULL;
+}
+
+static char *progname;
+
+/*
+ * Print usage
+ */
+static void
+usage(void)
+{
+ fprintf(stdout, _("\n"
+ "%s is a privileged program that allows non root users \n"
+ "specified in %s to join a Linux container \n"
+ "with a matching user name and launch a shell. \n"
+ "\n%s [options]\n\n"
+ " options:\n"
+ " -h | --help this help:\n\n"), progname, conf_file, progname);
+ return;
+}
+
+int
+main(int argc, char **argv)
+{
+ virConfPtr conf = NULL;
+ const char *login_shell_path = conf_file;
+ pid_t cpid;
+ int ret = EXIT_FAILURE;
+ int status;
+ int status2;
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+ char *name = NULL;
+ char **shargv = NULL;
+ virSecurityModelPtr secmodel = NULL;
+ virSecurityLabelPtr seclabel = NULL;
+ virDomainPtr dom = NULL;
+ virConnectPtr conn = NULL;
+ char *homedir = NULL;
+ int arg;
+ int longindex = -1;
+ int ngroups;
+ gid_t *groups = NULL;
+
+ struct option opt[] = {
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ if (virInitialize() < 0) {
+ fprintf(stderr, _("Failed to initialize libvirt Error Handling"));
+ return EXIT_FAILURE;
+ }
+
+ virSetErrorFunc(NULL, NULL);
+ virSetErrorLogPriorityFunc(NULL);
+
+ progname = argv[0];
+ if (!setlocale(LC_ALL, "")) {
+ perror("setlocale");
+ /* failure to setup locale is not fatal */
+ }
+ if (!bindtextdomain(PACKAGE, LOCALEDIR)) {
+ perror("bindtextdomain");
+ return ret;
+ }
+ if (!textdomain(PACKAGE)) {
+ perror("textdomain");
+ return ret;
+ }
+
+ /* The only option we support is help
+ */
+ while ((arg = getopt_long(argc, argv, "h", opt, &longindex)) != -1) {
+ switch (arg) {
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+ break;
+ }
+ }
+
+ if (argc > optind) {
+ virReportSystemError(EINVAL, _("%s takes no options"), progname);
+ errno = EINVAL;
+ goto cleanup;
+ }
+
+ if (uid == 0) {
+ virReportSystemError(EPERM, _("%s must be run by non root users"), progname);
+ goto cleanup;
+ }
+
+ name = virGetUserName(uid);
+ if (!name)
+ goto cleanup;
+
+ homedir = virGetUserDirectoryByUID(uid);
+ if (!homedir)
+ goto cleanup;
+
+ if (!(conf = virConfReadFile(login_shell_path, 0)))
+ goto cleanup;
+
+ if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0)
+ goto cleanup;
+
+ if (virLoginShellAllowedUser(conf, name, groups) < 0)
+ goto cleanup;
+
+ if (!(shargv = virLoginShellGetShellArgv(conf)))
+ goto cleanup;
+
+ conn = virConnectOpen("lxc:///");
+ if (!conn)
+ goto cleanup;
+
+ dom = virDomainLookupByName(conn, name);
+ if (!dom)
+ goto cleanup;
+
+ if (!virDomainIsActive(dom) && virDomainCreate(dom)) {
+ virErrorPtr last_error;
+ last_error = virGetLastError();
+ if (last_error->code != VIR_ERR_OPERATION_INVALID) {
+ virReportSystemError(last_error->code,_("Can't create %s container: %s"), name, virGetLastErrorMessage());
+ goto cleanup;
+ }
+ }
+
+ if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0)
+ goto cleanup;
+ if (VIR_ALLOC(secmodel) < 0)
+ goto cleanup;
+ if (VIR_ALLOC(seclabel) < 0)
+ goto cleanup;
+ if (virNodeGetSecurityModel(conn, secmodel) < 0)
+ goto cleanup;
+ if (virDomainGetSecurityLabel(dom, seclabel) < 0)
+ goto cleanup;
+
+ if (virFork(&cpid) < 0)
+ goto cleanup;
+
+ if (cpid == 0) {
+ pid_t ccpid;
+
+ /* Fork once because we don't want to affect
+ * virt-login-shell's namespace itself
+ */
+ if (virSetUIDGID(0, 0, NULL, 0) < 0)
+ return EXIT_FAILURE;
+
+ if (virDomainLxcEnterSecurityLabel(secmodel,
+ seclabel,
+ NULL,
+ 0) < 0)
+ return EXIT_FAILURE;
+
+ if (nfdlist > 0) {
+ if (virDomainLxcEnterNamespace(dom,
+ nfdlist,
+ fdlist,
+ NULL,
+ NULL,
+ 0) < 0)
+ return EXIT_FAILURE;
+ }
+
+ ret = virSetUIDGID(uid, gid, groups, ngroups);
+ VIR_FREE(groups);
+ if (ret < 0)
+ return EXIT_FAILURE;
+
+ if (virFork(&ccpid) < 0)
+ return EXIT_FAILURE;
+
+ if (ccpid == 0) {
+ if (chdir(homedir) < 0) {
+ virReportSystemError(errno, _("Unable chdir(%s)"), homedir);
+ return EXIT_FAILURE;
+ }
+ if (execv(shargv[0], (char *const*) shargv) < 0) {
+ virReportSystemError(errno, _("Unable exec shell %s"), shargv[0]);
+ return -errno;
+ }
+ }
+ return virProcessWait(ccpid, &status2);
+ }
+ ret = virProcessWait(cpid, &status);
+
+cleanup:
+ virConfFree(conf);
+ virLoginShellFini(conn, dom);
+ virStringFreeList(shargv);
+ VIR_FREE(name);
+ VIR_FREE(homedir);
+ VIR_FREE(seclabel);
+ VIR_FREE(secmodel);
+ VIR_FREE(groups);
+ if (ret)
+ virDispatchError(NULL);
+ return ret;
+}
--- /dev/null
+=head1 NAME
+
+virt-login-shell - tool to execute a shell within a container matching the users name
+
+=head1 SYNOPSIS
+
+B<virt-login-shell>
+
+=head1 DESCRIPTION
+
+The B<virt-login-shell> program is setuid shell that is used to join
+an LXC container that matches the users name. If the container is not
+running virt-login-shell will attempt to start the container.
+virt-sandbox-shell is not allowed to be run by root. Normal users will get
+added to a container that matches their username, if it exists. And they are
+configured in /etc/libvirt/virt-login-shell.conf.
+
+The basic structure of most virt-login-shell usage is:
+
+ virt-login-shell
+
+=head1 CONFIG
+
+By default, virt-login-shell will execute the /bin/sh program for the user.
+You can modify this behaviour by defining the shell variable in /etc/libvirt/virt-login-shell.conf.
+
+eg. shell = [ "/bin/ksh", "--login"]
+
+By default no users are allowed to user virt-login-shell, if you want to allow
+certain users to use virt-login-shell, you need to modify the allowed_users variable in /etc/libvirt/virt-login-shell.conf.
+
+eg. allowed_users = [ "tom", "dick", "harry" ]
+
+=head1 BUGS
+
+Report any bugs discovered to the libvirt community via the mailing
+list C<http://libvirt.org/contact.html> or bug tracker C<http://libvirt.org/bugs.html>.
+Alternatively report bugs to your software distributor / vendor.
+
+=head1 AUTHORS
+
+ Please refer to the AUTHORS file distributed with libvirt.
+
+ Daniel Walsh <dwalsh at redhat dot com>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2013 Red Hat, Inc., and the authors listed in the
+libvirt AUTHORS file.
+
+=head1 LICENSE
+
+virt-login-shell is distributed under the terms of the GNU LGPL v2+.
+This is free software; see the source for copying conditions. There
+is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE
+
+=head1 SEE ALSO
+
+L<virsh(1)>, L<http://www.libvirt.org/>
+
+=cut