/* Copyright (c) 2011-2012 Red Hat, Inc. This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser General Public License, version 3 or any later version (LGPLv3 or later), or the GNU General Public License, version 2 (GPLv2), in all cases as published by the Free Software Foundation. */ #ifndef _CONFIG_H #define _CONFIG_H #include "config.h" #endif #include #include #include #include #include /* for PATH_MAX */ /* NOTE (USE_LIBGLUSTERFS): * ------------------------ * When USE_LIBGLUSTERFS debugging sumbol is passed; perform * glusterfs translator like initialization so that glusterfs * globals, contexts are valid when glustefs api's are invoked. * We unconditionally pass then while building gsyncd binary. */ #ifdef USE_LIBGLUSTERFS #include "glusterfs.h" #include "globals.h" #endif #include "common-utils.h" #include "run.h" #include "procdiggy.h" #define _GLUSTERD_CALLED_ "_GLUSTERD_CALLED_" #define _GSYNCD_DISPATCHED_ "_GSYNCD_DISPATCHED_" #define GSYNCD_CONF_TEMPLATE "geo-replication/gsyncd_template.conf" #define GSYNCD_PY "gsyncd.py" #define RSYNC "rsync" int restricted = 0; static int duplexpand (void **buf, size_t tsiz, size_t *len) { size_t osiz = tsiz * *len; char *p = realloc (*buf, osiz << 1); if (!p) { free(*buf); return -1; } memset (p + osiz, 0, osiz); *buf = p; *len <<= 1; return 0; } static int str2argv (char *str, char ***argv) { char *p = NULL; char *savetok = NULL; int argc = 0; size_t argv_len = 32; int ret = 0; assert (str); str = strdup (str); if (!str) return -1; *argv = calloc (argv_len, sizeof (**argv)); if (!*argv) goto error; while ((p = strtok_r (str, " ", &savetok))) { str = NULL; argc++; if (argc == argv_len) { ret = duplexpand ((void *)argv, sizeof (**argv), &argv_len); if (ret == -1) goto error; } (*argv)[argc - 1] = p; } return argc; error: fprintf (stderr, "out of memory\n"); return -1; } static int invoke_gsyncd (int argc, char **argv) { char config_file[PATH_MAX] = {0,}; size_t gluster_workdir_len = 0; runner_t runner = {0,}; int i = 0; int j = 0; char *nargv[argc + 4]; char *python = NULL; if (restricted) { size_t len; /* in restricted mode we forcibly use the system-wide config */ runinit (&runner); runner_add_args (&runner, SBIN_DIR"/gluster", "--log-file=-", "system::", "getwd", NULL); runner_redir (&runner, STDOUT_FILENO, RUN_PIPE); if (runner_start (&runner) == 0 && fgets (config_file, PATH_MAX, runner_chio (&runner, STDOUT_FILENO)) != NULL && (len = strlen (config_file)) && config_file[len - 1] == '\n' && runner_end (&runner) == 0) gluster_workdir_len = len - 1; if (gluster_workdir_len) { if (gluster_workdir_len + 1 + strlen (GSYNCD_CONF_TEMPLATE) + 1 > PATH_MAX) goto error; config_file[gluster_workdir_len] = '/'; strcat (config_file, GSYNCD_CONF_TEMPLATE); } else goto error; if (setenv ("_GSYNCD_RESTRICTED_", "1", 1) == -1) goto error; } if (chdir ("/") == -1) goto error; j = 0; python = getenv("PYTHON"); if(!python) python = PYTHON; nargv[j++] = python; nargv[j++] = GSYNCD_PREFIX"/python/syncdaemon/"GSYNCD_PY; for (i = 1; i < argc; i++) nargv[j++] = argv[i]; if (config_file[0]) { nargv[j++] = "-c"; nargv[j++] = config_file; } nargv[j++] = NULL; execvp (python, nargv); fprintf (stderr, "exec of '%s' failed\n", python); return 127; error: fprintf (stderr, "gsyncd initializaion failed\n"); return 1; } static int find_gsyncd (pid_t pid, pid_t ppid, char *name, void *data) { char buf[NAME_MAX * 2] = {0,}; char path[PATH_MAX] = {0,}; char *p = NULL; int zeros = 0; int ret = 0; int fd = -1; pid_t *pida = (pid_t *)data; if (ppid != pida[0]) return 0; sprintf (path, PROC"/%d/cmdline", pid); fd = open (path, O_RDONLY); if (fd == -1) return 0; ret = read (fd, buf, sizeof (buf)); close (fd); if (ret == -1) return 0; for (zeros = 0, p = buf; zeros < 2 && p < buf + ret; p++) zeros += !*p; ret = 0; switch (zeros) { case 2: if ((strcmp (basename (buf), basename (PYTHON)) || strcmp (basename (buf + strlen (buf) + 1), GSYNCD_PY)) == 0) { ret = 1; break; } /* fallthrough */ case 1: if (strcmp (basename (buf), GSYNCD_PY) == 0) ret = 1; } if (ret == 1) { if (pida[1] != -1) { fprintf (stderr, GSYNCD_PY" sibling is not unique"); return -1; } pida[1] = pid; } return 0; } static int invoke_rsync (int argc, char **argv) { int i = 0; char path[PATH_MAX] = {0,}; pid_t pid = -1; pid_t ppid = -1; pid_t pida[] = {-1, -1}; char *name = NULL; char buf[PATH_MAX + 1] = {0,}; int ret = 0; assert (argv[argc] == NULL); if (argc < 2 || strcmp (argv[1], "--server") != 0) goto error; for (i = 2; i < argc && argv[i][0] == '-'; i++); if (!(i == argc - 2 && strcmp (argv[i], ".") == 0 && argv[i + 1][0] == '/')) { fprintf (stderr, "need an rsync invocation without protected args\n"); goto error; } /* look up sshd we are spawned from */ for (pid = getpid () ;; pid = ppid) { ppid = pidinfo (pid, &name); if (ppid < 0) { fprintf (stderr, "sshd ancestor not found\n"); goto error; } if (strcmp (name, "sshd") == 0) { GF_FREE (name); break; } GF_FREE (name); } /* look up "ssh-sibling" gsyncd */ pida[0] = pid; ret = prociter (find_gsyncd, pida); if (ret == -1 || pida[1] == -1) { fprintf (stderr, "gsyncd sibling not found\n"); goto error; } /* check if rsync target matches gsyncd target */ sprintf (path, PROC"/%d/cwd", pida[1]); ret = readlink (path, buf, sizeof (buf)); if (ret == -1 || ret == sizeof (buf)) goto error; if (strcmp (argv[argc - 1], "/") == 0 /* root dir cannot be a target */ || (strcmp (argv[argc - 1], path) /* match against gluster target */ && strcmp (argv[argc - 1], buf) /* match against file target */) != 0) { fprintf (stderr, "rsync target does not match "GEOREP" session\n"); goto error; } argv[0] = RSYNC; execvp (RSYNC, argv); fprintf (stderr, "exec of "RSYNC" failed\n"); return 127; error: fprintf (stderr, "disallowed "RSYNC" invocation\n"); return 1; } static int invoke_gluster (int argc, char **argv) { int i = 0; int j = 0; int optsover = 0; char *ov = NULL; for (i = 1; i < argc; i++) { ov = strtail (argv[i], "--"); if (ov && !optsover) { if (*ov == '\0') optsover = 1; continue; } switch (++j) { case 1: if (strcmp (argv[i], "volume") != 0) goto error; break; case 2: if (strcmp (argv[i], "info") != 0) goto error; break; case 3: break; default: goto error; } } argv[0] = "gluster"; execvp (SBIN_DIR"/gluster", argv); fprintf (stderr, "exec of gluster failed\n"); return 127; error: fprintf (stderr, "disallowed gluster invocation\n"); return 1; } struct invocable { char *name; int (*invoker) (int argc, char **argv); }; struct invocable invocables[] = { { "rsync", invoke_rsync }, { "gsyncd", invoke_gsyncd }, { "gluster", invoke_gluster }, { NULL, NULL} }; int main (int argc, char **argv) { char *evas = NULL; struct invocable *i = NULL; char *b = NULL; char *sargv = NULL; #ifdef USE_LIBGLUSTERFS glusterfs_ctx_t *ctx = NULL; ctx = glusterfs_ctx_new (); if (!ctx) return ENOMEM; if (glusterfs_globals_init (ctx)) return 1; THIS->ctx = ctx; #endif evas = getenv (_GLUSTERD_CALLED_); if (evas && strcmp (evas, "1") == 0) /* OK, we know glusterd called us, no need to look for further config * ... altough this conclusion should not inherit to our children */ unsetenv (_GLUSTERD_CALLED_); else { /* we regard all gsyncd invocations unsafe * that do not come from glusterd and * therefore restrict it */ restricted = 1; if (!getenv (_GSYNCD_DISPATCHED_)) { evas = getenv ("SSH_ORIGINAL_COMMAND"); if (evas) sargv = evas; else { evas = getenv ("SHELL"); if (evas && strcmp (basename (evas), "gsyncd") == 0 && argc == 3 && strcmp (argv[1], "-c") == 0) sargv = argv[2]; } } } if (!(sargv && restricted)) return invoke_gsyncd (argc, argv); argc = str2argv (sargv, &argv); if (argc == -1 || setenv (_GSYNCD_DISPATCHED_, "1", 1) == -1) { fprintf (stderr, "internal error\n"); return 1; } b = basename (argv[0]); for (i = invocables; i->name; i++) { if (strcmp (b, i->name) == 0) return i->invoker (argc, argv); } fprintf (stderr, "invoking %s in restricted SSH session is not allowed\n", b); return 1; }