The unified diff between revisions [869f0c37..] and [e24ccc23..] is displayed below. It can also be downloaded as a raw diff.

#
#
# add_file "tests/t_lua_includedir.at"
#  content [5b4ce1e6a2781a7a841039a1e02a96cc0221f3ff]
#
# add_file "tests/t_rcfile_dir.at"
#  content [06128ab21f137c3a69e4fce2db25b0ea3fa424de]
#
# patch "ChangeLog"
#  from [3d5fd1c1a0ef8a2d86cbd76a30d89c18f15fd496]
#    to [1a92ac866998074c2b9d6e2795fe322656a4666b]
#
# patch "INSTALL"
#  from [91300af1b7411653bb3eff5608a548b6a5ed633b]
#    to [400eaf8e798d44fce8b0622c88bc324fd31830c2]
#
# patch "Makefile.am"
#  from [bedd826d1991853466ab57d77d80be1635014d17]
#    to [8f57b338067b43d1d1ae5de118323a6c67f6be7d]
#
# patch "app_state.cc"
#  from [7059471c5febbb78676ba5b2e7e432ce3fe39977]
#    to [1e2cfd4af8c4ccc2b721c758469659dc3d7f4131]
#
# patch "commands.cc"
#  from [0219fa13f31351d209832ea210bb1e7a46bef761]
#    to [2b9bb2299e2fe0f337e33d1989d6f3f6d8c793c7]
#
# patch "constants.cc"
#  from [eabc0d5838a67e3e4d351ce3d28eac0387f773ae]
#    to [2258f6165b6db8f8c952f97aaacf0addbbed060f]
#
# patch "constants.hh"
#  from [4a4494bcaa97ec07c147fc066564519bb4d13481]
#    to [a4741c95ae4faeb5db1f36f6ccf406f732250241]
#
# patch "database.cc"
#  from [181e7b79e4435b2ffa1561ce8b9162a41563ed72]
#    to [1ce26993177acbeb3e3d18aba17f43700372fa3c]
#
# patch "diff_patch.cc"
#  from [cb8bea5dd5b9e5b9c4eb838dd89bb85200c434aa]
#    to [fbef84d6f575c4f14850b8a8e7d78e42021a0e80]
#
# patch "keys.cc"
#  from [a4ccef56d24ee9cda7a009144f5476e3cdcb07d7]
#    to [9c50dc0d669ffb6ad2328c46485bf3ae65dad4b5]
#
# patch "keys.hh"
#  from [f10aaea8dbaf4005a55ec388f278d1bb81d664af]
#    to [ec2da9cbde80341456204b6e0e0b4f727388ed8b]
#
# patch "lua.cc"
#  from [c5a01c71aef633d6ad5ad78650e4e0601295b349]
#    to [e4dd123e0c07b59361555b521f3b2cc17aa445b5]
#
# patch "monotone.texi"
#  from [28008d0dc7e6abd7e2e40cab143add9c4faecbca]
#    to [131933a2c2cd95c13bf8e3b8c988b8e4bc97664d]
#
# patch "netcmd.cc"
#  from [3d925448e00e4f5a0e70f9ac977899fbf9312a1e]
#    to [207caa3c268ad7e6a93eaff3f3298aa53f45fadd]
#
# patch "netcmd.hh"
#  from [901532e162e97d3df8f89ccc1198203eef8ed8fe]
#    to [b010800c5f7c9cb23ac8f0ac069b7a4fbdd17774]
#
# patch "netsync.cc"
#  from [218fabbe4a06f50089a50833c72f9c1b4ed78a91]
#    to [cba71cd069c2658e79568849fe0328c92695547d]
#
# patch "std_hooks.lua"
#  from [c06a28443baa42ca4d4f349a6dd9653d25fa8c0f]
#    to [9b1a27e30129929aa366223e51f1c11ec918fc93]
#
# patch "tests/t_cat_file_by_name.at"
#  from [009033538c4424ad6994c33eef9dab24bef15980]
#    to [45f9704b56b2b74f16c5579539bc1c6f777b1e56]
#
# patch "tests/t_netsync_diffbranch.at"
#  from [a529b5b568039685d66db00c8550b817fbc3a4a4]
#    to [6a2a4e9b6dada24dea2ad88bd0c3d0caf8d180bc]
#
# patch "testsuite.at"
#  from [2635480ab5d48f5eae96909a5e59ef20a7b0c36d]
#    to [2e17e62213b9878d08d7c792f244b6e193498618]
#
# patch "vocab.cc"
#  from [6e07baffa263c80f013d474ad362c3b52c162201]
#    to [0a568b1d7a988e146671055888136fdff7818916]
#
# patch "vocab_terms.hh"
#  from [c12dd87ed775d3e2d8713a393cc055ce68e5ae16]
#    to [fe3bf7a7700bdde82211ba0be39caf248025e00b]
#
============================================================
--- tests/t_lua_includedir.at	5b4ce1e6a2781a7a841039a1e02a96cc0221f3ff
+++ tests/t_lua_includedir.at	5b4ce1e6a2781a7a841039a1e02a96cc0221f3ff
@@ -0,0 +1,44 @@
+AT_SETUP([include() and includedir() lua functions])
+MONOTONE_SETUP
+
+AT_CHECK(mkdir gongolo)
+
+AT_DATA(include.lua, [include("../gongolo/aaa.rc")
+])
+
+AT_DATA(includedir.lua, [includedir("../gongolo")
+])
+
+# write two files and check that they will be invoked in alphabetic order
+AT_DATA(gongolo/aaa.rc, [function paraponzi()
+  io.write("BOOGA BOOGA")
+end
+paraponzi()
+])
+AT_DATA(gongolo/bbb.zz, [function labellagigogin()
+  io.write("CICCA CICCA")
+end
+labellagigogin()
+])
+
+# setup a wrk dir
+AT_CHECK(MONOTONE setup alt_wrk, [], [ignore], [ignore])
+
+# include directly a single file
+AT_CHECK(cd alt_wrk && MONOTONE --root=. --rcfile=../include.lua status, [], [stdout], [ignore])
+AT_CHECK(grep -q "BOOGA BOOGA" stdout)
+
+# include dirs
+AT_CHECK(cd alt_wrk && MONOTONE --root=. --rcfile=../includedir.lua status, [], [stdout], [ignore])
+AT_CHECK(grep -q "BOOGA BOOGACICCA CICCA" stdout)
+
+# write a third file: should be read beetween the two previous ones
+AT_DATA(gongolo/aba.rc, [function notwowithoutthree()
+  io.write("hu hu")
+end
+notwowithoutthree()
+])
+AT_CHECK(cd alt_wrk && MONOTONE --root=. --rcfile=../includedir.lua status, [], [stdout], [ignore])
+AT_CHECK(grep -q "BOOGA BOOGAhu huCICCA CICCA" stdout)
+
+AT_CLEANUP
============================================================
--- tests/t_rcfile_dir.at	06128ab21f137c3a69e4fce2db25b0ea3fa424de
+++ tests/t_rcfile_dir.at	06128ab21f137c3a69e4fce2db25b0ea3fa424de
@@ -0,0 +1,32 @@
+AT_SETUP([--rcfile=directory])
+MONOTONE_SETUP
+
+AT_CHECK(mkdir gongolo)
+
+# write two files and check that they will be invoked in alphabetic order
+AT_DATA(gongolo/aaa.rc, [function paraponzi()
+  io.write("BOOGA BOOGA")
+end
+paraponzi()
+])
+AT_DATA(gongolo/bbb.rc, [function labellagigogin()
+  io.write("CICCA CICCA")
+end
+labellagigogin()
+])
+
+# note: rcfile is placed outside workdir
+AT_CHECK(MONOTONE setup alt_wrk, [], [ignore], [ignore])
+AT_CHECK(cd alt_wrk && MONOTONE --root=. --rcfile=../gongolo status, [], [stdout], [ignore])
+AT_CHECK(grep -q "BOOGA BOOGACICCA CICCA" stdout)
+
+# write a third file: should be read beetween the two previous ones
+AT_DATA(gongolo/aba.rc, [function notwowithoutthree()
+  io.write("hu hu")
+end
+notwowithoutthree()
+])
+AT_CHECK(cd alt_wrk && MONOTONE --root=. --rcfile=../gongolo status, [], [stdout], [ignore])
+AT_CHECK(grep -q "BOOGA BOOGAhu huCICCA CICCA" stdout)
+
+AT_CLEANUP
============================================================
--- ChangeLog	3d5fd1c1a0ef8a2d86cbd76a30d89c18f15fd496
+++ ChangeLog	1a92ac866998074c2b9d6e2795fe322656a4666b
@@ -1,3 +1,97 @@
+2005-06-22  Nathaniel Smith  <njs@codesourcery.com>
+
+	* netcmd.hh (netcmd::read, netcmd::write): Don't have defaults for
+	key/hmac arguments.
+	* netcmd.cc (do_netcmd_roundtrip): New function.
+	(test_netcmd_functions): Use it.  Also, make work with hmac
+	changes.
+	(test_netcmd_mac): New test.
+	(add_netcmd_tests): Call it.
+
+2005-06-22  Nathaniel Smith  <njs@codesourcery.com>
+
+	* netcmd.cc (read): Remove unused variable.
+	* netsync.cc (call_server, process)
+	(arm_sessions_and_calculate_probe, handle_read_available): Give
+	better error message on bad_decode exceptions.
+
+2005-06-22  Nathaniel Smith  <njs@codesourcery.com>
+
+	* netcmd.cc, netsync.cc: Revert backwards compatibility code; 0.19
+	and 0.20 can't be usefully compatible, and the code as it existed
+	would cause real version mismatch error reporting to not work
+	right.  (Old client with new server would give a generic "server
+	disconnected" error message instead of something useful.)
+
+2005-06-21  Nathaniel Smith  <njs@codesourcery.com>
+
+	* netsync.cc (rebuild_merkle_trees): Fix FIXME comments to match
+	reality.
+	* tests/t_netsync_diffbranch.at: No longer a bug, remove
+	priority.
+
+2005-06-20  Nathaniel Smith  <njs@codesourcery.com>
+
+	* monotone.texi (Hook Reference): Oops, missed a @ref.
+
+2005-06-20  Nathaniel Smith  <njs@codesourcery.com>
+
+	* monotone.texi (Default monotonerc): Rename section to...
+	(Default hooks): ...this, to emphasize is still read even when a
+	monotonerc exists.
+
+2005-06-19  Richard Levitte  <richard@levitte.org>
+
+	* Makefile.am: There's no reason for monotone.pdf or .dvi to
+	depend on monotone.info, since they are built from the .texi
+	files.  Also, make the monotone.html and html targets depend
+	on version.texi and std_hooks.lua as well.
+
+2005-06-18  Matt Johnston  <matt@ucc.asn.au>
+
+	* INSTALL: fix typo, should be -Iboost_1_31_0 not -Iboost_1_31_2
+
+2005-06-18  Riccardo Ghetta  <birrachiara@tin.it>
+	* monotone.texi: include std_hooks.lua as an appendix and remove long
+	lua excerpts from hook reference.
+	* Makefile.am : make monotone.pdf/eps depend on monotone.info
+
+2005-06-17  Matt Johnston  <matt@ucc.asn.au>
+
+	* database.cc (database::execute()): truncate long query log messages
+	before copying, saving memory.
+	Patch from Eric Anderson < ea at cello hpl hp com >
+
+2005-06-17  Riccardo Ghetta  <birrachiara@tin.it>
+	Adds include()/includedir() to lua hooks and extend --rcfile
+	* lua.cc: handle --rcfile with directories, implement
+	include() and includedir()
+	* testsuite.at, t_lua_includedir.at, t_rcfile_dir.at:
+	test new functionality
+	* monotone.texi: document all functions available to hook
+	writers, including the new include() and includedir()
+
+2005-06-16  Nathaniel Smith  <njs@codesourcery.com>
+
+	* diff_patch.cc (merge_extents): Typo caught by anonymous reader.
+
+2005-06-16  Nathaniel Smith  <njs@codesourcery.com>
+
+	* commands.cc (cat): Account for being in a subdir in 'cat file
+	REV PATH'.
+	* tests/t_cat_file_by_name.at: Test.
+
+2005-06-17  Richard Levitte  <richard@levitte.org>
+
+	* app_state.cc (app_state::app_state()): Avoid a gcc warning by
+	having the class members initialised in the same order they are
+	defined in the class.
+
+2005-06-16  Nathaniel Smith  <njs@pobox.com>
+
+	* std_hooks.lua (ignore_file): Add Cons/SCons cache files to
+	default ignore list.
+
 2005-06-16  Matt Johnston  <matt@ucc.asn.au>

 	* ui.cc: increase the divisor as required so that we don't get spurious
@@ -5,6 +99,11 @@ 2005-06-16  Matt Johnston  <matt@ucc.asn

 2005-06-16  Matt Johnston  <matt@ucc.asn.au>

+	* ui.cc: increase the divisor as required so that we don't get spurious
+	screen updates when we're using the kilobyte/megabyte tickers
+
+2005-06-16  Matt Johnston  <matt@ucc.asn.au>
+
 	* netcmd.{cc,hh}, netsync.cc: only read a single command packet
 	worth of data at a time, to avoid having to shuffle buffers about.

============================================================
--- INSTALL	91300af1b7411653bb3eff5608a548b6a5ed633b
+++ INSTALL	400eaf8e798d44fce8b0622c88bc324fd31830c2
@@ -69,7 +69,7 @@ 1.1 building boost:
   standard include path, or you can pass additional configuration
   options to your monotone configure build, such as:

-    ./configure CPPFLAGS="-Iboost_1_31_2" LDFLAGS="-Lboost_1_31_0/libs"
+    ./configure CPPFLAGS="-Iboost_1_31_0" LDFLAGS="-Lboost_1_31_0/libs"

   monotone does not use all of boost -- for instance, people often
   have trouble building boost.python, which we do not use.  you don't
============================================================
--- Makefile.am	bedd826d1991853466ab57d77d80be1635014d17
+++ Makefile.am	8f57b338067b43d1d1ae5de118323a6c67f6be7d
@@ -297,10 +297,12 @@ MAKEINFOFLAGS=-I $(top_builddir)

 MAKEINFOFLAGS=-I $(top_builddir)

-monotone.pdf: monotone.texi version.texi $(PDF_FIGURES)
+monotone.info: monotone.texi version.texi std_hooks.lua

-monotone.dvi: monotone.texi version.texi $(EPS_FIGURES)
+monotone.pdf: monotone.texi version.texi std_hooks.lua $(PDF_FIGURES)

+monotone.dvi: monotone.texi version.texi std_hooks.lua $(EPS_FIGURES)
+
 #%.eps: %.epsi
 #	mv $< $@
 #
@@ -408,14 +410,14 @@ package_full_revision.c: package_full_re

 # automake doesn't build html docs

-monotone.html: monotone.texi texinfo.css
+monotone.html: monotone.texi version.texi std_hooks.lua texinfo.css
 	makeinfo --no-split --no-headers --output $@ --html $<
 	perl -i.perlbak -pe 's@<head>@<head>\n<link type="text/css" rel="stylesheet" href="texinfo.css" />@' $@
 	rm -f monotone.html.perlbak

 # The .perlbak thing is needed, otherwise the perl executions fails on MinGW

-html: monotone.texi texinfo.css
+html: monotone.texi version.texi std_hooks.lua texinfo.css
 	mkdir -p html
 	makeinfo --number-sections --html --output html $<
 	perl -i.perlbak -pe 's@<head>@<head>\n<link type="text/css" rel="stylesheet" href="texinfo.css" />@' html/*.html
============================================================
--- app_state.cc	7059471c5febbb78676ba5b2e7e432ce3fe39977
+++ app_state.cc	1e2cfd4af8c4ccc2b721c758469659dc3d7f4131
@@ -31,7 +31,7 @@ app_state::app_state()

 app_state::app_state()
   : branch_name(""), db(""), stdhooks(true), rcfiles(true), diffs(false),
-    search_root("/"), depth(-1), last(-1), verbose(false)
+    verbose(false), search_root("/"), depth(-1), last(-1)
 {
   db.set_app(this);
 }
============================================================
--- commands.cc	0219fa13f31351d209832ea210bb1e7a46bef761
+++ commands.cc	2b9bb2299e2fe0f337e33d1989d6f3f6d8c793c7
@@ -1370,7 +1370,7 @@ CMD(cat, "informative",
         {
           revision_id rid;
           complete(app, idx(args, 1)(), rid);
-          file_path fp(idx(args, 2)());
+          file_path fp = app.prefix(idx(args, 2));
           manifest_id mid;
           app.db.get_revision_manifest(rid, mid);
           manifest_map m;
============================================================
--- constants.cc	eabc0d5838a67e3e4d351ce3d28eac0387f773ae
+++ constants.cc	2258f6165b6db8f8c952f97aaacf0addbbed060f
@@ -160,5 +160,9 @@ namespace constants
   size_t const netsync_default_port = 5253;
   size_t const netsync_connection_limit = 1024;
   size_t const netsync_timeout_seconds = 21600; // 6 hours
+  size_t const netsync_session_key_length_in_bytes = 20;     // 160 bits
+  size_t const netsync_hmac_value_length_in_bytes = 20;      // 160 bits

+  std::string const & netsync_key_initializer = std::string(netsync_session_key_length_in_bytes, 0);
+
 }
============================================================
--- constants.hh	4a4494bcaa97ec07c147fc066564519bb4d13481
+++ constants.hh	a4741c95ae4faeb5db1f36f6ccf406f732250241
@@ -7,6 +7,7 @@
 // see the file COPYING for details

 #include <unistd.h>
+#include <string>
 #include "numeric_vocab.hh"

 namespace constants
@@ -120,6 +121,15 @@ namespace constants
   // number of seconds a connection can be idle before it's dropped
   extern size_t const netsync_timeout_seconds;

+  // netsync HMAC key length
+  extern size_t const netsync_session_key_length_in_bytes;
+
+  // netsync HMAC value length
+  extern size_t const netsync_hmac_value_length_in_bytes;
+
+  // netsync session key and HMAC key default initializer
+  extern std::string const & netsync_key_initializer;
+
 }

 #endif // __CONSTANTS_HH__
============================================================
--- database.cc	181e7b79e4435b2ffa1561ce8b9162a41563ed72
+++ database.cc	1ce26993177acbeb3e3d18aba17f43700372fa3c
@@ -12,6 +12,7 @@
 #include <vector>

 #include <stdarg.h>
+#include <string.h>

 #include <boost/shared_ptr.hpp>
 #include <boost/lexical_cast.hpp>
@@ -643,24 +644,28 @@ database::execute(char const * query, ..
 database::execute(char const * query, ...)
 {
   va_list ap;
-  int res;
-  char * errmsg = NULL;

   va_start(ap, query);

   // log it
   char * formatted = sqlite3_vmprintf(query, ap);
-  string qq(formatted);
-  if (qq.size() > constants::db_log_line_sz)
-    qq = qq.substr(0, constants::db_log_line_sz) + string(" ...");
+  string qq;
+
+  if (strlen(formatted) > constants::db_log_line_sz)
+    {
+      qq.assign(formatted, constants::db_log_line_sz);
+      qq.append(" ...");
+    }
+  else
+    {
+      qq = formatted;
+    }
   L(F("db.execute(\"%s\")\n") % qq);
-  sqlite3_free(formatted);

-  va_end(ap);
-  va_start(ap, query);
-
   // do it
-  res = sqlite3_exec_vprintf(sql(), query, NULL, NULL, &errmsg, ap);
+  char * errmsg = NULL;
+  int res = sqlite3_exec(sql(), formatted, NULL, NULL, &errmsg);
+  sqlite3_free(formatted);

   va_end(ap);

============================================================
--- diff_patch.cc	cb8bea5dd5b9e5b9c4eb838dd89bb85200c434aa
+++ diff_patch.cc	fbef84d6f575c4f14850b8a8e7d78e42021a0e80
@@ -307,7 +307,7 @@ void merge_extents(vector<extent> const
         }

       // mutual or single-edge deletes
-      else if ((i->type == deleted && j->len == deleted)
+      else if ((i->type == deleted && j->type == deleted)
                || (i->type == deleted && j->type == preserved)
                || (i->type == preserved && j->type == deleted))
         {
============================================================
--- keys.cc	a4ccef56d24ee9cda7a009144f5476e3cdcb07d7
+++ keys.cc	9c50dc0d669ffb6ad2328c46485bf3ae65dad4b5
@@ -166,7 +166,21 @@ write_der(T & val, SecByteBlock & sec)
     der_encoded[i] = '\0';
 }

+static bool
+blocking_rng(lua_hooks & lua)
+{
+  if (!lua.hook_non_blocking_rng_ok())
+    {
+#ifndef BLOCKING_RNG_AVAILABLE
+      throw oops("no blocking RNG available and non-blocking RNG rejected");
+#else
+      return true;
+#endif
+    };
+  return false;
+}

+
 void
 generate_key_pair(lua_hooks & lua,              // to hook for phrase
                   rsa_keypair_id const & id,    // to prompting user for phrase
@@ -176,17 +190,7 @@ generate_key_pair(lua_hooks & lua,
 {
   // we will panic here if the user doesn't like urandom and we can't give
   // them a real entropy-driven random.
-  bool request_blocking_rng = false;
-  if (!lua.hook_non_blocking_rng_ok())
-    {
-#ifndef BLOCKING_RNG_AVAILABLE
-      throw oops("no blocking RNG available and non-blocking RNG rejected");
-#else
-      request_blocking_rng = true;
-#endif
-    }
-
-  AutoSeededRandomPool rng(request_blocking_rng);
+  AutoSeededRandomPool rng(blocking_rng(lua));
   SecByteBlock phrase, pubkey, privkey;
   rsa_pub_key raw_pub_key;
   arc4<rsa_priv_key> raw_priv_key;
@@ -267,16 +271,7 @@ make_signature(lua_hooks & lua,

   // we will panic here if the user doesn't like urandom and we can't give
   // them a real entropy-driven random.
-  bool request_blocking_rng = false;
-  if (!lua.hook_non_blocking_rng_ok())
-    {
-#ifndef BLOCKING_RNG_AVAILABLE
-      throw oops("no blocking RNG available and non-blocking RNG rejected");
-#else
-      request_blocking_rng = true;
-#endif
-    }
-  AutoSeededRandomPool rng(request_blocking_rng);
+  AutoSeededRandomPool rng(blocking_rng(lua));

   // we permit the user to relax security here, by caching a decrypted key
   // (if they permit it) through the life of a program run. this helps when
@@ -396,6 +391,75 @@ check_signature(lua_hooks & lua,
   return vf->GetLastResult();
 }

+void encrypt_rsa(lua_hooks & lua,
+                 rsa_keypair_id const & id,
+                 base64<rsa_pub_key> & pub_encoded,
+                 std::string const & plaintext,
+                 rsa_oaep_sha_data & ciphertext)
+{
+  AutoSeededRandomPool rng(blocking_rng(lua));
+
+  rsa_pub_key pub;
+  decode_base64(pub_encoded, pub);
+  SecByteBlock pub_block;
+  pub_block.Assign(reinterpret_cast<byte const *>(pub().data()), pub().size());
+  StringSource keysource(pub_block.data(), pub_block.size(), true);
+
+  shared_ptr<RSAES_OAEP_SHA_Encryptor> encryptor;
+  encryptor = shared_ptr<RSAES_OAEP_SHA_Encryptor>
+    (new RSAES_OAEP_SHA_Encryptor(keysource));
+
+  string ciphertext_string;
+  StringSource tmp(plaintext, true,
+                   encryptor->CreateEncryptionFilter
+                   (rng, new StringSink(ciphertext_string)));
+
+  ciphertext = rsa_oaep_sha_data(ciphertext_string);
+}
+
+void decrypt_rsa(lua_hooks & lua,
+                 rsa_keypair_id const & id,
+                 base64< arc4<rsa_priv_key> > const & priv,
+                 rsa_oaep_sha_data const & ciphertext,
+                 std::string & plaintext)
+{
+  AutoSeededRandomPool rng(blocking_rng(lua));
+  arc4<rsa_priv_key> decoded_key;
+  SecByteBlock decrypted_key;
+  SecByteBlock phrase;
+  shared_ptr<RSAES_OAEP_SHA_Decryptor> decryptor;
+
+  for (int i = 0; i < 3; i++)
+    {
+      bool force = false;
+      decode_base64(priv, decoded_key);
+      decrypted_key.Assign(reinterpret_cast<byte const *>(decoded_key().data()),
+                           decoded_key().size());
+      get_passphrase(lua, id, phrase, false, force);
+
+      try
+        {
+          do_arc4(phrase, decrypted_key);
+          StringSource keysource(decrypted_key.data(), decrypted_key.size(), true);
+          decryptor = shared_ptr<RSAES_OAEP_SHA_Decryptor>
+            (new RSAES_OAEP_SHA_Decryptor(keysource));
+        }
+      catch (...)
+        {
+          if (i >= 2)
+            throw informative_failure("failed to decrypt private RSA key, "
+                                      "probably incorrect passphrase");
+          // don't use the cache bad one next time
+          force = true;
+          continue;
+        }
+    }
+
+  StringSource tmp(ciphertext(), true,
+                   decryptor->CreateDecryptionFilter
+                   (rng, new StringSink(plaintext)));
+}
+
 void
 read_pubkey(string const & in,
             rsa_keypair_id & id,
============================================================
--- keys.hh	f10aaea8dbaf4005a55ec388f278d1bb81d664af
+++ keys.hh	ec2da9cbde80341456204b6e0e0b4f727388ed8b
@@ -42,6 +42,18 @@ void require_password(rsa_keypair_id con
 void require_password(rsa_keypair_id const & id,
                       app_state & app);

+void encrypt_rsa(lua_hooks & lua,
+                 rsa_keypair_id const & id,
+                 base64<rsa_pub_key> & pub,
+                 std::string const & plaintext,
+                 rsa_oaep_sha_data & ciphertext);
+
+void decrypt_rsa(lua_hooks & lua,
+                 rsa_keypair_id const & id,
+                 base64< arc4<rsa_priv_key> > const & priv,
+                 rsa_oaep_sha_data const & ciphertext,
+                 std::string & plaintext);
+
 // netsync stuff

 void read_pubkey(std::string const & in,
============================================================
--- lua.cc	c5a01c71aef633d6ad5ad78650e4e0601295b349
+++ lua.cc	e4dd123e0c07b59361555b521f3b2cc17aa445b5
@@ -30,6 +30,7 @@ extern "C" {
 #include "sanity.hh"
 #include "vocab.hh"
 #include "platform.hh"
+#include "transforms.hh"

 // defined in {std,test}_hooks.lua, converted
 #include "test_hooks.h"
@@ -45,164 +46,6 @@ static int panic_thrower(lua_State * st)
 }
 */

-extern "C"
-{
-  static int
-  monotone_mkstemp_for_lua(lua_State *L)
-  {
-    int fd = -1;
-    FILE **pf = NULL;
-    char const *filename = lua_tostring (L, -1);
-    std::string dup(filename);
-
-    fd = monotone_mkstemp(dup);
-
-    if (fd == -1)
-      return 0;
-
-    // this magic constructs a lua object which the lua io library
-    // will enjoy working with
-    pf = static_cast<FILE **>(lua_newuserdata(L, sizeof(FILE *)));
-    *pf = fdopen(fd, "r+");
-    lua_pushstring(L, "FILE*");
-    lua_rawget(L, LUA_REGISTRYINDEX);
-    lua_setmetatable(L, -2);
-
-    lua_pushstring(L, dup.c_str());
-
-    if (*pf == NULL)
-      {
-        lua_pushnil(L);
-        lua_pushfstring(L, "%s", strerror(errno));
-        lua_pushnumber(L, errno);
-        return 3;
-      }
-    else
-      return 2;
-  }
-
-  static int
-  monotone_existsonpath_for_lua(lua_State *L)
-  {
-    const char *exe = lua_tostring(L, -1);
-    lua_pushnumber(L, existsonpath(exe));
-    return 1;
-  }
-
-  static int
-  monotone_is_executable_for_lua(lua_State *L)
-  {
-    const char *path = lua_tostring(L, -1);
-    lua_pushboolean(L, is_executable(path));
-    return 1;
-  }
-
-  static int
-  monotone_make_executable_for_lua(lua_State *L)
-  {
-    const char *path = lua_tostring(L, -1);
-    lua_pushnumber(L, make_executable(path));
-    return 1;
-  }
-
-  static int
-  monotone_spawn_for_lua(lua_State *L)
-  {
-    int n = lua_gettop(L);
-    const char *path = lua_tostring(L, -n);
-    char **argv = (char**)malloc((n+1)*sizeof(char*));
-    int i;
-    pid_t ret;
-    if (argv==NULL)
-      return 0;
-    argv[0] = (char*)path;
-    for (i=1; i<n; i++) argv[i] = (char*)lua_tostring(L, -(n - i));
-    argv[i] = NULL;
-    ret = process_spawn(argv);
-    free(argv);
-    lua_pushnumber(L, ret);
-    return 1;
-  }
-
-  static int
-  monotone_wait_for_lua(lua_State *L)
-  {
-    pid_t pid = (pid_t)lua_tonumber(L, -1);
-    int res;
-    int ret;
-    ret = process_wait(pid, &res);
-    lua_pushnumber(L, res);
-    lua_pushnumber(L, ret);
-    return 2;
-  }
-
-  static int
-  monotone_kill_for_lua(lua_State *L)
-  {
-    int n = lua_gettop(L);
-    pid_t pid = (pid_t)lua_tonumber(L, -2);
-    int sig;
-    if (n>1)
-      sig = (int)lua_tonumber(L, -1);
-    else
-      sig = SIGTERM;
-    lua_pushnumber(L, process_kill(pid, sig));
-    return 1;
-  }
-
-  static int
-  monotone_sleep_for_lua(lua_State *L)
-  {
-    int seconds = (int)lua_tonumber(L, -1);
-    lua_pushnumber(L, process_sleep(seconds));
-    return 1;
-  }
-
-  static int
-  monotone_guess_binary_for_lua(lua_State *L)
-  {
-    const char *path = lua_tostring(L, -1);
-    N(path, F("guess_binary called with an invalid parameter"));
-    lua_pushboolean(L, guess_binary(std::string(path, lua_strlen(L, -1))));
-    return 1;
-  }
-}
-
-
-lua_hooks::lua_hooks()
-{
-  st = lua_open ();
-  I(st);
-
-  // no atpanic support in 4.x
-  // lua_atpanic (st, &panic_thrower);
-
-  luaopen_base(st);
-  luaopen_io(st);
-  luaopen_string(st);
-  luaopen_math(st);
-  luaopen_table(st);
-  luaopen_debug(st);
-
-  // add monotone-specific functions
-  lua_register(st, "mkstemp", monotone_mkstemp_for_lua);
-  lua_register(st, "existsonpath", monotone_existsonpath_for_lua);
-  lua_register(st, "is_executable", monotone_is_executable_for_lua);
-  lua_register(st, "make_executable", monotone_make_executable_for_lua);
-  lua_register(st, "spawn", monotone_spawn_for_lua);
-  lua_register(st, "wait", monotone_wait_for_lua);
-  lua_register(st, "kill", monotone_kill_for_lua);
-  lua_register(st, "sleep", monotone_sleep_for_lua);
-  lua_register(st, "guess_binary", monotone_guess_binary_for_lua);
-}
-
-lua_hooks::~lua_hooks()
-{
-  if (st)
-    lua_close (st);
-}
-
-
 // This Lua object represents a single imperative transaction with the lua
 // interpreter. if it fails at any point, all further commands in the
 // transaction are ignored. it cleans the lua stack up when it is
@@ -535,6 +378,217 @@ std::set<string> Lua::missing_functions;

 std::set<string> Lua::missing_functions;

+
+
+
+extern "C"
+{
+  static int
+  monotone_mkstemp_for_lua(lua_State *L)
+  {
+    int fd = -1;
+    FILE **pf = NULL;
+    char const *filename = lua_tostring (L, -1);
+    std::string dup(filename);
+
+    fd = monotone_mkstemp(dup);
+
+    if (fd == -1)
+      return 0;
+
+    // this magic constructs a lua object which the lua io library
+    // will enjoy working with
+    pf = static_cast<FILE **>(lua_newuserdata(L, sizeof(FILE *)));
+    *pf = fdopen(fd, "r+");
+    lua_pushstring(L, "FILE*");
+    lua_rawget(L, LUA_REGISTRYINDEX);
+    lua_setmetatable(L, -2);
+
+    lua_pushstring(L, dup.c_str());
+
+    if (*pf == NULL)
+      {
+        lua_pushnil(L);
+        lua_pushfstring(L, "%s", strerror(errno));
+        lua_pushnumber(L, errno);
+        return 3;
+      }
+    else
+      return 2;
+  }
+
+  static int
+  monotone_existsonpath_for_lua(lua_State *L)
+  {
+    const char *exe = lua_tostring(L, -1);
+    lua_pushnumber(L, existsonpath(exe));
+    return 1;
+  }
+
+  static int
+  monotone_is_executable_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    lua_pushboolean(L, is_executable(path));
+    return 1;
+  }
+
+  static int
+  monotone_make_executable_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    lua_pushnumber(L, make_executable(path));
+    return 1;
+  }
+
+  static int
+  monotone_spawn_for_lua(lua_State *L)
+  {
+    int n = lua_gettop(L);
+    const char *path = lua_tostring(L, -n);
+    char **argv = (char**)malloc((n+1)*sizeof(char*));
+    int i;
+    pid_t ret;
+    if (argv==NULL)
+      return 0;
+    argv[0] = (char*)path;
+    for (i=1; i<n; i++) argv[i] = (char*)lua_tostring(L, -(n - i));
+    argv[i] = NULL;
+    ret = process_spawn(argv);
+    free(argv);
+    lua_pushnumber(L, ret);
+    return 1;
+  }
+
+  static int
+  monotone_wait_for_lua(lua_State *L)
+  {
+    pid_t pid = (pid_t)lua_tonumber(L, -1);
+    int res;
+    int ret;
+    ret = process_wait(pid, &res);
+    lua_pushnumber(L, res);
+    lua_pushnumber(L, ret);
+    return 2;
+  }
+
+  static int
+  monotone_kill_for_lua(lua_State *L)
+  {
+    int n = lua_gettop(L);
+    pid_t pid = (pid_t)lua_tonumber(L, -2);
+    int sig;
+    if (n>1)
+      sig = (int)lua_tonumber(L, -1);
+    else
+      sig = SIGTERM;
+    lua_pushnumber(L, process_kill(pid, sig));
+    return 1;
+  }
+
+  static int
+  monotone_sleep_for_lua(lua_State *L)
+  {
+    int seconds = (int)lua_tonumber(L, -1);
+    lua_pushnumber(L, process_sleep(seconds));
+    return 1;
+  }
+
+  static int
+  monotone_guess_binary_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    N(path, F("guess_binary called with an invalid parameter"));
+    lua_pushboolean(L, guess_binary(std::string(path, lua_strlen(L, -1))));
+    return 1;
+  }
+
+  static int
+  monotone_include_for_lua(lua_State *L)
+  {
+    const char *path = lua_tostring(L, -1);
+    N(path, F("Include called with an invalid parameter"));
+
+    bool res =Lua(L)
+    .loadfile(std::string(path, lua_strlen(L, -1)))
+    .call(0,1)
+    .ok();
+
+    lua_pushboolean(L, res);
+    return 1;
+  }
+
+  static int
+  monotone_includedir_for_lua(lua_State *L)
+  {
+    const char *pathstr = lua_tostring(L, -1);
+    N(pathstr, F("IncludeDir called with an invalid parameter"));
+
+    fs::path locpath(pathstr);
+    N(fs::exists(locpath), F("Directory '%s' does not exists") % pathstr);
+    N(fs::is_directory(locpath), F("'%s' is not a directory") % pathstr);
+
+    // directory, iterate over it, skipping subdirs, taking every filename,
+    // sorting them and loading in sorted order
+    fs::directory_iterator it(locpath);
+    std::vector<fs::path> arr;
+    while (it != fs::directory_iterator())
+      {
+        if (!fs::is_directory(*it))
+          arr.push_back(*it);
+        ++it;
+      }
+    std::sort(arr.begin(), arr.end());
+    for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i)
+      {
+        bool res =Lua(L)
+        .loadfile(i->string())
+        .call(0,1)
+        .ok();
+        N(res, F("lua error while loading rcfile '%s'") % i->string());
+      }
+
+    lua_pushboolean(L, true);
+    return 1;
+  }
+}
+
+
+lua_hooks::lua_hooks()
+{
+  st = lua_open ();
+  I(st);
+
+  // no atpanic support in 4.x
+  // lua_atpanic (st, &panic_thrower);
+
+  luaopen_base(st);
+  luaopen_io(st);
+  luaopen_string(st);
+  luaopen_math(st);
+  luaopen_table(st);
+  luaopen_debug(st);
+
+  // add monotone-specific functions
+  lua_register(st, "mkstemp", monotone_mkstemp_for_lua);
+  lua_register(st, "existsonpath", monotone_existsonpath_for_lua);
+  lua_register(st, "is_executable", monotone_is_executable_for_lua);
+  lua_register(st, "make_executable", monotone_make_executable_for_lua);
+  lua_register(st, "spawn", monotone_spawn_for_lua);
+  lua_register(st, "wait", monotone_wait_for_lua);
+  lua_register(st, "kill", monotone_kill_for_lua);
+  lua_register(st, "sleep", monotone_sleep_for_lua);
+  lua_register(st, "guess_binary", monotone_guess_binary_for_lua);
+  lua_register(st, "include", monotone_include_for_lua);
+  lua_register(st, "includedir", monotone_includedir_for_lua);
+}
+
+lua_hooks::~lua_hooks()
+{
+  if (st)
+    lua_close (st);
+}
+
 static bool
 run_string(lua_State * st, string const &str, string const & identity)
 {
@@ -591,6 +645,29 @@ lua_hooks::load_rcfile(utf8 const & rc)
 lua_hooks::load_rcfile(utf8 const & rc)
 {
   I(st);
+  if (rc() != "-")
+    {
+      fs::path locpath(localized(rc));
+      if (fs::exists(locpath) && fs::is_directory(locpath))
+        {
+          // directory, iterate over it, skipping subdirs, taking every filename,
+          // sorting them and loading in sorted order
+          fs::directory_iterator it(locpath);
+          std::vector<fs::path> arr;
+          while (it != fs::directory_iterator())
+            {
+              if (!fs::is_directory(*it))
+                arr.push_back(*it);
+              ++it;
+            }
+          std::sort(arr.begin(), arr.end());
+          for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i)
+            {
+              load_rcfile(*i, true);
+            }
+          return; // directory read, skip the rest ...
+        }
+    }
   data dat;
   L(F("opening rcfile '%s' ...\n") % rc);
   read_data_for_command_line(rc, dat);
============================================================
--- monotone.texi	28008d0dc7e6abd7e2e40cab143add9c4faecbca
+++ monotone.texi	131933a2c2cd95c13bf8e3b8c988b8e4bc97664d
@@ -77,6 +77,7 @@ @top Top
 * Hook Reference::      Functions which extend monotone
 * Special Topics::      Extra explanations and details
 * Man Page::            That other document
+* Default hooks::       The standard hook definitions
 * Index::               Index of concepts and functions
 @end menu

@@ -5280,10 +5281,27 @@ @chapter Hook Reference
 using the @option{--rcfile=@var{file}} option; hooks defined in files
 specified on the command-line will shadow hooks from the the automatic
 files.
+By specifying @option{--rcfile=@var{directory}} you can automatically
+load all the files contained into @var{directory}.

-The remainder of this section documents the existing hook functions
-and their default definitions.
+Monotone also makes available to hook writers a number of helper
+functions exposing functionality not available with standard lua.

+For the complete source of the default hooks see @ref{Default
+hooks}.
+
+@menu
+* Hooks::                     All hooks called by monotone.
+* Additional Lua Functions::  Extra functionality availabe to hook writers.
+@end menu
+
+@page
+@node   Hooks
+@section Hooks
+
+This section documents the existing hook functions and their default
+definitions.
+
 @ftable @code
 @item note_commit (@var{new_id}, @var{certs})

@@ -5380,46 +5398,8 @@ @chapter Hook Reference
 a successful commit, the contents of @file{MT/log} are erased setting
 the system up for another edit/commit cycle.

-The default definition of this hook is:
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group
-function edit_comment(commentary, user_log_message)
-        local exe = "vi"
-        local visual = os.getenv("VISUAL")
-        if (visual ~= nil) then exe = visual end
-        local editor = os.getenv("EDITOR")
-        if (editor ~= nil) then exe = editor end
-
-        local tmp, tname = temp_file()
-        if (tmp == nil) then return nil end
-        commentary = "MT: " .. string.gsub(commentary, "\n", "\nMT: ")
-        tmp:write(user_log_message)
-        tmp:write(commentary)
-        io.close(tmp)
-
-        if (os.execute(string.format("%s %s", exe, tname)) ~= 0) then
-                os.remove(tname)
-                return nil
-        end
-
-        tmp = io.open(tname, "r")
-        if (tmp == nil) then os.remove(tname); return nil end
-        local res = ""
-        local line = tmp:read()
-        while(line ~= nil) do
-                if (not string.find(line, "^MT:")) then
-                        res = res .. line .. "\n"
-                end
-                line = tmp:read()
-        end
-        io.close(tmp)
-        os.remove(tname)
-        return res
-end
-@end group
-@end smallexample
-
 @item persist_phrase_ok ()

 Returns @code{true} if you want monotone to remember the passphrase of
@@ -5520,34 +5500,9 @@ @chapter Hook Reference
 Returns @code{true} if @var{filename} should be ignored while adding,
 dropping, or moving files. Otherwise returns @code{false}. This is
 most important when performing recursive actions on directories, which
-may affect multiple files simultaneously. The default definition of
-this hook is:
+may affect multiple files simultaneously.
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group
-function ignore_file(name)
-   if (string.find(name, "%.a$")) then return true end
-   if (string.find(name, "%.so$")) then return true end
-   if (string.find(name, "%.o$")) then return true end
-   if (string.find(name, "%.la$")) then return true end
-   if (string.find(name, "%.lo$")) then return true end
-   if (string.find(name, "%.aux$")) then return true end
-   if (string.find(name, "%.bak$")) then return true end
-   if (string.find(name, "%.orig$")) then return true end
-   if (string.find(name, "%.rej$")) then return true end
-   if (string.find(name, "%~$")) then return true end
-   if (string.find(name, "/core$")) then return true end
-   if (string.find(name, "^CVS/")) then return true end
-   if (string.find(name, "/CVS/")) then return true end
-   if (string.find(name, "^%.svn/")) then return true end
-   if (string.find(name, "/%.svn/")) then return true end
-   if (string.find(name, "^SCCS/")) then return true end
-   if (string.find(name, "/SCCS/")) then return true end
-   return false;
-end
-@end group
-@end smallexample
-
 @item ignore_branch (@var{branchname})

 Returns @code{true} if @var{branchname} should be ignored while listing
@@ -5646,61 +5601,10 @@ @chapter Hook Reference
 strings, which are the contents of the @var{left} and @var{right}
 nodes of a file fork which monotone was unable to automatically
 merge. The merge should either call an intelligent merge program or
-interact with the user. The default definition of this hook is:
+interact with the user.
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group

-function merge2 (left_path, right_path, merged_path, left, right)
-   local ret = nil
-   local tbl = @{@}
-
-   tbl.lfile = nil
-   tbl.rfile = nil
-   tbl.outfile = nil
-   tbl.meld_exists = false
-
-   tbl.lfile = write_to_temporary_file (left)
-   tbl.rfile = write_to_temporary_file (right)
-   tbl.outfile = write_to_temporary_file ("")
-
-   if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.outfile ~= nil
-   then
-      tbl.left_path = left_path
-      tbl.right_path = right_path
-      tbl.merged_path = merged_path
-
-      local cmd = get_preferred_merge2_command (tbl)
-
-      if cmd ~=nil
-      then
-         io.write (
-					string.format("executing external 2-way merge command\n"))
-         cmd ()
-         if tbl.meld_exists
-         then
-            ret = read_contents_of_file (tbl.lfile)
-         else
-            ret = read_contents_of_file (tbl.outfile)
-         end
-         if string.len (ret) == 0
-         then
-            ret = nil
-         end
-      else
-         io.write ("no external 2-way merge command found\n")
-      end
-   end
-
-   os.remove (tbl.lfile)
-   os.remove (tbl.rfile)
-   os.remove (tbl.outfile)
-
-   return ret
-end
-@end group
-@end smallexample
-
 @anchor{get_preferred_merge2_command}
 @item get_preferred_merge2_command(@var{tbl})

@@ -5710,58 +5614,7 @@ @chapter Hook Reference
 that you would like to use to perform merge2 operations, override
 this hook to specify it.

-@smallexample
-@group

-function get_preferred_merge2_command (tbl)
-   local cmd = nil
-   local left_path = tbl.left_path
-   local right_path = tbl.right_path
-   local merged_path = tbl.merged_path
-   local lfile = tbl.lfile
-   local rfile = tbl.rfile
-   local outfile = tbl.outfile
-
-   local editor = string.lower(os.getenv("EDITOR"))
-
-
-   if program_exists_in_path("kdiff3") then
-      cmd =   merge2_kdiff3_cmd (left_path, right_path, merged_path,
-								 lfile, rfile, outfile)
-   elseif program_exists_in_path ("meld") then
-      tbl.meld_exists = true
-      io.write (string.format(
-			"\nWARNING: 'meld' was choosen to perform external 2-way merge.\n"..
-      "You should merge all changes to *LEFT* file due to limitation of program\n"..
-      "arguments.\n\n"))
-      cmd = merge2_meld_cmd (lfile, rfile)
-   elseif program_exists_in_path ("xxdiff") then
-      cmd = merge2_xxdiff_cmd (left_path, right_path, merged_path,
-							lfile, rfile, outfile)
-   else
-     if string.find(editor, "emacs") ~= nil
-				or string.find(editor, "gnu") ~= nil
-		 then
-       if program_exists_in_path ("emacs") then
-          cmd = merge2_emacs_cmd ("emacs", lfile, rfile, outfile)
-       elseif program_exists_in_path ("xemacs") then
-          cmd = merge2_emacs_cmd ("xemacs", lfile, rfile, outfile)
-       end
-     else if string.find(editor, "vim") ~= nil then
-       if os.getenv ("DISPLAY") ~= nil
-					and program_exists_in_path ("gvim")
-			 then
-          cmd = merge2_vim_cmd ("gvim", lfile, rfile, outfile)
-       elseif program_exists_in_path ("vim") then
-          cmd = merge2_vim_cmd ("vim", lfile, rfile, outfile)
-       end
-     end
-   end
-   return cmd
-end
-@end group
-@end smallexample
-
 @anchor{merge3}
 @item merge3 (@var{ancestor}, @var{left}, @var{right})

@@ -5769,65 +5622,10 @@ @chapter Hook Reference
 strings, which are the contents of @var{left} and @var{right} nodes,
 and least common @var{ancestor}, of a file fork which monotone was
 unable to automatically merge. This hook delegates the actual merge
-to the result of @ref{get_preferred_merge3_command}. The default
-definition of this hook is:
+to the result of @ref{get_preferred_merge3_command}.
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group
-function merge3 (anc_path, left_path, right_path, merged_path,
-								 ancestor, left, right)
-   local ret
-   local tbl = @{@}
-
-   tbl.anc_path = anc_path
-   tbl.left_path = left_path
-   tbl.right_path = right_path

-   tbl.merged_path = merged_path
-   tbl.afile = nil
-   tbl.lfile = nil
-   tbl.rfile = nil
-   tbl.outfile = nil
-   tbl.meld_exists = false
-   tbl.lfile = write_to_temporary_file (left)
-   tbl.afile =   write_to_temporary_file (ancestor)
-   tbl.rfile =   write_to_temporary_file (right)
-   tbl.outfile = write_to_temporary_file ("")
-
-   if tbl.lfile ~= nil and tbl.rfile ~= nil
-			and tbl.afile ~= nil and tbl.outfile ~= nil
-   then
-      local cmd =   get_preferred_merge3_command (tbl)
-      if cmd ~=nil
-      then
-         io.write (string.format(
-						"executing external 3-way merge command\n"))
-         cmd ()
-         if tbl.meld_exists
-         then
-            ret = read_contents_of_file (tbl.afile)
-         else
-            ret = read_contents_of_file (tbl.outfile)
-         end
-         if string.len (ret) == 0
-         then
-            ret = nil
-         end
-      else
-         io.write ("no external 3-way merge command found\n")
-      end
-   end
-
-   os.remove (tbl.lfile)
-   os.remove (tbl.rfile)
-   os.remove (tbl.afile)
-   os.remove (tbl.outfile)
-
-   return ret
-end
-@end group
-@end smallexample
-
 @anchor{get_preferred_merge3_command}
 @item get_preferred_merge3_command(@var{tbl})

@@ -5837,65 +5635,6 @@ @chapter Hook Reference
 that you would like to use to perform merge3 operations, override
 this hook to specify it.

-@smallexample
-@group
-function get_preferred_merge3_command (tbl)
-   local cmd = nil
-   local left_path = tbl.left_path
-   local anc_path = tbl.anc_path
-   local right_path = tbl.right_path
-   local merged_path = tbl.merged_path
-   local lfile = tbl.lfile
-   local afile = tbl.afile
-   local rfile = tbl.rfile
-   local outfile = tbl.outfile
-
-   local editor = string.lower(os.getenv("EDITOR"))
-
-   if program_exists_in_path("kdiff3") then
-      cmd = merge3_kdiff3_cmd (left_path, anc_path, right_path,
-								merged_path, lfile, afile, rfile, outfile)
-   elseif program_exists_in_path ("meld") then
-      tbl.meld_exists = true
-      io.write (string.format(
-					"\nWARNING: 'meld' was choosen to perform external 3-way merge.\n"..
-          "You should merge all changes to *CENTER* file due to limitation of program\n"..
-          "arguments.\n\n"))
-      cmd = merge3_meld_cmd (lfile, afile, rfile)
-   elseif program_exists_in_path ("xxdiff") then
-      cmd = merge3_xxdiff_cmd (left_path, anc_path, right_path,
-								merged_path, lfile, afile, rfile, outfile)
-   else
-     -- prefer emacs/xemacs
-     if string.find(editor, "emacs") ~= nil
-				or string.find(editor, "gnu") ~= nil
-		 then
-       if program_exists_in_path ("xemacs") then
-          cmd = merge3_emacs_cmd ("xemacs", lfile, afile,
-									rfile, outfile)
-       elseif program_exists_in_path ("emacs") then
-          cmd = merge3_emacs_cmd ("emacs", lfile, afile,
-									rfile, outfile)
-       end
-     elseif string.find(editor, "vim") ~= nil then  -- prefer vim
-       if os.getenv ("DISPLAY") ~= nil
-					and program_exists_in_path ("gvim")
-			 then
-          cmd = merge3_vim_cmd ("gvim", lfile, afile,
-									rfile, outfile)
-       elseif program_exists_in_path ("vim") then
-          cmd = merge3_vim_cmd ("vim", lfile, afile,
-									rfile, outfile)
-       end
-     end
-   end
-
-   return cmd
-end
-@end group
-@end smallexample
-
-
 @item expand_selector (@var{str})

 Attempts to expand @var{str} as a selector. Expansion generally means
@@ -5903,115 +5642,18 @@ @chapter Hook Reference
 authors or @code{d:} for dates. Expansion may also mean recognizing
 and interpreting special words such as @code{yesterday} or @code{6
 months ago} and converting them into well formed selectors. For more
-detail on the use of selectors, see @ref{Selectors}. The default
-definition of this hook is:
+detail on the use of selectors, see @ref{Selectors}.
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group
-function expand_selector(str)
-
-   -- something which looks like a generic cert pattern
-   if string.find(str, "^[^=]*=.*$")
-   then
-      return ("c:" .. str)
-   end
-
-   -- something which looks like an email address
-   if string.find(str, "[%w%-_]+@@[%w%-_]+")
-   then
-      return ("a:" .. str)
-   end
-
-   -- something which looks like a branch name
-   if string.find(str, "[%w%-]+%.[%w%-]+")
-   then
-      return ("b:" .. str)
-   end
-
-   -- a sequence of nothing but hex digits
-   if string.find(str, "^%x+$")
-   then
-      return ("i:" .. str)
-   end
-
-   -- tries to expand as a date
-   local dtstr = expand_date(str)
-   if  dtstr ~= nil
-   then
-      return ("d:" .. dtstr)
-   end
-
-   return nil
-end
-@end group
-@end smallexample
-
 @item expand_date (@var{str})

 Attempts to expand @var{str} as a date expression. Expansion means recognizing
 and interpreting special words such as @code{yesterday} or @code{6
 months ago} and converting them into well formed date expressions. For more
-detail on the use of selectors, see @ref{Selectors}. The default
-definition of this hook is:
+detail on the use of selectors, see @ref{Selectors}.
+For the default definition of this hook, see @ref{Default hooks}.

-@smallexample
-@group
-function expand_date(str)
-   -- simple date patterns
-   if string.find(str, "^19%d%d%-%d%d")
-      or string.find(str, "^20%d%d%-%d%d")
-   then
-      return (str)
-   end

-   -- "now"
-   if str == "now"
-   then
-      local t = os.time(os.date('!*t'))
-      return os.date("%FT%T", t)
-   end
-
-    -- today don't uses the time
-   if str == "today"
-   then
-      local t = os.time(os.date('!*t'))
-      return os.date("%F", t)
-   end
-
-   -- "yesterday", the source of all hangovers
-   if str == "yesterday"
-   then
-      local t = os.time(os.date('!*t'))
-      return os.date("%F", t - 86400)
-   end
-
-   -- "CVS style" relative dates such as "3 weeks ago"
-   local trans = @{
-      minute = 60;
-      hour = 3600;
-      day = 86400;
-      week = 604800;
-      month = 2678400;
-      year = 31536000
-   @}
-   local pos, len, n, type = string.find(str, "(%d+) ([minutehordaywk]+)s? ago")
-   if trans[type] ~= nil
-   then
-      local t = os.time(os.date('!*t'))
-      if trans[type] <= 3600
-      then
-	return os.date("%FT%T", t - (n * trans[type]))
-      else
-	return os.date("%F", t - (n * trans[type]))
-      end
-   end
-
-   return nil
-end
-@end group
-@end smallexample
-
-
 @item get_system_linesep ()

 Returns a string which defines the default system line separator.
@@ -6133,41 +5775,124 @@ @chapter Hook Reference
 @end group
 @end smallexample

-The @code{binary_file} function is also defined as a lua hook as
-follows:
-
+The @code{binary_file} function is also defined as a lua hook. See
+@ref{Default hooks}.
+
+@end ftable
+
+@page
+@node   Additional Lua Functions
+@section Additional Lua Functions
+
+This section documents the additional lua functions made available to
+hook writers.
+
+@ftable @code
+
+@item existonpath(@var{possible_command})
+
+This function receives a string containing the name of an external
+program and returns 0 if it exists on path and is executable, -1
+otherwise.
+As an example, @code{existonpath("xxdiff")} returns 0 if the
+program xxdiff is available.
+On windows, this function automatically appends ``.exe'' to the
+program name. In the previous example, @code{existonpath} would search
+for ``xxdiff.exe''.
+
+@item guess_binary(@var{filespec})
+
+Returns true if the file appears to be binary, i.e. contains one or
+more of the following characters:
 @smallexample
 @group
-function binary_file(name)
-   local lowname=string.lower(name)
-   -- some known binaries, return true
-   if (string.find(lowname, "%.gif$")) then return true end
-   if (string.find(lowname, "%.jpe?g$")) then return true end
-   if (string.find(lowname, "%.png$")) then return true end
-   if (string.find(lowname, "%.bz2$")) then return true end
-   if (string.find(lowname, "%.gz$")) then return true end
-   if (string.find(lowname, "%.zip$")) then return true end
-   -- some known text, return false
-   if (string.find(lowname, "%.cc?$")) then return false end
-   if (string.find(lowname, "%.cxx$")) then return false end
-   if (string.find(lowname, "%.hh?$")) then return false end
-   if (string.find(lowname, "%.hxx$")) then return false end
-   if (string.find(lowname, "%.lua$")) then return false end
-   if (string.find(lowname, "%.texi$")) then return false end
-   if (string.find(lowname, "%.sql$")) then return false end
-   -- unknown - read file and use the guess-binary built-in
-   -- monotone function
-   filedata=read_contents_of_file(name)
-   if (filedata ~= nil) then return guess_binary(filedata) end
-   -- if still unknown, treat as binary
-   return true
-end
+0x00 thru 0x06
+0x0E thru 0x1a
+0x1c thru 0x1f
 @end group
 @end smallexample

+@item include(@var{scriptfile})

+This function tries to load and execute the script contained into
+scriptfile.  It returns true for success and false if there is an
+error.
+
+@item includedir(@var{scriptpath})
+
+This function loads and executes in alphabetical order all the scripts
+contained into the directory scriptpath.
+If one of the scripts has an error, the functions doesn't process the
+remaining scripts and immediately returns false.
+
+@item is_executable(@var{filespec})
+
+This function returns true if the file is executable, false
+otherwise.  On windows this function returns always false.
+
+@item kill(@var{pid} [, @var{signal}])
+
+This function calls the kill() C library function on posix systems and
+TerminateProcess on Win32 (in that case @var{pid} is the process
+handle).  If the optional @var{signal} parameter is missing, SIGTERM
+will be used.
+Returns 0 on succes, -1 on error.
+
+@item make_executable(@var{filespec})
+
+This function marks the named file as executable.  On windows has no
+effect.
+
+@item mkstemp(@var{template})
+
+Like its C library counterpart,  mkstemp creates a unique name and
+returns a file descriptor for the newly created file.
+The value of template should be a pointer to a character buffer loaded
+with a null-terminated string that consists of contiguous, legal file
+ad path name characters followed by six Xs.
+The function mkstemp replaces the Xs by an alpha-numeric sequence
+that is chosen to ensure that no file in the chosen directory has that
+name.
+Furthermore, subsequent calls to mkstemp within the same process
+each yield different file names.
+Unlike other implementations, monotone mkstemp allows the template
+string to contain a complete path, not only a filename, allowing users
+to create temporary files outside the current directory.
+
+@strong{Important notice:}@*
+To create a temporary file, you must use the @code{temp_file()}
+function, unless you need to run monotone with the @option{--nostd}
+option.  @code{temp_file()} builds on @code{mkstemp()} and creates a
+file in the standard TMP/TEMP directories.
+For the definition of @code{temp_file()}, see @ref{Default hooks}.
+
+@item sleep(@var{seconds})
+
+Makes the calling process sleep for the specified number of seconds.
+
+@item spawn(@var{executable} [, @var{args ...}])
+
+Starts the named executable with the given arguments.  Returns the
+process pid on Posix systems, the process handle on Win32 or -1 if
+there was an error.
+Calls fork/execvp on Posix, CreateProcess on Win32.
+
+@strong{Important notice:}@*
+To spawn a process and wait for its completion, use the @code{execute()}
+function, unless you need to run monotone with the @option{--nostd}
+option.  @code{execute()} builds on @code{spawn()} and @code{wait()}
+in a standardized way.
+
+@item wait(@var{pid})
+
+Wait until the process with given pid (process handle on Win32) exits.
+Returns two values: a result value and the exit code of the waited-for
+process.
+The exit code is meaningful only if the result value is 0.
+
 @end ftable

+
 @node    Special Topics
 @chapter Special Topics

@@ -7240,6 +6965,16 @@ @section AUTHOR

 graydon hoare <graydon@@pobox.com>

+@node Default hooks
+@appendix Default hooks
+
+This section contains the entire source code of the standard hook file,
+that is built in to the monotone executable, and read before any user
+hooks files (unless @option{--nostd} is passed).  It contains the
+default values for all hooks.
+
+@verbatiminclude std_hooks.lua
+
 @node Index
 @unnumbered Index

============================================================
--- netcmd.cc	3d925448e00e4f5a0e70f9ac977899fbf9312a1e
+++ netcmd.cc	207caa3c268ad7e6a93eaff3f3298aa53f45fadd
@@ -8,6 +8,8 @@
 #include <utility>

 #include "cryptopp/gzip.h"
+#include "cryptopp/hmac.h"
+#include "cryptopp/sha.h"

 #include "adler32.hh"
 #include "constants.hh"
@@ -50,9 +52,6 @@ netcmd::netcmd() : version(constants::ne
                    cmd_code(bye_cmd),
                    payload_len(0), length_len(0)
 {}
-
-netcmd::netcmd(u8 _version) : version(_version), cmd_code(bye_cmd),
-                   payload_len(0), length_len(0)
 {}

 size_t netcmd::encoded_size()
@@ -82,61 +81,53 @@ void
 }

 void
-netcmd::write(string & out) const
+netcmd::write(string & out, netsync_session_key const & key,
+              netsync_hmac_value & hmac_val) const
 {
+  size_t oldlen = out.size();
   out += static_cast<char>(version);
   out += static_cast<char>(cmd_code);
   insert_variable_length_string(payload, out);
-  adler32 check(reinterpret_cast<u8 const *>(payload.data()), payload.size());
-  insert_datum_lsb<u32>(check.sum(), out);
-}

-// last should be zero (doesn't mean we're compatible with version 0).
-// The nonzero elements are the historical netsync/netcmd versions we can
-// interoperate with. For interoperating with newer versions, assume
-// compatibility and let the remote host make the call.
-static u8 const compatible_versions[] = {4, 0};
-
-bool is_compatible(u8 version)
-{
-  for (u8 const *x = compatible_versions; *x; ++x)
+  I(key().size() == CryptoPP::SHA::DIGESTSIZE);
+  I(key().size() == hmac_val().size());
+  byte keybuf[CryptoPP::SHA::DIGESTSIZE];
+  for (size_t i = 0; i < sizeof(keybuf); i++)
     {
-      if (*x == version)
-        return true;
+      keybuf[i] = key()[i] ^ hmac_val()[i];
     }
-  return false;
+  CryptoPP::HMAC<CryptoPP::SHA> hmac(keybuf, sizeof(keybuf));
+  char digest[CryptoPP::SHA::DIGESTSIZE];
+  hmac.CalculateDigest(reinterpret_cast<byte *>(digest),
+                       reinterpret_cast<const byte *>(out.data() + oldlen),
+                       out.size() - oldlen);
+  string digest_str(digest, sizeof(digest));
+  hmac_val = netsync_hmac_value(digest_str);
+  out.append(digest_str);
 }
-
+
 bool
-netcmd::read(string & inbuf)
+netcmd::read(string & inbuf, netsync_session_key const & key,
+             netsync_hmac_value & hmac_val)
 {
   size_t pos = 0;

   if (inbuf.size() < constants::netcmd_minsz)
     return false;

-  u8 ver = extract_datum_lsb<u8>(inbuf, pos, "netcmd protocol number");
-  int v = version;
+  u8 extracted_ver = extract_datum_lsb<u8>(inbuf, pos, "netcmd protocol number");
+  if (extracted_ver != version)
+    throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
+                     % widen<u32,u8>(version)
+                     % widen<u32,u8>(extracted_ver));
+  version = extracted_ver;

   u8 cmd_byte = extract_datum_lsb<u8>(inbuf, pos, "netcmd code");
   switch (cmd_byte)
     {
-    // hello may be newer than expected, or one we're compatible with
     case static_cast<u8>(hello_cmd):
-      if (ver < version && !is_compatible(ver))
-        throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
-                         % widen<u32,u8>(v)
-                         % widen<u32,u8>(ver));
-      break;
-    // these may be older compatible versions
     case static_cast<u8>(anonymous_cmd):
     case static_cast<u8>(auth_cmd):
-      if (ver != version && (ver > version || !is_compatible(ver)))
-        throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
-                         % widen<u32,u8>(v)
-                         % widen<u32,u8>(ver));
-      break;
-    // these must match exactly what's expected
     case static_cast<u8>(error_cmd):
     case static_cast<u8>(bye_cmd):
     case static_cast<u8>(confirm_cmd):
