diff options
author | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
commit | 458120dd40db6b4df55a4e96b650e16798ef06a0 (patch) | |
tree | 8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/hg/contrib/hgsh/hgsh.c | |
parent | 3a742c699f6806c1145aea5149bf15de15a0afd7 (diff) |
add hg and python
Diffstat (limited to 'sys/src/cmd/hg/contrib/hgsh/hgsh.c')
-rw-r--r-- | sys/src/cmd/hg/contrib/hgsh/hgsh.c | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/sys/src/cmd/hg/contrib/hgsh/hgsh.c b/sys/src/cmd/hg/contrib/hgsh/hgsh.c new file mode 100644 index 000000000..a6ce0063a --- /dev/null +++ b/sys/src/cmd/hg/contrib/hgsh/hgsh.c @@ -0,0 +1,439 @@ +/* + * hgsh.c - restricted login shell for mercurial + * + * Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> + * + * This software may be used and distributed according to the terms of the + * GNU General Public License, incorporated herein by reference. + * + * this program is login shell for dedicated mercurial user account. it + * only allows few actions: + * + * 1. run hg in server mode on specific repository. no other hg commands + * are allowed. we try to verify that repo to be accessed exists under + * given top-level directory. + * + * 2. (optional) forward ssh connection from firewall/gateway machine to + * "real" mercurial host, to let users outside intranet pull and push + * changes through firewall. + * + * 3. (optional) run normal shell, to allow to "su" to mercurial user, use + * "sudo" to run programs as that user, or run cron jobs as that user. + * + * only tested on linux yet. patches for non-linux systems welcome. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* for asprintf */ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sysexits.h> +#include <unistd.h> + +/* + * user config. + * + * if you see a hostname below, just use first part of hostname. example, + * if you have host named foo.bar.com, use "foo". + */ + +/* + * HG_GATEWAY: hostname of gateway/firewall machine that people outside your + * intranet ssh into if they need to ssh to other machines. if you do not + * have such machine, set to NULL. + */ +#ifndef HG_GATEWAY +#define HG_GATEWAY "gateway" +#endif + +/* + * HG_HOST: hostname of mercurial server. if any machine is allowed, set to + * NULL. + */ +#ifndef HG_HOST +#define HG_HOST "mercurial" +#endif + +/* + * HG_USER: username to log in from HG_GATEWAY to HG_HOST. if gateway and + * host username are same, set to NULL. + */ +#ifndef HG_USER +#define HG_USER "hg" +#endif + +/* + * HG_ROOT: root of tree full of mercurial repos. if you do not want to + * validate location of repo when someone is try to access, set to NULL. + */ +#ifndef HG_ROOT +#define HG_ROOT "/home/hg/repos" +#endif + +/* + * HG: path to the mercurial executable to run. + */ +#ifndef HG +#define HG "/home/hg/bin/hg" +#endif + +/* + * HG_SHELL: shell to use for actions like "sudo" and "su" access to + * mercurial user, and cron jobs. if you want to make these things + * impossible, set to NULL. + */ +#ifndef HG_SHELL +#define HG_SHELL NULL +// #define HG_SHELL "/bin/bash" +#endif + +/* + * HG_HELP: some way for users to get support if they have problem. if they + * should not get helpful message, set to NULL. + */ +#ifndef HG_HELP +#define HG_HELP "please contact support@example.com for help." +#endif + +/* + * SSH: path to ssh executable to run, if forwarding from HG_GATEWAY to + * HG_HOST. if you want to use rsh instead (why?), you need to modify + * arguments it is called with. see forward_through_gateway. + */ +#ifndef SSH +#define SSH "/usr/bin/ssh" +#endif + +/* + * tell whether to print command that is to be executed. useful for + * debugging. should not interfere with mercurial operation, since + * mercurial only cares about stdin and stdout, and this prints to stderr. + */ +static const int debug = 0; + +static void print_cmdline(int argc, char **argv) +{ + FILE *fp = stderr; + int i; + + fputs("command: ", fp); + + for (i = 0; i < argc; i++) { + char *spc = strpbrk(argv[i], " \t\r\n"); + if (spc) { + fputc('\'', fp); + } + fputs(argv[i], fp); + if (spc) { + fputc('\'', fp); + } + if (i < argc - 1) { + fputc(' ', fp); + } + } + fputc('\n', fp); + fflush(fp); +} + +static void usage(const char *reason, int exitcode) +{ + char *hg_help = HG_HELP; + + if (reason) { + fprintf(stderr, "*** Error: %s.\n", reason); + } + fprintf(stderr, "*** This program has been invoked incorrectly.\n"); + if (hg_help) { + fprintf(stderr, "*** %s\n", hg_help); + } + exit(exitcode ? exitcode : EX_USAGE); +} + +/* + * run on gateway host to make another ssh connection, to "real" mercurial + * server. it sends its command line unmodified to far end. + * + * never called if HG_GATEWAY is NULL. + */ +static void forward_through_gateway(int argc, char **argv) +{ + char *ssh = SSH; + char *hg_host = HG_HOST; + char *hg_user = HG_USER; + char **nargv = alloca((10 + argc) * sizeof(char *)); + int i = 0, j; + + nargv[i++] = ssh; + nargv[i++] = "-q"; + nargv[i++] = "-T"; + nargv[i++] = "-x"; + if (hg_user) { + nargv[i++] = "-l"; + nargv[i++] = hg_user; + } + nargv[i++] = hg_host; + + /* + * sshd called us with added "-c", because it thinks we are a shell. + * drop it if we find it. + */ + j = 1; + if (j < argc && strcmp(argv[j], "-c") == 0) { + j++; + } + + for (; j < argc; i++, j++) { + nargv[i] = argv[j]; + } + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(ssh, nargv); + perror(ssh); + exit(EX_UNAVAILABLE); +} + +/* + * run shell. let administrator "su" to mercurial user's account to do + * administrative works. + * + * never called if HG_SHELL is NULL. + */ +static void run_shell(int argc, char **argv) +{ + char *hg_shell = HG_SHELL; + char **nargv; + char *c; + int i; + + nargv = alloca((argc + 3) * sizeof(char *)); + c = strrchr(hg_shell, '/'); + + /* tell "real" shell it is login shell, if needed. */ + + if (argv[0][0] == '-' && c) { + nargv[0] = strdup(c); + if (nargv[0] == NULL) { + perror("malloc"); + exit(EX_OSERR); + } + nargv[0][0] = '-'; + } else { + nargv[0] = hg_shell; + } + + for (i = 1; i < argc; i++) { + nargv[i] = argv[i]; + } + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(hg_shell, nargv); + perror(hg_shell); + exit(EX_OSFILE); +} + +enum cmdline { + hg_init, + hg_serve, +}; + + +/* + * attempt to verify that a directory is really a hg repo, by testing + * for the existence of a subdirectory. + */ +static int validate_repo(const char *repo_root, const char *subdir) +{ + char *abs_path; + struct stat st; + int ret; + + if (asprintf(&abs_path, "%s.hg/%s", repo_root, subdir) == -1) { + ret = -1; + goto bail; + } + + /* verify that we really are looking at valid repo. */ + + if (stat(abs_path, &st) == -1) { + ret = 0; + } else { + ret = 1; + } + +bail: + return ret; +} + +/* + * paranoid wrapper, runs hg executable in server mode. + */ +static void serve_data(int argc, char **argv) +{ + char *hg_root = HG_ROOT; + char *repo, *repo_root; + enum cmdline cmd; + char *nargv[6]; + size_t repolen; + int i; + + /* + * check argv for looking okay. we should be invoked with argv + * resembling like this: + * + * hgsh + * -c + * hg -R some/path serve --stdio + * + * the "-c" is added by sshd, because it thinks we are login shell. + */ + + if (argc != 3) { + goto badargs; + } + + if (strcmp(argv[1], "-c") != 0) { + goto badargs; + } + + if (sscanf(argv[2], "hg init %as", &repo) == 1) { + cmd = hg_init; + } + else if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) == 1) { + cmd = hg_serve; + } else { + goto badargs; + } + + repolen = repo ? strlen(repo) : 0; + + if (repolen == 0) { + goto badargs; + } + + if (hg_root) { + if (asprintf(&repo_root, "%s/%s/", hg_root, repo) == -1) { + goto badargs; + } + + /* + * attempt to stop break out from inside the repository tree. could + * do something more clever here, because e.g. we could traverse a + * symlink that looks safe, but really breaks us out of tree. + */ + + if (strstr(repo_root, "/../") != NULL) { + goto badargs; + } + + /* only hg init expects no repo. */ + + if (cmd != hg_init) { + int valid; + + valid = validate_repo(repo_root, "data"); + + if (valid == -1) { + goto badargs; + } + + if (valid == 0) { + valid = validate_repo(repo_root, "store"); + + if (valid == -1) { + goto badargs; + } + } + + if (valid == 0) { + perror(repo); + exit(EX_DATAERR); + } + } + + if (chdir(hg_root) == -1) { + perror(hg_root); + exit(EX_SOFTWARE); + } + } + + i = 0; + + switch (cmd) { + case hg_serve: + nargv[i++] = HG; + nargv[i++] = "-R"; + nargv[i++] = repo; + nargv[i++] = "serve"; + nargv[i++] = "--stdio"; + break; + case hg_init: + nargv[i++] = HG; + nargv[i++] = "init"; + nargv[i++] = repo; + break; + } + + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(HG, nargv); + perror(HG); + exit(EX_UNAVAILABLE); + +badargs: + /* print useless error message. */ + + usage("invalid arguments", EX_DATAERR); +} + +int main(int argc, char **argv) +{ + char host[1024]; + char *c; + + if (gethostname(host, sizeof(host)) == -1) { + perror("gethostname"); + exit(EX_OSERR); + } + + if ((c = strchr(host, '.')) != NULL) { + *c = '\0'; + } + + if (getenv("SSH_CLIENT")) { + char *hg_gateway = HG_GATEWAY; + char *hg_host = HG_HOST; + + if (hg_gateway && strcmp(host, hg_gateway) == 0) { + forward_through_gateway(argc, argv); + } + + if (hg_host && strcmp(host, hg_host) != 0) { + usage("invoked on unexpected host", EX_USAGE); + } + + serve_data(argc, argv); + } else if (HG_SHELL) { + run_shell(argc, argv); + } else { + usage("invalid arguments", EX_DATAERR); + } + + return 0; +} |