# # add_file "globish.cc" # # add_file "globish.hh" # # patch "ChangeLog" # from [f15c2359c3a9d16158dcac9119c14fd4b68d8194] # to [81eece5640970d2c6a838995ff56ed60e0868cd9] # # patch "Makefile.am" # from [f2cebcb5b9a6cf9ca254765464af8cccc11da225] # to [cbd9fbc3d73a88f56a538bc5050a2ce3a2ffee31] # # patch "commands.cc" # from [f9e412756872786e2b1e29b7628331844712d796] # to [c9dd7afdabd028f1a9ec18da9aa1a9de1670765d] # # patch "globish.cc" # from [] # to [6ae57bf040ce47674abb56f2e0c764f994d5ee62] # # patch "globish.hh" # from [] # to [bcbaf2efddd720efb786f5f2843890071e42e3d8] # # patch "lua.cc" # from [e4dd123e0c07b59361555b521f3b2cc17aa445b5] # to [a8af56ad7fe0d9baa85c265b05dd94877a806c19] # # patch "lua.hh" # from [0c4357742dc32474757a7905380b407e532563f4] # to [0c4f6966a60230b591f07d0a0519485bf451a349] # # patch "monotone.1" # from [8b3699fb3547458585d9e9acb48f4baf5fd0b6fa] # to [da6b32b820606a2fa282d47374330c0ca455ffb8] # # patch "monotone.texi" # from [131933a2c2cd95c13bf8e3b8c988b8e4bc97664d] # to [18fdc4eac00664cacf2ca7e151a07c8ece75a479] # # patch "netcmd.cc" # from [f1f30a4a9655a95d2007a309a0d4a618229dcdca] # to [b8f2bfffbb6ebefdf274ea1d5ac59b58cd837671] # # patch "netcmd.hh" # from [f5565d11c6b2db20139d0aab07a5b8bfe37b3cd8] # to [f6262bdd5719defdd5ef6237e090687701ef4006] # # patch "netsync.cc" # from [8cd3b5995624e0fe9d57a9954b88aef6944474f0] # to [f1056e86785d18c2a7da04c76086fd3c5106ab9c] # # patch "netsync.hh" # from [758b6ef261dc3d69e99e4fbcd79571e89749c9f4] # to [276fdfd34e38b36adc1a45d012792fd43db07a2c] # # patch "tests/t_netsync_defaults.at" # from [c041c4c1f93c04041b0fb53257e56c4b3b24a2cf] # to [ad699c5f7470abec2b97ac1b4d0f71250d134d25] # # patch "tests/t_netsync_permissions.at" # from [73b0366351e254dd0c272fc9d01c04d2b5fba795] # to [fb766246587fa8cf19f1b2952ec63be65ad82b63] # # patch "tests/t_netsync_single.at" # from [10e65a3f83698c36866333909f01cbdb6915efe3] # to [b3d48b1881c63ccb549d37726ae2d7144a856820] # # patch "testsuite.at" # from [2e17e62213b9878d08d7c792f244b6e193498618] # to [e6acd9db25c55028d15b0e56f32214a94e55b4fc] # # patch "transforms.cc" # from [041db043a6e340a55705de868e77a0fad8b5a413] # to [589f2e927da5c33a2adb9bfea8f9b70d041ce212] # # patch "transforms.hh" # from [10da66c1dec345841caf96a4c7bc2291b7d3ecb3] # to [9f9adf9a36619e493cceea57d78e6c4cc456598d] # # patch "unit_tests.cc" # from [230b4227d08bed323a2625f593073427bca9fe9b] # to [99e4b00f7e1071461ac6d79372edda008bb5b182] # # patch "unit_tests.hh" # from [b4b20ac190077a08db547eb1dcd79f40c58b2ebc] # to [340f8c9e449facd6744cf6b926367fbd98bbb062] # --- ChangeLog +++ ChangeLog @@ -1,3 +1,98 @@ +2005-07-04 Nathaniel Smith + + * tests/t_netsync_defaults.at: Update for new var names. All + tests now pass. + +2005-07-04 Nathaniel Smith + + * lua.cc (hook_get_netsync_write_permitted): Fix typo. + +2005-07-04 Nathaniel Smith + + * globish.cc (globish_matcher_test): Add check for {foo} (no + commas). + +2005-07-04 Nathaniel Smith + + * globish.cc (checked_globish_to_regex): Make the special case for + the empty pattern, actually work. Unit tests now pass. + +2005-07-04 Nathaniel Smith + + * netcmd.cc (test_netcmd_functions): Update for new anonymous/auth + packet formats. + +2005-07-04 Nathaniel Smith + + * monotone.texi, monotone.1: Update for new glob stuff. + * commands.cc (process_netsync_args, push, pull, sync, serve): + 'serve' always requires arguments, rather than falling back on db + defaults. + +2005-07-04 Nathaniel Smith + + * commands.cc (process_netsync_args, push, pull, sync, serve): + Adapt for patterns instead of regexen; slight refactoring too. + +2005-07-03 Nathaniel Smith + + * netsync.cc: Finally self-consistent. + +2005-07-03 Nathaniel Smith + + * netsync.hh (run_netsync_protocol): Fix prototype. + +2005-07-03 Nathaniel Smith + + * globish.hh: Document the empty pattern as never matching. + * globish.cc (checked_globish_to_regex): Implement it. + (globish_matcher_test): Check it. + +2005-07-03 Nathaniel Smith + + * monotone.texi (Network Service, Hooks): + * testsuite.at: + * tests/t_netsync_permissions.at: + * tests/t_netsync_single.at: Update to match new + get_netsync_write_permitted definition. + +2005-07-03 Nathaniel Smith + + * lua.{cc,hh} (hook_get_netsync_write_permitted): Don't take a + branch argument; write permission is now all or none. (It really + was before anyway...) + * netsync.cc: Update accordingly. + +2005-07-03 Nathaniel Smith + + * netsync.cc: More updating for pattern stuff; getting there... + +2005-06-28 Nathaniel Smith + + * netsync.cc: Update low-level functions to use include_pattern + and exclude_pattern. + +2005-06-28 Nathaniel Smith + + * netcmd.{cc,hh} (read_anonymous_cmd, write_anonymous_cmd) + (read_auth_cmd, write_auth_cmd): Take include_pattern and + exclude_pattern arguments. + +2005-06-28 Nathaniel Smith + + * globish.{cc,hh}: New files. + * Makefile.am (MOST_SOURCES): Add them. + * transforms.{cc,hh}: Remove glob-related stuff. + * unit_tests.{cc,hh}: Call globish unit tests. + +2005-06-27 Nathaniel Smith + + * transforms.cc (glob_to_regex, globs_to_regex, regexes_to_regex): + Choose "regex" as standard spelling. Clean up code, add code for + handling sets, start improving tests (don't currently pass). + * transforms.hh (glob_to_regex, globs_to_regex, regexes_to_regex): + Prototype. + 2005-06-28 Matt Johnston * constants.cc: increase db_version_cache_sz to 7 MB --- Makefile.am +++ Makefile.am @@ -41,6 +41,7 @@ annotate.cc annotate.hh \ restrictions.cc restrictions.hh \ hmac.cc hmac.hh \ + globish.cc globish.hh \ \ cleanup.hh unit_tests.hh interner.hh \ cycle_detector.hh randomfile.hh adler32.hh quick_alloc.hh \ --- commands.cc +++ commands.cc @@ -49,6 +49,7 @@ #include "selectors.hh" #include "annotate.hh" #include "options.hh" +#include "globish.hh" // // this file defines the task-oriented "top level" commands which can be @@ -1976,22 +1977,23 @@ static const var_key default_server_key(var_domain("database"), var_name("default-server")); -static const var_key default_pattern_key(var_domain("database"), - var_name("default-pattern")); +static const var_key default_include_pattern_key(var_domain("database"), + var_name("default-include-pattern")); +static const var_key default_exclude_pattern_key(var_domain("database"), + var_name("default-exclude-pattern")); static void -process_netsync_client_args(std::string const & name, - std::vector const & args, - utf8 & addr, std::vector & patterns, - app_state & app) +process_netsync_args(std::string const & name, + std::vector const & args, + utf8 & addr, + utf8 & include_pattern, utf8 & exclude_pattern, + bool use_defaults, + app_state & app) { - if (args.size() > 2) - throw usage(name); - if (args.size() >= 1) { addr = idx(args, 0); - if (!app.db.var_exists(default_server_key)) + if (use_defaults && !app.db.var_exists(default_server_key)) { P(F("setting default server to %s\n") % addr); app.db.set_var(default_server_key, var_value(addr())); @@ -1999,6 +2001,7 @@ } else { + N(use_defaults, F("no hostname given")); N(app.db.var_exists(default_server_key), F("no server given and no default server set")); var_value addr_value; @@ -2006,74 +2009,73 @@ addr = utf8(addr_value()); L(F("using default server address: %s\n") % addr); } - // NB: even though the netsync code wants a vector of patterns, in fact - // this only works for the server; when we're a client, our vector must have - // length exactly 1. - utf8 pattern; if (args.size() >= 2) { - pattern = idx(args, 1); - if (!app.db.var_exists(default_pattern_key)) + std::set patterns(args.begin() + 1, args.end()); + combine_and_check_globish(patterns, include_pattern); + if (use_defaults && !app.db.var_exists(default_include_pattern_key)) { - P(F("setting default regex pattern to %s\n") % pattern); - app.db.set_var(default_pattern_key, var_value(pattern())); + P(F("setting default branch pattern to %s\n") % include_pattern); + app.db.set_var(default_include_pattern_key, var_value(include_pattern())); } } else { - N(app.db.var_exists(default_pattern_key), - F("no regex pattern given and no default pattern set")); + N(use_defaults, F("no branch pattern given")); + N(app.db.var_exists(default_include_pattern_key), + F("no branch pattern given and no default pattern set")); var_value pattern_value; - app.db.get_var(default_pattern_key, pattern_value); - pattern = utf8(pattern_value()); - L(F("using default regex pattern: %s\n") % pattern); + app.db.get_var(default_include_pattern_key, pattern_value); + include_pattern = utf8(pattern_value()); + L(F("using default branch pattern: %s\n") % include_pattern); } - patterns.push_back(pattern); + + // For now, don't handle excludes. + exclude_pattern = utf8(""); } -CMD(push, "network", "[ADDRESS[:PORTNUMBER] [REGEX]]", +CMD(push, "network", "[ADDRESS[:PORTNUMBER] [PATTERN]]", "push branches matching REGEX to netsync server at ADDRESS", OPT_NONE) { - utf8 addr; - vector patterns; - process_netsync_client_args(name, args, addr, patterns, app); + utf8 addr, include_pattern, exclude_pattern; + process_netsync_args(name, args, addr, include_pattern, exclude_pattern, true, app); rsa_keypair_id key; N(guess_default_key(key, app), F("could not guess default signing key")); app.signing_key = key; - run_netsync_protocol(client_voice, source_role, addr, patterns, app); + run_netsync_protocol(client_voice, source_role, addr, + include_pattern, exclude_pattern, app); } -CMD(pull, "network", "[ADDRESS[:PORTNUMBER] [REGEX]]", +CMD(pull, "network", "[ADDRESS[:PORTNUMBER] [PATTERN]]", "pull branches matching REGEX from netsync server at ADDRESS", OPT_NONE) { - utf8 addr; - vector patterns; - process_netsync_client_args(name, args, addr, patterns, app); + utf8 addr, include_pattern, exclude_pattern; + process_netsync_args(name, args, addr, include_pattern, exclude_pattern, true, app); if (app.signing_key() == "") W(F("doing anonymous pull\n")); - run_netsync_protocol(client_voice, sink_role, addr, patterns, app); + run_netsync_protocol(client_voice, sink_role, addr, + include_pattern, exclude_pattern, app); } -CMD(sync, "network", "[ADDRESS[:PORTNUMBER] [REGEX]]", +CMD(sync, "network", "[ADDRESS[:PORTNUMBER] [PATTERN]]", "sync branches matching REGEX with netsync server at ADDRESS", OPT_NONE) { - utf8 addr; - vector patterns; - process_netsync_client_args(name, args, addr, patterns, app); + utf8 addr, include_pattern, exclude_pattern; + process_netsync_args(name, args, addr, include_pattern, exclude_pattern, true, app); rsa_keypair_id key; N(guess_default_key(key, app), F("could not guess default signing key")); app.signing_key = key; - run_netsync_protocol(client_voice, source_and_sink_role, addr, patterns, - app); + run_netsync_protocol(client_voice, source_and_sink_role, addr, + include_pattern, exclude_pattern, app); } -CMD(serve, "network", "ADDRESS[:PORTNUMBER] REGEX ...", +CMD(serve, "network", "ADDRESS[:PORTNUMBER] PATTERN ...", "listen on ADDRESS and serve the specified branches to connecting clients", OPT_PIDFILE) { if (args.size() < 2) @@ -2089,9 +2091,10 @@ F("need permission to store persistent passphrase (see hook persist_phrase_ok())")); require_password(key, app); - utf8 addr(idx(args,0)); - vector patterns(args.begin() + 1, args.end()); - run_netsync_protocol(server_voice, source_and_sink_role, addr, patterns, app); + utf8 addr, include_pattern, exclude_pattern; + process_netsync_args(name, args, addr, include_pattern, exclude_pattern, false, app); + run_netsync_protocol(server_voice, source_and_sink_role, addr, + include_pattern, exclude_pattern, app); } --- globish.cc +++ globish.cc @@ -0,0 +1,227 @@ +// copyright (C) 2005 Richard Levitte +// copyright (C) 2005 nathaniel smith +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +#include "sanity.hh" +#include "globish.hh" + +// this converts a globish pattern to a regex. The regex should be usable by +// the Boost regex library operating in default mode, i.e., it should be a +// valid ECMAscript regex. +// +// Pattern tranformation: +// +// - As a special case, the empty pattern is translated to "$.^", which cannot +// match any string. +// +// - Any character except those described below are copied as they are. +// - The backslash (\) escapes the following character. The escaping +// backslash is copied to the regex along with the following character. +// - * is transformed to .* in the regex. +// - ? is transformed to . in the regex. +// - { is transformed to ( in the regex +// - } is transformed to ) in the regex +// - , is transformed to | in the regex, if within { and } +// - ^ is escaped unless it comes directly after an unescaped [. +// - ! is transformed to ^ in the regex if it comes directly after an +// unescaped [. +// - ] directly following an unescaped [ is escaped. +static void +maybe_quote(char c, std::string & re) +{ + if (!(isalnum(c) || c == '_')) + { + re += '\\'; + } + re += c; +} + +static void +checked_globish_to_regex(std::string const & glob, std::string & regex) +{ + int in_braces = 0; // counter for levels if {} + + regex.clear(); + regex.reserve(glob.size() * 2); + + L(F("checked_globish_to_regex: input = '%s'\n") % glob); + + if (glob == "") + { + regex = "$.^"; + // and the below loop will do nothing + } + for (std::string::const_iterator i = glob.begin(); i != glob.end(); ++i) + { + char c = *i; + + N(in_braces < 5, F("braces nested too deep in pattern '%s'") % glob); + + switch(c) + { + case '*': + regex += ".*"; + break; + case '?': + regex += '.'; + break; + case '{': + in_braces++; + regex += '('; + break; + case '}': + N(in_braces != 0, + F("trying to end a brace expression in a glob when none is started")); + regex += ')'; + in_braces--; + break; + case ',': + if (in_braces > 0) + regex += '|'; + else + maybe_quote(c, regex); + break; + case '\\': + N(++i != glob.end(), F("pattern '%s' ends with backslash") % glob); + maybe_quote(*i, regex); + break; + default: + maybe_quote(c, regex); + break; + } + } + + N(in_braces == 0, + F("run-away brace expression in pattern '%s'") % glob); + + L(F("checked_globish_to_regex: output = '%s'\n") % regex); +} + +void +combine_and_check_globish(std::set const &patterns, utf8 & pattern) +{ + std::string p; + p += '{'; + bool first = true; + for (std::set::const_iterator i = patterns.begin(); i != patterns.end(); ++i) + { + std::string tmp; + // run for the checking it does + checked_globish_to_regex((*i)(), tmp); + if (!first) + p += ','; + first = false; + p += (*i)(); + } + p += '}'; + pattern = utf8(p); +} + +globish_matcher::globish_matcher(utf8 const & include_pat, utf8 const & exclude_pat) +{ + std::string re; + checked_globish_to_regex(include_pat(), re); + r_inc = re; + checked_globish_to_regex(exclude_pat(), re); + r_exc = re; +} + +bool +globish_matcher::operator()(std::string const & s) +{ + // regex_match may throw a std::runtime_error, if the regex turns out to be + // really pathological + return boost::regex_match(s, r_inc) && !boost::regex_match(s, r_exc); +} + +#ifdef BUILD_UNIT_TESTS +#include "unit_tests.hh" + +static void +checked_globish_to_regex_test() +{ + std::string pat; + + checked_globish_to_regex("*", pat); + BOOST_CHECK(pat == ".*"); + checked_globish_to_regex("?", pat); + BOOST_CHECK(pat == "."); + checked_globish_to_regex("{a,b,c}d", pat); + BOOST_CHECK(pat == "(a|b|c)d"); + checked_globish_to_regex("foo{a,{b,c},?*}d", pat); + BOOST_CHECK(pat == "foo(a|(b|c)|..*)d"); + checked_globish_to_regex("\\a\\b\\|\\{\\*", pat); + BOOST_CHECK(pat == "ab\\|\\{\\*"); + checked_globish_to_regex(".+$^{}", pat); + BOOST_CHECK(pat == "\\.\\+\\$\\^()"); + checked_globish_to_regex(",", pat); + // we're very conservative about metacharacters, and quote all + // non-alphanumerics, hence the backslash + BOOST_CHECK(pat == "\\,"); + + BOOST_CHECK_THROW(checked_globish_to_regex("foo\\", pat), informative_failure); + BOOST_CHECK_THROW(checked_globish_to_regex("{foo", pat), informative_failure); + BOOST_CHECK_THROW(checked_globish_to_regex("{foo,bar{baz,quux}", pat), informative_failure); + BOOST_CHECK_THROW(checked_globish_to_regex("foo}", pat), informative_failure); + BOOST_CHECK_THROW(checked_globish_to_regex("foo,bar{baz,quux}}", pat), informative_failure); + BOOST_CHECK_THROW(checked_globish_to_regex("{{{{{{{{{{a,b},c},d},e},f},g},h},i},j},k}", pat), informative_failure); +} + +static void +combine_and_check_globish_test() +{ + std::set s; + s.insert(utf8("a")); + s.insert(utf8("b")); + s.insert(utf8("c")); + utf8 combined; + combine_and_check_globish(s, combined); + BOOST_CHECK(combined() == "{a,b,c}"); +} + +static void +globish_matcher_test() +{ + { + globish_matcher m(utf8("{a,b}?*\\*|"), utf8("*c*")); + BOOST_CHECK(m("aq*|")); + BOOST_CHECK(m("bq*|")); + BOOST_CHECK(!m("bc*|")); + BOOST_CHECK(!m("bq|")); + BOOST_CHECK(!m("b*|")); + BOOST_CHECK(!m("")); + } + { + globish_matcher m(utf8("{a,\\\\,b*}"), utf8("*c*")); + BOOST_CHECK(m("a")); + BOOST_CHECK(!m("ab")); + BOOST_CHECK(m("\\")); + BOOST_CHECK(!m("\\\\")); + BOOST_CHECK(m("b")); + BOOST_CHECK(m("bfoobar")); + BOOST_CHECK(!m("bfoobarcfoobar")); + } + { + globish_matcher m(utf8("*"), utf8("")); + BOOST_CHECK(m("foo")); + BOOST_CHECK(m("")); + } + { + globish_matcher m(utf8("{foo}"), utf8("")); + BOOST_CHECK(m("foo")); + BOOST_CHECK(!m("bar")); + } +} + + +void add_globish_tests(test_suite * suite) +{ + I(suite); + suite->add(BOOST_TEST_CASE(&checked_globish_to_regex_test)); + suite->add(BOOST_TEST_CASE(&combine_and_check_globish_test)); + suite->add(BOOST_TEST_CASE(&globish_matcher_test)); +} + +#endif // BUILD_UNIT_TESTS --- globish.hh +++ globish.hh @@ -0,0 +1,47 @@ +#ifndef __GLOBISH_HH__ +#define __GLOBISH_HH__ + +// copyright (C) 2005 nathaniel smith +// all rights reserved. +// licensed to the public under the terms of the GNU GPL (>= 2) +// see the file COPYING for details + +// a sort of glob-like pattern matcher, for use in specifying branch +// collections for netsync. it is important that it not be too expensive to +// match (as opposed to common regex engines, which can be exponential on +// pathological patterns), because we must match branches against untrusted +// patterns when doing netsync. + +// the syntax is: +// most things - match themselves +// * - match 0 or more characters +// ? - match 0 or 1 characters +// \ - match +// {,,...} - match any of the given items +// so like standard globs, except without [] character sets, and with {} +// alternation. +// the one strange thing is there is a special-case -- the empty pattern +// matches nothing, not even the empty string. this hardly ever matters, but +// it's nice to have some way to say "don't exclude anything", for instance. + +#include +#include +#include + +#include "vocab.hh" + +void combine_and_check_globish(std::set const &patterns, utf8 & pattern); + +class globish_matcher +{ +public: + // this may throw an informative_failure if a pattern is invalid + globish_matcher(utf8 const & include_pat, utf8 const & exclude_pat); + // this method may throw a std::runtime_error if the pattern is really + // pathological + bool operator()(std::string const & s); +private: + boost::regex r_inc, r_exc; +}; + +#endif --- lua.cc +++ lua.cc @@ -738,14 +738,14 @@ lua_hooks::hook_expand_date(std::string const & sel, std::string & exp) { - exp.clear(); + exp.clear(); bool res= Lua(st) .func("expand_date") .push_str(sel) .call(1,1) .extract_str(exp) .ok(); - return res && exp.size(); + return res && exp.size(); } bool @@ -1038,14 +1038,14 @@ } bool -lua_hooks::hook_get_netsync_read_permitted(std::string const & pattern, +lua_hooks::hook_get_netsync_read_permitted(std::string const & branch, rsa_keypair_id const & identity) { bool permitted = false, exec_ok = false; exec_ok = Lua(st) .func("get_netsync_read_permitted") - .push_str(pattern) + .push_str(branch) .push_str(identity()) .call(2,1) .extract_bool(permitted) @@ -1055,13 +1055,13 @@ } bool -lua_hooks::hook_get_netsync_anonymous_read_permitted(std::string const & pattern) +lua_hooks::hook_get_netsync_anonymous_read_permitted(std::string const & branch) { bool permitted = false, exec_ok = false; exec_ok = Lua(st) .func("get_netsync_anonymous_read_permitted") - .push_str(pattern) + .push_str(branch) .call(1,1) .extract_bool(permitted) .ok(); @@ -1070,16 +1070,14 @@ } bool -lua_hooks::hook_get_netsync_write_permitted(std::string const & pattern, - rsa_keypair_id const & identity) +lua_hooks::hook_get_netsync_write_permitted(rsa_keypair_id const & identity) { bool permitted = false, exec_ok = false; exec_ok = Lua(st) .func("get_netsync_write_permitted") - .push_str(pattern) .push_str(identity()) - .call(2,1) + .call(1,1) .extract_bool(permitted) .ok(); --- lua.hh +++ lua.hh @@ -62,11 +62,10 @@ std::map const & new_results); // network hooks - bool hook_get_netsync_read_permitted(std::string const & pattern, + bool hook_get_netsync_read_permitted(std::string const & branch, rsa_keypair_id const & identity); - bool hook_get_netsync_anonymous_read_permitted(std::string const & pattern); - bool hook_get_netsync_write_permitted(std::string const & pattern, - rsa_keypair_id const & identity); + bool hook_get_netsync_anonymous_read_permitted(std::string const & branch); + bool hook_get_netsync_write_permitted(rsa_keypair_id const & identity); // local repo hooks bool hook_ignore_file(file_path const & p); --- monotone.1 +++ monotone.1 @@ -162,17 +162,17 @@ \fBrefresh_inodeprints\fP Turn on inodeprints mode, and force a cache refresh. .TP -\fBpush\fP \fI[ []]\fP -Push contents of \fI\fP to database on \fI\fP. +\fBpush\fP \fI[ []]\fP +Push contents of \fI\fP to database on \fI\fP. .TP -\fBpull\fP \fI[ []]\fP -Push contents of \fI\fP from database on \fI\fP. +\fBpull\fP \fI[ []]\fP +Push contents of \fI\fP from database on \fI\fP. .TP -\fBsync\fP \fI \fP -Sync contents of \fI\fP with database on \fI\fP. +\fBsync\fP \fI \fP +Sync contents of \fI\fP with database on \fI\fP. .TP -\fBserve\fP \fI[--pid-file=] \fP -Serve contents of \fI\fP at network address \fI\fP. If a +\fBserve\fP \fI[--pid-file=] \fP +Serve contents of \fI\fP at network address \fI\fP. If a --pid-file option is provided on the command line, monotone will store the process id of the server in the specified file. .TP --- monotone.texi +++ monotone.texi @@ -1641,7 +1641,7 @@ return false end -function get_netsync_write_permitted (branch, identity) +function get_netsync_write_permitted (identity) if (identity == "abe@@juicebot.co.jp") then return true end if (identity == "beth@@juicebot.co.jp") then return true end return false @@ -1660,22 +1660,24 @@ @smallexample @group -$ monotone --db=jim.db serve jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7.*" +$ monotone --db=jim.db serve jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7*" @end group @end smallexample This command sets up a single listener loop on the host @code{jim-laptop.juicebot.co.jp}, serving all branches matching address@hidden This will naturally -include the @code{jp.co.juicebot.jb7} branch, and any sub-branches. address@hidden This will naturally include the address@hidden branch, and any sub-branches. The quotes +around @code{"jp.co.juicebot.jb7*"} are there to protect the @code{*} +from expansion by the shell; they have no meaning to monotone. Now Abe decides he wishes to fetch Jim's code. To do this he issues the monotone @code{sync} command: @smallexample @group -monotone --db=abe.db sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7.*" -monotone: rebuilding merkle trees for pattern jp.co.juicebot.jb7.* +monotone --db=abe.db sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7*" +monotone: rebuilding merkle trees ... monotone: connecting to jim-laptop.juicebot.co.jp monotone: [bytes in: 3200] [bytes out: 673] monotone: successful exchange with jim-laptop.juicebot.co.jp @@ -1820,8 +1822,8 @@ @smallexample @group -$ monotone sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7.*" -monotone: rebuilding merkle trees for pattern jp.co.juicebot.jb7.* +$ monotone sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7*" +monotone: rebuilding merkle trees ... monotone: including branch jp.co.juicebot.jb7 monotone: [keys: 2] [rcerts: 8] monotone: connecting to jim-laptop.juicebot.co.jp @@ -1835,8 +1837,8 @@ @smallexample @group -$ monotone --db=beth.db sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7.*" -monotone: rebuilding merkle trees for pattern jp.co.juicebot.jb7.* +$ monotone --db=beth.db sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7*" +monotone: rebuilding merkle trees ... monotone: connecting to jim-laptop.juicebot.co.jp monotone: [bytes in: 3200] [bytes out: 673] monotone: successful exchange with jim-laptop.juicebot.co.jp @@ -1887,8 +1889,8 @@ @smallexample @group -$ monotone sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7.*" -monotone: rebuilding merkle trees for pattern jp.co.juicebot.jb7.* +$ monotone sync jim-laptop.juicebot.co.jp "jp.co.juicebot.jb7*" +monotone: rebuilding merkle trees ... monotone: including branch jp.co.juicebot.jb7 monotone: [keys: 3] [rcerts: 12] monotone: connecting to jim-laptop.juicebot.co.jp @@ -2641,9 +2643,9 @@ @item default-server The default server for netsync operations to use. Automatically set by first use of netsync. address@hidden default-pattern -The default regex pattern for netsync operations to use. Automatically -set by first use of netsync. address@hidden default-include-pattern +The default branch glob pattern for netsync operations to use. +Automatically set by first use of netsync. @end table @item known-servers @@ -3047,7 +3049,7 @@ @tab @smallexample @group -$ monotone pull www.foo.com com.foo.wobbler +$ monotone pull www.foo.com com.foo.wobbler* $ monotone checkout --revision=fe37 wobbler @end group @end smallexample @@ -3056,7 +3058,7 @@ The CVS command contacts a network server, retrieves a revision, and stores it in your working copy. There are two cosmetic differences with the monotone command: remote databases are specified by hostnames -and regexes, and revisions are denoted by @sc{sha1} values (or +and globs, and revisions are denoted by @sc{sha1} values (or selectors). There is also one deep difference: pulling revisions into your @@ -3080,7 +3082,7 @@ @smallexample @group $ monotone commit --message="log message" -$ monotone push www.foo.com com.foo.wobbler +$ monotone push www.foo.com com.foo.wobbler* @end group @end smallexample @end multitable @@ -3102,7 +3104,7 @@ @tab @smallexample @group -$ monotone pull www.foo.com com.foo.wobbler +$ monotone pull www.foo.com com.foo.wobbler* $ monotone merge $ monotone update @end group @@ -3653,10 +3655,10 @@ @section Network @ftable @command address@hidden monotone serve @var{address}[:@var{port}] @var{regex} [...] address@hidden monotone pull address@hidden:@var{port}] address@hidden address@hidden monotone push address@hidden:@var{port}] address@hidden address@hidden monotone sync address@hidden:@var{port}] address@hidden address@hidden monotone serve @var{address}[:@var{port}] @var{glob} [...] address@hidden monotone pull address@hidden:@var{port}] address@hidden [...]]] address@hidden monotone push address@hidden:@var{port}] address@hidden [...]]] address@hidden monotone sync address@hidden:@var{port}] address@hidden [...]]] These commands operate the ``netsync'' protocol built into monotone. This is a custom protocol for rapidly synchronizing two @@ -3666,25 +3668,25 @@ The network @var{address} specified in each case should be the same: a host name to listen on, or connect to, optionally followed by a colon -and a port number. The @var{regex} parameter indicates a set of -branches to exchange; every branch which matches @var{regex} exactly +and a port number. The @var{glob} parameters indicate a set of +branches to exchange; every branch which matches a @var{glob} exactly will be indexed and made available for synchronization. -The @command{serve} command can take multiple regexes, and it will -make available all branches matching any of the listed regexes. Different -permissions can be applied to each branch; see the hooks +The @command{serve} command can take multiple globs, and it will make +available all branches matching any of them. Different permissions can +be applied to each branch; see the hooks @code{get_netsync_read_permitted}, @code{get_netsync_write_permitted}, -and @code{get_netsync_anonymous_read_permitted}, all of which take a address@hidden argument (see @ref{Hook Reference}). +and @code{get_netsync_anonymous_read_permitted} (see @ref{Hook +Reference}). -For example, supposing Bob and Alice wish to synchronize their +For example, perhaps Bob and Alice wish to synchronize their @code{net.venge.monotone.win32} and @code{net.venge.monotone.i18n} branches. Supposing Alice's computer has hostname @code{alice.someisp.com}, then Alice might run: @smallexample @group -$ monotone serve alice.someisp.com "net.venge.monotone.*" +$ monotone serve alice.someisp.com "net.venge.monotone*" @end group @end smallexample @@ -3692,19 +3694,19 @@ @smallexample @group -$ monotone sync alice.someisp.com "net.venge.monotone.*" +$ monotone sync alice.someisp.com "net.venge.monotone*" @end group @end smallexample When the operation completes, all branches matching address@hidden will be synchronized between Alice and Bob's address@hidden will be synchronized between Alice and Bob's databases. The @command{pull}, @command{push}, and @command{sync} commands only -require you pass @var{address} and @var{regex} the first time you -use one of them; monotone will memorize this use and in the future -default to the same server and regex. For instance, if Bob wants -to @command{sync} with Alice again, he can simply run: +require you pass @var{address} and @var{glob} the first time you use one +of them; monotone will memorize this use and in the future default to +the same server and glob. For instance, if Bob wants to @command{sync} +with Alice again, he can simply run: @smallexample @group @@ -3713,18 +3715,26 @@ @end smallexample Of course, he can still @command{sync} with other people and other -branches by passing an address or address plus regex on the command +branches by passing an address or address plus globs on the command line; this will not affect his default affinity for Alice. If you ever do want to change your defaults, use @code{monotone unset database -default-server} or @code{monotone unset database default-pattern}; -these will clear your defaults, and cause them to be reset to the next -person you netsync with. +default-server} or @code{monotone unset database +default-include-pattern}; these will clear your defaults, and cause them +to be reset to the next person you netsync with. If a @option{--pid-file} option is specified, the command @command{serve} will create the specified file and record the process identifier of the server in the file. This file can then be read to identify specific monotone server processes. +The syntax for patterns is very simple. @code{*} matches 0 or more +arbitrary characters. @code{?} matches exactly 1 arbitrary character. address@hidden@{foo,bar,address@hidden matches ``foo'', or ``bar'', or ``baz''. These +can be combined arbitrarily. A backslash, @code{\}, can be prefixed to +any character, to match exactly that character --- this might be useful +in case someone, for some odd reason, decides to put a ``*'' into their +branch name. + @end ftable @@ -5477,17 +5487,13 @@ access hook. This hook has no default definition, therefore the default behavior is to deny all anonymous reads. address@hidden get_netsync_write_permitted (@var{branch}, @var{identity}) address@hidden get_netsync_write_permitted (@var{identity}) -Returns @code{true} if a peer authenticated as key @var{identity} -should be allowed to write into your database certs, revisions, -manifests, and files associated with @var{branch}; otherwise @code{false}. -This hook has no default definition, therefore the default behavior is to deny all writes. +Returns @code{true} if a peer authenticated as key @var{identity} should +be allowed to write into your database certs, revisions, manifests, and +files; otherwise @code{false}. This hook has no default definition, +therefore the default behavior is to deny all writes. -Note that if write access is granted for one branch it is effectively -granted for the entire database, as there is currently no way to -restrict that access to only that branch. - Note that the @var{identity} value is a key ID (such as address@hidden@@pobox.com}'') but will correspond to a @emph{unique} key fingerprint (hash) in your database. Monotone will not permit two @@ -6729,20 +6735,20 @@ @item @b{refresh_inodeprints} Turn on inodeprints mode, and force a cache refresh. address@hidden @b{push} @i{ } -Push contents of branches matching @i{} to database on @i{} address@hidden @b{push} @i{ } +Push contents of branches matching @i{} to database on @i{} @comment TROFF INPUT: .SH DESCRIPTION address@hidden @b{pull} @i{ } -Pull contents of branches matching @i{} from database on @i{} address@hidden @b{pull} @i{ } +Pull contents of branches matching @i{} from database on @i{} @comment TROFF INPUT: .SH DESCRIPTION address@hidden @b{sync} @i{ } -Sync contents of branches matching @i{} with database on @i{} address@hidden @b{sync} @i{ } +Sync contents of branches matching @i{} with database on @i{} @comment TROFF INPUT: .SH DESCRIPTION address@hidden @b{serve} @i{ } -Serve contents of branches matching @i{} at network address @i{} address@hidden @b{serve} @i{ } +Serve contents of branches matching @i{} at network address @i{} @comment TROFF INPUT: .SH DESCRIPTION @item @b{automate} @i{(interface_version|heads|ancestors|attributes|parents|descendents|children|graph|erase_ancestors|toposort|ancestry_difference|leaves|inventory|stdio|certs|select)} --- netcmd.cc +++ netcmd.cc @@ -214,19 +214,25 @@ void netcmd::read_anonymous_cmd(protocol_role & role, - std::string & pattern, + utf8 & include_pattern, + utf8 & exclude_pattern, rsa_oaep_sha_data & hmac_key_encrypted) const { size_t pos = 0; - // syntax is: + // syntax is: u8 role_byte = extract_datum_lsb(payload, pos, "anonymous(hmac) netcmd, role"); if (role_byte != static_cast(source_role) && role_byte != static_cast(sink_role) && role_byte != static_cast(source_and_sink_role)) throw bad_decode(F("unknown role specifier %d") % widen(role_byte)); role = static_cast(role_byte); - extract_variable_length_string(payload, pattern, pos, - "anonymous(hmac) netcmd, pattern"); + std::string pattern_string; + extract_variable_length_string(payload, pattern_string, pos, + "anonymous(hmac) netcmd, include_pattern"); + include_pattern = utf8(pattern_string); + extract_variable_length_string(payload, pattern_string, pos, + "anonymous(hmac) netcmd, exclude_pattern"); + exclude_pattern = utf8(pattern_string); string hmac_key_string; extract_variable_length_string(payload, hmac_key_string, pos, "anonymous(hmac) netcmd, hmac_key_encrypted"); @@ -236,25 +242,28 @@ void netcmd::write_anonymous_cmd(protocol_role role, - std::string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, rsa_oaep_sha_data const & hmac_key_encrypted) { cmd_code = anonymous_cmd; payload = static_cast(role); - insert_variable_length_string(pattern, payload); + insert_variable_length_string(include_pattern(), payload); + insert_variable_length_string(exclude_pattern(), payload); insert_variable_length_string(hmac_key_encrypted(), payload); } void netcmd::read_auth_cmd(protocol_role & role, - string & pattern, + utf8 & include_pattern, + utf8 & exclude_pattern, id & client, id & nonce1, rsa_oaep_sha_data & hmac_key_encrypted, string & signature) const { size_t pos = 0; - // syntax is: + // syntax is: // // u8 role_byte = extract_datum_lsb(payload, pos, "auth netcmd, role"); @@ -263,7 +272,13 @@ && role_byte != static_cast(source_and_sink_role)) throw bad_decode(F("unknown role specifier %d") % widen(role_byte)); role = static_cast(role_byte); - extract_variable_length_string(payload, pattern, pos, "auth(hmac) netcmd, pattern"); + std::string pattern_string; + extract_variable_length_string(payload, pattern_string, pos, + "auth(hmac) netcmd, include_pattern"); + include_pattern = utf8(pattern_string); + extract_variable_length_string(payload, pattern_string, pos, + "auth(hmac) netcmd, exclude_pattern"); + exclude_pattern = utf8(pattern_string); client = id(extract_substring(payload, pos, constants::merkle_hash_length_in_bytes, "auth(hmac) netcmd, client identifier")); @@ -281,7 +296,8 @@ void netcmd::write_auth_cmd(protocol_role role, - string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & client, id const & nonce1, rsa_oaep_sha_data const & hmac_key_encrypted, @@ -291,7 +307,8 @@ I(client().size() == constants::merkle_hash_length_in_bytes); I(nonce1().size() == constants::merkle_hash_length_in_bytes); payload = static_cast(role); - insert_variable_length_string(pattern, payload); + insert_variable_length_string(include_pattern(), payload); + insert_variable_length_string(exclude_pattern(), payload); payload += client(); payload += nonce1(); insert_variable_length_string(hmac_key_encrypted(), payload); @@ -658,12 +675,15 @@ // total cheat, since we don't actually verify that rsa_oaep_sha_data // is sensible anywhere here... rsa_oaep_sha_data out_key("nonce start my heart"), in_key; - string out_pattern("radishes galore!"), in_pattern; + utf8 out_include_pattern("radishes galore!"), in_include_pattern; + utf8 out_exclude_pattern("turnips galore!"), in_exclude_pattern; - out_cmd.write_anonymous_cmd(out_role, out_pattern, out_key); + out_cmd.write_anonymous_cmd(out_role, out_include_pattern, out_exclude_pattern, out_key); do_netcmd_roundtrip(out_cmd, in_cmd, buf); - in_cmd.read_anonymous_cmd(in_role, in_pattern, in_key); + in_cmd.read_anonymous_cmd(in_role, in_include_pattern, in_exclude_pattern, in_key); BOOST_CHECK(in_key == out_key); + BOOST_CHECK(in_include_pattern == out_include_pattern); + BOOST_CHECK(in_exclude_pattern == out_exclude_pattern); BOOST_CHECK(in_role == out_role); L(F("anonymous_cmd test done, buffer was %d bytes\n") % buf.size()); } @@ -679,19 +699,22 @@ // total cheat, since we don't actually verify that rsa_oaep_sha_data // is sensible anywhere here... rsa_oaep_sha_data out_key("nonce start my heart"), in_key; - string out_signature(raw_sha1("burble") + raw_sha1("gorby")), out_pattern("radishes galore!"), - in_signature, in_pattern; + string out_signature(raw_sha1("burble") + raw_sha1("gorby")), in_signature; + utf8 out_include_pattern("radishes galore!"), in_include_pattern; + utf8 out_exclude_pattern("turnips galore!"), in_exclude_pattern; - out_cmd.write_auth_cmd(out_role, out_pattern, out_client, out_nonce1, - out_key, out_signature); + out_cmd.write_auth_cmd(out_role, out_include_pattern, out_exclude_pattern + , out_client, out_nonce1, out_key, out_signature); do_netcmd_roundtrip(out_cmd, in_cmd, buf); - in_cmd.read_auth_cmd(in_role, in_pattern, in_client, - in_nonce1, in_key, in_signature); + in_cmd.read_auth_cmd(in_role, in_include_pattern, in_exclude_pattern, + in_client, in_nonce1, in_key, in_signature); BOOST_CHECK(in_client == out_client); BOOST_CHECK(in_nonce1 == out_nonce1); BOOST_CHECK(in_key == out_key); BOOST_CHECK(in_signature == out_signature); BOOST_CHECK(in_role == out_role); + BOOST_CHECK(in_include_pattern == out_include_pattern); + BOOST_CHECK(in_exclude_pattern == out_exclude_pattern); L(F("auth_cmd test done, buffer was %d bytes\n") % buf.size()); } --- netcmd.hh +++ netcmd.hh @@ -83,20 +83,24 @@ id const & nonce); void read_anonymous_cmd(protocol_role & role, - std::string & pattern, + utf8 & include_pattern, + utf8 & exclude_pattern, rsa_oaep_sha_data & hmac_key_encrypted) const; void write_anonymous_cmd(protocol_role role, - std::string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, rsa_oaep_sha_data const & hmac_key_encrypted); void read_auth_cmd(protocol_role & role, - std::string & pattern, + utf8 & include_pattern, + utf8 & exclude_pattern, id & client, id & nonce1, rsa_oaep_sha_data & hmac_key_encrypted, std::string & signature) const; void write_auth_cmd(protocol_role role, - std::string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & client, id const & nonce1, rsa_oaep_sha_data const & hmac_key_encrypted, --- netsync.cc +++ netsync.cc @@ -34,6 +34,7 @@ #include "epoch.hh" #include "platform.hh" #include "hmac.hh" +#include "globish.hh" #include "cryptopp/osrng.h" @@ -154,17 +155,18 @@ // // The client then responds with either: // -// An "auth (source|sink|both) -// " command, which identifies its RSA key, notes the role it -// wishes to play in the synchronization, identifies the pattern it -// wishes to sync with, signs the previous nonce with its own key, and -// informs the server of the HMAC key it wishes to use for this -// session (encrypted with the server's public key); or +// An "auth (source|sink|both) +// " command, which identifies its RSA key, notes the +// role it wishes to play in the synchronization, identifies the pattern it +// wishes to sync with, signs the previous nonce with its own key, and informs +// the server of the HMAC key it wishes to use for this session (encrypted +// with the server's public key); or // -// An "anonymous (source|sink|both) " command, -// which identifies the role it wishes to play in the synchronization, -// the pattern it ishes to sync with, and the HMAC key it wishes to -// use for this session (also encrypted with the server's public key). +// An "anonymous (source|sink|both) +// " command, which identifies the role it wishes to play in the +// synchronization, the pattern it ishes to sync with, and the HMAC key it +// wishes to use for this session (also encrypted with the server's public +// key). // // The server then replies with a "confirm" command, which contains no // other data but will only have the correct HMAC integrity code if @@ -172,24 +174,6 @@ // the client. This transitions the peers into an authenticated state // and begins refinement. // -// ---- Pre-v5 authentication process notes ---- -// -// the exchange begins in a non-authenticated state. the server sends a -// "hello " command, which identifies the server's RSA key and -// issues a nonce which must be used for a subsequent authentication. -// the client can then respond with an "auth (source|sink|both) -// " command which identifies its -// RSA key, notes the role it wishes to play in the synchronization, -// identifies the pattern it wishes to sync with, signs the previous -// nonce with its own key, and issues a nonce of its own for mutual -// authentication. -// -// the server can then respond with a "confirm " command, which is -// the signature of the second nonce sent by the client. this -// transitions the peers into an authenticated state and begins refinement. -// -// ---- End pre-v5 authentication process ---- -// // refinement begins with the client sending its root public key and // manifest certificate merkle nodes to the server. the server then // compares the root to each slot in *its* root node, and for each slot @@ -254,7 +238,9 @@ { protocol_role role; protocol_voice const voice; - vector patterns; + utf8 const & our_include_pattern; + utf8 const & our_exclude_pattern; + globish_matcher our_matcher; app_state & app; string peer_id; @@ -273,8 +259,6 @@ bool armed; bool arm(); - utf8 pattern; - boost::regex pattern_re; id remote_peer_key_hash; rsa_keypair_id remote_peer_key_name; netsync_session_key session_key; @@ -313,7 +297,8 @@ session(protocol_role role, protocol_voice voice, - vector const & patterns, + utf8 const & our_include_pattern, + utf8 const & our_exclude_pattern, app_state & app, string const & peer, Netxx::socket_type sock, @@ -373,11 +358,13 @@ void queue_hello_cmd(id const & server, id const & nonce); void queue_anonymous_cmd(protocol_role role, - string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & nonce2, base64 server_key_encoded); void queue_auth_cmd(protocol_role role, - string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & client, id const & nonce1, id const & nonce2, @@ -407,9 +394,11 @@ rsa_pub_key const & server_key, id const & nonce); bool process_anonymous_cmd(protocol_role role, - string const & pattern); + utf8 const & their_include_pattern, + utf8 const & their_exclude_pattern); bool process_auth_cmd(protocol_role role, - string const & pattern, + utf8 const & their_include_pattern, + utf8 const & their_exclude_pattern, id const & client, id const & nonce1, string const & signature); @@ -474,14 +463,17 @@ session::session(protocol_role role, protocol_voice voice, - vector const & patterns, + utf8 const & our_include_pattern, + utf8 const & our_exclude_pattern, app_state & app, string const & peer, Netxx::socket_type sock, Netxx::Timeout const & to) : role(role), voice(voice), - patterns(patterns), + our_include_pattern(our_include_pattern), + our_exclude_pattern(our_exclude_pattern), + our_matcher(our_include_pattern, our_exclude_pattern), app(app), peer_id(peer), fd(sock), @@ -489,8 +481,6 @@ inbuf(""), outbuf_size(0), armed(false), - pattern(""), - pattern_re(".*"), remote_peer_key_hash(""), remote_peer_key_name(""), session_key(constants::netsync_key_initializer), @@ -512,14 +502,6 @@ dbw(app, true), encountered_error(false) { - if (voice == client_voice) - { - N(patterns.size() == 1, - F("client can only sync one pattern at a time")); - this->pattern = idx(patterns, 0); - this->pattern_re = boost::regex(this->pattern()); - } - dbw.set_on_revision_written(boost::bind(&session::rev_written_callback, this, _1)); dbw.set_on_cert_written(boost::bind(&session::cert_written_callback, @@ -1177,8 +1159,6 @@ // Write permissions checking: // remove heads w/o proper certs, add their children to heads // 1) remove unwanted branch certs from consideration - // - server: check write permission hook - // - client: check against sync pattern // 2) remove heads w/o a branch tag, process new exposed heads // 3) repeat 2 until no change @@ -1202,13 +1182,7 @@ ; else { - bool ok; - if (voice == server_voice) - ok = app.lua.hook_get_netsync_write_permitted(name(), - remote_peer_key_name); - else - ok = boost::regex_match(name(), pattern_re); - if (ok) + if (our_matcher(name())) { ok_branches.insert(name()); keeping.push_back(*j); @@ -1443,7 +1417,8 @@ void session::queue_anonymous_cmd(protocol_role role, - string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & nonce2, base64 server_key_encoded) { @@ -1451,14 +1426,16 @@ rsa_oaep_sha_data hmac_key_encrypted; encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded, nonce2(), hmac_key_encrypted); - cmd.write_anonymous_cmd(role, pattern, hmac_key_encrypted); + cmd.write_anonymous_cmd(role, include_pattern, exclude_pattern, + hmac_key_encrypted); write_netcmd_and_try_flush(cmd); set_session_key(nonce2()); } void session::queue_auth_cmd(protocol_role role, - string const & pattern, + utf8 const & include_pattern, + utf8 const & exclude_pattern, id const & client, id const & nonce1, id const & nonce2, @@ -1469,7 +1446,8 @@ rsa_oaep_sha_data hmac_key_encrypted; encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded, nonce2(), hmac_key_encrypted); - cmd.write_auth_cmd(role, pattern, client, nonce1, hmac_key_encrypted, signature); + cmd.write_auth_cmd(role, include_pattern, exclude_pattern, client, + nonce1, hmac_key_encrypted, signature); write_netcmd_and_try_flush(cmd); set_session_key(nonce2()); } @@ -1784,14 +1762,13 @@ this->remote_peer_key_hash = their_key_hash_decoded; } - utf8 pat(pattern); vector branchnames; set ok_branches; get_branches(app, branchnames); for (vector::const_iterator i = branchnames.begin(); i != branchnames.end(); i++) { - if (boost::regex_match(*i, pattern_re)) + if (our_matcher(*i)) ok_branches.insert(utf8(*i)); } rebuild_merkle_trees(app, ok_branches); @@ -1817,31 +1794,22 @@ decode_base64(sig, sig_raw); // make a new nonce of our own and send off the 'auth' - queue_auth_cmd(this->role, this->pattern(), our_key_hash_raw, - nonce, mk_nonce(), sig_raw(), their_key_encoded); + queue_auth_cmd(this->role, our_include_pattern, our_exclude_pattern, + our_key_hash_raw, nonce, mk_nonce(), sig_raw(), + their_key_encoded); } else { - queue_anonymous_cmd(this->role, this->pattern(), - mk_nonce(), their_key_encoded); + queue_anonymous_cmd(this->role, our_include_pattern, + our_exclude_pattern, mk_nonce(), their_key_encoded); } return true; } -bool -matches_one(string s, vector r) -{ - for (vector::const_iterator i = r.begin(); i != r.end(); i++) - { - if (boost::regex_match(s, *i)) - return true; - } - return false; -} - bool session::process_anonymous_cmd(protocol_role role, - string const & pattern) + utf8 const & their_include_pattern, + utf8 const & their_exclude_pattern) { // // internally netsync thinks in terms of sources and sinks. users like @@ -1875,35 +1843,27 @@ vector branchnames; set ok_branches; get_branches(app, branchnames); - vector allowed; - boost::regex reg(pattern); - for (vector::const_iterator i = patterns.begin(); - i != patterns.end(); ++i) - { - allowed.push_back(boost::regex((*i)())); - } + globish_matcher their_matcher(their_include_pattern, their_exclude_pattern); for (vector::const_iterator i = branchnames.begin(); i != branchnames.end(); i++) { - if (boost::regex_match(*i, reg) - && (allowed.size() == 0 || matches_one(*i, allowed))) - { - if (app.lua.hook_get_netsync_anonymous_read_permitted(*i)) - ok_branches.insert(utf8(*i)); - } + if (our_matcher(*i) && their_matcher(*i) + && app.lua.hook_get_netsync_anonymous_read_permitted(*i)) + ok_branches.insert(utf8(*i)); } - if (!ok_branches.size()) + if (ok_branches.empty()) { - W(F("denied anonymous read permission for '%s'\n") % pattern); + W(F("denied anonymous read permission for '%s' excluding '%s'\n") + % their_include_pattern % their_exclude_pattern); this->saved_nonce = id(""); return false; } - P(F("allowed anonymous read permission for '%s'\n") % pattern); + P(F("allowed anonymous read permission for '%s' excluding '%s'\n") + % their_include_pattern % their_exclude_pattern); rebuild_merkle_trees(app, ok_branches); - this->pattern = pattern; this->remote_peer_key_name = rsa_keypair_id(""); this->authenticated = true; this->role = source_role; @@ -1911,8 +1871,9 @@ } bool -session::process_auth_cmd(protocol_role role, - string const & pattern, +session::process_auth_cmd(protocol_role their_role, + utf8 const & their_include_pattern, + utf8 const & their_exclude_pattern, id const & client, id const & nonce1, string const & signature) @@ -1925,13 +1886,7 @@ set ok_branches; vector branchnames; get_branches(app, branchnames); - vector allowed; - for (vector::const_iterator i = patterns.begin(); - i != patterns.end(); ++i) - { - allowed.push_back(boost::regex((*i)())); - } - boost::regex reg(pattern); + globish_matcher their_matcher(their_include_pattern, their_exclude_pattern); // check that they replied with the nonce we asked for if (!(nonce1 == this->saved_nonce)) @@ -1950,7 +1905,7 @@ // if the user asks to run a "read only" service, this means they are // willing to be a source but not a sink. // - // nb: the "role" here is the role the *client* wants to play + // nb: the "their_role" here is the role the *client* wants to play // so we need to check that the opposite role is allowed for us, // in our this->role field. // @@ -1969,12 +1924,12 @@ // client as sink, server as source (reading) - if (role == sink_role || role == source_and_sink_role) + if (their_role == sink_role || their_role == source_and_sink_role) { if (this->role != source_role && this->role != source_and_sink_role) { - W(F("denied '%s' read permission for '%s' while running as sink\n") - % their_id % pattern); + W(F("denied '%s' read permission for '%s' excluding '%s' while running as pure sink\n") + % their_id % their_include_pattern % their_exclude_pattern); this->saved_nonce = id(""); return false; } @@ -1982,48 +1937,46 @@ for (vector::const_iterator i = branchnames.begin(); i != branchnames.end(); i++) { - if (boost::regex_match(*i, reg) - && (allowed.size() == 0 || matches_one(*i, allowed))) - { - if (app.lua.hook_get_netsync_read_permitted(*i, their_id)) - ok_branches.insert(utf8(*i)); - } + if (our_matcher(*i) && their_matcher(*i) + && app.lua.hook_get_netsync_read_permitted(*i, their_id)) + ok_branches.insert(utf8(*i)); } //if we're source_and_sink_role, continue even with no branches readable //ex: serve --db=empty.db - if (!ok_branches.size() && role == sink_role) + if (ok_branches.empty() && their_role == sink_role) { - W(F("denied '%s' read permission for '%s'\n") % their_id % pattern); + W(F("denied '%s' read permission for '%s' excluding '%s'\n") + % their_id % their_include_pattern % their_exclude_pattern); this->saved_nonce = id(""); return false; } - P(F("allowed '%s' read permission for '%s'\n") % their_id % pattern); + P(F("allowed '%s' read permission for '%s' excluding '%s'\n") + % their_id % their_include_pattern % their_exclude_pattern); } // client as source, server as sink (writing) - if (role == source_role || role == source_and_sink_role) + if (their_role == source_role || their_role == source_and_sink_role) { if (this->role != sink_role && this->role != source_and_sink_role) { - W(F("denied '%s' write permission for '%s' while running as source\n") - % their_id % pattern); + W(F("denied '%s' write permission for '%s' excluding '%s' while running as pure source\n") + % their_id % their_include_pattern % their_exclude_pattern); this->saved_nonce = id(""); return false; } - // Write permissions are now checked from analyze_ancestry_graph. - if (role == source_role) + if (!app.lua.hook_get_netsync_write_permitted(their_id)) { - for (vector::const_iterator i = branchnames.begin(); - i != branchnames.end(); i++) - { - ok_branches.insert(utf8(*i)); - } + W(F("denied '%s' write permission for '%s' excluding '%s' while running as pure source\n") + % their_id % their_include_pattern % their_exclude_pattern); + this->saved_nonce = id(""); + return false; } - P(F("allowed '%s' write permission for '%s'\n") % their_id % pattern); + P(F("allowed '%s' write permission for '%s' excluding '%s'\n") + % their_id % their_include_pattern % their_exclude_pattern); } rebuild_merkle_trees(app, ok_branches); @@ -2038,12 +1991,10 @@ { // get our private key and sign back L(F("client signature OK, accepting authentication\n")); - this->pattern = pattern; - this->pattern_re = boost::regex(this->pattern()); this->authenticated = true; this->remote_peer_key_name = their_id; // assume the (possibly degraded) opposite role - switch (role) + switch (their_role) { case source_role: I(this->role != source_role); @@ -2088,9 +2039,10 @@ encode_hexenc(id(remote_peer_key_hash), their_key_hash); // nb. this->role is our role, the server is in the opposite role - L(F("received 'confirm' netcmd from server '%s' for pattern '%s' in %s mode\n") - % their_key_hash % this->pattern % (this->role == source_and_sink_role ? "source and sink" : - (this->role == source_role ? "sink" : "source"))); + L(F("received 'confirm' netcmd from server '%s' for pattern '%s' exclude '%s' in %s mode\n") + % their_key_hash % our_include_pattern % our_exclude_pattern + % (this->role == source_and_sink_role ? "source and sink" : + (this->role == source_role ? "sink" : "source"))); // check their signature if (app.db.public_key_exists(their_key_hash)) @@ -3039,15 +2991,16 @@ "anonymous netcmd received in source or source/sink role"); { protocol_role role; - string pattern; + utf8 their_include_pattern, their_exclude_pattern; rsa_oaep_sha_data hmac_key_encrypted; - cmd.read_anonymous_cmd(role, pattern, hmac_key_encrypted); - L(F("received 'anonymous' netcmd from client for pattern '%s' " + cmd.read_anonymous_cmd(role, their_include_pattern, their_exclude_pattern, hmac_key_encrypted); + L(F("received 'anonymous' netcmd from client for pattern '%s' excluding '%s' " "in %s mode\n") - % pattern % (role == source_and_sink_role ? "source and sink" : - (role == source_role ? "source " : "sink"))); + % their_include_pattern % their_exclude_pattern + % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink"))); - if (!process_anonymous_cmd(role, pattern)) + if (!process_anonymous_cmd(role, their_include_pattern, their_exclude_pattern)) return false; respond_to_auth_cmd(hmac_key_encrypted); return true; @@ -3059,11 +3012,12 @@ require(voice == server_voice, "auth netcmd received in server voice"); { protocol_role role; - string pattern, signature; + string signature; + utf8 their_include_pattern, their_exclude_pattern; id client, nonce1, nonce2; rsa_oaep_sha_data hmac_key_encrypted; - cmd.read_auth_cmd(role, pattern, client, nonce1, - hmac_key_encrypted, signature); + cmd.read_auth_cmd(role, their_include_pattern, their_exclude_pattern, + client, nonce1, hmac_key_encrypted, signature); hexenc their_key_hash; encode_hexenc(client, their_key_hash); @@ -3071,12 +3025,14 @@ encode_hexenc(nonce1, hnonce1); L(F("received 'auth(hmac)' netcmd from client '%s' for pattern '%s' " - "in %s mode with nonce1 '%s'\n") - % their_key_hash % pattern % (role == source_and_sink_role ? "source and sink" : - (role == source_role ? "source " : "sink")) + "exclude '%s' in %s mode with nonce1 '%s'\n") + % their_key_hash % their_include_pattern % their_exclude_pattern + % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink")) % hnonce1); - if (!process_auth_cmd(role, pattern, client, nonce1, signature)) + if (!process_auth_cmd(role, their_include_pattern, their_exclude_pattern, + client, nonce1, signature)) return false; respond_to_auth_cmd(hmac_key_encrypted); return true; @@ -3251,7 +3207,8 @@ static void call_server(protocol_role role, - vector const & patterns, + utf8 const & include_pattern, + utf8 const & exclude_pattern, app_state & app, utf8 const & address, Netxx::port_type default_port, @@ -3264,8 +3221,8 @@ P(F("connecting to %s\n") % address()); Netxx::Stream server(address().c_str(), default_port, timeout); - session sess(role, client_voice, patterns, app, - address(), server.get_socketfd(), timeout); + session sess(role, client_voice, include_pattern, exclude_pattern, + app, address(), server.get_socketfd(), timeout); while (true) { @@ -3394,7 +3351,8 @@ Netxx::StreamServer & server, Netxx::Timeout & timeout, protocol_role role, - vector const & patterns, + utf8 const & include_pattern, + utf8 const & exclude_pattern, map > & sessions, app_state & app) { @@ -3409,7 +3367,8 @@ else { P(F("accepted new client connection from %s\n") % client); - shared_ptr sess(new session(role, server_voice, patterns, + shared_ptr sess(new session(role, server_voice, + include_pattern, exclude_pattern, app, lexical_cast(client), client.get_socketfd(), timeout)); @@ -3524,7 +3483,8 @@ static void serve_connections(protocol_role role, - vector const & patterns, + utf8 const & include_pattern, + utf8 const & exclude_pattern, app_state & app, utf8 const & address, Netxx::port_type default_port, @@ -3577,7 +3537,7 @@ // we either got a new connection else if (fd == server) handle_new_connection(addr, server, timeout, role, - patterns, sessions, app); + include_pattern, exclude_pattern, sessions, app); // or an existing session woke up else @@ -3776,7 +3736,8 @@ run_netsync_protocol(protocol_voice voice, protocol_role role, utf8 const & addr, - vector patterns, + utf8 const & include_pattern, + utf8 const & exclude_pattern, app_state & app) { try @@ -3784,7 +3745,7 @@ start_platform_netsync(); if (voice == server_voice) { - serve_connections(role, patterns, app, + serve_connections(role, include_pattern, exclude_pattern, app, addr, static_cast(constants::netsync_default_port), static_cast(constants::netsync_timeout_seconds), static_cast(constants::netsync_connection_limit)); @@ -3793,7 +3754,7 @@ { I(voice == client_voice); transaction_guard guard(app.db); - call_server(role, patterns, app, + call_server(role, include_pattern, exclude_pattern, app, addr, static_cast(constants::netsync_default_port), static_cast(constants::netsync_timeout_seconds)); guard.commit(); --- netsync.hh +++ netsync.hh @@ -22,7 +22,8 @@ void run_netsync_protocol(protocol_voice voice, protocol_role role, utf8 const & addr, - std::vector patterns, + utf8 const & include_pattern, + utf8 const & exclude_pattern, app_state & app); #endif // __NETSYNC_H__ --- tests/t_netsync_defaults.at +++ tests/t_netsync_defaults.at @@ -22,21 +22,21 @@ # First make sure netsync with explicit server/pattern override defaults AT_CHECK(MONOTONE2 set database default-server nonsense, [], [ignore], [ignore]) -AT_CHECK(MONOTONE2 set database default-pattern nonsense, [], [ignore], [ignore]) +AT_CHECK(MONOTONE2 set database default-include-pattern nonsense, [], [ignore], [ignore]) NETSYNC_CLIENT_RUN(pull, testbranch, 0) AT_CHECK(MONOTONE2 checkout --branch=testbranch --revision=$TESTBRANCH_R testdir1, [], [ignore], [ignore]) AT_CHECK(test -f testdir1/testfile) # Now make sure explicit server with default pattern works... AT_CHECK(MONOTONE2 set database default-server nonsense, [], [ignore], [ignore]) -AT_CHECK(MONOTONE2 set database default-pattern otherbranch, [], [ignore], [ignore]) +AT_CHECK(MONOTONE2 set database default-include-pattern otherbranch, [], [ignore], [ignore]) NETSYNC_CLIENT_RUN(pull, []) AT_CHECK(MONOTONE2 checkout --branch=otherbranch --revision=$OTHERBRANCH_R testdir2, [], [ignore], [ignore]) AT_CHECK(test -f testdir2/testfile) -# And finally, +# And finally, make sure that passing nothing at all also works (uses default) AT_CHECK(MONOTONE2 set database default-server 127.0.0.1:$_PORT, [], [ignore], [ignore]) -AT_CHECK(MONOTONE2 set database default-pattern thirdbranch, [], [ignore], [ignore]) +AT_CHECK(MONOTONE2 set database default-include-pattern thirdbranch, [], [ignore], [ignore]) AT_CHECK(MONOTONE2 sync, [], [ignore], [ignore]) AT_CHECK(MONOTONE2 checkout --branch=thirdbranch --revision=$THIRDBRANCH_R testdir3, [], [ignore], [ignore]) AT_CHECK(test -f testdir3/testfile) --- tests/t_netsync_permissions.at +++ tests/t_netsync_permissions.at @@ -18,7 +18,7 @@ return true end -function get_netsync_write_permitted(pattern, identity) +function get_netsync_write_permitted(identity) return true end @@ -106,7 +106,7 @@ return false end -function get_netsync_write_permitted(pattern, identity) +function get_netsync_write_permitted(identity) if (identity == "address@hidden") then return true end return false end --- tests/t_netsync_single.at +++ tests/t_netsync_single.at @@ -11,7 +11,7 @@ return true end -function get_netsync_write_permitted(pattern, identity) +function get_netsync_write_permitted(identity) return true end ]) --- testsuite.at +++ testsuite.at @@ -327,7 +327,7 @@ return true end -function get_netsync_write_permitted(pattern, identity) +function get_netsync_write_permitted(identity) return true end ]) --- transforms.cc +++ transforms.cc @@ -870,142 +870,8 @@ dst += linesep_str; } -// glob_to_regexp converts a sh file glob to a regexp. The regexp should -// be usable by the Boost regexp library. -// -// Pattern tranformation: -// -// - Any character except those described below are copied as they are. -// - The backslash (\) escapes the following character. The escaping -// backslash is copied to the regexp along with the following character. -// - * is transformed to .* in the regexp. -// - ? is transformed to . in the regexp. -// - { is transformed to ( in the regexp, unless within [ and ]. -// - } is transformed to ) in the regexp, unless within [ and ]. -// - , is transformed to | in the regexp, if within { and } and not -// within [ and ]. -// - ^ is escaped unless it comes directly after an unescaped [. -// - ! is transformed to ^ in the regexp if it comes directly after an -// unescaped [. -// - ] directly following an unescaped [ is escaped. -string glob_to_regexp(const string & glob) -{ - int in_braces = 0; // counter for levels if {} - bool in_brackets = false; // flags if we're inside a [], which - // has higher precedence than {}. - // Also, [ is accepted inside [] unescaped. - bool this_was_opening_bracket = false; - string tmp; - tmp.reserve(glob.size() * 2); - #ifdef BUILD_UNIT_TESTS - cerr << "DEBUG[glob_to_regexp]: input = \"" << glob << "\"" << endl; -#endif - - for (string::const_iterator i = glob.begin(); i != glob.end(); ++i) - { - char c = *i; - bool last_was_opening_bracket = this_was_opening_bracket; - this_was_opening_bracket = false; - - // Special case ^ and ! at the beginning of a [] expression. - if (in_brackets && last_was_opening_bracket - && (c == '!' || c == '^')) - { - tmp += '^'; - if (++i == glob.end()) - break; - c = *i; - } - - if (c == '\\') - { - tmp += c; - if (++i == glob.end()) - break; - tmp += *i; - } - else if (in_brackets) - { - switch(c) - { - case ']': - if (!last_was_opening_bracket) - { - in_brackets = false; - tmp += c; - break; - } - // Trickling through to the standard character conversion, - // because ] as the first character of a set is regarded as - // a normal character. - default: - if (!(isalnum(c) || c == '_')) - { - tmp += '\\'; - } - tmp += c; - break; - } - } - else - { - switch(c) - { - case '*': - tmp += ".*"; - break; - case '?': - tmp += '.'; - break; - case '{': - in_braces++; - tmp += '('; - break; - case '}': - N(in_braces != 0, - F("trying to end a brace expression in a glob when none is started")); - tmp += ')'; - in_braces--; - break; - case '[': - in_brackets = true; - this_was_opening_bracket = true; - tmp += c; - break; - case ',': - if (in_braces > 0) - { - tmp += '|'; - break; - } - // Trickling through to default: here, since a comma outside of - // brace notation is just a normal character. - default: - if (!(isalnum(c) || c == '_')) - { - tmp += '\\'; - } - tmp += c; - break; - } - } - } - - N(!in_brackets, - F("run-away bracket expression in glob")); - N(in_braces == 0, - F("run-away brace expression in glob")); - -#ifdef BUILD_UNIT_TESTS - cerr << "DEBUG[glob_to_regexp]: output = \"" << tmp << "\"" << endl; -#endif - - return tmp; -} - -#ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" static void @@ -1283,15 +1149,6 @@ check_idna_encoding(); } -static void glob_to_regexp_test() -{ - BOOST_CHECK(glob_to_regexp("abc,v") == "abc\\,v"); - BOOST_CHECK(glob_to_regexp("foo[12m,]") == "foo[12m\\,]"); - // A full fledged, use all damn features test... - BOOST_CHECK(glob_to_regexp("foo.{bar*,cookie?{haha,hehe[^\\123!,]}}[!]a^b]") - == "foo\\.(bar.*|cookie.(haha|hehe[^\\123\\!\\,]))[^\\]a\\^b]"); -} - void add_transform_tests(test_suite * suite) { @@ -1303,7 +1160,6 @@ suite->add(BOOST_TEST_CASE(&join_lines_test)); suite->add(BOOST_TEST_CASE(&strip_ws_test)); suite->add(BOOST_TEST_CASE(&encode_test)); - suite->add(BOOST_TEST_CASE(&glob_to_regexp_test)); } #endif // BUILD_UNIT_TESTS --- transforms.hh +++ transforms.hh @@ -193,6 +193,4 @@ // line-ending conversion void line_end_convert(std::string const & linesep, std::string const & src, std::string & dst); - - #endif // __TRANSFORMS_HH__ --- unit_tests.cc +++ unit_tests.cc @@ -69,9 +69,11 @@ if (t.empty() || t.find("netcmd") != t.end()) add_netcmd_tests(suite); - if (t.empty() || t.find("netcmd") != t.end()) + if (t.empty() || t.find("path_component") != t.end()) add_path_component_tests(suite); + if (t.empty() || t.find("globish") != t.end()) + add_globish_tests(suite); // all done, add our clean-shutdown-indicator suite->add(BOOST_TEST_CASE(&clean_shutdown_dummy_test)); --- unit_tests.hh +++ unit_tests.hh @@ -32,5 +32,6 @@ void add_packet_tests(test_suite * suite); void add_netcmd_tests(test_suite * suite); void add_path_component_tests(test_suite * suite); +void add_globish_tests(test_suite * suite); #endif