@@ -147,16 +138,11 @@ netcmd::read(string & inbuf)
     case static_cast<u8>(data_cmd):
     case static_cast<u8>(delta_cmd):
     case static_cast<u8>(nonexistant_cmd):
-      if (ver != version)
-        throw bad_decode(F("protocol version mismatch: wanted '%d' got '%d'")
-                         % widen<u32,u8>(v)
-                         % widen<u32,u8>(ver));
+      cmd_code = static_cast<netcmd_code>(cmd_byte);
       break;
     default:
       throw bad_decode(F("unknown netcmd code 0x%x") % widen<u32,u8>(cmd_byte));
     }
-  cmd_code = static_cast<netcmd_code>(cmd_byte);
-  version = ver;

   // check to see if we have even enough bytes for a complete uleb128
   size_t old_pos = pos;
@@ -170,9 +156,9 @@ netcmd::read(string & inbuf)
     throw bad_decode(F("oversized payload of '%d' bytes") % payload_len);

   // there might not be enough data yet in the input buffer
-  if (inbuf.size() < pos + payload_len + sizeof(u32))
+  if (inbuf.size() < pos + payload_len + CryptoPP::SHA::DIGESTSIZE)
     {
-      inbuf.reserve(pos + payload_len + sizeof(u32) + constants::bufsz);
+      inbuf.reserve(pos + payload_len + CryptoPP::SHA::DIGESTSIZE + constants::bufsz);
       return false;
     }

