summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--xlators/features/marker/utils/src/gsyncd.c205
-rw-r--r--xlators/features/marker/utils/syncdaemon/gsyncd.py15
2 files changed, 182 insertions, 38 deletions
diff --git a/xlators/features/marker/utils/src/gsyncd.c b/xlators/features/marker/utils/src/gsyncd.c
index ec1d3a65a98..cebca1aeaae 100644
--- a/xlators/features/marker/utils/src/gsyncd.c
+++ b/xlators/features/marker/utils/src/gsyncd.c
@@ -32,52 +32,67 @@
#include "run.h"
#define _GLUSTERD_CALLED_ "_GLUSTERD_CALLED_"
+#define _GSYNCD_DISPATCHED_ "_GSYNCD_DISPATCHED_"
#define GSYNCD_CONF "geo-replication/gsyncd.conf"
+#define RSYNC "rsync"
+
+int restricted = 0;
static int
-config_wanted (int argc, char **argv)
+duplexpand (void **buf, size_t tsiz, size_t *len)
{
- char *evas = NULL;
- char *oa = NULL;
- int i = 0;
- int one_more_arg = 0;
+ size_t osiz = tsiz * *len;
- 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_);
- return 0;
- }
+ *buf = realloc (*buf, osiz << 1);
+ if (!buf)
+ return -1;
- for (i = 1; i < argc; i++) {
- /* -c found, see if it has an argument */
- if (one_more_arg) {
- if (argv[i][0] != '-')
- return 0;
- one_more_arg = 0;
- }
+ memset ((char *)*buf + osiz, 0, osiz);
+ *len <<= 1;
- if ((strcmp (argv[i], "-c") && strcmp (argv[i], "--config-file")) == 0) {
- one_more_arg = 1;
- continue;
- }
+ return 0;
+}
+
+static int
+str2argv (char *str, char ***argv)
+{
+ char *p = NULL;
+ int argc = 0;
+ size_t argv_len = 32;
+ int ret = 0;
+
+ assert (str);
+ str = strdup (str);
+ if (!str)
+ return -1;
- oa = strtail (argv[i], "-c");
- if (oa && !*oa)
- oa = NULL;
- if (!oa)
- oa = strtail (argv[i], "--config-file=");
- if (oa)
- return 0;
+ *argv = calloc (argv_len, sizeof (**argv));
+ if (!*argv)
+ goto error;
+
+ while ((p = strtok (str, " "))) {
+ 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 1;
+ return argc;
+
+ error:
+ fprintf (stderr, "out of memory\n");
+ return -1;
}
-int
-main(int argc, char **argv)
+static int
+invoke_gsyncd (int argc, char **argv)
{
char config_file[PATH_MAX] = {0,};
size_t gluster_workdir_len = 0;
@@ -86,7 +101,8 @@ main(int argc, char **argv)
int j = 0;
char *nargv[argc + 4];
- if (config_wanted (argc, argv)) {
+ if (restricted) {
+ /* in restricted mode we forcibly use the system-wide config */
runinit (&runner);
runner_add_args (&runner, SBIN_DIR"/gluster",
"--log-file=/dev/stderr", "system::", "getwd",
@@ -106,18 +122,21 @@ main(int argc, char **argv)
config_file[gluster_workdir_len] = '/';
strcat (config_file, GSYNCD_CONF);
} else
- config_file[0] = '\0';
+ goto error;
+
+ if (setenv ("_GSYNCD_RESTRICTED_", "1", 1) == -1)
+ goto error;
}
j = 0;
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;
}
- for (i = 1; i < argc; i++)
- nargv[j++] = argv[i];
nargv[j++] = NULL;
execvp (PYTHON, nargv);
@@ -129,3 +148,113 @@ main(int argc, char **argv)
fprintf (stderr, "gsyncd initializaion failed\n");
return 1;
}
+
+static int
+invoke_rsync (int argc, char **argv)
+{
+ int i = 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 ||
+ (i == argc - 2 && strcmp (argv[i], ".") == 0 && argv[i + 1][0] == '/')))
+ goto error;
+
+ /* XXX a proper check would involve the following:
+ * - require rsync to not protect args (ie. pass target in command line)
+ * - find out proper synchronization target by:
+ * - looking up sshd process we origin from
+ * - within its children, find the gsyncd process
+ * that maintains the aux mount
+ * - find out mount directory by checking the working directory
+ * of the gsyncd process
+ * - demand that rsync target equals to sync target
+ *
+ * As of now, what we implement is dispatching rsync invocation to
+ * our system rsync, that handles the cardinal issue of controlling
+ * remote-requested command invocations.
+ */
+
+ argv[0] = RSYNC;
+
+ execvp (RSYNC, argv);
+
+ fprintf (stderr, "exec of "RSYNC" failed\n");
+ return 127;
+
+ error:
+ fprintf (stderr, "disallowed "RSYNC" invocation\n");
+ return 1;
+}
+
+struct invocable {
+ char *name;
+ int (*invoker) (int argc, char **argv);
+};
+
+struct invocable invocables[] = {
+ { "rsync", invoke_rsync },
+ { "gsyncd", invoke_gsyncd },
+ { NULL, NULL}
+};
+
+int
+main (int argc, char **argv)
+{
+ char *evas = NULL;
+ struct invocable *i = NULL;
+ char *b = NULL;
+ char *sargv = NULL;
+
+ 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;
+}
diff --git a/xlators/features/marker/utils/syncdaemon/gsyncd.py b/xlators/features/marker/utils/syncdaemon/gsyncd.py
index 9cae4d407f4..6747acbce6f 100644
--- a/xlators/features/marker/utils/syncdaemon/gsyncd.py
+++ b/xlators/features/marker/utils/syncdaemon/gsyncd.py
@@ -190,6 +190,8 @@ def main_i():
op.add_option('--canonicalize-escape-url', dest='url_print', action='callback', callback=store_local_curry('canon_esc'))
tunables = [ norm(o.get_opt_string()[2:]) for o in op.option_list if o.callback in (store_abs, 'store_true', None) and o.get_opt_string() not in ('--version', '--help') ]
+ remote_tunables = [ 'listen', 'go_daemon', 'timeout', 'session_owner', 'config_file' ]
+ rq_remote_tunables = { 'listen': True }
# precedence for sources of values: 1) commandline, 2) cfg file, 3) defaults
# -- for this to work out we need to tell apart defaults from explicitly set
@@ -206,6 +208,19 @@ def main_i():
sys.stderr.write(op.get_usage() + "\n")
sys.exit(1)
+ if os.getenv('_GSYNCD_RESTRICTED_'):
+ allopts = {}
+ allopts.update(opts.__dict__)
+ allopts.update(rconf)
+ bannedtuns = set(allopts.keys()) - set(remote_tunables)
+ if bannedtuns:
+ raise GsyncdError('following tunables cannot be set with restricted SSH invocaton: ' + \
+ ', '.join(bannedtuns))
+ for k, v in rq_remote_tunables.items():
+ if not k in allopts or allopts[k] != v:
+ raise GsyncdError('tunable %s is not set to value %s required for restricted SSH invocaton' % \
+ (k, v))
+
if getattr(confdata, 'rx', None):
# peers are regexen, don't try to parse them
canon_peers = args