virt-login-shell-helper.c 11.8 KB
Newer Older
D
Dan Walsh 已提交
1
/*
2
 * virt-login-shell-helper.c: a shell to connect to a container
D
Dan Walsh 已提交
3
 *
4
 * Copyright (C) 2013-2014 Red Hat, Inc.
D
Dan Walsh 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * 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/>.
 */
#include <config.h>

#include <fnmatch.h>
E
Eric Blake 已提交
23 24 25
#include <getopt.h>
#include <signal.h>
#include <stdarg.h>
M
Michal Privoznik 已提交
26
#include <unistd.h>
D
Dan Walsh 已提交
27 28 29 30 31 32 33 34 35 36 37

#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"
38
#include "virgettext.h"
D
Dan Walsh 已提交
39 40 41 42 43 44
#define VIR_FROM_THIS VIR_FROM_NONE

static const char *conf_file = SYSCONFDIR "/libvirt/virt-login-shell.conf";

static int virLoginShellAllowedUser(virConfPtr conf,
                                    const char *name,
45 46
                                    gid_t *groups,
                                    size_t ngroups)
D
Dan Walsh 已提交
47 48 49 50
{
    int ret = -1;
    size_t i;
    char *gname = NULL;
51
    char **users = NULL, **entries;
D
Dan Walsh 已提交
52

53 54
    if (virConfGetValueStringList(conf, "allowed_users", false, &users) < 0)
        goto cleanup;
D
Dan Walsh 已提交
55

56

57
    for (entries = users; entries && *entries; entries++) {
58 59 60 61 62 63 64 65 66 67 68
        char *entry = *entries;
        /*
          If string begins with a % this indicates a linux group.
          Check to see if the user is in the Linux Group.
        */
        if (entry[0] == '%') {
            entry++;
            if (!*entry)
                continue;
            for (i = 0; i < ngroups; i++) {
                if (!(gname = virGetGroupName(groups[i])))
D
Dan Walsh 已提交
69
                    continue;
70
                if (fnmatch(entry, gname, 0) == 0) {
D
Dan Walsh 已提交
71 72 73
                    ret = 0;
                    goto cleanup;
                }
74 75 76 77 78 79
                VIR_FREE(gname);
            }
        } else {
            if (fnmatch(entry, name, 0) == 0) {
                ret = 0;
                goto cleanup;
D
Dan Walsh 已提交
80 81 82
            }
        }
    }
83 84 85
    virReportSystemError(EPERM,
                         _("%s not matched against 'allowed_users' in %s"),
                         name, conf_file);
86
 cleanup:
D
Dan Walsh 已提交
87
    VIR_FREE(gname);
88
    virStringListFree(users);
D
Dan Walsh 已提交
89 90 91
    return ret;
}

92

93
static int virLoginShellGetShellArgv(virConfPtr conf,
94 95
                                     char ***shargv,
                                     size_t *shargvlen)
D
Dan Walsh 已提交
96
{
97 98 99
    int rv;

    if ((rv = virConfGetValueStringList(conf, "shell", true, shargv)) < 0)
100
        return -1;
101

102
    if (rv == 0) {
103 104 105 106 107
        if (VIR_ALLOC_N(*shargv, 2) < 0)
            return -1;
        if (VIR_STRDUP((*shargv)[0], "/bin/sh") < 0) {
            VIR_FREE(*shargv);
            return -1;
D
Dan Walsh 已提交
108
        }
109 110 111
        *shargvlen = 1;
    } else {
        *shargvlen = virStringListLength((const char *const *)shargv);
D
Dan Walsh 已提交
112
    }
113
    return 0;
D
Dan Walsh 已提交
114 115 116 117 118 119 120 121 122 123
}

static char *progname;

/*
 * Print usage
 */
static void
usage(void)
{
124 125 126
    fprintf(stdout,
            _("\n"
              "Usage:\n"
E
Eric Blake 已提交
127
              "  %s [option]\n\n"
128
              "Options:\n"
E
Eric Blake 已提交
129 130
              "  -h | --help            Display program help\n"
              "  -V | --version         Display program version\n"
131
              "  -c CMD                 Run CMD via shell\n"
132 133 134
              "\n"
              "libvirt login shell\n"),
            progname);
D
Dan Walsh 已提交
135 136 137
    return;
}

138 139 140 141 142 143 144 145
/* Display version information. */
static void
show_version(void)
{
    printf("%s (%s) %s\n", progname, PACKAGE_NAME, PACKAGE_VERSION);
}


146 147 148 149 150 151
static void
hideErrorFunc(void *opaque ATTRIBUTE_UNUSED,
              virErrorPtr err ATTRIBUTE_UNUSED)
{
}