@@ -192,10 +178,25 @@ netcmd::read(string & inbuf)
   length_len = 0;

   // they might have given us bogus data
-  adler32 check(reinterpret_cast<u8 const *>(payload.data()),
-                payload.size());
-  if (checksum != check.sum())
-    throw bad_decode(F("bad checksum 0x%x vs. 0x%x") % checksum % check.sum());
+  string cmd_digest = extract_substring(inbuf, pos, CryptoPP::SHA::DIGESTSIZE,
+                                        "netcmd HMAC");
+  I(key().size() == CryptoPP::SHA::DIGESTSIZE);
+  I(key().size() == hmac_val().size());
+  byte keybuf[CryptoPP::SHA::DIGESTSIZE];
+  for (size_t i = 0; i < sizeof(keybuf); i++)
+    {
+      keybuf[i] = key()[i] ^ hmac_val()[i];
+    }
+  CryptoPP::HMAC<CryptoPP::SHA> hmac(keybuf, sizeof(keybuf));
+  char digest_buf[CryptoPP::SHA::DIGESTSIZE];
+  hmac.CalculateDigest(reinterpret_cast<byte *>(digest_buf),
+                       reinterpret_cast<const byte *>(payload.data()),
+                       payload.size());
+  string digest(digest_buf, sizeof(digest_buf));
+  if (cmd_digest != digest)
+    throw bad_decode(F("bad HMAC %s vs. %s") % encode_hexenc(cmd_digest)
+                     % encode_hexenc(digest));
+  hmac_val = netsync_hmac_value(digest);

   return true;
 }
