#
#
# add_file "tests/t_netsync_pipe.at"
# content [34d87cd35050ed0affb1e70e61756ae9805a6663]
#
# add_file "uri.cc"
# content [bee5841639bfb4eb75f876c0113d32a2501e313f]
#
# add_file "uri.hh"
# content [67c843f71a924884f16c2f3893e81763e5c857e3]
#
# patch "ChangeLog"
# from [18e30f0a0d156b95eb8ab37c71d3d6a685bc84de]
# to [753e4315d9448f5e92074a053205dedbeed79cc5]
#
# patch "Makefile.am"
# from [eeeba6b1fdbe46f7152547cb8a6bd8442a8ecd86]
# to [7843b99fee376b698a82c36fe38fb5c855edfbd0]
#
# patch "app_state.cc"
# from [404998f5d4476cd09438a1d10bbed53bd630ee1e]
# to [bfec9d7853b8ee16c2e58db6751b1388994863a7]
#
# patch "app_state.hh"
# from [6485c2ec335e5d8150627d10f9ec280d62382544]
# to [13dc8197e52bfe0f1b3b86605de4ce722c78be55]
#
# patch "cmd_netsync.cc"
# from [7651e092ad580f471946abf9877ca161235fb767]
# to [a8117edff38b7ecc81c3fecd5b1eb571ef5b86d0]
#
# patch "constants.hh"
# from [b8396f4cd024f5c1fd0d1cbd1975d2359e3b7771]
# to [3d276dea19a717e26308eb6ebf8ce2ea90bda51a]
#
# patch "hmac.cc"
# from [01430adfa6339a539db4327741755a05191bab7b]
# to [ee71008df67eb9675d129a259d2098f33cd8f995]
#
# patch "hmac.hh"
# from [759ade0869017270c8f10bf4307e7ab463f18868]
# to [e3082711a437a693ba707ca763e1c2f6755efaba]
#
# patch "lua.cc"
# from [6abb5987206ee45b2389366b815cc391e9ecce7b]
# to [42620cdba2f9a763c501835933c645086107f6ff]
#
# patch "lua.hh"
# from [664f93c8b443254f5a75fc40ee0bd12d50a38240]
# to [dc4ffe6138529720f8ce71d3772728f0e1fa5ab5]
#
# patch "monotone.cc"
# from [44be693267e1ec6d4071b3b09396f1b603ccfb34]
# to [bace69db039d59243f79a46a12cb6d987fb8be3c]
#
# patch "monotone.texi"
# from [f0de15af6282b1884fbd6ef3e0fb25a8d69fb3fe]
# to [89a70b4f75af8ca1093f9fcf20f3a2ec1e436dab]
#
# patch "netcmd.cc"
# from [da3773c93069c0834ce0b3caf34020305dd7d8b2]
# to [946d279a09466cac51782ee1cb415080ef6aef79]
#
# patch "netsync.cc"
# from [901a77d250f4a5185209f8f7bc6ab7f9d7c7766c]
# to [0f736c6e24489bcf0cba06b1f14c09074ab3fc16]
#
# patch "options.hh"
# from [6ea8ce2a5b90b4bd1fffc77df22160eec911e985]
# to [ab30747195c445f90863f386adf2e5f9c8700349]
#
# patch "std_hooks.lua"
# from [05ecfce99ad27ce5042eacfe0c9c08d21cd11c3e]
# to [614066fdf36561a75e035ba121c8f18aae0a69dc]
#
# patch "testsuite.at"
# from [804e08edab3e29dd67002c80eed79c3fd467e64d]
# to [983ee4bb284c08f42937f7faad74f6b29e094ac0]
#
# patch "unit_tests.cc"
# from [6ce81ae44beea87dae0c70d780da8e665d83fa19]
# to [124bec7d54edfb20b1321c478c753bec8f6e9951]
#
# patch "unit_tests.hh"
# from [96b6a1b7b6d7d5d85e026fcbd69af860da478739]
# to [a9415430326fcb490e7e923a4a3fa73c0f1135a3]
#
============================================================
--- tests/t_netsync_pipe.at 34d87cd35050ed0affb1e70e61756ae9805a6663
+++ tests/t_netsync_pipe.at 34d87cd35050ed0affb1e70e61756ae9805a6663
@@ -0,0 +1,13 @@
+AT_SETUP([netsync over pipes])
+MTN_SETUP
+
+AT_CHECK(cp test.db test2.db, [], [ignore], [ignore])
+
+ADD_FILE(testfile, [foo
+])
+COMMIT(testbranch)
+
+AT_CHECK(MTN sync file:test2.db testbranch, [], [ignore], [ignore])
+CHECK_SAME_DB_CONTENTS(test.db, test2.db)
+
+AT_CLEANUP
============================================================
--- uri.cc bee5841639bfb4eb75f876c0113d32a2501e313f
+++ uri.cc bee5841639bfb4eb75f876c0113d32a2501e313f
@@ -0,0 +1,188 @@
+// copyright (C) 2006 graydon hoare
+// all rights reserved.
+// licensed to the public under the terms of the GNU GPL (>= 2)
+// see the file COPYING for details
+
+#include
+
+#include
+
+#include "sanity.hh"
+#include "uri.hh"
+
+using std::string;
+
+bool
+parse_uri(string const & in, uri & out)
+{
+ uri u;
+
+ // This is a simplified URI grammar. It does the basics.
+
+ string scheme_part = "(?:([^:/?#]+):)?";
+ string authority_part = "(?://([^/?#]*))?";
+ string path_part = "([^?#]*)";
+ string query_part = "(?:\\?([^#]*))?";
+ string fragment_part = "(?:#(.*))?";
+
+ string uri_rx = (string("^")
+ + scheme_part
+ + authority_part
+ + path_part
+ + query_part
+ + fragment_part
+ + "$");
+
+ boost::match_results uri_matches;
+ if (boost::regex_match(in, uri_matches, boost::regex(uri_rx)))
+ {
+
+ u.scheme = uri_matches.str(1);
+
+ // The "authority" fragment gets a bit more post-processing.
+ L(FL("matched URI scheme: '%s'") % u.scheme);
+
+ if (uri_matches[2].matched)
+ {
+ string authority = uri_matches.str(2);
+ L(FL("matched URI authority: '%s'") % authority);
+
+ string user_part = "(?:(address@hidden)@)?";
+ string ipv6_host_part = "\\[([^\\]]+)]\\]";
+ string normal_host_part = "([^:/]+)";
+ string host_part = "(?:" + ipv6_host_part + "|" + normal_host_part + ")";
+ string port_part = "(?::([[:digit:]]+))?";
+ string auth_rx = user_part + host_part + port_part;
+ boost::match_results auth_matches;
+ I(boost::regex_match(authority, auth_matches, boost::regex(auth_rx)));
+ u.user = auth_matches.str(1);
+ u.port = auth_matches.str(4);
+ if (auth_matches[2].matched)
+ u.host = auth_matches.str(2);
+ else
+ {
+ I(auth_matches[3].matched);
+ u.host = auth_matches.str(3);
+ }
+ L(FL("matched URI user: '%s'") % u.user);
+ L(FL("matched URI host: '%s'") % u.host);
+ L(FL("matched URI port: '%s'") % u.port);
+
+ }
+
+ u.path = uri_matches.str(3);
+ u.query = uri_matches.str(4);
+ u.fragment = uri_matches.str(5);
+ L(FL("matched URI path: '%s'") % u.path);
+ L(FL("matched URI query: '%s'") % u.query);
+ L(FL("matched URI fragment: '%s'") % u.fragment);
+ out = u;
+ return true;
+ }
+ else
+ return false;
+}
+
+
+
+#ifdef BUILD_UNIT_TESTS
+#include "unit_tests.hh"
+#include "transforms.hh"
+
+static void
+test_one_uri(string scheme,
+ string user,
+ string ipv6_host,
+ string normal_host,
+ string port,
+ string path,
+ string query,
+ string fragment)
+{
+ string built;
+
+ if (!scheme.empty())
+ built += scheme + ':';
+
+ string host;
+
+ if (! ipv6_host.empty())
+ {
+ I(normal_host.empty());
+ host += '[';
+ host += (ipv6_host + ']');
+ }
+ else
+ host = normal_host;
+
+ if (! (user.empty()
+ && host.empty()
+ && port.empty()))
+ {
+ built += "//";
+
+ if (! user.empty())
+ built += (user + '@');
+
+ if (! host.empty())
+ built += host;
+
+ if (! port.empty())
+ {
+ built += ':';
+ built += port;
+ }
+ }
+
+ if (! path.empty())
+ {
+ I(path[0] == '/');
+ built += path;
+ }
+
+ if (! query.empty())
+ {
+ built += '?';
+ built += query;
+ }
+
+ if (! fragment.empty())
+ {
+ built += '#';
+ built += fragment;
+ }
+
+ L(FL("testing parse of URI '%s'") % built);
+ uri u;
+ BOOST_CHECK(parse_uri(built, u));
+ BOOST_CHECK(u.scheme == scheme);
+ BOOST_CHECK(u.user == user);
+ BOOST_CHECK(u.host == host);
+ BOOST_CHECK(u.port == port);
+ BOOST_CHECK(u.path == path);
+ BOOST_CHECK(u.query == query);
+ BOOST_CHECK(u.fragment == fragment);
+}
+
+
+static void
+uri_test()
+{
+ test_one_uri("ssh", "graydon", "", "venge.net", "22", "/tmp/foo.mtn", "", "");
+ test_one_uri("ssh", "graydon", "", "venge.net", "", "/tmp/foo.mtn", "", "");
+ test_one_uri("ssh", "", "", "venge.net", "22", "/tmp/foo.mtn", "", "");
+ test_one_uri("ssh", "", "", "venge.net", "", "/tmp/foo.mtn", "", "");
+ test_one_uri("file", "", "", "", "", "/tmp/foo.mtn", "", "");
+ test_one_uri("", "", "", "", "", "/tmp/foo.mtn", "", "");
+ test_one_uri("http", "graydon", "", "venge.net", "8080", "/foo.cgi", "branch=foo", "tip");
+}
+
+
+void
+add_uri_tests(test_suite * suite)
+{
+ I(suite);
+ suite->add(BOOST_TEST_CASE(&uri_test));
+}
+
+#endif // BUILD_UNIT_TESTS
============================================================
--- uri.hh 67c843f71a924884f16c2f3893e81763e5c857e3
+++ uri.hh 67c843f71a924884f16c2f3893e81763e5c857e3
@@ -0,0 +1,23 @@
+#ifndef __URI_HH__
+#define __URI_HH__
+
+// copyright (C) 2006 graydon hoare
+// all rights reserved.
+// licensed to the public under the terms of the GNU GPL (>= 2)
+// see the file COPYING for details
+
+struct uri
+{
+ std::string scheme;
+ std::string user;
+ std::string host;
+ std::string port;
+ std::string path;
+ std::string query;
+ std::string fragment;
+};
+
+bool
+parse_uri(std::string const & in, uri & out);
+
+#endif
============================================================
--- ChangeLog 18e30f0a0d156b95eb8ab37c71d3d6a685bc84de
+++ ChangeLog 753e4315d9448f5e92074a053205dedbeed79cc5
@@ -1,3 +1,24 @@
+2006-05-22 Graydon Hoare
+
+ * Makefile.am: Add uri.{cc,hh}.
+ * app_state.{cc,hh} (use_transport_auth): New state variable.
+ * cmd_netsync.cc (serve): Support use_transport_auth.
+ * constants.hh (netcmd_minsz): Remove dead byte count related to adler32.
+ * hmac.{cc,hh} (active): New state variable.
+ * lua.{cc,hh} (lua_hooks::hook_get_netsync_connect_command):
+ (lua_hooks::hook_use_transport_auth): New hooks.
+ * monotone.cc: Support --no-transport-auth.
+ * monotone.texi: Document all this stuff.
+ * netcmd.cc: Predicate hmac activity on hmac.is_active().
+ * netsync.cc: Parse URIs via lua, optionally disable transport auth.
+ * options.hh (OPT_NO_TRANSPORT_AUTH): New option.
+ * std_hooks.lua (get_netsync_connect_command):
+ (use_transport_auth): New default definitions.
+ * tests/t_netsync_pipe.at: New test.
+ * testsuite.at: Add new hooks and t_netsync_pipe.at.
+ * unit_tests.{cc,hh}: Support URI tests.
+ * uri.{cc,hh}: New files.
+
2006-05-15 Matt Johnston
* cmd_diff_log (log, dump_diffs): limit diffs to restricted files,
============================================================
--- Makefile.am eeeba6b1fdbe46f7152547cb8a6bd8442a8ecd86
+++ Makefile.am 7843b99fee376b698a82c36fe38fb5c855edfbd0
@@ -55,6 +55,7 @@
roster_merge.cc roster_merge.hh \
merge.cc merge.hh \
legacy.cc legacy.hh \
+ uri.cc uri.hh \
\
lru_cache.h \
\
============================================================
--- app_state.cc 404998f5d4476cd09438a1d10bbed53bd630ee1e
+++ app_state.cc bfec9d7853b8ee16c2e58db6751b1388994863a7
@@ -36,7 +36,7 @@
search_root("/"),
depth(-1), last(-1), next(-1), diff_format(unified_diff), diff_args_provided(false),
use_lca(false), execute(false), bind_address(""), bind_port(""), bind_stdio(false),
- missing(false), unknown(false),
+ use_transport_auth(true), missing(false), unknown(false),
confdir(get_default_confdir()), have_set_key_dir(false), no_files(false)
{
db.set_app(this);
============================================================
--- app_state.hh 6485c2ec335e5d8150627d10f9ec280d62382544
+++ app_state.hh 13dc8197e52bfe0f1b3b86605de4ce722c78be55
@@ -67,6 +67,7 @@
utf8 bind_address;
utf8 bind_port;
bool bind_stdio;
+ bool use_transport_auth;
bool missing;
bool unknown;
std::vector keys_to_push;
============================================================
--- cmd_netsync.cc 7651e092ad580f471946abf9877ca161235fb767
+++ cmd_netsync.cc a8117edff38b7ecc81c3fecd5b1eb571ef5b86d0
@@ -168,20 +168,23 @@
CMD_NO_WORKSPACE(serve, N_("network"), N_("PATTERN ..."),
N_("serve the branches specified by PATTERNs to connecting clients"),
- OPT_BIND % OPT_STDIO % OPT_PIDFILE % OPT_EXCLUDE)
+ OPT_BIND % OPT_STDIO % OPT_NO_TRANSPORT_AUTH % OPT_PIDFILE % OPT_EXCLUDE)
{
if (args.size() < 1)
throw usage(name);
pid_file pid(app.pidfile);
- rsa_keypair_id key;
- get_user_key(key, app);
- app.signing_key = key;
+ if (app.use_transport_auth)
+ {
+ rsa_keypair_id key;
+ get_user_key(key, app);
+ app.signing_key = key;
- N(app.lua.hook_persist_phrase_ok(),
- F("need permission to store persistent passphrase (see hook persist_phrase_ok())"));
- require_password(key, app);
+ N(app.lua.hook_persist_phrase_ok(),
+ F("need permission to store persistent passphrase (see hook persist_phrase_ok())"));
+ require_password(key, app);
+ }
app.db.ensure_open();
============================================================
--- constants.hh b8396f4cd024f5c1fd0d1cbd1975d2359e3b7771
+++ constants.hh 3d276dea19a717e26308eb6ebf8ce2ea90bda51a
@@ -117,8 +117,7 @@
// minimum size of any netcmd on the wire
static size_t const netcmd_minsz = (1 // version
+ 1 // cmd code
- + 1 // smallest uleb possible
- + 4); // adler32
+ + 1); // smallest uleb possible
// largest command *payload* allowed in a netcmd
============================================================
--- hmac.cc 01430adfa6339a539db4327741755a05191bab7b
+++ hmac.cc ee71008df67eb9675d129a259d2098f33cd8f995
@@ -7,8 +7,9 @@
#include "vocab.hh"
#include "constants.hh"
-chained_hmac::chained_hmac(netsync_session_key const & session_key) :
+chained_hmac::chained_hmac(netsync_session_key const & session_key, bool active) :
hmac_length(constants::sha1_digest_length),
+ active(active),
key(reinterpret_cast(session_key().data()), session_key().size())
{
chain_val.assign(hmac_length, 0x00);
@@ -17,13 +18,18 @@
void
chained_hmac::set_key(netsync_session_key const & session_key)
{
- key = Botan::SymmetricKey(reinterpret_cast(session_key().data()),
- session_key().size());
+ if (active)
+ {
+ key = Botan::SymmetricKey(reinterpret_cast(session_key().data()),
+ session_key().size());
+ }
}
std::string
chained_hmac::process(std::string const & str, size_t pos, size_t n)
{
+ I(active);
+
I(pos < str.size());
if (n == std::string::npos)
n = str.size() - pos;
@@ -46,6 +52,8 @@
std::string
chained_hmac::process(string_queue const & str, size_t pos, size_t n)
{
+ I(active);
+
I(pos < str.size());
if (n == std::string::npos)
n = str.size() - pos;
============================================================
--- hmac.hh 759ade0869017270c8f10bf4307e7ab463f18868
+++ hmac.hh e3082711a437a693ba707ca763e1c2f6755efaba
@@ -10,19 +10,21 @@
struct chained_hmac
{
- public:
- chained_hmac(netsync_session_key const & session_key);
- void set_key(netsync_session_key const & session_key);
- std::string process(std::string const & str, size_t pos = 0,
- size_t n = std::string::npos);
- std::string process(string_queue const & str, size_t pos = 0,
- size_t n = std::string::npos);
-
- size_t const hmac_length;
-
- private:
- Botan::SymmetricKey key;
- std::string chain_val;
+public:
+ chained_hmac(netsync_session_key const & session_key, bool active);
+ void set_key(netsync_session_key const & session_key);
+ std::string process(std::string const & str, size_t pos = 0,
+ size_t n = std::string::npos);
+ std::string process(string_queue const & str, size_t pos = 0,
+ size_t n = std::string::npos);
+
+ size_t const hmac_length;
+ bool is_active() { return active; }
+
+private:
+ bool active;
+ Botan::SymmetricKey key;
+ std::string chain_val;
};
============================================================
--- lua.cc 6abb5987206ee45b2389366b815cc391e9ecce7b
+++ lua.cc 42620cdba2f9a763c501835933c645086107f6ff
@@ -38,6 +38,7 @@
#include "paths.hh"
#include "globish.hh"
#include "basic_io.hh"
+#include "uri.hh"
// defined in {std,test}_hooks.lua, converted
#include "test_hooks.h"
@@ -1229,7 +1230,128 @@
return use && exec_ok;
}
+static void
+push_uri(uri const & u, Lua & ll)
+{
+ ll.push_table();
+
+ if (!u.scheme.empty())
+ {
+ ll.push_str("scheme");
+ ll.push_str(u.scheme);
+ ll.set_table();
+ }
+
+ if (!u.user.empty())
+ {
+ ll.push_str("user");
+ ll.push_str(u.user);
+ ll.set_table();
+ }
+
+ if (!u.host.empty())
+ {
+ ll.push_str("host");
+ ll.push_str(u.host);
+ ll.set_table();
+ }
+
+ if (!u.port.empty())
+ {
+ ll.push_str("port");
+ ll.push_str(u.port);
+ ll.set_table();
+ }
+
+ if (!u.path.empty())
+ {
+ ll.push_str("path");
+ ll.push_str(u.path);
+ ll.set_table();
+ }
+
+ if (!u.query.empty())
+ {
+ ll.push_str("query");
+ ll.push_str(u.query);
+ ll.set_table();
+ }
+
+ if (!u.fragment.empty())
+ {
+ ll.push_str("fragment");
+ ll.push_str(u.fragment);
+ ll.set_table();
+ }
+}
+
bool
+lua_hooks::hook_get_netsync_connect_command(uri const & u,
+ std::string const & include_pattern,
+ std::string const & exclude_pattern,
+ bool debug,
+ std::vector & argv)
+{
+ bool cmd = false, exec_ok = false;
+ Lua ll(st);
+ ll.func("get_netsync_connect_command");
+
+ push_uri(u, ll);
+
+ ll.push_table();
+
+ if (!include_pattern.empty())
+ {
+ ll.push_str("include");
+ ll.push_str(include_pattern);
+ ll.set_table();
+ }
+
+ if (!exclude_pattern.empty())
+ {
+ ll.push_str("exclude");
+ ll.push_str(exclude_pattern);
+ ll.set_table();
+ }
+
+ if (debug)
+ {
+ ll.push_str("debug");
+ ll.push_bool(debug);
+ ll.set_table();
+ }
+
+ ll.call(2,1);
+
+ ll.begin();
+
+ argv.clear();
+ while(ll.next())
+ {
+ std::string s;
+ ll.extract_str(s).pop();
+ argv.push_back(s);
+ }
+ return ll.ok() && !argv.empty();
+}
+
+
+bool
+lua_hooks::hook_use_transport_auth(uri const & u)
+{
+ bool use_auth = true;
+ Lua ll(st);
+ ll.func("use_transport_auth");
+ push_uri(u, ll);
+ ll.call(1,1);
+ ll.extract_bool(use_auth);
+
+ // NB: we want to return *true* here if there's a failure.
+ return use_auth;
+}
+
+
+bool
lua_hooks::hook_get_netsync_read_permitted(std::string const & branch,
rsa_keypair_id const & identity)
{
============================================================
--- lua.hh 664f93c8b443254f5a75fc40ee0bd12d50a38240
+++ lua.hh dc4ffe6138529720f8ce71d3772728f0e1fa5ab5
@@ -16,7 +16,7 @@
#include "vocab.hh"
#include "paths.hh"
-struct patch_set;
+struct uri;
struct lua_State;
@@ -59,6 +59,13 @@
std::map const & new_results);
// network hooks
+ bool hook_get_netsync_connect_command(uri const & u,
+ std::string const & include_pattern,
+ std::string const & exclude_pattern,
+ bool debug,
+ std::vector & argv);
+ bool hook_use_transport_auth(uri const & u);
+
bool hook_get_netsync_read_permitted(std::string const & branch,
rsa_keypair_id const & identity);
// anonymous no-key version
============================================================
--- monotone.cc 44be693267e1ec6d4071b3b09396f1b603ccfb34
+++ monotone.cc bace69db039d59243f79a46a12cb6d987fb8be3c
@@ -79,6 +79,7 @@
{"unknown", 0, POPT_ARG_NONE, NULL, OPT_UNKNOWN, gettext_noop("perform the operations for unknown files from workspace"), NULL},
{"key-to-push", 0, POPT_ARG_STRING, &argstr, OPT_KEY_TO_PUSH, gettext_noop("push the specified key even if it hasn't signed anything"), NULL},
{"stdio", 0, POPT_ARG_NONE, NULL, OPT_STDIO, gettext_noop("serve netsync on stdio"), NULL},
+ {"no-transport-auth", 0, POPT_ARG_NONE, NULL, OPT_NO_TRANSPORT_AUTH, gettext_noop("disable transport authentication"), NULL},
{"drop-attr", 0, POPT_ARG_STRING, &argstr, OPT_DROP_ATTR, gettext_noop("when rosterifying, drop attrs entries with the given key"), NULL},
{"no-files", 0, POPT_ARG_NONE, NULL, OPT_NO_FILES, gettext_noop("exclude files when printing logs"), NULL},
{"recursive", 'R', POPT_ARG_NONE, NULL, OPT_RECURSIVE, gettext_noop("also operate on the contents of any listed directories"), NULL},
@@ -500,6 +501,10 @@
app.bind_stdio = true;
break;
+ case OPT_NO_TRANSPORT_AUTH:
+ app.use_transport_auth = false;
+ break;
+
case OPT_BIND:
{
std::string arg(argstr);
============================================================
--- monotone.texi f0de15af6282b1884fbd6ef3e0fb25a8d69fb3fe
+++ monotone.texi 89a70b4f75af8ca1093f9fcf20f3a2ec1e436dab
@@ -2303,11 +2303,11 @@
@itemize
@item
-Firstly, Jim is finding that he has to spend more of his time
+First, Jim is finding that he has to spend more of his time
travelling, demonstrating the new juicebot features to customers; thus
his laptop is spending more time disconnected from the network, or
connected at dynamic addresses where it's not convenient for Abe and
-Beth to find him and sync.
+Beth to find him and sync.
This doesn't prevent them doing any work, but it does have some
uncomfortable consequences: they're more likely to have to manually
@@ -2318,11 +2318,11 @@
with another database.
@item
-Secondly, because Jim has been using the one database file both
-for his own local work, and for serving to the others in the team, he
+Second, because Jim has been using the one database file both for his
+own local work, and for serving to the others in the team, he
occasionally finds that the monotone serve process (busy syncing with
Abe or Beth) has a lock on the database, while he's trying to do local
-work like updates or commits.
+work like updates or commits.
The level of project activity is picking up, and there are more and more
changes to be synced in the narrower window of time while Jim is
@@ -2555,6 +2555,7 @@
familiarize yourself with its operation.
@menu
+* Other Transports:: Using netsync over SSH, or other transports.
* Selectors:: Selecting revisions by certificate.
* Restrictions:: Limit workspace changes to specified files.
* Scripting:: Running monotone from other programs.
@@ -2571,7 +2572,59 @@
* Using packets:: Transferring data ``by hand''.
@end menu
address@hidden
address@hidden Other Transports
address@hidden Other Transports
+Monotone's database synchronization system is based on a protocol
+called netsync. By default, monotone transports this protocol over a
+plain TCP connection, but this is not the only transport monotone can
+use. It can also transport netsync through SSH, or any program which
+can provide a full-duplex connection over @code{stdio}.
+
+When a monotone client initiates a push, pull, or sync operation, it
+parses the first command-line argument as a URI and calls a lua hook
+to convert that URI into a @dfn{connection command}. If the lua hook
+returns a connection command, monotone spawns the command locally and
+speaks netsync over a pipe connected to the command's standard I/O
+handles.
+
+If the lua hook does not return a connection command, monotone
+attempts to parse the command-line argument as a TCP address -- a
+hostname with an optional port number -- connects a TCP socket the
+host and port, and speaks netsync over the socket.
+
+By default, monotone understands two URI schemes:
+
address@hidden
address@hidden SSH URIs, of the form
+ @code{ssh://address@hidden@@address@hidden:@var{port}]/@var{path/to/db.mtn}},
+ to synchronize between private databases on hosts accessible only through SSH.
address@hidden File URIs, of the form
+ @code{file:@var{/path/to/db.mtn}}, to synchronize between local databases.
address@hidden enumerate
+
+In the case of SSH URIs, the @command{ssh} program must be in your
+command execution path, either @var{$PATH} on Unix-like systems or
address@hidden on Windows systems. Monotone will execute @command{ssh}
+as a subprocess, running @command{mtn serve} on the other end of the
+SSH connection. You will need @command{mtn} to be in the command
+execution path of the remote shell environment.
+
+In the case of File URIs, @command{mtn} is run locally, so must be
+in your command execution path.
+
+In both cases, the database specified in the URI needs to exist
+already, and will be locked for the duration of the synchronization
+operation. Also note that monotone's default transport authentication
+is @emph{disabled} over these transports, to reduce the complexity of
+configuration and eliminate redundant protocol cost.
+
+Additional URI schemes can be supported by customization of the lua
+hooks @code{get_netsync_connect_command} and
address@hidden For details on these hooks, see
address@hidden Transport Hooks}.
+
@page
@node Selectors
@section Selectors
@@ -4310,24 +4363,49 @@
@ftable @command
@item mtn serve address@hidden:@var{port}]] @var{glob} [...] address@hidden
address@hidden mtn pull [--set-default] address@hidden:@var{port}] address@hidden [...] address@hidden
address@hidden mtn push [--set-default] address@hidden:@var{port}] address@hidden [...] address@hidden
address@hidden mtn sync [--set-default] address@hidden:@var{port}] address@hidden [...] address@hidden
address@hidden mtn serve --stdio [--no-transport-auth] @var{glob} [...] address@hidden
address@hidden mtn pull [--set-default] address@hidden address@hidden [...] address@hidden
address@hidden mtn push [--set-default] address@hidden address@hidden [...] address@hidden
address@hidden mtn sync [--set-default] address@hidden address@hidden [...] address@hidden
These commands operate the ``netsync'' protocol built into
monotone. This is a custom protocol for rapidly synchronizing two
monotone databases using a hash tree index. The protocol is ``peer to
peer'', but requires one peer to listen for incoming connections (the
-server) and the other peer (the client) to connect to the server.
+server) and the other peer (the client) to connect to the server. When
+run with @option{--stdio}, the server listens for a single connection
+then terminates. When run with @option{--bind}, or with neither
+option, the server listens for TCP connections and serves them
+continuously, until it is shut down.
-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 default port number is 4691. The @var{glob}
-parameters indicate a set of branches to exchange. Multiple @var{glob}
-and @option{--exclude} options can be specified; every branch which
-matches a @var{glob} exactly, and does not match an @var{exclude-glob},
-will be indexed and made available for synchronization.
+The network @var{address} given to @command{serve} as an argument to
address@hidden should be a host name to listen on, optionally
+followed by a colon and a port number. The default port number is
+4691. If no @option{--bind} option is given, the server listens on
+port 4691 of every network interface.
+If @command{serve} is run with @option{--stdio}, a single netsync
+session is served over the @code{stdin} and @code{stdout} file
+descriptors. If @option{--no-transport-auth} is provided along with
address@hidden, transport authentication and access control mechanisms
+are disabled. Only use @option{--no-transport-auth} if you are certain
+that the transport channel in use already provides sufficient
+authentication and authorization facilities.
+
+The @var{uri-or-address} arguments given to @command{push},
address@hidden, and @command{sync} can be of two possible forms. If
+the argument is a URI, a lua hook may transform the URI into a
+connection command, and execute the command as a transport channel for
+netsync. If the argument is a simple hostname (with optional port
+number), monotone will use a TCP socket to the specified host and port
+as a transport channel for netsync.
+
+The @var{glob} parameters indicate a set of branches to exchange.
+Multiple @var{glob} and @option{--exclude} options can be specified;
+every branch which matches a @var{glob} exactly, and does not match an
address@hidden, will be indexed and made available for
+synchronization.
+
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
@@ -6976,6 +7054,135 @@
@end ftable
address@hidden Transport Hooks}
address@hidden Netsync Transport Hooks
+
+When a monotone client initiates a netsync connection, these hooks are
+called to attempt to parse the host argument provided on the command
+line. If the hooks fail or return nil, monotone will interpret the
+host argument as a network name (possibly with a port number) and open
+a TCP socket.
+
address@hidden @code
address@hidden get_netsync_connect_command (@var{uri}, @var{args})
+
+Returns a table describing a command to run to connect to the
+specified host. The @var{uri} argument is a table containing
+between 0 and 7 components:
+
address@hidden
address@hidden @code{uri["scheme"]}, such as @code{"ssh"} or @code{"file"}
address@hidden @code{uri["user"]}, the name of a remote user
address@hidden @code{uri["host"]}, the name or address of a remote host
address@hidden @code{uri["port"]}, a network port number
address@hidden @code{uri["path"]}, a filesystem path
address@hidden @code{uri["query"]}, for additional parameters
address@hidden @code{uri["fragment"]}, to describe a sub-location within the remote resource
address@hidden itemize
+
+The @var{args} argument is a table containing between 0 and 3
+components:
+
address@hidden
address@hidden @code{args["include"]}, the branch pattern to include
address@hidden @code{args["exclude"]}, the branch pattern to exclude
address@hidden @code{args["debug"]}, whether to run the connection in debug mode
address@hidden itemize
+
+The default definition of this hook follows:
+
address@hidden
address@hidden
+function get_netsync_connect_command(uri, args)
+
+ local argv = nil
+
+ if uri["scheme"] == "ssh"
+ and uri["host"]
+ and uri["path"] then
+
+ argv = @{ "ssh" @}
+ if uri["user"] then
+ table.insert(argv, "-l")
+ table.insert(argv, uri["user"])
+ end
+ if uri["port"] then
+ table.insert(argv, "-p")
+ table.insert(argv, uri["port"])
+ end
+
+ table.insert(argv, uri["host"])
+ end
+
+ if uri["scheme"] == "file" and uri["path"] then
+ argv = @{ @}
+ end
+
+ if argv then
+
+ table.insert(argv, "mtn")
+
+ if args["debug"] then
+ table.insert(argv, "--debug")
+ else
+ table.insert(argv, "--quiet")
+ end
+
+ table.insert(argv, "--db")
+ table.insert(argv, uri["path"])
+ table.insert(argv, "serve")
+ table.insert(argv, "--stdio")
+ table.insert(argv, "--no-transport-auth")
+
+ if args["include"] then
+ table.insert(argv, args["include"])
+ end
+
+ if args["exclude"] then
+ table.insert(argv, "--exclude")
+ table.insert(argv, args["exclude"])
+ end
+ end
+ return argv
+end
address@hidden group
address@hidden smallexample
+
+
address@hidden use_transport_auth (@var{uri})
+
+Returns a boolean indicating whether monotone should use transport
+authentication mechanisms when communicating with @var{uri}. If this
+hook fails, the return value is assumed to be @code{true}. The form of
+the @var{uri} argument is a table, identical to the table provided as
+an argument to @code{get_netsync_connect_command}.
+
+Note that the return value of this hook must "match" the semantics of
+the command returned by @code{get_netsync_connect_command}. In
+particular, if this hook returns @code{false}, the @command{serve}
+command line arguments passed to the remote end of the connection
+should include the @option{--no-transport-auth} option. A mismatch
+between this hook's return value and the command line returned by
address@hidden will cause a communication failure,
+as the local and remote monotone processes will have mismatched
+authentication assumptions.
+
address@hidden
address@hidden
+function use_transport_auth(uri)
+ if uri["scheme"] == "ssh"
+ or uri["scheme"] == "file" then
+ return false
+ else
+ return true
+ end
+end
address@hidden group
address@hidden smallexample
+
address@hidden ftable
+
+
@anchor{Trust Evaluation Hooks}
@subsection Trust Evaluation Hooks
============================================================
--- netcmd.cc da3773c93069c0834ce0b3caf34020305dd7d8b2
+++ netcmd.cc 946d279a09466cac51782ee1cb415080ef6aef79
@@ -7,7 +7,6 @@
#include
#include
-#include "adler32.hh"
#include "constants.hh"
#include "netcmd.hh"
#include "netio.hh"
@@ -70,7 +69,7 @@
out += static_cast(cmd_code);
insert_variable_length_string(payload, out);
- if (cmd_code != usher_reply_cmd)
+ if (hmac.is_active() && cmd_code != usher_reply_cmd)
{
string digest = hmac.process(out, oldlen);
I(hmac.hmac_length == constants::netsync_hmac_value_length_in_bytes);
@@ -134,36 +133,48 @@
// there might not be enough data yet in the input buffer
unsigned int minsize;
- if (cmd_code == usher_cmd)
- minsize = pos + payload_len;
+ if (hmac.is_active() && cmd_code != usher_cmd)
+ minsize = pos + payload_len + constants::netsync_hmac_value_length_in_bytes;
else
- minsize = pos + payload_len + constants::netsync_hmac_value_length_in_bytes;
+ minsize = pos + payload_len;
+
if (inbuf.size() < minsize)
{
return false;
}
- // grab it before the data gets munged
- I(hmac.hmac_length == constants::netsync_hmac_value_length_in_bytes);
string digest;
- if (cmd_code != usher_cmd)
- digest = hmac.process(inbuf, 0, pos + payload_len);
+ string cmd_digest;
+ if (hmac.is_active() && cmd_code != usher_cmd)
+ {
+ // grab it before the data gets munged
+ I(hmac.hmac_length == constants::netsync_hmac_value_length_in_bytes);
+ digest = hmac.process(inbuf, 0, pos + payload_len);
+ }
+
payload = extract_substring(inbuf, pos, payload_len, "netcmd payload");
+
+ if (hmac.is_active() && cmd_code != usher_cmd)
+ {
+ // they might have given us bogus data
+ cmd_digest = extract_substring(inbuf, pos,
+ constants::netsync_hmac_value_length_in_bytes,
+ "netcmd HMAC");
+ }
- // they might have given us bogus data
- string cmd_digest;
- if (cmd_code != usher_cmd)
- cmd_digest = extract_substring(inbuf, pos,
- constants::netsync_hmac_value_length_in_bytes,
- "netcmd HMAC");
inbuf.pop_front(pos);
- if (cmd_digest != digest)
- throw bad_decode(F("bad HMAC checksum (got %s, wanted %s)\n"
- "this suggests data was corrupted in transit\n")
- % encode_hexenc(cmd_digest)
- % encode_hexenc(digest));
+ if (hmac.is_active()
+ && cmd_code != usher_cmd
+ && cmd_digest != digest)
+ {
+ throw bad_decode(F("bad HMAC checksum (got %s, wanted %s)\n"
+ "this suggests data was corrupted in transit\n")
+ % encode_hexenc(cmd_digest)
+ % encode_hexenc(digest));
+ }
+
return true;
}
============================================================
--- netsync.cc 901a77d250f4a5185209f8f7bc6ab7f9d7c7766c
+++ netsync.cc 0f736c6e24489bcf0cba06b1f14c09074ab3fc16
@@ -39,6 +39,7 @@
#include "platform.hh"
#include "hmac.hh"
#include "globish.hh"
+#include "uri.hh"
#include "botan/botan.h"
@@ -428,6 +429,7 @@
transaction_guard & guard);
// Various helpers.
+ void assume_corresponding_role(protocol_role their_role);
void respond_to_confirm_cmd();
bool data_exists(netcmd_item_type type,
id const & item);
@@ -465,8 +467,8 @@
remote_peer_key_hash(""),
remote_peer_key_name(""),
session_key(constants::netsync_key_initializer),
- read_hmac(constants::netsync_key_initializer),
- write_hmac(constants::netsync_key_initializer),
+ read_hmac(constants::netsync_key_initializer, app.use_transport_auth),
+ write_hmac(constants::netsync_key_initializer, app.use_transport_auth),
authenticated(false),
last_io_time(::time(NULL)),
byte_in_ticker(NULL),
@@ -690,12 +692,15 @@
void
session::set_session_key(rsa_oaep_sha_data const & hmac_key_encrypted)
{
- keypair our_kp;
- load_key_pair(app, app.signing_key, our_kp);
- string hmac_key;
- decrypt_rsa(app.lua, app.signing_key, our_kp.priv,
- hmac_key_encrypted, hmac_key);
- set_session_key(hmac_key);
+ if (app.use_transport_auth)
+ {
+ keypair our_kp;
+ load_key_pair(app, app.signing_key, our_kp);
+ string hmac_key;
+ decrypt_rsa(app.lua, app.signing_key, our_kp.priv,
+ hmac_key_encrypted, hmac_key);
+ set_session_key(hmac_key);
+ }
}
void
@@ -1041,7 +1046,8 @@
id const & nonce)
{
rsa_pub_key pub;
- decode_base64(pub_encoded, pub);
+ if (app.use_transport_auth)
+ decode_base64(pub_encoded, pub);
cmd.write_hello_cmd(key_name, pub, nonce);
write_netcmd_and_try_flush(cmd);
}
@@ -1055,8 +1061,9 @@
{
netcmd cmd;
rsa_oaep_sha_data hmac_key_encrypted;
- encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded,
- nonce2(), hmac_key_encrypted);
+ if (app.use_transport_auth)
+ encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded,
+ nonce2(), hmac_key_encrypted);
cmd.write_anonymous_cmd(role, include_pattern, exclude_pattern,
hmac_key_encrypted);
write_netcmd_and_try_flush(cmd);
@@ -1075,6 +1082,7 @@
{
netcmd cmd;
rsa_oaep_sha_data hmac_key_encrypted;
+ I(app.use_transport_auth);
encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded,
nonce2(), hmac_key_encrypted);
cmd.write_auth_cmd(role, include_pattern, exclude_pattern, client,
@@ -1195,60 +1203,61 @@
{
I(this->remote_peer_key_hash().size() == 0);
I(this->saved_nonce().size() == 0);
-
- hexenc their_key_hash;
+
base64 their_key_encoded;
- encode_base64(their_key, their_key_encoded);
- key_hash_code(their_keyname, their_key_encoded, their_key_hash);
- L(FL("server key has name %s, hash %s\n") % their_keyname % their_key_hash);
- var_key their_key_key(known_servers_domain, var_name(peer_id));
- if (app.db.var_exists(their_key_key))
+
+ if (app.use_transport_auth)
{
- var_value expected_key_hash;
- app.db.get_var(their_key_key, expected_key_hash);
- if (expected_key_hash() != their_key_hash())
+ hexenc their_key_hash;
+ encode_base64(their_key, their_key_encoded);
+ key_hash_code(their_keyname, their_key_encoded, their_key_hash);
+ L(FL("server key has name %s, hash %s\n") % their_keyname % their_key_hash);
+ var_key their_key_key(known_servers_domain, var_name(peer_id));
+ if (app.db.var_exists(their_key_key))
{
- P(F("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
- "@ WARNING: SERVER IDENTIFICATION HAS CHANGED @\n"
- "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
- "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY\n"
- "it is also possible that the server key has just been changed\n"
- "remote host sent key %s\n"
- "I expected %s\n"
- "'%s unset %s %s' overrides this check\n")
- % their_key_hash % expected_key_hash
- % app.prog_name % their_key_key.first % their_key_key.second);
- E(false, F("server key changed"));
+ var_value expected_key_hash;
+ app.db.get_var(their_key_key, expected_key_hash);
+ if (expected_key_hash() != their_key_hash())
+ {
+ P(F("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+ "@ WARNING: SERVER IDENTIFICATION HAS CHANGED @\n"
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+ "IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY\n"
+ "it is also possible that the server key has just been changed\n"
+ "remote host sent key %s\n"
+ "I expected %s\n"
+ "'%s unset %s %s' overrides this check\n")
+ % their_key_hash % expected_key_hash
+ % app.prog_name % their_key_key.first % their_key_key.second);
+ E(false, F("server key changed"));
+ }
}
- }
- else
- {
- P(F("first time connecting to server %s\n"
- "I'll assume it's really them, but you might want to double-check\n"
- "their key's fingerprint: %s\n") % peer_id % their_key_hash);
- app.db.set_var(their_key_key, var_value(their_key_hash()));
- }
- if (!app.db.public_key_exists(their_key_hash))
- {
- W(F("saving public key for %s to database\n") % their_keyname);
- app.db.put_key(their_keyname, their_key_encoded);
- }
+ else
+ {
+ P(F("first time connecting to server %s\n"
+ "I'll assume it's really them, but you might want to double-check\n"
+ "their key's fingerprint: %s\n") % peer_id % their_key_hash);
+ app.db.set_var(their_key_key, var_value(their_key_hash()));
+ }
+ if (!app.db.public_key_exists(their_key_hash))
+ {
+ W(F("saving public key for %s to database\n") % their_keyname);
+ app.db.put_key(their_keyname, their_key_encoded);
+ }
+ {
+ hexenc hnonce;
+ encode_hexenc(nonce, hnonce);
+ L(FL("received 'hello' netcmd from server '%s' with nonce '%s'\n")
+ % their_key_hash % hnonce);
+ }
+
+ I(app.db.public_key_exists(their_key_hash));
- {
- hexenc hnonce;
- encode_hexenc(nonce, hnonce);
- L(FL("received 'hello' netcmd from server '%s' with nonce '%s'\n")
- % their_key_hash % hnonce);
- }
-
- I(app.db.public_key_exists(their_key_hash));
-
- // save their identity
- {
- id their_key_hash_decoded;
- decode_hexenc(their_key_hash, their_key_hash_decoded);
- this->remote_peer_key_hash = their_key_hash_decoded;
- }
+ // save their identity
+ id their_key_hash_decoded;
+ decode_hexenc(their_key_hash, their_key_hash_decoded);
+ this->remote_peer_key_hash = their_key_hash_decoded;
+ }
// clients always include in the synchronization set, every branch that the
// user requested
@@ -1265,7 +1274,8 @@
setup_client_tickers();
- if (app.signing_key() != "")
+ if (app.use_transport_auth &&
+ app.signing_key() != "")
{
// get our key pair
keypair our_kp;
@@ -1298,7 +1308,7 @@
}
bool
-session::process_anonymous_cmd(protocol_role role,
+session::process_anonymous_cmd(protocol_role their_role,
utf8 const & their_include_pattern,
utf8 const & their_exclude_pattern)
{
@@ -1315,22 +1325,28 @@
//
// Client must be a sink and server must be a source (anonymous
- // read-only).
+ // read-only), unless transport auth is disabled.
+ //
+ // If running in no-transport-auth mode, we operate anonymously and
+ // permit adoption of any role.
- if (role != sink_role)
+ if (app.use_transport_auth)
{
- W(F("rejected attempt at anonymous connection for write\n"));
- this->saved_nonce = id("");
- return false;
+ if (their_role != sink_role)
+ {
+ W(F("rejected attempt at anonymous connection for write\n"));
+ this->saved_nonce = id("");
+ return false;
+ }
+
+ if (this->role == sink_role)
+ {
+ W(F("rejected attempt at anonymous connection while running as sink\n"));
+ this->saved_nonce = id("");
+ return false;
+ }
}
- if (this->role != source_role && this->role != source_and_sink_role)
- {
- W(F("rejected attempt at anonymous connection while running as sink\n"));
- this->saved_nonce = id("");
- return false;
- }
-
vector branchnames;
set ok_branches;
get_branches(app, branchnames);
@@ -1344,7 +1360,8 @@
error((F("not serving branch '%s'") % *i).str());
return true;
}
- else if (!app.lua.hook_get_netsync_read_permitted(*i))
+ else if (app.use_transport_auth &&
+ !app.lua.hook_get_netsync_read_permitted(*i))
{
error((F("anonymous access to branch '%s' denied by server") % *i).str());
return true;
@@ -1353,17 +1370,48 @@
ok_branches.insert(utf8(*i));
}
- P(F("allowed anonymous read permission for '%s' excluding '%s'\n")
- % their_include_pattern % their_exclude_pattern);
+ if (app.use_transport_auth)
+ {
+ P(F("allowed anonymous read permission for '%s' excluding '%s'\n")
+ % their_include_pattern % their_exclude_pattern);
+ this->role = source_role;
+ }
+ else
+ {
+ P(F("allowed anonymous read/write permission for '%s' excluding '%s'\n")
+ % their_include_pattern % their_exclude_pattern);
+ assume_corresponding_role(their_role);
+ }
rebuild_merkle_trees(app, ok_branches);
this->remote_peer_key_name = rsa_keypair_id("");
this->authenticated = true;
- this->role = source_role;
return true;
}
+void
+session::assume_corresponding_role(protocol_role their_role)
+{
+ // Assume the (possibly degraded) opposite role.
+ switch (their_role)
+ {
+ case source_role:
+ I(this->role != source_role);
+ this->role = sink_role;
+ break;
+
+ case source_and_sink_role:
+ I(this->role == source_and_sink_role);
+ break;
+
+ case sink_role:
+ I(this->role != sink_role);
+ this->role = source_role;
+ break;
+ }
+}
+
bool
session::process_auth_cmd(protocol_role their_role,
utf8 const & their_include_pattern,
@@ -1499,23 +1547,7 @@
this->authenticated = true;
this->remote_peer_key_name = their_id;
- // Assume the (possibly degraded) opposite role.
- switch (their_role)
- {
- case source_role:
- I(this->role != source_role);
- this->role = sink_role;
- break;
-
- case source_and_sink_role:
- I(this->role == source_and_sink_role);
- break;
-
- case sink_role:
- I(this->role != sink_role);
- this->role = source_role;
- break;
- }
+ assume_corresponding_role(their_role);
return true;
}
else
@@ -2038,6 +2070,7 @@
% hnonce1);
set_session_key(hmac_key_encrypted);
+
if (!process_auth_cmd(role, their_include_pattern, their_exclude_pattern,
client, nonce1, signature))
return false;
@@ -2126,7 +2159,8 @@
session::begin_service()
{
keypair kp;
- app.keys.get_key_pair(app.signing_key, kp);
+ if (app.use_transport_auth)
+ app.keys.get_key_pair(app.signing_key, kp);
queue_hello_cmd(app.signing_key, kp.pub, mk_nonce());
}
@@ -2214,145 +2248,32 @@
}
-static bool
-parse_ssh_url(const std::string & address,
- std::string & host,
- std::string & user,
- std::string & port,
- std::string & dbpath)
-{
- std::string::size_type
- wordbegin = 0,
- wordend = std::string::npos;
-
- if (address.size() >= 2 &&
- address.substr(wordbegin, 2) == "//")
- wordbegin += 2;
-
- wordend = address.find_first_of("@:/", wordbegin);
-
- if (wordend == string::npos)
- return false;
-
- if (address.at(wordend) == '@')
- {
- user = address.substr(wordbegin, wordend - wordbegin);
- wordbegin = wordend + 1;
- wordend = address.find_first_of(":/", wordbegin);
- if (wordend == string::npos)
- return false;
- }
-
- if (address.at(wordend) == ':')
- {
- host = address.substr(wordbegin, wordend-wordbegin);
- wordbegin = wordend + 1;
- wordend = address.find_first_of("/", wordbegin);
- if (wordend == string::npos)
- return false;
- }
-
- if (address.at(wordend) != '/')
- return false;
-
- if (wordbegin == wordend)
- return false; // empty port/host
-
- if (host.empty())
- host = address.substr(wordbegin, wordend-wordbegin);
- else
- port = address.substr(wordbegin, wordend-wordbegin);
-
- dbpath = address.substr(wordend); // with leading '/' !
- return true;
-}
-
static shared_ptr
-build_stream_to_server(utf8 const & include_pattern,
+build_stream_to_server(app_state & app,
+ utf8 const & include_pattern,
utf8 const & exclude_pattern,
utf8 const & address,
Netxx::port_type default_port,
Netxx::Timeout timeout)
{
shared_ptr server;
-
- if (address().size() > 5 &&
- address().substr(0,5)=="file:")
- {
- std::vector args;
- std::string db_path = address().substr(5);
- if (global_sanity.debug)
- args.push_back("--debug");
- else
- args.push_back("--quiet");
-
- args.push_back("--db");
- args.push_back(db_path);
-
- if (exclude_pattern().size())
- {
- args.push_back("--exclude");
- args.push_back(exclude_pattern());
- }
-
- args.push_back("serve");
- args.push_back("--stdio");
- args.push_back(include_pattern());
-
+ uri u;
+ vector argv;
+ if (parse_uri(address(), u)
+ && app.lua.hook_get_netsync_connect_command(u,
+ include_pattern(),
+ exclude_pattern(),
+ global_sanity.debug,
+ argv))
+ {
+ I(argv.size() > 0);
+ string cmd = argv[0];
+ argv.erase(argv.begin());
+ app.use_transport_auth = app.lua.hook_use_transport_auth(u);
return shared_ptr
- (new Netxx::PipeStream("mtn", args));
+ (new Netxx::PipeStream(cmd, argv));
+
}
-
- else if (address().size() > 4 &&
- address().substr(0,4)=="ssh:")
- {
- std::vector args;
- std::string user, host, port, db_path;
-
- if (!parse_ssh_url(address().substr(4),
- host, user, port, db_path))
- {
- N(false,
- F("url %s is not of form "
- "ssh:[//address@hidden:port/dbpath\n") % address());
- }
-
- if (!port.empty())
- {
- args.push_back("-p");
- args.push_back(port);
- }
-
- if (!user.empty())
- {
- args.push_back("-l");
- args.push_back(user);
- }
-
- args.push_back(host);
- args.push_back("mtn");
-
- if (global_sanity.debug)
- args.push_back("--debug");
- else
- args.push_back("--quiet");
-
- args.push_back("--db");
- args.push_back(db_path);
-
- if (exclude_pattern().size())
- {
- args.push_back("--exclude");
- args.push_back(exclude_pattern());
- }
-
- args.push_back("--stdio");
- args.push_back("serve");
- args.push_back(include_pattern());
-
- return shared_ptr
- (new Netxx::PipeStream("ssh", args));
- }
else
{
#ifdef USE_IPV6
@@ -2386,7 +2307,8 @@
P(F("connecting to %s\n") % address());
shared_ptr server
- = build_stream_to_server(include_pattern,
+ = build_stream_to_server(app,
+ include_pattern,
exclude_pattern,
address, default_port,
timeout);
============================================================
--- options.hh 6ea8ce2a5b90b4bd1fffc77df22160eec911e985
+++ options.hh ab30747195c445f90863f386adf2e5f9c8700349
@@ -55,3 +55,4 @@
#define OPT_LOG 46
#define OPT_RECURSIVE 47
#define OPT_STDIO 48
+#define OPT_NO_TRANSPORT_AUTH 49
============================================================
--- std_hooks.lua 05ecfce99ad27ce5042eacfe0c9c08d21cd11c3e
+++ std_hooks.lua 614066fdf36561a75e035ba121c8f18aae0a69dc
@@ -695,3 +695,70 @@
io.close(permfile)
return matches
end
+
+-- This is a simple funciton which assumes you're going to be spawning
+-- a copy of mtn, so reuses a common bit at the end for converting
+-- local args into remote args. You might need to massage the logic a
+-- bit if this doesn't fit your assumptions.
+
+function get_netsync_connect_command(uri, args)
+
+ local argv = nil
+
+ if uri["scheme"] == "ssh"
+ and uri["host"]
+ and uri["path"] then
+
+ argv = { "ssh" }
+ if uri["user"] then
+ table.insert(argv, "-l")
+ table.insert(argv, uri["user"])
+ end
+ if uri["port"] then
+ table.insert(argv, "-p")
+ table.insert(argv, uri["port"])
+ end
+
+ table.insert(argv, uri["host"])
+ end
+
+ if uri["scheme"] == "file" and uri["path"] then
+ argv = { }
+ end
+
+ if argv then
+
+ table.insert(argv, "mtn")
+
+ if args["debug"] then
+ table.insert(argv, "--debug")
+ else
+ table.insert(argv, "--quiet")
+ end
+
+ table.insert(argv, "--db")
+ table.insert(argv, uri["path"])
+ table.insert(argv, "serve")
+ table.insert(argv, "--stdio")
+ table.insert(argv, "--no-transport-auth")
+
+ if args["include"] then
+ table.insert(argv, args["include"])
+ end
+
+ if args["exclude"] then
+ table.insert(argv, "--exclude")
+ table.insert(argv, args["exclude"])
+ end
+ end
+ return argv
+end
+
+function use_transport_auth(uri)
+ if uri["scheme"] == "ssh"
+ or uri["scheme"] == "file" then
+ return false
+ else
+ return true
+ end
+end
============================================================
--- testsuite.at 804e08edab3e29dd67002c80eed79c3fd467e64d
+++ testsuite.at 983ee4bb284c08f42937f7faad74f6b29e094ac0
@@ -164,6 +164,68 @@
io.write(string.format("test:test_attr:%s:%s\n", filename, value))
end
+function get_netsync_connect_command(uri, args)
+
+ local argv = nil
+
+ if uri[["scheme"]] == "ssh"
+ and uri[["host"]]
+ and uri[["path"]] then
+
+ argv = { "ssh" }
+ if uri[["user"]] then
+ table.insert(argv, "-l")
+ table.insert(argv, uri[["user"]])
+ end
+ if uri[["port"]] then
+ table.insert(argv, "-p")
+ table.insert(argv, uri[["port"]])
+ end
+
+ table.insert(argv, uri[["host"]])
+ end
+
+ if uri[["scheme"]] == "file" and uri[["path"]] then
+ argv = { }
+ end
+
+ if argv then
+
+ table.insert(argv, "mtn")
+
+ if args[["debug"]] then
+ table.insert(argv, "--debug")
+ else
+ table.insert(argv, "--quiet")
+ end
+
+ table.insert(argv, "--db")
+ table.insert(argv, uri[["path"]])
+ table.insert(argv, "serve")
+ table.insert(argv, "--stdio")
+ table.insert(argv, "--no-transport-auth")
+
+ if args[["include"]] then
+ table.insert(argv, args[["include"]])
+ end
+
+ if args[["exclude"]] then
+ table.insert(argv, "--exclude")
+ table.insert(argv, args[["exclude"]])
+ end
+ end
+ return argv
+end
+
+function use_transport_auth(uri)
+ if uri[["scheme"]] == "ssh"
+ or uri[["scheme"]] == "file" then
+ return false
+ else
+ return true
+ end
+end
+
])
AT_CHECK(MTN db init, [], [ignore], [ignore])
@@ -889,3 +951,4 @@
m4_include(tests/t_automate_get_current_revision_id.at)
m4_include(tests/t_log_diffs.at)
m4_include(tests/t_db_init_info.at)
+m4_include(tests/t_netsync_pipe.at)
============================================================
--- unit_tests.cc 6ce81ae44beea87dae0c70d780da8e665d83fa19
+++ unit_tests.cc 124bec7d54edfb20b1321c478c753bec8f6e9951
@@ -97,6 +97,9 @@
if (t.empty() || t.find("restrictions") != t.end())
add_restrictions_tests(suite);
+ if (t.empty() || t.find("uri") != t.end())
+ add_uri_tests(suite);
+
// all done, add our clean-shutdown-indicator
suite->add(BOOST_TEST_CASE(&clean_shutdown_dummy_test));
============================================================
--- unit_tests.hh 96b6a1b7b6d7d5d85e026fcbd69af860da478739
+++ unit_tests.hh a9415430326fcb490e7e923a4a3fa73c0f1135a3
@@ -39,5 +39,6 @@
void add_roster_tests(test_suite * suite);
void add_roster_merge_tests(test_suite * suite);
void add_restrictions_tests(test_suite * suite);
+void add_uri_tests(test_suite * suite);
#endif