#
#
# patch "Makefile.am"
# from [75cc211300b3ca382c67d8a6b4332120bbf75e96]
# to [fedc7408b6e6a7adbe30ef90698ad174679b7992]
#
# patch "mkstemp.cc"
# from [b5f55d45673507ffbb231f1f3630615b0940d69c]
# to [7f6d4d917c23c237bf9c38d98ecc916c76a8d1d9]
#
============================================================
--- Makefile.am 75cc211300b3ca382c67d8a6b4332120bbf75e96
+++ Makefile.am fedc7408b6e6a7adbe30ef90698ad174679b7992
@@ -128,7 +128,7 @@ UNIT_TEST_SOURCES = \
# these files contain unit tests
UNIT_TEST_SOURCES = \
basic_io.cc charset.cc commands.cc crypto_tests.cc cset.cc \
- dates.cc diff_patch.cc globish.cc graph.cc netcmd.cc \
+ dates.cc diff_patch.cc globish.cc graph.cc mkstemp.cc netcmd.cc \
netxx_pipe.cc numeric_vocab.cc option.cc outdated_indicator.cc \
packet.cc paths.cc refiner.cc restrictions.cc rev_height.cc \
revision.cc roster.cc roster_merge.cc simplestring_xform.cc \
============================================================
--- mkstemp.cc b5f55d45673507ffbb231f1f3630615b0940d69c
+++ mkstemp.cc 7f6d4d917c23c237bf9c38d98ecc916c76a8d1d9
@@ -1,86 +1,216 @@
-
-// this is a somewhat more portable mkstemp which uses the system prng to
-// seed. from what I've seen on other system mkstemps, they are usually
-// *worse* than this (or non-existant).
+// this file is partially cribbed from gfileutils.c in glib, which is
+// copyright (c) 2000 Red Hat. It was released as LGPL version 2.0 or
+// greater, so I have copied some of its text into this file and am
+// relicensing my derivative work (this file):
+// copyright (C) 2004 graydon hoare, as LGPL version 2.0 or greater also.
+// revised, copyright (C) 2009 zack weinberg
//
-// the source is partially cribbed from gfileutils.c in glib, which is
-// copyright (c) 2000 Red Hat. It was released as LGPL, so I have copied
-// some of its text into this file and am relicensing my derivative work
-// (this file) copyright (C) 2004 graydon hoare, as LGPL also.
+// this is a portable implementation of mkstemp, which is not available on
+// all systems and not always correctly implemented even when available.
+//
+// as with other mkstemp implementations, caller is expected to put the
+// string "XXXXXX" somewhere in the template, which will be replaced with a
+// random string. it does not need to be at the end. unlike the usual
+// mkstemp, the return value is just a boolean success/failure condition,
+// not a file descriptor; however, the file *has* been created. only 100
+// filenames are tried before we give up.
+//
+// we use only uppercase letters and digits in the random string, to avoid
+// all problems with case (in)sensitivity and magic characters (filesystem
+// or shell). the letters I, L, O, X are not used; I, L, and O are often
+// easily confused with the digits 1 or 0, and excluding X ensures that all
+// six of the placeholder characters will be modified when the function
+// returns, which makes it easier to test. this also arranges for there to
+// be 32 possible characters in the random string, which means there are
+// exactly 2^30 possible random strings.
+//
+// security notes: because we use open() correctly, the worst thing an
+// attacker can do to us is predict all 100 candidate filenames and cause
+// the function to fail. it is thus desirable for the attacker not to be
+// able to deduce the state of the RNG from the output string. however, the
+// function is not used very often and the runtime cost of a fully seeded,
+// cryptographically secure PRNG is very high. we're not about to drop the
+// mersenne twister in here, either, but we don't want something so shabby
+// as a 32-bit LCG, which is easily vulnerable to attack as used here (for
+// any output string there would be only four possible internal states).
+//
+// a reasonable choice is Pierre L'Ecuyer's "maximally equidistributed
+// combined LFSR (Tausworthe) generator" as described at
+// http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme2.ps -- this has
+// 128 bits of state and is nice and short.
+//
+// we don't use the system rand() because we don't know if it has been
+// seeded, we don't know if anyone else in the application is using it,
+// and it can be extremely poor quality (RANDU, anyone?)
-
#include "base.hh"
+#include "numeric_vocab.hh"
+
+#include
+#include
+#include
+
#include
#include
#include
-#include
#include
-#include
+#ifdef _MSC_VER // bleh, is this really necessary?
+ #undef open
+ #define open(p, f, m) _open(p, f, m)
+ #undef close
+ #define close(f) _close(f)
+ #undef O_RDWR
+ #define O_RDWR _O_RDWR
+ #undef O_CREAT
+ #define O_CREAT _O_CREAT
+ #undef O_EXCL
+ #define O_EXCL _O_EXCL
+ #undef O_BINARY
+ #define O_BINARY _O_BINARY
+#endif
-#include
-#include
-
#ifndef O_BINARY
-#define O_BINARY 0
+ #define O_BINARY 0
#endif
using std::string;
-bool
-monotone_mkstemp(string &tmpl)
+// RNG implementation: this code is taken from figure 1 of the paper cited
+// above, with revision for clarity.
+
+static u32 z1, z2, z3, z4;
+
+static u32
+lfsr113()
{
- unsigned int len = 0;
- int i = 0;
- int count = 0, fd = -1;
- string tmp;
+ z1 = ((z1 & 0xfffffffeu) << 18) ^ (((z1 << 6) ^ z1) >> 13);
+ z2 = ((z2 & 0xfffffff8u) << 2) ^ (((z2 << 2) ^ z2) >> 27);
+ z3 = ((z3 & 0xfffffff0u) << 7) ^ (((z3 << 13) ^ z3) >> 21);
+ z4 = ((z4 & 0xffffff80u) << 13) ^ (((z4 << 3) ^ z4) >> 12);
+ return z1 ^ z2 ^ z3 ^ z4;
+}
-#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,7,7)
- boost::shared_ptr rng(
- Botan::RandomNumberGenerator::make_rng());
-#endif
+// RNG implementation: i made this up based on the advice in the paper cited
+// above: "before calling lfsr113 for the first time, the variables z1, z2,
+// z3, and z4 must be initialized to any (random) integers larger than 1, 7,
+// 15, and 127, respectively." the current time is the traditional source
+// of true indeterminacy for this purpose. note that we perturb the values
+// already there rather than replacing them -- this is so successive calls
+// to monotone_mkstemp() within the system time granularity do not produce
+// the same sequence. the constants were chosen using a different PRNG.
- static const char letters[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+static void
+seed_lfsr113()
+{
+ u32 b = time(0);
+ z1 += b ^ 2421089565;
+ z2 += b ^ 3453830001;
+ z3 += b ^ 1437919543;
+ z4 += b ^ 1406684125;
+
+ if (z1 < 1) z1 += 1;
+ if (z2 < 7) z2 += 7;
+ if (z3 < 15) z3 += 15;
+ if (z4 < 127) z4 += 127;
+}
+
+bool
+monotone_mkstemp(string & tmpl)
+{
+ static const char letters[] = "0123456789ABCDEFGHJKMNPQRSTUVWYZ";
static const int NLETTERS = sizeof (letters) - 1;
- len = tmpl.length();
- if (len < 6 || tmpl.rfind("XXXXXX") != len-6)
- return -1;
+ typedef string::size_type position;
- for (count = 0; count < 100; ++count)
- {
- tmp = tmpl.substr(0, len-6);
+ position len = tmpl.length();
+ if (len < 6)
+ return false;
- for (i = 0; i < 6; ++i)
-#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,7,7)
- tmp.append(1, letters[rng->next_byte() % NLETTERS]);
-#else
- tmp.append(1, letters[Botan::Global_RNG::random() % NLETTERS]);
-#endif
+ position xes = tmpl.find("XXXXXX");
+ if (xes == string::npos)
+ return false;
-#ifdef _MSC_VER
- fd = _open(tmp.c_str(), _O_RDWR | _O_CREAT | _O_EXCL | _O_BINARY, 0600);
-#else
- fd = open(tmp.c_str(), O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
-#endif
- if (fd >= 0)
- {
- tmpl = tmp;
-#ifdef _MSC_VER
- _close(fd);
-#else
- close(fd);
-#endif
- return true;
- }
+ char buf[len+1];
+ memcpy(buf, tmpl.data(), len);
+ buf[len] = 0;
+
+ seed_lfsr113();
+
+ // if we can't find a name in 100 tries there's probably a problem
+ // requiring user intervention.
+ for (int count = 0; count < 100; ++count)
+ {
+ u32 x = lfsr113();
+ for (int i = 0; i < 6; i++)
+ {
+ buf[xes + i] = letters[x % NLETTERS];
+ x /= NLETTERS;
+ }
+
+ int fd = open(buf, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0600);
+ if (fd >= 0)
+ {
+ close(fd);
+ tmpl.replace(xes, 6, buf+xes, 6);
+ return true;
+ }
else if (errno != EEXIST)
break;
}
return false;
}
+#ifdef BUILD_UNIT_TESTS
+#include "base.hh"
+#include "sanity.hh"
+#include "unit_tests.hh"
+UNIT_TEST(mkstemp, basic)
+{
+ // This test verifies that we can create 100x3 temporary files in the
+ // same directory (using 3 different templates) and that the correct
+ // part of the template pathname is modified in each case.
+
+ char const * const cases[4] = {
+ "a-XXXXXX", "XXXXXX-b", "c-XXXXXX.dat", 0
+ };
+
+ for (int i = 0; cases[i]; i++)
+ for (int j = 0; j < 100; j++)
+ {
+ string r(cases[i]);
+ string s(cases[i]);
+ if (monotone_mkstemp(s))
+ {
+ UNIT_TEST_CHECK_MSG(r.length() == s.length(),
+ FL("same length: from %s got %s")
+ % r % s);
+ bool no_scribble = true;
+ for (string::size_type n = 0; n < r.length(); n++)
+ {
+ bool ok = r[n] == s[n];
+ if (r[n] == 'X')
+ ok = !ok;
+ if (!ok)
+ no_scribble = false;
+ }
+ UNIT_TEST_CHECK_MSG(no_scribble,
+ FL("modify correct segment: from %s got %s")
+ % r % s);
+ }
+ else
+ {
+ UNIT_TEST_CHECK_MSG(false,
+ FL("mkstemp failed with template %s "
+ "(iteration %d, os error %s)")
+ % r % (j+1) % strerror(errno));
+ break;
+ }
+ }
+}
+#endif
+
// Local Variables:
// mode: C++
// fill-column: 76