@@ -256,37 +257,37 @@ netcmd::write_hello_cmd(rsa_keypair_id c
 }


-void
-netcmd::read_anonymous_cmd(protocol_role & role,
+void
+netcmd::read_anonymous_cmd(protocol_role & role,
                            std::string & pattern,
-                           id & nonce2) const
+                           rsa_oaep_sha_data & hmac_key_encrypted) const
 {
   size_t pos = 0;
-  // syntax is: <role:1 byte> <pattern: vstr> <nonce2: 20 random bytes>
-  u8 role_byte = extract_datum_lsb<u8>(payload, pos, "anonymous netcmd, role");
+  // syntax is: <role:1 byte> <pattern: vstr> <hmac_key_encrypted: vstr>
+  u8 role_byte = extract_datum_lsb<u8>(payload, pos, "anonymous(hmac) netcmd, role");
   if (role_byte != static_cast<u8>(source_role)
       && role_byte != static_cast<u8>(sink_role)
       && role_byte != static_cast<u8>(source_and_sink_role))
     throw bad_decode(F("unknown role specifier %d") % widen<u32,u8>(role_byte));
   role = static_cast<protocol_role>(role_byte);
   extract_variable_length_string(payload, pattern, pos,
-                                 "anonymous netcmd, pattern");
-  nonce2 = id(extract_substring(payload, pos,
-                                constants::merkle_hash_length_in_bytes,
-                                "anonymous netcmd, nonce2"));
-  assert_end_of_buffer(payload, pos, "anonymous netcmd payload");
+                                 "anonymous(hmac) netcmd, pattern");
+  string hmac_key_string;
+  extract_variable_length_string(payload, hmac_key_string, pos,
+                                 "anonymous(hmac) netcmd, hmac_key_encrypted");
+  hmac_key_encrypted = rsa_oaep_sha_data(hmac_key_string);
+  assert_end_of_buffer(payload, pos, "anonymous(hmac) netcmd payload");
 }

