entd: Present version information to enterprise extension

BUG=chromium-os:15553
TEST=start and verify logs indicate parsed version number

Change-Id: Ic78a27956920ecc39e79ff6a7f82df46aedd3d71
Reviewed-on: http://gerrit.chromium.org/gerrit/3598
Reviewed-by: Ken Mixter <kmixter@chromium.org>
Tested-by: Ken Mixter <kmixter@chromium.org>
diff --git a/README.devtest b/README.devtest
index 7e2bae4..0a1edee 100644
--- a/README.devtest
+++ b/README.devtest
@@ -8,19 +8,22 @@
 can be launched using scripts/run_32bit.sh, or the run_tests.sh script from
 Entd.
 
+If you are not using /build/x86-generic as your x86-generic board, set
+DEFAULT_BOARD to the board you are using.
+
 To build an x86 version of entd, run...
 
   $ cd platform/entd
-  $ scons CHOST=i686-pc-linux-gnu SYSROOT=/build/x86-generic/ 
+  $ scons CHOST=i686-pc-linux-gnu SYSROOT=/build/$DEFAULT_BOARD/
 
 This will create a platform/entd/out/i686-pc-linux-gnu/ directory and place
 the target executable there.
 
 Then, to launch entd...
 
-  $ ../../scripts/run_32bit.sh ./out/i686-pc-linux-gnu/entd
+  $ ./run_32bit.sh ./out/i686-pc-linux-gnu/entd
   [0721/093224:INFO:main.cc(130)] Starting entd
-  ...
+  [0721/103352:WARNING:entd.cc(375)] Problem loading chromeos shared object: Couldn't load libcros from: /opt/google/chrome/chromeos/libcros.so error: /opt/google/chrome/chromeos/libcros.so: cannot open shared object file: No such file or directory
   [0721/093224:ERROR:entd.cc(471)] Can't determine hostname from username: 
   [0721/093224:INFO:main.cc(208)] Exiting entd with code: 1
 
@@ -29,7 +32,7 @@
 the run_32bit.sh script, we need to separate the script's arguments from
 entd's, using --, as in...
 
-  $ ../../scripts/run_32bit.sh ./out/i686-pc-linux-gnu/entd -- \
+  $ ./run_32bit.sh ./out/i686-pc-linux-gnu/entd -- \
     --username=user@example.com
   [0721/094034:INFO:main.cc(130)] Starting entd
   ...
@@ -40,7 +43,7 @@
 do that using --policy.  Before that, we'll introduce a shell variable to make
 the command lines slightly shorter...
 
-  $ ENTD="../../scripts/run_32bit.sh ./out/i686-pc-linux-gnu/entd --"
+  $ ENTD="./run_32bit.sh ./out/i686-pc-linux-gnu/entd --"
   $ $ENTD --username=user@example.com --policy=test_data/hello-world.js
 
 This time you'll notice that entd doesn't exit on its own.  This is an
