hgsh.c
438 lines
| 8.5 KiB
| text/x-c
|
CLexer
Vadim Gelfer
|
r2341 | /* | ||
* 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 | ||||
Augie Fackler
|
r35978 | #define HG_GATEWAY "gateway" | ||
Vadim Gelfer
|
r2341 | #endif | ||
/* | ||||
* HG_HOST: hostname of mercurial server. if any machine is allowed, set to | ||||
* NULL. | ||||
*/ | ||||
#ifndef HG_HOST | ||||
Augie Fackler
|
r35978 | #define HG_HOST "mercurial" | ||
Vadim Gelfer
|
r2341 | #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 | ||||
Augie Fackler
|
r35978 | #define HG_USER "hg" | ||
Vadim Gelfer
|
r2341 | #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 | ||||
Augie Fackler
|
r35978 | #define HG_ROOT "/home/hg/repos" | ||
Vadim Gelfer
|
r2341 | #endif | ||
/* | ||||
* HG: path to the mercurial executable to run. | ||||
*/ | ||||
#ifndef HG | ||||
Augie Fackler
|
r35978 | #define HG "/home/hg/bin/hg" | ||
Vadim Gelfer
|
r2341 | #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 | ||||
Augie Fackler
|
r35978 | #define HG_SHELL NULL | ||
Matt Mackall
|
r10282 | /* #define HG_SHELL "/bin/bash" */ | ||
Vadim Gelfer
|
r2341 | #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 | ||||
Augie Fackler
|
r35978 | #define HG_HELP "please contact support@example.com for help." | ||
Vadim Gelfer
|
r2341 | #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 | ||||
Augie Fackler
|
r35978 | #define SSH "/usr/bin/ssh" | ||
Vadim Gelfer
|
r2341 | #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) | ||||
{ | ||||
Matt Mackall
|
r10282 | FILE *fp = stderr; | ||
int i; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | fputs("command: ", fp); | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | 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); | ||||
Vadim Gelfer
|
r2341 | } | ||
static void usage(const char *reason, int exitcode) | ||||
{ | ||||
Matt Mackall
|
r10282 | char *hg_help = HG_HELP; | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | 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); | ||||
Vadim Gelfer
|
r2341 | } | ||
/* | ||||
* 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) | ||||
{ | ||||
Matt Mackall
|
r10282 | char *ssh = SSH; | ||
char *hg_host = HG_HOST; | ||||
char *hg_user = HG_USER; | ||||
char **nargv = alloca((10 + argc) * sizeof(char *)); | ||||
int i = 0, j; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | 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; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | /* | ||
* 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++; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | for (; j < argc; i++, j++) { | ||
nargv[i] = argv[j]; | ||||
} | ||||
nargv[i] = NULL; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (debug) { | ||
print_cmdline(i, nargv); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | execv(ssh, nargv); | ||
perror(ssh); | ||||
exit(EX_UNAVAILABLE); | ||||
Vadim Gelfer
|
r2341 | } | ||
/* | ||||
* 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) | ||||
{ | ||||
Matt Mackall
|
r10282 | char *hg_shell = HG_SHELL; | ||
char **nargv; | ||||
char *c; | ||||
int i; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | nargv = alloca((argc + 3) * sizeof(char *)); | ||
c = strrchr(hg_shell, '/'); | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | /* tell "real" shell it is login shell, if needed. */ | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | 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; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | for (i = 1; i < argc; i++) { | ||
nargv[i] = argv[i]; | ||||
} | ||||
nargv[i] = NULL; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (debug) { | ||
print_cmdline(i, nargv); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | execv(hg_shell, nargv); | ||
perror(hg_shell); | ||||
exit(EX_OSFILE); | ||||
Vadim Gelfer
|
r2341 | } | ||
Vadim Gelfer
|
r2602 | enum cmdline { | ||
Matt Mackall
|
r10282 | hg_init, | ||
hg_serve, | ||||
Vadim Gelfer
|
r2602 | }; | ||
Vadim Gelfer
|
r2341 | /* | ||
Bryan O'Sullivan
|
r4419 | * 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) | ||||
{ | ||||
Matt Mackall
|
r10282 | char *abs_path; | ||
struct stat st; | ||||
int ret; | ||||
Bryan O'Sullivan
|
r4419 | |||
Matt Mackall
|
r10282 | if (asprintf(&abs_path, "%s.hg/%s", repo_root, subdir) == -1) { | ||
ret = -1; | ||||
goto bail; | ||||
} | ||||
Bryan O'Sullivan
|
r4419 | |||
Matt Mackall
|
r10282 | /* verify that we really are looking at valid repo. */ | ||
Bryan O'Sullivan
|
r4419 | |||
Matt Mackall
|
r10282 | if (stat(abs_path, &st) == -1) { | ||
ret = 0; | ||||
} else { | ||||
ret = 1; | ||||
} | ||||
Bryan O'Sullivan
|
r4419 | |||
bail: | ||||
Matt Mackall
|
r10282 | return ret; | ||
Bryan O'Sullivan
|
r4419 | } | ||
/* | ||||
Vadim Gelfer
|
r2341 | * paranoid wrapper, runs hg executable in server mode. | ||
*/ | ||||
static void serve_data(int argc, char **argv) | ||||
{ | ||||
Matt Mackall
|
r10282 | char *hg_root = HG_ROOT; | ||
char *repo, *repo_root; | ||||
enum cmdline cmd; | ||||
char *nargv[6]; | ||||
size_t repolen; | ||||
int i; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | /* | ||
* 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. | ||||
*/ | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (argc != 3) { | ||
goto badargs; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (strcmp(argv[1], "-c") != 0) { | ||
goto badargs; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (sscanf(argv[2], "hg init %as", &repo) == 1) { | ||
cmd = hg_init; | ||||
Augie Fackler
|
r35978 | } else if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) == 1) { | ||
Matt Mackall
|
r10282 | cmd = hg_serve; | ||
} else { | ||||
Vadim Gelfer
|
r2602 | goto badargs; | ||
Matt Mackall
|
r10282 | } | ||
Thomas Arendsen Hein
|
r5081 | |||
Matt Mackall
|
r10282 | repolen = repo ? strlen(repo) : 0; | ||
Vadim Gelfer
|
r2602 | |||
Matt Mackall
|
r10282 | if (repolen == 0) { | ||
goto badargs; | ||||
Vadim Gelfer
|
r2602 | } | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | 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; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | /* 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; | ||||
} | ||||
} | ||||
Vadim Gelfer
|
r2602 | |||
Matt Mackall
|
r10282 | if (valid == 0) { | ||
perror(repo); | ||||
exit(EX_DATAERR); | ||||
} | ||||
} | ||||
if (chdir(hg_root) == -1) { | ||||
perror(hg_root); | ||||
exit(EX_SOFTWARE); | ||||
} | ||||
} | ||||
i = 0; | ||||
Thomas Arendsen Hein
|
r5081 | |||
Matt Mackall
|
r10282 | 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; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | nargv[i] = NULL; | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (debug) { | ||
print_cmdline(i, nargv); | ||||
} | ||||
execv(HG, nargv); | ||||
perror(HG); | ||||
exit(EX_UNAVAILABLE); | ||||
Vadim Gelfer
|
r2341 | |||
badargs: | ||||
Matt Mackall
|
r10282 | /* print useless error message. */ | ||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | usage("invalid arguments", EX_DATAERR); | ||
Vadim Gelfer
|
r2341 | } | ||
int main(int argc, char **argv) | ||||
{ | ||||
Matt Mackall
|
r10282 | char host[1024]; | ||
char *c; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (gethostname(host, sizeof(host)) == -1) { | ||
perror("gethostname"); | ||||
exit(EX_OSERR); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if ((c = strchr(host, '.')) != NULL) { | ||
*c = '\0'; | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (getenv("SSH_CLIENT")) { | ||
char *hg_gateway = HG_GATEWAY; | ||||
char *hg_host = HG_HOST; | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (hg_gateway && strcmp(host, hg_gateway) == 0) { | ||
forward_through_gateway(argc, argv); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | if (hg_host && strcmp(host, hg_host) != 0) { | ||
usage("invoked on unexpected host", EX_USAGE); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | serve_data(argc, argv); | ||
} else if (HG_SHELL) { | ||||
run_shell(argc, argv); | ||||
} else { | ||||
usage("invalid arguments", EX_DATAERR); | ||||
} | ||||
Vadim Gelfer
|
r2341 | |||
Matt Mackall
|
r10282 | return 0; | ||
Vadim Gelfer
|
r2341 | } | ||