-void
-netcmd::write_anonymous_cmd(protocol_role role,
+void
+netcmd::write_anonymous_cmd(protocol_role role,
                             std::string const & pattern,
-                            id const & nonce2)
+                            rsa_oaep_sha_data const & hmac_key_encrypted)
 {
   cmd_code = anonymous_cmd;
-  I(nonce2().size() == constants::merkle_hash_length_in_bytes);
   payload = static_cast<char>(role);
   insert_variable_length_string(pattern, payload);
-  payload += nonce2();
+  insert_variable_length_string(hmac_key_encrypted(), payload);
 }

 void
@@ -294,74 +295,68 @@ netcmd::read_auth_cmd(protocol_role & ro
                       string & pattern,
                       id & client,
                       id & nonce1,
-                      id & nonce2,
+                      rsa_oaep_sha_data & hmac_key_encrypted,
                       string & signature) const
 {
   size_t pos = 0;
   // syntax is: <role:1 byte> <pattern: vstr>
-  //            <client: 20 bytes sha1> <nonce1: 20 random bytes> <nonce2: 20 random bytes>
-  //            <signature: vstr>
+  //            <client: 20 bytes sha1> <nonce1: 20 random bytes>
+  //            <hmac_key_encrypted: vstr> <signature: vstr>
   u8 role_byte = extract_datum_lsb<u8>(payload, pos, "auth netcmd, role");
   if (role_byte != static_cast<u8>(source_role)
       && role_byte != static_cast<u8>(sink_role)
       && role_byte != static_cast<u8>(source_and_sink_role))
     throw bad_decode(F("unknown role specifier %d") % widen<u32,u8>(role_byte));
   role = static_cast<protocol_role>(role_byte);
-  extract_variable_length_string(payload, pattern, pos, "auth netcmd, pattern");
+  extract_variable_length_string(payload, pattern, pos, "auth(hmac) netcmd, pattern");
   client = id(extract_substring(payload, pos,
                                 constants::merkle_hash_length_in_bytes,
-                                "auth netcmd, client identifier"));
+                                "auth(hmac) netcmd, client identifier"));
   nonce1 = id(extract_substring(payload, pos,
                                 constants::merkle_hash_length_in_bytes,
-                                "auth netcmd, nonce1"));
-  nonce2 = id(extract_substring(payload, pos,
-                                constants::merkle_hash_length_in_bytes,
-                                "auth netcmd, nonce2"));
+                                "auth(hmac) netcmd, nonce1"));
+  string hmac_key;
+  extract_variable_length_string(payload, hmac_key, pos,
+                                 "auth(hmac) netcmd, hmac_key_encrypted");
+  hmac_key_encrypted = rsa_oaep_sha_data(hmac_key);
   extract_variable_length_string(payload, signature, pos,
-                                 "auth netcmd, signature");
-  assert_end_of_buffer(payload, pos, "auth netcmd payload");
+                                 "auth(hmac) netcmd, signature");
+  assert_end_of_buffer(payload, pos, "auth(hmac) netcmd payload");
 }