diff --git a/base_policy/policy-utils.js b/base_policy/policy-utils.js
index 806fa00..9426de3 100644
--- a/base_policy/policy-utils.js
+++ b/base_policy/policy-utils.js
@@ -156,6 +156,69 @@
 Policy.PKCS11_USER_PIN = '111111';
 
 /**
+ * Parse an lsb-release file's contents.
+ * @param {string} contents File contents.
+ * @return {Object} dictionary of key/value pairs in that file.
+ */
+Policy.parseLsbRelease =
+function parseLsbRelease(contents) {
+  var lines = contents.split('\n');
+  var result = {};
+  for (var i = 0; i < lines.length; ++i) {
+    // Ignore comments.
+    if (lines[i].search(/[\s]*#/) >= 0)
+      continue;
+    var pieces = lines[i].split('=');
+    // Ignore lines not in X=Y format.
+    if (pieces.length != 2)
+      continue;
+    result[pieces[0]] = pieces[1];
+  }
+  return result;
+};
+
+/**
+ * Parse the ChromeOS version out of the parsed lsb-release file contents.
+ * @param {Object} parsedContents Dictionary of key/value pairs.
+ * @returns {Array.<Object>} Array of version numbers.
+ */
+Policy.parseChromeOSVersion =
+function parseChromeOSVersion(parsedContents) {
+  const versionKey = 'CHROMEOS_RELEASE_VERSION';
+  if (!(versionKey in parsedContents))
+    return [];
+  var versionString = parsedContents[versionKey];
+  var numbers = versionString.split('.');
+  return numbers.map(function(str) { return parseInt(str) });
+}
+
+/**
+ * Compare the given Chrome OS version to current version.
+ * @param {Array.<Number>} minVersion minimum version (inclusive) that will
+ * return true
+ * @param {Boolean} Whether minVersion is less or equal to given version.
+ */
+Policy.isAtLeastVersion =
+function isAtLeastVersion(minVersion) {
+  /* Search to find the first different version number.
+   * Consider that first different version element:
+   *   If current version is greater than min version, true.
+   *   If current version is less than min version, false.
+   * If there are no difference before one or other version ends:
+   *   If min version has more elements, false.
+   *   Otherwise the two are equal or current is slightly newer, true.
+   */
+  var i = 0;
+  while (i < entd.parsedChromeOSVersion.length && i < minVersion.length) {
+    if (entd.parsedChromeOSVersion[i] != minVersion[i]) {
+      return entd.parsedChromeOSVersion[i] > minVersion[i];
+    }
+    ++i;
+  }
+  return i == minVersion.length;
+}
+
+/**
  * Log an information status message.
  *
  * @param  {string} str The informational message to log.
@@ -1260,6 +1323,8 @@
   var callback_data = {
    description: this.policy.manifest.description,
    version: this.policy.manifest.version,
+   systemVersion: entd.parsedChromeOSVersion,
+   lsbRelease: entd.parsedLsbRelease,
    username: entd.username,
    browserPolicyChanged: this.policy.browserPolicyChanged,
    isLibcrosLoaded: entd.isLibcrosLoaded,
@@ -2006,3 +2071,17 @@
   }
   return result.join('');
 }
+
+/**
+ * Initialization of entd version information.  Must be done early and
+ * regardless of Policy being created since it may be used in
+ * policy.js's entd.Unload.
+ */
+function __init__() {
+  entd.parsedLsbRelease = Policy.parseLsbRelease(entd.lsbRelease);
+  entd.parsedChromeOSVersion =
+      Policy.parseChromeOSVersion(entd.parsedLsbRelease);
+  entd.syslog.info('Parsed ChromeOS version: ' + entd.parsedChromeOSVersion);
+}
+
+__init__();
diff --git a/entd.cc b/entd.cc
index 84aff20..044f22c 100644
--- a/entd.cc
+++ b/entd.cc
@@ -28,7 +28,7 @@
 namespace entd {
 
 using std::string;
-static const char* kOpenSSLConfigName = "entd";
+static const char kOpenSSLConfigName[] = "entd";
 
 void dispatch_OnTimeout(int fd, short flags, void* arg);
 
@@ -37,6 +37,8 @@
   return tv;
 }
 
+static const char kDefaultLsbRelease[] = "/etc/lsb-release";
+
 // Class to represent a pending timeout event.
 class Timeout {
  public:
@@ -319,7 +321,8 @@
 }
 
 Entd::Entd()
-    : callback_server_(NULL),
+    : lsb_release_filename_(kDefaultLsbRelease),
+      callback_server_(NULL),
       flimflam_(new Flimflam()),
       syslog_(new Syslog()) {
   ::g_type_init();
@@ -500,6 +503,16 @@
   entd->Set(v8::String::NewSymbol("username"),
             v8::String::New(username_.c_str()), v8::ReadOnly);
 
+  // Set entd.lsbRelease to the contents the lsb_release file.
+  std::string lsb_file_contents;
+  if (!file_util::ReadFileToString(FilePath(lsb_release_filename_),
+                                   &lsb_file_contents)) {
+    LOG(ERROR) << "Could not read " << lsb_release_filename_;
+    return false;
+  }
+  entd->Set(v8::String::NewSymbol("lsbRelease"),
+            v8::String::New(lsb_file_contents.c_str()), v8::ReadOnly);
+
   size_t at_pos = username_.find("@");
   if (at_pos == std::string::npos || at_pos == username_.length() - 1) {
     LOG(ERROR) << "Can't determine hostname from username: " << username_;
diff --git a/entd.h b/entd.h
index 1b6ecbb..ffdb0d9 100644
--- a/entd.h
+++ b/entd.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -95,6 +95,11 @@
     return hostname_;
   }
 
+  void SetLsbRelease(const std::string& filename) {
+    LOG(INFO) << "lsb-release file set to: '" << filename << "''";
+    lsb_release_filename_ = filename;
+  }
+
   static const char* GetClassName() { return "entd"; }
 
   // Bind functions / properties to the V8 template object
@@ -200,6 +205,7 @@
   std::string utility_filename_;
   std::string manifest_filename_;
   std::string policy_filename_;
+  std::string lsb_release_filename_;
 
   // Starts out as just the hostname portion of the username, but may be
   // set to a subdomain of that by the manifest file.
diff --git a/main.cc b/main.cc
index 21e0b7c..4b95965 100644
--- a/main.cc
+++ b/main.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -19,40 +19,42 @@
 
 namespace switches {
 // Path to search for extensions; can contain ~ or env variables (e.g. ${HOME})
-static const char *kExtensionPath = "extension-path";
+static const char kExtensionPath[] = "extension-path";
 
 // User Name
-static const char *kUsername = "username";
+static const char kUsername[] = "username";
 
 // Policy files
-static const char *kManifest = "manifest";
-static const char *kPolicy =   "policy";
-static const char *kUtility =  "utility";
+static const char kManifest[] = "manifest";
+static const char kPolicy[] =   "policy";
+static const char kUtility[] =  "utility";
 
 // Root CA for HTTPS requests.
-static const char *kRootCAFile = "root-ca-file";
+static const char kRootCAFile[] = "root-ca-file";
 
 // If specified, then self-signed server certs are ok for HTTPS
-static const char *kAllowSelfSigned = "allow-self-signed";
+static const char kAllowSelfSigned[] = "allow-self-signed";
 
 // If specified, then file operations are allowed (e.g. for testing)
-static const char *kAllowFileIO = "allow-file-io";
+static const char kAllowFileIO[] = "allow-file-io";
 
 // If specified, don't watch for signals.  This allows the process to exit
 // automatically when all events have been processed.  See the comment in
 // entd.h for a little more info.
-static const char *kAllowDirtyExit = "allow-dirty-exit";
+static const char kAllowDirtyExit[] = "allow-dirty-exit";
 
 // Syslogging is enabled by default if stdout is not a tty.  These flags can
 // be used to override the default logic.
-static const char *kEnableSyslog = "enable-syslog";
-static const char *kDisableSyslog = "disable-syslog";
+static const char kEnableSyslog[] = "enable-syslog";
+static const char kDisableSyslog[] = "disable-syslog";
 
-static const char *kLibcrosLocation = "libcros-location";
+static const char kLibcrosLocation[] = "libcros-location";
 
-static const char *kCallbackOrigin = "callback-origin";
+static const char kCallbackOrigin[] = "callback-origin";
 
-static const char *kSessionId = "session-id";
+static const char kSessionId[] = "session-id";
+
+static const char kSetLsbRelease[] = "lsb-release";
 
 }  // namespace switches
 
@@ -179,6 +181,9 @@
   if (!policy.empty())
     d.SetPolicyFile(policy);
 
+  if (cl->HasSwitch(switches::kSetLsbRelease))
+    d.SetLsbRelease(cl->GetSwitchValueASCII(switches::kSetLsbRelease));
+
   uint32_t rv = d.Run();
   LOG(INFO) << "Exiting entd with code: " << rv;
   return rv;
diff --git a/reference_extension/client.js b/reference_extension/client.js
index 80b677c..c7f5b68 100644
--- a/reference_extension/client.js
+++ b/reference_extension/client.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -85,10 +85,8 @@
     if (this.readyState != 4)
       return;
 
-    if (this.status == 0) {
-      // Status is 0 rather than 200 for this.  I assume that's because we're
-      // loading from the chrome-extension: scheme, rather than a real
-      // web server.
+    if (this.status == 0 || this.status == 200) {
+      // Status was 0 in early versions of Chrome.
       client.manifest = JSON.parse(this.responseText);
       console.log('manifest: ' + this.responseText);
       client.onManifestLoaded();
@@ -110,10 +108,8 @@
     if (this.readyState != 4)
       return;
 
-    if (this.status == 0) {
-      // Status is 0 rather than 200 for this.  I assume that's because we're
-      // loading from the chrome-extension: scheme, rather than a real
-      // web server.
+    if (this.status == 0 || this.status == 200) {
+      // Status was 0 in earlier versions of Chrome.
       if (this.responseText != "") {
         client.session_id = JSON.parse(this.responseText);
       } else {
@@ -155,6 +151,19 @@
       $('#user-status').
         text(retval.data.username);
 
+      if ('systemVersion' in retval.data) {
+        var board = 'Unknown hardware';
+        if ('lsbRelease' in retval.data &&
+            'CHROMEOS_RELEASE_BOARD' in retval.data.lsbRelease) {
+          board = retval.data.lsbRelease.CHROMEOS_RELEASE_BOARD;
+        }
+        $('#system-info').text('Chrome OS ' +
+                               retval.data.systemVersion.join('.') + ', ' +
+                               board);
+      } else {
+        $('#system-info').text('Chrome OS earlier than 0.14');
+      }
+
       var pkcs11 = retval.data.pkcs11;
       if (pkcs11.state != 'stop:ready') {
         $("#pkcs11-status").
diff --git a/reference_extension/options.html b/reference_extension/options.html
index 868eb20..40d991c 100644
--- a/reference_extension/options.html
+++ b/reference_extension/options.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<!-- Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+<!-- Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
   -- Use of this source code is governed by a BSD-style license that can be
   -- found in the LICENSE file.
   -->
@@ -271,6 +271,10 @@
                 <td id="pkcs11-status">Unknown</td>
               </tr>
               <tr>
+                <td class="label">System</td>
+                <td id="system-info">Unknown</td>
+              </tr>
+              <tr>
                 <td id="entd-message" colspan="4"></td>
               </tr>
             </table>
diff --git a/run_32bit.sh b/run_32bit.sh
new file mode 100755
index 0000000..35a12d5
--- /dev/null
+++ b/run_32bit.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Run a 32 bit binary on 64 bit linux, can be run from inside or outside
+# the chroot.
+
+. "$(dirname "$0")/../../scripts/common.sh"
+
+get_default_board
+
+# Command line options
+DEFINE_string chroot "$DEFAULT_CHROOT_DIR" "Location of chroot"
+DEFINE_string board  "$DEFAULT_BOARD" "x86 board to use"
+
+# Parse command line and update positional args
+FLAGS "$@" || exit 1
+eval set -- "${FLAGS_ARGV}"
+
+  # Die on any errors
+set -e
+
+if [ -z "$SYSROOT" ]; then
+  if [ $INSIDE_CHROOT == 1 ]; then
+    SYSROOT=/build/$FLAGS_board
+  else
+    SYSROOT=$FLAGS_chroot/build/$FLAGS_board
+  fi
+fi
+
+if [ -z "$CHOST" ]; then
+  CHOST=i686-pc-linux-gnu
+fi
+
+LIB_PATHS="/lib32:/usr/lib32:$LIB_PATHS:$SYSROOT/usr/lib:$SYSROOT/lib:."
+LIB_PATHS="$LIB_PATHS:$SYSROOT/opt/google/chrome/chromeos"
+export LD_LIBRARY_PATH=$LIB_PATHS
+
+exec "$@"
diff --git a/run_tests.sh b/run_tests.sh
index 90c6052..b8d05b7 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,18 +1,25 @@
 #!/bin/bash
 
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 # TODO: Get these tests running in a cross-platform fashion from the src_test
 # stanza of the entd ebuild.
 
+. "$(dirname "$0")/../../scripts/common.sh"
+
+DEFINE_boolean skip_tpm ${FLAGS_FALSE} "Skip tests requiring TPM"
+
+# Parse command line and update positional args
+FLAGS "$@" || exit 1
+eval set -- "${FLAGS_ARGV}"
+
 USERNAME="user@google.com"
 ALLOW_DIRTY_EXIT=1
 ALLOW_FILE_IO=1
 ENABLE_OPENCRYPTOKI="1"
-SYSROOT="/build/x86-generic"
-RUN32="../../scripts/run_32bit.sh"
+RUN32="./run_32bit.sh"
 ENTD="$RUN32 out/i686-pc-linux-gnu/entd --"
 PKCSSLOTD="$RUN32 $SYSROOT/usr/sbin/pkcsslotd --"
 PKCS_SLOT="$RUN32 $SYSROOT/usr/sbin/pkcs_slot --"
@@ -70,6 +77,7 @@
   fi
 
   local out
+  echo $cmd
   out=$($cmd 2>&1)
   local code=$?
 
@@ -204,18 +212,22 @@
 
   greptest "simple-shutdown.js" "LOOKS OK"
 
+  local session="test-session"
+
   # Fire off this bg task to make an http request
   (sleep 2; \
     curl --data-binary '{"function": "stop"}' \
     -H "Content-Type: application/json; charset=UTF-8" \
     -H "X-Entd-Request: test-magic" \
+    -H "X-Entd-Session-Id: $session" \
     -H "Origin: test-origin" \
     localhost:5200/dispatch -so /dev/null) &
 
   # Then start up the callback server before the timeout fires.  This tests
   # that we can successfully talk to the callback server, and that it respects
   # valid X-Entd-Request and Origin headers.
-  greptest "simple-callback.js --callback-origin=test-origin" \
+  greptest "simple-callback.js --callback-origin=test-origin \
+           --session-id=$session" \
     "Stopping callback server"
 
   # Now fire off a bg task to make an http request with a bogus Origin header.
@@ -223,6 +235,7 @@
     curl --data-binary '{"function": "stop"}' \
     -H "Content-Type: application/json; charset=UTF-8" \
     -H "X-Entd-Request: test-magic" \
+    -H "X-Entd-Session-Id: $session" \
     -H "Origin: bogus-origin" \
     localhost:5200/dispatch -so /dev/null) &
 
@@ -231,20 +244,25 @@
     curl --data-binary '{"function": "stop"}' \
     -H "Content-Type: application/json; charset=UTF-8" \
     -H "X-Entd-Request: test-magic" \
+    -H "X-Entd-Session-Id: $session" \
     -H "Origin: test-origin" \
     localhost:5200/dispatch -so /dev/null) &
 
   # Then start up the callback server before the timeouts fire.  This tests
   # that the bogus Origin header is properly rejected.
-  greptest "simple-callback.js --callback-origin=test-origin" \
+  greptest "simple-callback.js --callback-origin=test-origin \
+            --session-id=$session" \
     "Bad or missing Origin header"
 }
 
 function all_tests() {
   basic_tests
   http_tests
-  crypto_pkcs11_tests
-  pkcs11_tests
+  if [[ ${FLAGS_skip_tpm} -ne ${FLAGS_TRUE} ]]; then
+    warn "Assuming PKCS11/TPM is set up. Pass --skip_tpm otherwise."
+    crypto_pkcs11_tests
+    pkcs11_tests
+  fi
   slow_tests
 }