D
Dan Walsh 已提交
152 153 154 155 156
int
main(int argc, char **argv)
{
    virConfPtr conf = NULL;
    const char *login_shell_path = conf_file;
E
Eric Blake 已提交
157 158
    pid_t cpid = -1;
    int ret = EXIT_CANCELED;
D
Dan Walsh 已提交
159
    int status;
160 161 162 163
    unsigned long long uidval;
    unsigned long long gidval;
    uid_t uid;
    gid_t gid;
D
Dan Walsh 已提交
164 165
    char *name = NULL;
    char **shargv = NULL;
166
    size_t shargvlen = 0;
167
    char *shcmd = NULL;
D
Dan Walsh 已提交
168 169 170 171 172 173 174 175 176
    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;
177 178 179 180
    ssize_t nfdlist = 0;
    int *fdlist = NULL;
    int openmax;
    size_t i;
181
    const char *cmdstr = NULL;
182
    char *tmp;
183
    char *term = NULL;
184
    virErrorPtr saved_err = NULL;
185
    bool autoshell = false;
D
Dan Walsh 已提交
186 187 188

    struct option opt[] = {
        {"help", no_argument, NULL, 'h'},
189
        {"version", optional_argument, NULL, 'V'},
D
Dan Walsh 已提交
190 191 192
        {NULL, 0, NULL, 0}
    };
    if (virInitialize() < 0) {
E
Eric Blake 已提交
193 194
        fprintf(stderr, _("Failed to initialize libvirt error handling"));
        return EXIT_CANCELED;
D
Dan Walsh 已提交
195 196
    }

197
    virSetErrorFunc(NULL, hideErrorFunc);
D
Dan Walsh 已提交
198 199 200
    virSetErrorLogPriorityFunc(NULL);

    progname = argv[0];
201
    if (virGettextInitialize() < 0)
D
Dan Walsh 已提交
202 203
        return ret;

204 205 206 207 208 209 210 211 212 213
    if (geteuid() != 0) {
        fprintf(stderr, _("%s: must be run as root\n"), argv[0]);
        return ret;
    }

    if (getuid() != 0) {
        fprintf(stderr, _("%s: must not be run setuid root\n"), argv[0]);
        return ret;
    }

214
    while ((arg = getopt_long(argc, argv, "hVc:", opt, &longindex)) != -1) {
D
Dan Walsh 已提交
215 216 217 218
        switch (arg) {
        case 'h':
            usage();
            exit(EXIT_SUCCESS);
219 220 221 222 223

        case 'V':
            show_version();
            exit(EXIT_SUCCESS);

224 225 226 227
        case 'c':
            cmdstr = optarg;
            break;

228 229 230
        case '?':
        default:
            usage();
E
Eric Blake 已提交
231
            exit(EXIT_CANCELED);
D
Dan Walsh 已提交
232 233 234
        }
    }

235 236
    if (optind != (argc - 2)) {
        virReportSystemError(EINVAL, _("%s expects UID and GID parameters"), progname);
D
Dan Walsh 已提交
237 238 239
        goto cleanup;
    }

240 241 242 243
    if (virStrToLong_ull(argv[optind], NULL, 10, &uidval) < 0 ||
        ((uid_t)uidval) != uidval) {
        virReportSystemError(EINVAL, _("%s cannot parse UID '%s'"),
                             progname, argv[optind]);
D
Dan Walsh 已提交
244 245 246
        goto cleanup;
    }

247 248 249 250 251 252 253 254 255 256 257
    optind++;
    if (virStrToLong_ull(argv[optind], NULL, 10, &gidval) < 0 ||
        ((gid_t)gidval) != gidval) {
        virReportSystemError(EINVAL, _("%s cannot parse GID '%s'"),
                             progname, argv[optind]);
        goto cleanup;
    }

    uid = (uid_t)uidval;
    gid = (gid_t)gidval;

D
Dan Walsh 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270 271
    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;

272
    if (virLoginShellAllowedUser(conf, name, groups, ngroups) < 0)
D
Dan Walsh 已提交
273 274
        goto cleanup;

275
    if (virLoginShellGetShellArgv(conf, &shargv, &shargvlen) < 0)
D
Dan Walsh 已提交
276 277
        goto cleanup;

278
    if (virConfGetValueBool(conf, "auto_shell", &autoshell) < 0)
279 280
        goto cleanup;

281
    conn = virConnectOpen("lxc:///system");
D
Dan Walsh 已提交
282 283 284 285 286 287 288
    if (!conn)
        goto cleanup;

    dom = virDomainLookupByName(conn, name);
    if (!dom)
        goto cleanup;

J
John Ferlan 已提交
289
    if (!virDomainIsActive(dom) && virDomainCreate(dom) < 0) {
D
Dan Walsh 已提交
290 291 292
        virErrorPtr last_error;
        last_error = virGetLastError();
        if (last_error->code != VIR_ERR_OPERATION_INVALID) {
293 294 295
            virReportSystemError(last_error->code,
                                 _("Can't create %s container: %s"),
                                 name, last_error->message);
D
Dan Walsh 已提交
296 297 298 299
            goto cleanup;
        }
    }

300 301 302 303 304 305 306
    openmax = sysconf(_SC_OPEN_MAX);
    if (openmax < 0) {
        virReportSystemError(errno,  "%s",
                             _("sysconf(_SC_OPEN_MAX) failed"));
        goto cleanup;
    }

D
Dan Walsh 已提交
307 308 309 310 311 312 313 314 315 316
    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;
317 318 319 320
    if (virSetUIDGID(0, 0, NULL, 0) < 0)
        goto cleanup;
    if (virDomainLxcEnterSecurityLabel(secmodel, seclabel, NULL, 0) < 0)
        goto cleanup;
321 322
    if (virDomainLxcEnterCGroup(dom, 0) < 0)
        goto cleanup;
323 324 325 326 327 328 329 330 331
    if (nfdlist > 0 &&
        virDomainLxcEnterNamespace(dom, nfdlist, fdlist, NULL, NULL, 0) < 0)
        goto cleanup;
    if (virSetUIDGID(uid, gid, groups, ngroups) < 0)
        goto cleanup;
    if (chdir(homedir) < 0) {
        virReportSystemError(errno, _("Unable to chdir(%s)"), homedir);
        goto cleanup;
    }
D
Dan Walsh 已提交
332

333 334 335
    if (autoshell) {
        tmp = virGetUserShell(uid);
        if (tmp) {
336
            virStringListFree(shargv);
337 338 339 340 341 342 343 344 345 346
            shargvlen = 1;
            if (VIR_ALLOC_N(shargv[0], shargvlen + 1) < 0) {
                VIR_FREE(tmp);
                goto cleanup;
            }
            shargv[0] = tmp;
            shargv[1] = NULL;
        }
    }

347 348 349 350 351 352 353 354 355 356
    if (cmdstr) {
        if (VIR_REALLOC_N(shargv, shargvlen + 3) < 0)
            goto cleanup;
        if (VIR_STRDUP(shargv[shargvlen++], "-c") < 0)
            goto cleanup;
        if (VIR_STRDUP(shargv[shargvlen++], cmdstr) < 0)
            goto cleanup;
        shargv[shargvlen] = NULL;
    }

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    /* We need to modify the first elementin shargv
     * so that it has the relative filename and has
     * a leading '-' to indicate it is a login shell
     */
    shcmd = shargv[0];
    if (shcmd[0] != '/') {
        virReportSystemError(errno,
                             _("Shell '%s' should have absolute path"),
                             shcmd);
        goto cleanup;
    }
    tmp = strrchr(shcmd, '/');
    if (VIR_STRDUP(shargv[0], tmp) < 0)
        goto cleanup;
    shargv[0][0] = '-';

373 374 375 376 377 378
    /* We're duping the string because the clearenv()
     * call will shortly release the pointer we get
     * back from virGetEnvAllowSUID() right here */
    if (VIR_STRDUP(term, virGetEnvAllowSUID("TERM")) < 0)
        goto cleanup;

379
    /* A fork is required to create new process in correct pid namespace.  */
E
Eric Blake 已提交
380
    if ((cpid = virFork()) < 0)
D
Dan Walsh 已提交
381 382 383
        goto cleanup;

    if (cpid == 0) {
384
        int tmpfd;
385

386 387
        for (i = 3; i < openmax; i++) {
            tmpfd = i;
388 389
            VIR_MASS_CLOSE(tmpfd);
        }
390 391 392 393 394 395 396 397 398 399

        clearenv();
        setenv("PATH", "/bin:/usr/bin", 1);
        setenv("SHELL", shcmd, 1);
        setenv("USER", name, 1);
        setenv("LOGNAME", name, 1);
        setenv("HOME", homedir, 1);
        if (term)
            setenv("TERM", term, 1);

400
        if (execv(shcmd, (char *const*) shargv) < 0) {
401
            virReportSystemError(errno, _("Unable to exec shell %s"),
402
                                 shcmd);
E
Eric Blake 已提交
403 404
            virDispatchError(NULL);
            return errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
D
Dan Walsh 已提交
405 406
        }
    }
407

E
Eric Blake 已提交
408 409
    /* At this point, the parent is now waiting for the child to exit,
     * but as that may take a long time, we release resources now.  */
410
 cleanup:
411 412
    saved_err = virSaveLastError();

413 414 415
    if (nfdlist > 0)
        for (i = 0; i < nfdlist; i++)
            VIR_FORCE_CLOSE(fdlist[i]);
416
    VIR_FREE(fdlist);
D
Dan Walsh 已提交
417
    virConfFree(conf);
418 419 420 421
    if (dom)
        virDomainFree(dom);
    if (conn)
        virConnectClose(conn);
422
    virStringListFree(shargv);
423
    VIR_FREE(shcmd);
424
    VIR_FREE(term);
D
Dan Walsh 已提交
425 426 427 428 429
    VIR_FREE(name);
    VIR_FREE(homedir);
    VIR_FREE(seclabel);
    VIR_FREE(secmodel);
    VIR_FREE(groups);
E
Eric Blake 已提交
430 431 432 433

    if (virProcessWait(cpid, &status, true) == 0)
        virProcessExitWithStatus(status);

434 435
    if (saved_err) {
        virSetError(saved_err);
436
        fprintf(stderr, "%s: %s\n", argv[0], virGetLastErrorMessage());
437
    }
D
Dan Walsh 已提交
438 439
    return ret;
}