-void
-netcmd::write_auth_cmd(protocol_role role,
-                       string const & pattern,
+void
+netcmd::write_auth_cmd(protocol_role role,
+                       string const & pattern,
                        id const & client,
-                       id const & nonce1,
-                       id const & nonce2,
+                       id const & nonce1,
+                       rsa_oaep_sha_data const & hmac_key_encrypted,
                        string const & signature)
 {
   cmd_code = auth_cmd;
   I(client().size() == constants::merkle_hash_length_in_bytes);
   I(nonce1().size() == constants::merkle_hash_length_in_bytes);
-  I(nonce2().size() == constants::merkle_hash_length_in_bytes);
   payload = static_cast<char>(role);
   insert_variable_length_string(pattern, payload);
   payload += client();
   payload += nonce1();
-  payload += nonce2();
+  insert_variable_length_string(hmac_key_encrypted(), payload);
   insert_variable_length_string(signature, payload);
 }
-

-void
-netcmd::read_confirm_cmd(string & signature) const
+void
+netcmd::read_confirm_cmd() const
 {
   size_t pos = 0;
-
-  // syntax is: <signature: vstr>
-  extract_variable_length_string(payload, signature, pos,
-                                 "confirm netcmd, signature");
   assert_end_of_buffer(payload, pos, "confirm netcmd payload");
 }

-void
-netcmd::write_confirm_cmd(string const & signature)
+void
+netcmd::write_confirm_cmd()
 {
   cmd_code = confirm_cmd;
   payload.clear();
-  insert_variable_length_string(signature, payload);
 }
-
+
 void
 netcmd::read_refine_cmd(merkle_node & node) const
 {
@@ -577,6 +572,65 @@ netcmd::write_nonexistant_cmd(netcmd_ite
 #include "transforms.hh"
 #include <boost/lexical_cast.hpp>

+void
+test_netcmd_mac()
+{
+  netcmd out_cmd, in_cmd;
+  string buf;
+  netsync_session_key key(constants::netsync_key_initializer);
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    // mutates mac
+    out_cmd.write(buf, key, mac);
+    BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+  }
+
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    out_cmd.write(buf, key, mac);
+  }
+  buf[0] ^= 0xff;
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+  }
+
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    out_cmd.write(buf, key, mac);
+  }
+  buf[buf.size() - 1] ^= 0xff;
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+  }
+
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    out_cmd.write(buf, key, mac);
+  }
+  buf += '\0';
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    BOOST_CHECK_THROW(in_cmd.read(buf, key, mac), bad_decode);
+  }
+}
+
+static void
+do_netcmd_roundtrip(netcmd const & out_cmd, netcmd & in_cmd, string & buf)
+{
+  netsync_session_key key(constants::netsync_key_initializer);
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    out_cmd.write(buf, key, mac);
+  }
+  {
+    netsync_hmac_value mac(constants::netsync_key_initializer);
+    BOOST_CHECK(in_cmd.read(buf, key, mac));
+  }
+  BOOST_CHECK(in_cmd == out_cmd);
+}
+
 void
 test_netcmd_functions()
 {
@@ -591,10 +645,8 @@ test_netcmd_functions()
         string out_errmsg("your shoelaces are untied"), in_errmsg;
         string buf;
         out_cmd.write_error_cmd(out_errmsg);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_error_cmd(in_errmsg);
-        BOOST_CHECK(in_cmd == out_cmd);
         BOOST_CHECK(in_errmsg == out_errmsg);
         L(F("errmsg_cmd test done, buffer was %d bytes\n") % buf.size());
       }
@@ -604,9 +656,7 @@ test_netcmd_functions()
         L(F("checking i/o round trip on bye_cmd\n"));
         netcmd out_cmd, in_cmd;
         string buf;
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
-        BOOST_CHECK(in_cmd == out_cmd);
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         L(F("bye_cmd test done, buffer was %d bytes\n") % buf.size());
       }

@@ -619,10 +669,8 @@ test_netcmd_functions()
         rsa_pub_key out_server_key("9387938749238792874"), in_server_key;
         id out_nonce(raw_sha1("nonce it up")), in_nonce;
         out_cmd.write_hello_cmd(out_server_keyname, out_server_key, out_nonce);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_hello_cmd(in_server_keyname, in_server_key, in_nonce);
-        BOOST_CHECK(in_cmd == out_cmd);
         BOOST_CHECK(in_server_keyname == out_server_keyname);
         BOOST_CHECK(in_server_key == out_server_key);
         BOOST_CHECK(in_nonce == out_nonce);
@@ -635,15 +683,15 @@ test_netcmd_functions()
         netcmd out_cmd, in_cmd;
         protocol_role out_role = source_and_sink_role, in_role;
         string buf;
-        id out_nonce2(raw_sha1("nonce start my heart")), in_nonce2;
+        // 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;

-        out_cmd.write_anonymous_cmd(out_role, out_pattern, out_nonce2);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
-        in_cmd.read_anonymous_cmd(in_role, in_pattern, in_nonce2);
-        BOOST_CHECK(in_cmd == out_cmd);
-        BOOST_CHECK(in_nonce2 == out_nonce2);
+        out_cmd.write_anonymous_cmd(out_role, out_pattern, out_key);
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
+        in_cmd.read_anonymous_cmd(in_role, in_pattern, in_key);
+        BOOST_CHECK(in_key == out_key);
         BOOST_CHECK(in_role == out_role);
         L(F("anonymous_cmd test done, buffer was %d bytes\n") % buf.size());
       }
@@ -655,21 +703,21 @@ test_netcmd_functions()
         protocol_role out_role = source_and_sink_role, in_role;
         string buf;
         id out_client(raw_sha1("happy client day")), out_nonce1(raw_sha1("nonce me amadeus")),
-          out_nonce2(raw_sha1("nonce start my heart")),
-          in_client, in_nonce1, in_nonce2;
+          in_client, in_nonce1;
+        // 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;

         out_cmd.write_auth_cmd(out_role, out_pattern, out_client, out_nonce1,
-                               out_nonce2, out_signature);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+                               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_nonce2, in_signature);
-        BOOST_CHECK(in_cmd == out_cmd);
+                             in_nonce1, in_key, in_signature);
         BOOST_CHECK(in_client == out_client);
         BOOST_CHECK(in_nonce1 == out_nonce1);
-        BOOST_CHECK(in_nonce2 == out_nonce2);
+        BOOST_CHECK(in_key == out_key);
         BOOST_CHECK(in_signature == out_signature);
         BOOST_CHECK(in_role == out_role);
         L(F("auth_cmd test done, buffer was %d bytes\n") % buf.size());
@@ -680,14 +728,9 @@ test_netcmd_functions()
         L(F("checking i/o round trip on confirm_cmd\n"));
         netcmd out_cmd, in_cmd;
         string buf;
-        string out_signature(raw_sha1("egg") + raw_sha1("tomago")), in_signature;
-
-        out_cmd.write_confirm_cmd(out_signature);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
-        in_cmd.read_confirm_cmd(in_signature);
-        BOOST_CHECK(in_cmd == out_cmd);
-        BOOST_CHECK(in_signature == out_signature);
+        out_cmd.write_confirm_cmd();
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
+        in_cmd.read_confirm_cmd();
         L(F("confirm_cmd test done, buffer was %d bytes\n") % buf.size());
       }

@@ -708,10 +751,8 @@ test_netcmd_functions()
         out_node.set_slot_state(15, subtree_state);

         out_cmd.write_refine_cmd(out_node);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_refine_cmd(in_node);
-        BOOST_CHECK(in_cmd == out_cmd);
         BOOST_CHECK(in_node == out_node);
         L(F("refine_cmd test done, buffer was %d bytes\n") % buf.size());
       }
@@ -725,8 +766,7 @@ test_netcmd_functions()
         string buf;

         out_cmd.write_done_cmd(out_level, out_type);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_done_cmd(in_level, in_type);
         BOOST_CHECK(in_level == out_level);
         BOOST_CHECK(in_type == out_type);
@@ -742,8 +782,7 @@ test_netcmd_functions()
         string buf;

         out_cmd.write_send_data_cmd(out_type, out_id);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_send_data_cmd(in_type, in_id);
         BOOST_CHECK(in_type == out_type);
         BOOST_CHECK(in_id == out_id);
@@ -760,8 +799,7 @@ test_netcmd_functions()
         string buf;

         out_cmd.write_send_delta_cmd(out_type, out_head, out_base);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_send_delta_cmd(in_type, in_head, in_base);
         BOOST_CHECK(in_type == out_type);
         BOOST_CHECK(in_head == out_head);
@@ -778,8 +816,7 @@ test_netcmd_functions()
         string out_dat("thank you for flying northwest"), in_dat;
         string buf;
         out_cmd.write_data_cmd(out_type, out_id, out_dat);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_data_cmd(in_type, in_id, in_dat);
         BOOST_CHECK(in_id == out_id);
         BOOST_CHECK(in_dat == out_dat);
@@ -797,8 +834,7 @@ test_netcmd_functions()
         string buf;

         out_cmd.write_delta_cmd(out_type, out_head, out_base, out_delta);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_delta_cmd(in_type, in_head, in_base, in_delta);
         BOOST_CHECK(in_type == out_type);
         BOOST_CHECK(in_head == out_head);
@@ -816,8 +852,7 @@ test_netcmd_functions()
         string buf;

         out_cmd.write_nonexistant_cmd(out_type, out_id);
-        out_cmd.write(buf);
-        BOOST_CHECK(in_cmd.read(buf));
+        do_netcmd_roundtrip(out_cmd, in_cmd, buf);
         in_cmd.read_nonexistant_cmd(in_type, in_id);
         BOOST_CHECK(in_type == out_type);
         BOOST_CHECK(in_id == out_id);
@@ -836,6 +871,7 @@ add_netcmd_tests(test_suite * suite)
 add_netcmd_tests(test_suite * suite)
 {
   suite->add(BOOST_TEST_CASE(&test_netcmd_functions));
+  suite->add(BOOST_TEST_CASE(&test_netcmd_mac));
 }

 #endif // BUILD_UNIT_TESTS
============================================================
--- netcmd.hh	901532e162e97d3df8f89ccc1198203eef8ed8fe
+++ netcmd.hh	b010800c5f7c9cb23ac8f0ac069b7a4fbdd17774
@@ -65,8 +65,11 @@ public:


   // basic cmd i/o (including checksums)
-  void write(std::string & out) const;
-  bool read(std::string & inbuf);
+  void write(std::string & out,
+             netsync_session_key const & key,
+             netsync_hmac_value & hmac_val) const;
+  bool read(std::string & inbuf,
+            netsync_session_key const & key, netsync_hmac_value & hmac_val);

   // i/o functions for each type of command payload
   void read_error_cmd(std::string & errmsg) const;
@@ -82,28 +85,28 @@ public:
                        rsa_pub_key const & server_key,
                        id const & nonce);

-  void read_anonymous_cmd(protocol_role & role,
+  void read_anonymous_cmd(protocol_role & role,
                           std::string & pattern,
-                          id & nonce2) const;
+                          rsa_oaep_sha_data & hmac_key_encrypted) const;
   void write_anonymous_cmd(protocol_role role,
                            std::string const & pattern,
-                           id const & nonce2);
+                           rsa_oaep_sha_data const & hmac_key_encrypted);

   void read_auth_cmd(protocol_role & role,
                      std::string & pattern,
                      id & client,
                      id & nonce1,
-                     id & nonce2,
+                     rsa_oaep_sha_data & hmac_key_encrypted,
                      std::string & signature) const;
   void write_auth_cmd(protocol_role role,
                       std::string const & pattern,
                       id const & client,
                       id const & nonce1,
-                      id const & nonce2,
+                      rsa_oaep_sha_data const & hmac_key_encrypted,
                       std::string const & signature);

-  void read_confirm_cmd(std::string & signature) const;
-  void write_confirm_cmd(std::string const & signature);
+  void read_confirm_cmd() const;
+  void write_confirm_cmd();

   void read_refine_cmd(merkle_node & node) const;
   void write_refine_cmd(merkle_node const & node);
============================================================
--- netsync.cc	218fabbe4a06f50089a50833c72f9c1b4ed78a91
+++ netsync.cc	cba71cd069c2658e79568849fe0328c92695547d
@@ -112,18 +112,34 @@
 // protocol
 // --------
 //
