#include "svn_cmdline.h"
#include "svn_dirent_uri.h"
#include "svn_opt.h"
#include "svn_pools.h"
#include "svn_repos.h"
#include "svn_utf.h"
#include "svn_path.h"
#include "private/svn_fspath.h"
#include "private/svn_cmdline_private.h"
enum svnauthz__cmdline_options_t
{
svnauthz__version = SVN_OPT_FIRST_LONGOPT_ID,
svnauthz__username,
svnauthz__path,
svnauthz__repos,
svnauthz__is,
svnauthz__groups_file
};
static const apr_getopt_option_t options_table[] =
{
{"help", 'h', 0, ("show help on a subcommand")},
{NULL, '?', 0, ("show help on a subcommand")},
{"version", svnauthz__version, 0, ("show program version information")},
{"username", svnauthz__username, 1, ("username to check access of")},
{"path", svnauthz__path, 1, ("path within repository to check access of")},
{"repository", svnauthz__repos, 1, ("repository authz name")},
{"transaction", 't', 1, ("transaction id")},
{"is", svnauthz__is, 1,
("instead of outputting, test if the access is\n"
" "
"exactly ARG\n"
" "
"ARG can be one of the following values:\n"
" "
" rw write access (which also implies read)\n"
" "
" r read-only access\n"
" "
" no no access")
},
{"groups-file", svnauthz__groups_file, 1,
("use the groups from file ARG")},
{"recursive", 'R', 0,
("determine recursive access to PATH")},
{0, 0, 0, 0}
};
struct svnauthz_opt_state
{
svn_boolean_t help;
svn_boolean_t version;
svn_boolean_t recursive;
const char *authz_file;
const char *groups_file;
const char *username;
const char *fspath;
const char *repos_name;
const char *txn;
const char *repos_path;
const char *is;
};
#define SVNAUTHZ_COMPAT_NAME "svnauthz-validate"
#define SVNAUTHZ_LT_PREFIX "lt-"
static svn_opt_subcommand_t
subcommand_help,
subcommand_validate,
subcommand_accessof;
static const svn_opt_subcommand_desc2_t cmd_table[] =
{
{"help", subcommand_help, {"?", "h"},
("usage: svnauthz help [SUBCOMMAND...]\n\n"
"Describe the usage of this program or its subcommands.\n"),
{0} },
{"validate", subcommand_validate, {0} ,
("Checks the syntax of an authz file.\n"
"usage: 1. svnauthz validate TARGET\n"
" 2. svnauthz validate --transaction TXN REPOS_PATH FILE_PATH\n\n"
" 1. Loads and validates the syntax of the authz file at TARGET.\n"
" TARGET can be a path to a file or an absolute file:// URL to an authz\n"
" file in a repository, but cannot be a repository relative URL (^/).\n\n"
" 2. Loads and validates the syntax of the authz file at FILE_PATH in the\n"
" transaction TXN in the repository at REPOS_PATH.\n\n"
"Returns:\n"
" 0 when syntax is OK.\n"
" 1 when syntax is invalid.\n"
" 2 operational error\n"
),
{'t'} },
{"accessof", subcommand_accessof, {0} ,
("Print or test the permissions set by an authz file.\n"
"usage: 1. svnauthz accessof TARGET\n"
" 2. svnauthz accessof -t TXN REPOS_PATH FILE_PATH\n"
"\n"
" 1. Prints the access of USER to PATH based on authorization file at TARGET.\n"
" TARGET can be a path to a file or an absolute file:// URL to an authz\n"
" file in a repository, but cannot be a repository relative URL (^/).\n"
"\n"
" 2. Prints the access of USER to PATH based on authz file at FILE_PATH in the\n"
" transaction TXN in the repository at REPOS_PATH.\n"
"\n"
" USER is the argument to the --username option; if that option is not\n"
" provided, then access of an anonymous user will be printed or tested.\n"
"\n"
" PATH is the argument to the --path option; if that option is not provided,\n"
" the maximal access to any path in the repository will be considered.\n"
"\n"
"Outputs one of the following:\n"
" rw write access (which also implies read)\n"
" r read access\n"
" no no access\n"
"\n"
"Returns:\n"
" 0 when syntax is OK and '--is' argument (if any) matches.\n"
" 1 when syntax is invalid.\n"
" 2 operational error\n"
" 3 when '--is' argument doesn't match\n"
),
{'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is,
svnauthz__groups_file, 'R'} },
{ NULL, NULL, {0}, NULL, {0} }
};
static svn_error_t *
subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
struct svnauthz_opt_state *opt_state = baton;
const char *header =
("general usage: svnauthz SUBCOMMAND TARGET [ARGS & OPTIONS ...]\n"
" " SVNAUTHZ_COMPAT_NAME " TARGET\n\n"
"If the command name starts with '" SVNAUTHZ_COMPAT_NAME "', runs in\n"
"pre-1.8 compatibility mode: run the 'validate' subcommand on TARGET.\n\n"
"Type 'svnauthz help <subcommand>' for help on a specific subcommand.\n"
"Type 'svnauthz --version' to see the program version.\n\n"
"Available subcommands:\n");
const char *fs_desc_start
= ("The following repository back-end (FS) modules are available:\n\n");
svn_stringbuf_t *version_footer;
version_footer = svn_stringbuf_create(fs_desc_start, pool);
SVN_ERR(svn_fs_print_modules(version_footer, pool));
SVN_ERR(svn_opt_print_help4(os, "svnauthz",
opt_state ? opt_state->version : FALSE,
FALSE,
FALSE,
version_footer->data,
header, cmd_table, options_table, NULL, NULL,
pool));
return SVN_NO_ERROR;
}
static svn_error_t *
read_file_contents(svn_stream_t **contents, const char *filename,
svn_fs_root_t *root, apr_pool_t *pool)
{
svn_node_kind_t node_kind;
SVN_ERR(svn_fs_check_path(&node_kind, root, filename, pool));
if (node_kind != svn_node_file)
return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
"Path '%s' is not a file", filename);
SVN_ERR(svn_fs_file_contents(contents, root, filename, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_authz_from_txn(svn_authz_t **authz, const char *repos_path,
const char *authz_file, const char *groups_file,
const char *txn_name, apr_pool_t *pool)
{
svn_repos_t *repos;
svn_fs_t *fs;
svn_fs_txn_t *txn;
svn_fs_root_t *root;
svn_stream_t *authz_contents;
svn_stream_t *groups_contents;
svn_error_t *err;
SVN_ERR(svn_repos_open3(&repos, repos_path, NULL, pool, pool));
fs = svn_repos_fs(repos);
SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, pool));
SVN_ERR(svn_fs_txn_root(&root, txn, pool));
SVN_ERR(read_file_contents(&authz_contents, authz_file, root, pool));
if (groups_file)
SVN_ERR(read_file_contents(&groups_contents, groups_file, root, pool));
else
groups_contents = NULL;
err = svn_repos_authz_parse(authz, authz_contents, groups_contents, pool);
if (err != SVN_NO_ERROR)
return svn_error_createf(err->apr_err, err,
"Error parsing authz file: '%s':", authz_file);
return SVN_NO_ERROR;
}
static svn_error_t *
get_authz(svn_authz_t **authz, struct svnauthz_opt_state *opt_state,
apr_pool_t *pool)
{
if (opt_state->txn)
return get_authz_from_txn(authz, opt_state->repos_path,
opt_state->authz_file,
opt_state->groups_file,
opt_state->txn, pool);
return svn_repos_authz_read3(authz, opt_state->authz_file,
opt_state->groups_file,
TRUE, NULL, pool, pool);
}
static svn_error_t *
subcommand_validate(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
struct svnauthz_opt_state *opt_state = baton;
svn_authz_t *authz;
return get_authz(&authz, opt_state, pool);
}
static svn_error_t *
subcommand_accessof(apr_getopt_t *os, void *baton, apr_pool_t *pool)
{
svn_authz_t *authz;
svn_boolean_t read_access = FALSE, write_access = FALSE;
svn_boolean_t check_r = FALSE, check_rw = FALSE, check_no = FALSE;
svn_error_t *err;
struct svnauthz_opt_state *opt_state = baton;
const char *user = opt_state->username;
const char *path = opt_state->fspath;
const char *repos = opt_state->repos_name;
const char *is = opt_state->is;
svn_repos_authz_access_t request;
if (opt_state->recursive && !path)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("--recursive not valid without --path"));
if (is) {
if (0 == strcmp(is, "rw"))
check_rw = TRUE;
else if (0 == strcmp(is, "r"))
check_r = TRUE;
else if (0 == strcmp(is, "no"))
check_no = TRUE;
else
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("'%s' is not a valid argument for --is"), is);
}
SVN_ERR(get_authz(&authz, opt_state, pool));
request = svn_authz_write;
if (opt_state->recursive)
request |= svn_authz_recursive;
err = svn_repos_authz_check_access(authz, repos, path, user,
request, &write_access,
pool);
if (!write_access && !err)
{
request = svn_authz_read;
if (opt_state->recursive)
request |= svn_authz_recursive;
err = svn_repos_authz_check_access(authz, repos, path, user,
request, &read_access,
pool);
}
if (!err)
{
const char *access_str = write_access ? "rw" : read_access ? "r" : "no";
if (is)
{
if (check_rw && !write_access)
err = svn_error_createf(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
("%s is '%s', not writable"),
path ? path : ("Repository"), access_str);
else if (check_r && !read_access)
err = svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL,
("%s is '%s', not read only"),
path ? path : ("Repository"), access_str);
else if (check_no && (read_access || write_access))
err = svn_error_createf(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
NULL, ("%s is '%s', not no access"),
path ? path : ("Repository"), access_str);
}
else
{
err = svn_cmdline_printf(pool, "%s\n", access_str);
}
}
return err;
}
#undef EXIT_FAILURE
#define EXIT_FAILURE 2
static svn_boolean_t
use_compat_mode(const char *cmd, apr_pool_t *pool)
{
cmd = svn_dirent_internal_style(cmd, pool);
cmd = svn_dirent_basename(cmd, NULL);
if (0 == strncmp(SVNAUTHZ_LT_PREFIX, cmd, sizeof(SVNAUTHZ_LT_PREFIX)-1))
cmd += sizeof(SVNAUTHZ_LT_PREFIX) - 1;
return 0 == strncmp(SVNAUTHZ_COMPAT_NAME, cmd,
sizeof(SVNAUTHZ_COMPAT_NAME)-1);
}
static svn_error_t *
canonicalize_access_file(const char **canonicalized_access_file,
const char *access_file,
svn_boolean_t within_txn,
apr_pool_t *pool)
{
if (svn_path_is_repos_relative_url(access_file))
{
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("'%s' is a repository relative URL when it "
"should be a local path or file:// URL"),
access_file);
}
else if (svn_path_is_url(access_file))
{
if (within_txn)
{
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("'%s' is a URL when it should be a "
"repository-relative path"),
access_file);
}
*canonicalized_access_file = svn_uri_canonicalize(access_file, pool);
}
else if (within_txn)
{
*canonicalized_access_file =
svn_fspath__canonicalize(access_file, pool);
}
else
{
*canonicalized_access_file =
svn_dirent_internal_style(access_file, pool);
}
return SVN_NO_ERROR;
}
static svn_error_t *
sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
{
svn_error_t *err;
const svn_opt_subcommand_desc2_t *subcommand = NULL;
struct svnauthz_opt_state opt_state = { 0 };
apr_getopt_t *os;
apr_array_header_t *received_opts;
int i;
SVN_ERR(svn_fs_initialize(pool));
received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
opt_state.username = opt_state.fspath = opt_state.repos_name = NULL;
opt_state.txn = opt_state.repos_path = opt_state.groups_file = NULL;
SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
os->interleave = 1;
if (!use_compat_mode(argv[0], pool))
{
while (1)
{
int opt;
const char *arg;
apr_status_t status = apr_getopt_long(os, options_table, &opt, &arg);
if (APR_STATUS_IS_EOF(status))
break;
if (status != APR_SUCCESS)
{
SVN_ERR(subcommand_help(NULL, NULL, pool));
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
APR_ARRAY_PUSH(received_opts, int) = opt;
switch (opt)
{
case 'h':
case '?':
opt_state.help = TRUE;
break;
case 't':
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.txn, arg, pool));
break;
case 'R':
opt_state.recursive = TRUE;
break;
case svnauthz__version:
opt_state.version = TRUE;
break;
case svnauthz__username:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.username, arg, pool));
break;
case svnauthz__path:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fspath, arg, pool));
opt_state.fspath = svn_fspath__canonicalize(opt_state.fspath,
pool);
break;
case svnauthz__repos:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.repos_name, arg, pool));
break;
case svnauthz__is:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.is, arg, pool));
break;
case svnauthz__groups_file:
SVN_ERR(
svn_utf_cstring_to_utf8(&opt_state.groups_file,
arg, pool));
break;
default:
{
SVN_ERR(subcommand_help(NULL, NULL, pool));
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
}
}
}
else
{
if (argc == 1)
subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
else
subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "validate");
}
if (opt_state.help)
subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
if (subcommand == NULL)
{
if (os->ind >= os->argc)
{
if (opt_state.version)
{
static const svn_opt_subcommand_desc2_t pseudo_cmd =
{ "--version", subcommand_help, {0}, "",
{svnauthz__version } };
subcommand = &pseudo_cmd;
}
else
{
svn_error_clear(svn_cmdline_fprintf(stderr, pool,
("subcommand argument required\n")));
SVN_ERR(subcommand_help(NULL, NULL, pool));
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
}
else
{
const char *first_arg;
SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
pool));
subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
if (subcommand == NULL)
{
os->ind++;
svn_error_clear(
svn_cmdline_fprintf(stderr, pool,
("Unknown subcommand: '%s'\n"),
first_arg));
SVN_ERR(subcommand_help(NULL, NULL, pool));
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
}
}
if (subcommand->cmd_func != subcommand_help)
{
if (opt_state.txn)
{
if (os->ind +2 != argc)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("Repository and authz file arguments "
"required"));
}
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.repos_path, os->argv[os->ind],
pool));
os->ind++;
opt_state.repos_path = svn_dirent_internal_style(opt_state.repos_path, pool);
}
if (os->ind + 1 != argc)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
("Authz file argument required"));
}
SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.authz_file, os->argv[os->ind],
pool));
SVN_ERR(canonicalize_access_file(&opt_state.authz_file,
opt_state.authz_file,
opt_state.txn != NULL, pool));
if (opt_state.groups_file)
{
SVN_ERR(canonicalize_access_file(&opt_state.groups_file,
opt_state.groups_file,
opt_state.txn != NULL, pool));
}
}
for (i = 0; i < received_opts->nelts; i++)
{
int opt_id = APR_ARRAY_IDX(received_opts, i, int);
if (opt_id == 'h' || opt_id == '?')
continue;
if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
{
const char *optstr;
const apr_getopt_option_t *badopt =
svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
pool);
svn_opt_format_option(&optstr, badopt, FALSE, pool);
if (subcommand->name[0] == '-')
SVN_ERR(subcommand_help(NULL, NULL, pool));
else
svn_error_clear(svn_cmdline_fprintf(stderr, pool,
("Subcommand '%s' doesn't accept option '%s'\n"
"Type 'svnauthz help %s' for usage.\n"),
subcommand->name, optstr, subcommand->name));
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
}
err = (*subcommand->cmd_func)(os, &opt_state, pool);
if (err)
{
if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
|| err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
{
err = svn_error_quick_wrap(err,
("Try 'svnauthz help' for more info"));
}
else if (err->apr_err == SVN_ERR_AUTHZ_INVALID_CONFIG
|| err->apr_err == SVN_ERR_MALFORMED_FILE)
{
*exit_code = 1;
return err;
}
else if (err->apr_err == SVN_ERR_AUTHZ_UNREADABLE
|| err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE
|| err->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
{
*exit_code = 3;
return err;
}
return err;
}
return SVN_NO_ERROR;
}
int
main(int argc, const char *argv[])
{
apr_pool_t *pool;
int exit_code = EXIT_SUCCESS;
svn_error_t *err;
if (svn_cmdline_init(argv[0], stderr) != EXIT_SUCCESS)
return EXIT_FAILURE;
pool = svn_pool_create(NULL);
err = sub_main(&exit_code, argc, argv, pool);
err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
if (err)
{
if (exit_code == 0)
exit_code = EXIT_FAILURE;
svn_cmdline_handle_exit_error(err, NULL, "svnauthz: ");
}
svn_pool_destroy(pool);
return exit_code;
}