+// The protocol is a simple binary command-packet system over TCP;
+// each packet consists of a single byte which identifies the protocol
+// version, a byte which identifies the command name inside that
+// version, a size_t sent as a uleb128 indicating the length of the
+// packet, that many bytes of payload, and finally 20 bytes of SHA-1
+// HMAC calculated over the payload.  The key for the SHA-1 HMAC is 20
+// bytes of 0 during authentication, and a 20-byte random key chosen
+// by the client after authentication (discussed below).
+//
+//---- Pre-v5 packet format ----
+//
 // the protocol is a simple binary command-packet system over tcp; each
 // packet consists of a byte which identifies the protocol version, a byte
 // which identifies the command name inside that version, a size_t sent as
 // a uleb128 indicating the length of the packet, and then that many bytes
 // of payload, and finally 4 bytes of adler32 checksum (in LSB order) over
-// the payload. decoding involves simply buffering until a sufficient
-// number of bytes are received, then advancing the buffer pointer. any
-// time an adler32 check fails, the protocol is assumed to have lost
-// synchronization, and the connection is dropped. the parties are free to
-// drop the tcp stream at any point, if too much data is received or too
-// much idle time passes; no commitments or transactions are made.
+// the payload.
 //
+// ---- end pre-v5 packet format ----
+//
+// decoding involves simply buffering until a sufficient number of
+// bytes are received, then advancing the buffer pointer. any time an
+// integrity check (adler32 for pre-v5, HMAC for post-v5) fails, the
+// protocol is assumed to have lost synchronization, and the
+// connection is dropped. the parties are free to drop the tcp stream
+// at any point, if too much data is received or too much idle time
+// passes; no commitments or transactions are made.
+//
 // one special command, "bye", is used to shut down a connection
 // gracefully.  once each side has received all the data they want, they
 // can send a "bye" command to the other side. as soon as either side has
@@ -135,6 +151,31 @@
 // "hello <id> <nonce>" command, which identifies the server's RSA key and
 // issues a nonce which must be used for a subsequent authentication.
 //
+// The client then responds with either:
+//
+// An "auth (source|sink|both) <pattern> <id> <nonce1> <hmac key>
+// <sig>" 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) <pattern> <hmac key>" 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
+// the server received and properly decrypted the HMAC key offered by
+// 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 <id> <nonce>" 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)
 // <pattern> <id> <nonce1> <nonce2> <sig>" command which identifies its
 // RSA key, notes the role it wishes to play in the synchronization,
@@ -146,6 +187,8 @@
 // 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
@@ -221,7 +264,6 @@ session
   string outbuf;

   netcmd cmd;
-  u8 protocol_version;
   bool armed;
   bool arm();

@@ -229,6 +271,9 @@ session
   boost::regex pattern_re;
   id remote_peer_key_hash;
   rsa_keypair_id remote_peer_key_name;
+  netsync_session_key session_key;
+  netsync_hmac_value read_hmac;
+  netsync_hmac_value write_hmac;
   bool authenticated;

   time_t last_io_time;
@@ -321,14 +366,16 @@ session
                        id const & nonce);
   void queue_anonymous_cmd(protocol_role role,
                            string const & pattern,
-                           id const & nonce2);
+                           id const & nonce2,
+                           base64<rsa_pub_key> server_key_encoded);
   void queue_auth_cmd(protocol_role role,
                       string const & pattern,
                       id const & client,
                       id const & nonce1,
                       id const & nonce2,
-                      string const & signature);
-  void queue_confirm_cmd(string const & signature);
+                      string const & signature,
+                      base64<rsa_pub_key> server_key_encoded);
+  void queue_confirm_cmd();
   void queue_refine_cmd(merkle_node const & node);
   void queue_send_data_cmd(netcmd_item_type type,
                            id const & item);
@@ -352,15 +399,15 @@ session
                          rsa_pub_key const & server_key,
                          id const & nonce);
   bool process_anonymous_cmd(protocol_role role,
-                             string const & pattern,
-                             id const & nonce2);
+                             string const & pattern);
   bool process_auth_cmd(protocol_role role,
                         string const & pattern,
                         id const & client,
                         id const & nonce1,
-                        id const & nonce2,
                         string const & signature);
+  void respond_to_auth_cmd(rsa_oaep_sha_data hmac_key_encrypted);
   bool process_confirm_cmd(string const & signature);
+  void respond_to_confirm_cmd();
   bool process_refine_cmd(merkle_node const & node);
   bool process_send_data_cmd(netcmd_item_type type,
                              id const & item);
@@ -433,12 +480,14 @@ session::session(protocol_role role,
   str(sock, to),
   inbuf(""),
   outbuf(""),
-  protocol_version(constants::netcmd_current_protocol_version),
   armed(false),
   pattern(""),
   pattern_re(".*"),
   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),
   authenticated(false),
   last_io_time(::time(NULL)),
   byte_in_ticker(NULL),
@@ -729,7 +778,7 @@ session::write_netcmd_and_try_flush(netc
 session::write_netcmd_and_try_flush(netcmd const & cmd)
 {
   if (!encountered_error)
-    cmd.write(outbuf);
+    cmd.write(outbuf, session_key, write_hmac);
   else
     L(F("dropping outgoing netcmd (because we're in error unwind mode)\n"));
   // FIXME: this helps keep the protocol pipeline full but it seems to
@@ -1320,7 +1369,7 @@ session::queue_bye_cmd()
 session::queue_bye_cmd()
 {
   L(F("queueing 'bye' command\n"));
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_bye_cmd();
   write_netcmd_and_try_flush(cmd);
   this->sent_goodbye = true;
@@ -1330,7 +1379,7 @@ session::queue_error_cmd(string const &
 session::queue_error_cmd(string const & errmsg)
 {
   L(F("queueing 'error' command\n"));
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_error_cmd(errmsg);
   write_netcmd_and_try_flush(cmd);
   this->sent_goodbye = true;
@@ -1343,7 +1392,7 @@ session::queue_done_cmd(size_t level,
   string typestr;
   netcmd_item_type_to_string(type, typestr);
   L(F("queueing 'done' command for %s level %s\n") % typestr % level);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_done_cmd(level, type);
   write_netcmd_and_try_flush(cmd);
 }
@@ -1352,7 +1401,7 @@ session::queue_hello_cmd(id const & serv
 session::queue_hello_cmd(id const & server,
                          id const & nonce)
 {
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   hexenc<id> server_encoded;
   encode_hexenc(server, server_encoded);

@@ -1369,11 +1418,16 @@ session::queue_anonymous_cmd(protocol_ro
 void
 session::queue_anonymous_cmd(protocol_role role,
                              string const & pattern,
-                             id const & nonce2)
+                             id const & nonce2,
+                             base64<rsa_pub_key> server_key_encoded)
 {
-  netcmd cmd(protocol_version);
-  cmd.write_anonymous_cmd(role, pattern, nonce2);
+  netcmd cmd;
+  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);
   write_netcmd_and_try_flush(cmd);
+  session_key = netsync_session_key(nonce2());
 }

 void
@@ -1382,18 +1436,23 @@ session::queue_auth_cmd(protocol_role ro
                         id const & client,
                         id const & nonce1,
                         id const & nonce2,
-                        string const & signature)
+                        string const & signature,
+                        base64<rsa_pub_key> server_key_encoded)
 {
-  netcmd cmd(protocol_version);
-  cmd.write_auth_cmd(role, pattern, client, nonce1, nonce2, signature);
+  netcmd cmd;
+  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);
   write_netcmd_and_try_flush(cmd);
+  session_key = netsync_session_key(nonce2());
 }

-void
-session::queue_confirm_cmd(string const & signature)
+void
+session::queue_confirm_cmd()
 {
-  netcmd cmd(protocol_version);
-  cmd.write_confirm_cmd(signature);
+  netcmd cmd;
+  cmd.write_confirm_cmd();
   write_netcmd_and_try_flush(cmd);
 }

@@ -1406,7 +1465,7 @@ session::queue_refine_cmd(merkle_node co
   netcmd_item_type_to_string(node.type, typestr);
   L(F("queueing request for refinement of %s node '%s', level %d\n")
     % typestr % hpref % static_cast<int>(node.level));
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_refine_cmd(node);
   write_netcmd_and_try_flush(cmd);
 }
@@ -1436,7 +1495,7 @@ session::queue_send_data_cmd(netcmd_item

   L(F("queueing request for data of %s item '%s'\n")
     % typestr % hid);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_send_data_cmd(type, item);
   write_netcmd_and_try_flush(cmd);
   note_item_requested(type, item);
@@ -1472,7 +1531,7 @@ session::queue_send_delta_cmd(netcmd_ite

   L(F("queueing request for contents of %s delta '%s' -> '%s'\n")
     % typestr % base_hid % ident_hid);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_send_delta_cmd(type, base, ident);
   write_netcmd_and_try_flush(cmd);
   note_item_requested(type, ident);
@@ -1497,7 +1556,7 @@ session::queue_data_cmd(netcmd_item_type

   L(F("queueing %d bytes of data for %s item '%s'\n")
     % dat.size() % typestr % hid);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_data_cmd(type, item, dat);
   write_netcmd_and_try_flush(cmd);
   note_item_sent(type, item);
@@ -1527,7 +1586,7 @@ session::queue_delta_cmd(netcmd_item_typ

   L(F("queueing %s delta '%s' -> '%s'\n")
     % typestr % base_hid % ident_hid);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_delta_cmd(type, base, ident, del);
   write_netcmd_and_try_flush(cmd);
   note_item_sent(type, ident);
@@ -1550,7 +1609,7 @@ session::queue_nonexistant_cmd(netcmd_it

   L(F("queueing note of nonexistance of %s item '%s'\n")
     % typestr % hid);
-  netcmd cmd(protocol_version);
+  netcmd cmd;
   cmd.write_nonexistant_cmd(type, item);
   write_netcmd_and_try_flush(cmd);
 }
@@ -1636,22 +1695,6 @@ get_branches(app_state & app, vector<str
     W(F("No branches found."));
 }

-void
-convert_pattern(utf8 & pat, utf8 & conv)
-{
-  string x = pat();
-  string pattern = "";
-  string e = ".|*?+()[]{}^$\\";
-  for (string::const_iterator i = x.begin(); i != x.end(); i++)
-    {
-      if (e.find(*i) != e.npos)
-        pattern += '\\';
-      pattern += *i;
-    }
-  conv = pattern + ".*";
-}
-
-
 static const var_domain known_servers_domain = var_domain("known-servers");

 bool
@@ -1716,13 +1759,6 @@ session::process_hello_cmd(rsa_keypair_i
   }

   utf8 pat(pattern);
-  if (protocol_version < 5)
-    {
-      W(F("Talking to an old server. "
-          "Using %s as a collection, not a regex.") % pattern);
-      convert_pattern(pattern, pat);
-      this->pattern_re = boost::regex(pat());
-    }
   vector<string> branchnames;
   set<utf8> ok_branches;
   get_branches(app, branchnames);
@@ -1756,11 +1792,12 @@ session::process_hello_cmd(rsa_keypair_i

       // 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());
+                     nonce, mk_nonce(), sig_raw(), their_key_encoded);
     }
   else
     {
-      queue_anonymous_cmd(this->role, this->pattern(), mk_nonce());
+      queue_anonymous_cmd(this->role, this->pattern(),
+                          mk_nonce(), their_key_encoded);
     }
   return true;
 }
@@ -1778,18 +1815,8 @@ session::process_anonymous_cmd(protocol_

 bool
 session::process_anonymous_cmd(protocol_role role,
-                               string const & pattern,
-                               id const & nonce2)
+                               string const & pattern)
 {
-  hexenc<id> hnonce2;
-  encode_hexenc(nonce2, hnonce2);
-
-  L(F("received 'anonymous' netcmd from client for pattern '%s' "
-      "in %s mode with nonce2 '%s'\n")
-    %  pattern % (role == source_and_sink_role ? "source and sink" :
-                     (role == source_role ? "source " : "sink"))
-    % hnonce2);
-
   //
   // internally netsync thinks in terms of sources and sinks. users like
   // thinking of repositories as "readonly", "readwrite", or "writeonly".
@@ -1850,15 +1877,6 @@ session::process_anonymous_cmd(protocol_

   rebuild_merkle_trees(app, ok_branches);

-  // get our private key and sign back
-  L(F("anonymous read permitted, signing back nonce\n"));
-  base64<rsa_sha1_signature> sig;
-  rsa_sha1_signature sig_raw;
-  base64< arc4<rsa_priv_key> > our_priv;
-  load_priv_key(app, app.signing_key, our_priv);
-  make_signature(app.lua, app.signing_key, our_priv, nonce2(), sig);
-  decode_base64(sig, sig_raw);
-  queue_confirm_cmd(sig_raw());
   this->pattern = pattern;
   this->remote_peer_key_name = rsa_keypair_id("");
   this->authenticated = true;
@@ -1866,20 +1884,16 @@ session::process_anonymous_cmd(protocol_
   return true;
 }

-bool
-session::process_auth_cmd(protocol_role role,
-                          string const & pattern,
-                          id const & client,
-                          id const & nonce1,
-                          id const & nonce2,
+bool
+session::process_auth_cmd(protocol_role role,
+                          string const & pattern,
+                          id const & client,
+                          id const & nonce1,
                           string const & signature)
 {
   I(this->remote_peer_key_hash().size() == 0);
   I(this->saved_nonce().size() == constants::merkle_hash_length_in_bytes);

-  hexenc<id> hnonce1, hnonce2;
-  encode_hexenc(nonce1, hnonce1);
-  encode_hexenc(nonce2, hnonce2);
   hexenc<id> their_key_hash;
   encode_hexenc(client, their_key_hash);
   set<utf8> ok_branches;
@@ -1893,12 +1907,6 @@ session::process_auth_cmd(protocol_role
     }
   boost::regex reg(pattern);

-  L(F("received 'auth' netcmd from client '%s' for pattern '%s' "
-      "in %s mode with nonce1 '%s' and nonce2 '%s'\n")
-    % their_key_hash % pattern % (role == source_and_sink_role ? "source and sink" :
-                                     (role == source_role ? "source " : "sink"))
-    % hnonce1 % hnonce2);
-
   // check that they replied with the nonce we asked for
   if (!(nonce1 == this->saved_nonce))
     {
@@ -2004,13 +2012,6 @@ session::process_auth_cmd(protocol_role
     {
       // get our private key and sign back
       L(F("client signature OK, accepting authentication\n"));
-      base64<rsa_sha1_signature> sig;
-      rsa_sha1_signature sig_raw;
-      base64< arc4<rsa_priv_key> > our_priv;
-      load_priv_key(app, app.signing_key, our_priv);
-      make_signature(app.lua, app.signing_key, our_priv, nonce2(), sig);
-      decode_base64(sig, sig_raw);
-      queue_confirm_cmd(sig_raw());
       this->pattern = pattern;
       this->pattern_re = boost::regex(this->pattern());
       this->authenticated = true;
@@ -2039,6 +2040,18 @@ session::process_auth_cmd(protocol_role
   return false;
 }

+void
+session::respond_to_auth_cmd(rsa_oaep_sha_data hmac_key_encrypted)
+{
+  L(F("Writing HMAC confirm command"));
+  base64< arc4<rsa_priv_key> > our_priv;
+  load_priv_key(app, app.signing_key, our_priv);
+  string hmac_key;
+  decrypt_rsa(app.lua, app.signing_key, our_priv, hmac_key_encrypted, hmac_key);
+  session_key = netsync_session_key(hmac_key);
+  queue_confirm_cmd();
+}
+
 bool
 session::process_confirm_cmd(string const & signature)
 {
@@ -2065,20 +2078,6 @@ session::process_confirm_cmd(string cons
       if (check_signature(app.lua, their_id, their_key, this->saved_nonce(), sig))
         {
           L(F("server signature OK, accepting authentication\n"));
-          this->authenticated = true;
-
-          merkle_ptr root;
-          load_merkle_node(epoch_item, 0, get_root_prefix().val, root);
-          queue_refine_cmd(*root);
-          queue_done_cmd(0, epoch_item);
-
-          load_merkle_node(key_item, 0, get_root_prefix().val, root);
-          queue_refine_cmd(*root);
-          queue_done_cmd(0, key_item);
-
-          load_merkle_node(cert_item, 0, get_root_prefix().val, root);
-          queue_refine_cmd(*root);
-          queue_done_cmd(0, cert_item);
           return true;
         }
       else
@@ -2093,6 +2092,23 @@ session::process_confirm_cmd(string cons
   return false;
 }

+void
+session::respond_to_confirm_cmd()
+{
+  merkle_ptr root;
+  load_merkle_node(epoch_item, 0, get_root_prefix().val, root);
+  queue_refine_cmd(*root);
+  queue_done_cmd(0, epoch_item);
+
+  load_merkle_node(key_item, 0, get_root_prefix().val, root);
+  queue_refine_cmd(*root);
+  queue_done_cmd(0, key_item);
+
+  load_merkle_node(cert_item, 0, get_root_prefix().val, root);
+  queue_refine_cmd(*root);
+  queue_done_cmd(0, cert_item);
+}
+
 static bool
 data_exists(netcmd_item_type type,
             id const & item,
@@ -2985,8 +3001,6 @@ session::dispatch_payload(netcmd const &
         rsa_pub_key server_key;
         id nonce;
         cmd.read_hello_cmd(server_keyname, server_key, nonce);
-        if (cmd.get_version() < protocol_version)
-          protocol_version = cmd.get_version();
         return process_hello_cmd(server_keyname, server_key, nonce);
       }
       break;
@@ -3000,11 +3014,17 @@ session::dispatch_payload(netcmd const &
       {
         protocol_role role;
         string pattern;
-        id nonce2;
-        cmd.read_anonymous_cmd(role, pattern, nonce2);
-        if (cmd.get_version() < protocol_version)
-          protocol_version = cmd.get_version();
-        return process_anonymous_cmd(role, pattern, nonce2);
+        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' "
+            "in %s mode\n")
+          %  pattern % (role == source_and_sink_role ? "source and sink" :
+                        (role == source_role ? "source " : "sink")));
+
+        if (!process_anonymous_cmd(role, pattern))
+            return false;
+        respond_to_auth_cmd(hmac_key_encrypted);
+        return true;
       }
       break;

@@ -3015,11 +3035,25 @@ session::dispatch_payload(netcmd const &
         protocol_role role;
         string pattern, signature;
         id client, nonce1, nonce2;
-        cmd.read_auth_cmd(role, pattern, client, nonce1, nonce2, signature);
-        if (cmd.get_version() < protocol_version)
-          protocol_version = cmd.get_version();
-        return process_auth_cmd(role, pattern, client,
-                                nonce1, nonce2, signature);
+        rsa_oaep_sha_data hmac_key_encrypted;
+        cmd.read_auth_cmd(role, pattern, client, nonce1,
+                          hmac_key_encrypted, signature);
+
+        hexenc<id> their_key_hash;
+        encode_hexenc(client, their_key_hash);
+        hexenc<id> hnonce1;
+        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"))
+          % hnonce1);
+
+        if (!process_auth_cmd(role, pattern, client, nonce1, signature))
+            return false;
+        respond_to_auth_cmd(hmac_key_encrypted);
+        return true;
       }
       break;

@@ -3028,8 +3062,10 @@ session::dispatch_payload(netcmd const &
       require(voice == client_voice, "confirm netcmd received in client voice");
       {
         string signature;
-        cmd.read_confirm_cmd(signature);
-        return process_confirm_cmd(signature);
+        cmd.read_confirm_cmd();
+        this->authenticated = true;
+        respond_to_confirm_cmd();
+        return true;
       }
       break;

@@ -3151,7 +3187,7 @@ session::arm()
 {
   if (!armed)
     {
-      if (cmd.read(inbuf))
+      if (cmd.read(inbuf, session_key, read_hmac))
         {
 //          inbuf.erase(0, cmd.encoded_size());
           armed = true;
@@ -3179,7 +3215,7 @@ bool session::process()
     }
   catch (bad_decode & bd)
     {
-      W(F("caught bad_decode exception processing peer %s: '%s'\n") % peer_id % bd.what);
+      W(F("protocol error while processing peer %s: '%s'\n") % peer_id % bd.what);
       return false;
     }
 }
@@ -3212,7 +3248,7 @@ call_server(protocol_role role,
         }
       catch (bad_decode & bd)
         {
-          W(F("caught bad_decode exception decoding input from peer %s: '%s'\n")
+          W(F("protocol error while processing peer %s: '%s'\n")
             % sess.peer_id % bd.what);
           return;
         }
@@ -3239,7 +3275,7 @@ call_server(protocol_role role,
                 }
               catch (bad_decode & bd)
                 {
-                  W(F("caught bad_decode exception decoding input from peer %s: '%s'\n")
+                  W(F("protocol error while processing peer %s: '%s'\n")
                     % sess.peer_id % bd.what);
                   return;
                 }
@@ -3313,7 +3349,7 @@ arm_sessions_and_calculate_probe(Netxx::
         }
       catch (bad_decode & bd)
         {
-          W(F("caught bad_decode exception decoding input from peer %s: '%s', marking as bad\n")
+          W(F("protocol error while processing peer %s: '%s', marking as bad\n")
             % i->second->peer_id % bd.what);
           arm_failed.insert(i->first);
         }
@@ -3370,7 +3406,7 @@ handle_read_available(Netxx::socket_type
         }
       catch (bad_decode & bd)
         {
-          W(F("caught bad_decode exception decoding input from peer %s: '%s', disconnecting\n")
+          W(F("protocol error while processing peer %s: '%s', disconnecting\n")
             % sess->peer_id % bd.what);
           sessions.erase(fd);
           live_p = false;
@@ -3616,21 +3652,6 @@ session::rebuild_merkle_trees(app_state
   ticker certs_ticker("certs", "c", 256);
   ticker keys_ticker("keys", "k", 1);

-  // this code is wrong.  the way the logic _should_ work is:
-  //   -- start with all branches we want to include
-  //   -- for each such branch, find all branch certs for that branch
-  //   -- for each such cert, note down its revision
-  //      (or these two steps can be replaced with anything else that gives us
-  //      list of all revisions in the branch)
-  //   -- expand this set of revisions to include all of their ancestors
-  //   -- for each such revision, insert all of its certs into the cert table,
-  //      and note all of its branches and keys
-  //   -- for each such branch, insert its epoch into the epoch table, and for
-  //      each such key, insert its key into the key table.
-  // this somewhat convoluted approach is necessary to handle cases where
-  // ancestors leave the branch inclusion set, where revisions carry branches
-  // that are otherwise outside of the inclusion set, etc.
-
   set<revision_id> revision_ids;
   set<rsa_keypair_id> inserted_keys;

@@ -3649,6 +3670,9 @@ session::rebuild_merkle_trees(app_state
           }
       }

+    // FIXME: we should probably include epochs for all branches mentioned in
+    // any included branch cert, rather than just for branches included by the
+    // branch mask
     {
       map<cert_value, epoch_data> epochs;
       app.db.get_epochs(epochs);
============================================================
--- std_hooks.lua	c06a28443baa42ca4d4f349a6dd9653d25fa8c0f
+++ std_hooks.lua	9b1a27e30129929aa366223e51f1c11ec918fc93
@@ -86,6 +86,11 @@ function ignore_file(name)
    if (string.find(name, "/autom4te.cache/")) then return true end
    if (string.find(name, "^.deps/")) then return true end
    if (string.find(name, "/.deps/")) then return true end
+   -- Cons/SCons detritus:
+   if (string.find(name, "^.consign$")) then return true end
+   if (string.find(name, "/.consign$")) then return true end
+   if (string.find(name, "^.sconsign$")) then return true end
+   if (string.find(name, "/.sconsign$")) then return true end
    -- other VCSes:
    if (string.find(name, "^CVS/")) then return true end
    if (string.find(name, "/CVS/")) then return true end
============================================================
--- tests/t_cat_file_by_name.at	009033538c4424ad6994c33eef9dab24bef15980
+++ tests/t_cat_file_by_name.at	45f9704b56b2b74f16c5579539bc1c6f777b1e56
@@ -7,10 +7,14 @@ AT_DATA(r1testfile, [r1 test file
 ])
 AT_DATA(r1testfile, [r1 test file
 ])
+AT_DATA(subfile, [data in subfile
+])

 AT_CHECK(cp r0testfile testfile)
 AT_CHECK(cp r0otherfile otherfile)
-AT_CHECK(MONOTONE add testfile otherfile, [], [ignore], [ignore])
+AT_CHECK(mkdir subdir)
+AT_CHECK(cp subfile subdir/testfile)
+AT_CHECK(MONOTONE add testfile otherfile subdir/testfile, [], [ignore], [ignore])
 COMMIT(testbranch)
 R0=`BASE_REVISION`

@@ -30,6 +34,12 @@ AT_CHECK(cmp stdout r1testfile, [], [ign
 AT_CHECK(CANONICALISE(stdout))
 AT_CHECK(cmp stdout r1testfile, [], [ignore])

+CHECK_SAME_CANONICALISED_STDOUT(cd subdir && MONOTONE cat file $R0 testfile, cat subfile)
+
+AT_CHECK(rm -rf MT)
+
+CHECK_SAME_CANONICALISED_STDOUT(MONOTONE cat file $R0 testfile, cat r0testfile)
+
 AT_CHECK(MONOTONE cat file $R0 no_such_file, [1], [ignore], [ignore])
 AT_CHECK(MONOTONE cat file $R0 "", [1], [ignore], [ignore])

============================================================
--- tests/t_netsync_diffbranch.at	a529b5b568039685d66db00c8550b817fbc3a4a4
+++ tests/t_netsync_diffbranch.at	6a2a4e9b6dada24dea2ad88bd0c3d0caf8d180bc
@@ -1,4 +1,4 @@
-AT_SETUP([(normal) pull a netsync branch which has a parent from another branch])
+AT_SETUP([pull a netsync branch which has a parent from another branch])
 AT_KEYWORDS([netsync])


============================================================
--- testsuite.at	2635480ab5d48f5eae96909a5e59ef20a7b0c36d
+++ testsuite.at	2e17e62213b9878d08d7c792f244b6e193498618
@@ -659,4 +659,5 @@ m4_include(tests/t_status.at)
 m4_include(tests/t_merge_manual.at)
 m4_include(tests/t_revert_restrict.at)
 m4_include(tests/t_status.at)
+m4_include(tests/t_rcfile_dir.at)
+m4_include(tests/t_lua_includedir.at)
-
============================================================
--- vocab.cc	6e07baffa263c80f013d474ad362c3b52c162201
+++ vocab.cc	0a568b1d7a988e146671055888136fdff7818916
@@ -102,7 +102,43 @@ verify(rsa_keypair_id & val)
   val.ok = true;
 }

+inline void
+verify(netsync_session_key & val)
+{
+  if (val.ok)
+    return;

+  if (val().size() == 0)
+    {
+      val.s.append(constants::netsync_session_key_length_in_bytes, 0);
+      return;
+    }
+
+  N(val().size() == constants::netsync_session_key_length_in_bytes,
+    F("Invalid key length of %d bytes") % val().length());
+
+  val.ok = true;
+}
+
+inline void
+verify(netsync_hmac_value & val)
+{
+  if (val.ok)
+    return;
+
+  if (val().size() == 0)
+    {
+      val.s.append(constants::netsync_hmac_value_length_in_bytes, 0);
+      return;
+    }
+
+  N(val().size() == constants::netsync_hmac_value_length_in_bytes,
+    F("Invalid key length of %d bytes") % val().length());
+
+  val.ok = true;
+}
+
+
 inline void
 verify(local_path & val)
 {
============================================================
--- vocab_terms.hh	c12dd87ed775d3e2d8713a393cc055ce68e5ae16
+++ vocab_terms.hh	fe3bf7a7700bdde82211ba0be39caf248025e00b
@@ -34,7 +34,11 @@ ATOMIC_NOVERIFY(rsa_sha1_signature); //
 ATOMIC_NOVERIFY(rsa_pub_key);        // some nice numbers
 ATOMIC_NOVERIFY(rsa_priv_key);       // some nice numbers
 ATOMIC_NOVERIFY(rsa_sha1_signature); // some other nice numbers
+ATOMIC_NOVERIFY(rsa_oaep_sha_data);

+ATOMIC(netsync_session_key);  // key for netsync session HMAC
+ATOMIC(netsync_hmac_value);   // 160-bit SHA-1 HMAC
+
 DECORATE(revision);           // thing associated with a revision
 DECORATE(manifest);           // thing associated with a manifest
 DECORATE(file);               // thing associated with a file