Merge "gfch100: more peer cleanup"
diff --git a/craftui/.gitignore b/craftui/.gitignore
index 5d9b5ae..7dd7a03 100644
--- a/craftui/.gitignore
+++ b/craftui/.gitignore
@@ -1,7 +1,9 @@
 *.pyo
 *.swp
 .started
-.sim.extracted
-sim
+.sim1.extracted
+.sim2.extracted
+sim1
+sim2
 LOG
 tmp-certs
diff --git a/craftui/HOW.restart_if_changed b/craftui/HOW.restart_if_changed
index 0bfd11f..2a26b18 100644
--- a/craftui/HOW.restart_if_changed
+++ b/craftui/HOW.restart_if_changed
@@ -4,21 +4,26 @@
 
 export PATH="$(pwd)/../../../../out.gfch100_defconfig/host/usr/bin:$PATH"
 
-pid=
+pid1=
+pid2=
 
 restart() {
-  [ -n "$pid" ] && kill $pid
+  [ -n "$pid1" ] && kill $pid1
+  [ -n "$pid2" ] && kill $pid2
   echo "######################################################################"
   echo "# starting craftui"
   gpylint *.py
   make test
   ./craftui &
-  pid=$!
+  pid1=$!
+  ./craftui -2 &
+  pid2=$!
   touch .started
 }
 
 onExit() {
-  [ -n "$pid" ] && kill $pid
+  [ -n "$pid1" ] && kill $pid1
+  [ -n "$pid2" ] && kill $pid2
   exit 1
 }
 
@@ -26,7 +31,7 @@
 restart
 
 while sleep 1; do
-  if ! kill -0 $pid; then
+  if ! kill -0 $pid1 || ! kill -0 $pid2; then
     restart
     continue
   fi
diff --git a/craftui/HOW.updatesim b/craftui/HOW.updatesim
index d5f2e6a..f01d964 100644
--- a/craftui/HOW.updatesim
+++ b/craftui/HOW.updatesim
@@ -1,27 +1,31 @@
 #! /bin/sh
 
-ssh chimera '
-	rm -rf /tmp/sim;
-	mkdir -p /tmp/sim/proc && cat /proc/uptime > /tmp/sim/proc/uptime;
-	for n in /sys/class/net/*/statistics/*; do
-		mkdir -p /tmp/sim/$(dirname $n);
-		test ! -d $n && cat $n > /tmp/sim/$n;
-	done;
-	ip -o -d link > /tmp/sim/ip.link.txt;
-	ip -o addr > /tmp/sim/ip.addr.txt;
-	presterastats > /tmp/sim/presterastats.json;
-	'
+for suffix in 1 2; do
 
-ssh chimera cd / "&&" tar czf - -C / \
-	config/settings \
-	etc/platform \
-	etc/serial \
-	etc/softwaredate \
-	etc/version \
-	tmp/glaukus \
-	tmp/serial \
-	tmp/ssl \
-	tmp/platform \
-	tmp/gpio \
-	tmp/sim \
-	> sim.tgz
+	ssh chimera$suffix '
+		rm -rf /tmp/sim;
+		mkdir -p /tmp/sim/proc && cat /proc/uptime > /tmp/sim/proc/uptime;
+		for n in /sys/class/net/*/statistics/*; do
+			mkdir -p /tmp/sim/$(dirname $n);
+			test ! -d $n && cat $n > /tmp/sim/$n;
+		done;
+		ip -o -d link > /tmp/sim/ip.link.txt;
+		ip -o addr > /tmp/sim/ip.addr.txt;
+		presterastats > /tmp/sim/presterastats.json;
+		'
+
+	ssh chimera$suffix cd / "&&" tar czf - -C / \
+		config/settings \
+		etc/platform \
+		etc/serial \
+		etc/softwaredate \
+		etc/version \
+		tmp/glaukus \
+		tmp/serial \
+		tmp/ssl \
+		tmp/platform \
+		tmp/gpio \
+		tmp/sim \
+		> sim$suffix.tgz
+
+done
diff --git a/craftui/Makefile b/craftui/Makefile
index 782ca22..dbb2082 100644
--- a/craftui/Makefile
+++ b/craftui/Makefile
@@ -15,14 +15,14 @@
 install-libs:
 	@echo "No libs to install."
 
-.sim.extracted: sim.tgz
-	-chmod -R +w sim
-	rm -rf sim
-	rsync -av sim-tools/ sim
-	tar xf sim.tgz -C sim
+.sim%.extracted: sim%.tgz
+	-chmod -R +w sim$*
+	rm -rf sim$*
+	rsync -av sim-tools/ sim$*
+	tar xf sim$*.tgz -C sim$*
 	touch $@
 
-test: .sim.extracted lint
+test: .sim1.extracted .sim2.extracted lint
 	set -e; \
 	for n in $(wildcard ./*_test.*); do \
 		echo; \
diff --git a/craftui/craftui b/craftui/craftui
index 527eba7..9d2a17a 100755
--- a/craftui/craftui
+++ b/craftui/craftui
@@ -7,15 +7,22 @@
 
 # in developer environment if vendor/google/catawapus is above us
 if [ -d "$devcw" ]; then
-  isdev=1
+  sim=1
+fi
+
+if [ -n "$sim" ] && [ "$1" = "-2" ]; then
+  sim=2
+  shift
 fi
 
 # if running from developer desktop, use simulated data
-if [ "$isdev" = 1 ]; then
+if [ -n "$sim" ]; then
   cw="$devcw"
-  args="$args --http-port=8888 --https-port=8889 --sim=./sim"
+  args="$args --http-port=$((8888+2*($sim-1)))"
+  args="$args --https-port=$((8889+2*($sim-1)))"
+  args="$args --sim=./sim$sim"
   pycode=./craftui_fortesting.py
-  export PATH="$PWD/sim/bin:$PATH"
+  export PATH="$PWD/sim1/bin:$PATH"
 fi
 
 # for debugging on the device, use the local (/tmp/www?) web tree
@@ -32,13 +39,6 @@
     continue
   fi
 
-  # enable https
-  if [ "$1" = -S ]; then
-    httpsmode="-S"
-    shift
-    continue
-  fi
-
   echo "$0: '$1': unknown command line option" >&2
   exit 1
 done
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 10f5e91..9afe28d 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -262,7 +262,7 @@
     print 'Glaukus: ', url, payload
 
     try:
-      fd = urllib2.urlopen(url, payload)
+      fd = urllib2.urlopen(url, payload, timeout=2)
     except urllib2.URLError as ex:
       print 'Connection to %s failed: %s' % (url, ex.reason)
       raise ConfigError('failed to contact glaukus')
@@ -381,16 +381,18 @@
       'tx_errors',
       'tx_dropped'
   ]
-  realm = 'gfch100'
 
   def __init__(self, wwwroot, http_port, https_port, sim):
-    """initialize."""
+    """Initialize."""
     self.wwwroot = wwwroot
     self.http_port = http_port
     self.https_port = https_port
     self.sim = sim
     self.data = {}
     self.data['refreshCount'] = 0
+    platform = self.ReadFile(sim + '/etc/platform')
+    serial = self.ReadFile(sim + '/etc/serial')
+    self.realm = '%s-%s' % (platform, serial)
 
   def ApplyChanges(self, changes):
     """Apply changes to system."""
@@ -556,6 +558,7 @@
     return response
 
   def GetUserCreds(self, user):
+    """Create a dict with the requested password."""
     if user not in ('admin', 'guest'):
       return None
     b64 = self.ReadFile('%s/config/settings/password_%s' % (self.sim, user))
@@ -583,32 +586,110 @@
     """Common class to add args to html template."""
     auth = 'unset'
 
+    def IsProxy(self):
+      """Check if this request was proxied, (ie, we are the peer)."""
+      return self.request.headers.get('craftui-proxy', 0) == '1'
+
+    def IsPeer(self):
+      """Check args to see if this is a request for the peer."""
+      return self.get_argument('peer', default='0') == '1'
+
+    def IsHttps(self):
+      """See if https:// was used."""
+      return (self.request.protocol == 'https' or
+              self.request.headers.get('craftui-https', 0) == '1')
+
     def TemplateArgs(self):
+      """Build template args to dynamically adjust html file."""
+      is_https = self.IsHttps()
+      is_proxy = self.IsProxy()
+
+      peer_arg = '?peer=1'
+
       args = {}
-      args['hidepeer'] = ''
-      args['hidehttps'] = ''
-      args['peer'] = ''
-      if self.request.protocol is 'https':
-        args['hidehttps'] = 'hidden'
-      if self.get_argument('peer', default='0') == '1':
-        args['peer'] = '?peer=1'
-        args['hidepeer'] = 'hidden'
-      print args
+      args['hidden_on_https'] = 'hidden' if is_https else ''
+      args['hidden_on_peer'] = 'hidden' if is_proxy else ''
+      args['shown_on_peer'] = 'hidden' if not is_proxy else ''
+      args['peer_arg'] = peer_arg
+      args['peer_arg_on_peer'] = peer_arg if is_proxy else ''
       return args
 
-    def get(self):
-      ui = self.settings['ui']
-      if self.auth is 'any':
-        if not ui.Authenticate(self):
-          return
-      elif self.auth is 'admin':
-        if not ui.AuthenticateAdmin(self):
-          return
-      elif self.auth is not 'none':
-        raise Exception('unknown authentication type "%s"' % self.auth)
+    def TryProxy(self):
+      """Check if we should proxy this request to the peer."""
+      if not self.IsPeer() or self.IsProxy():
+        return False
+      self.Proxy()
+      return True
 
+    class ErrorHandler(urllib2.HTTPDefaultErrorHandler):
+      """Catch the error, don't raise exception."""
+      error = {}
+
+      def http_error_default(self, req, fd, code, msg, hdrs):
+        self.error = {
+            'request': req,
+            'fd': fd,
+            'code': code,
+            'msg': msg,
+            'hdrs': hdrs
+        }
+
+    def Proxy(self):
+      """Proxy to the peer."""
+      ui = self.settings['ui']
+      r = self.request
+      cs = '/config/settings/'
+      peer_ipaddr = ui.ReadFile(ui.sim + cs + 'peer_ipaddr')
+      peer_ipaddr = re.sub(r'/\d+$', '', peer_ipaddr)
+      if ui.sim:
+        peer_ipaddr = 'localhost:8890'
+      url = 'http://' + peer_ipaddr + r.uri
+      print 'proxy: ', url
+
+      eh = self.ErrorHandler()
+      opener = urllib2.build_opener(eh)
+
+      body = None
+      if r.method == 'POST':
+        body = '' if r.body is None else r.body
+      req = urllib2.Request(url, body, r.headers)
+      req.add_header('CraftUI-Proxy', 1)
+      req.add_header('CraftUI-Https', int(self.IsHttps()))
+      fd = opener.open(req, timeout=2)
+      if eh.error:
+        fd = eh.error['fd']
+        self.set_status(eh.error['code'])
+        hdrs = eh.error['hdrs']
+        for h in hdrs:
+          v = hdrs.get(h)
+          self.set_header(h, v)
+
+      response = fd.read()
+      if response:
+        self.write(response)
+      self.finish()
+
+    def Authenticated(self):
+      """Authenticate the user per the required auth type."""
+      ui = self.settings['ui']
+      if self.auth == 'any':
+        if not ui.Authenticate(self):
+          return False
+      elif self.auth == 'admin':
+        if not ui.AuthenticateAdmin(self):
+          return False
+      elif self.auth != 'none':
+        raise Exception('unknown authentication type "%s"' % self.auth)
+      return True
+
+    def get(self):
+      if self.TryProxy():
+        return
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
       path = ui.wwwroot + '/' + self.page + '.thtml'
-      print 'GET %s page' % self.page
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       self.render(path, **self.TemplateArgs())
 
   class WelcomeHandler(CraftHandler):
@@ -625,22 +706,29 @@
 
   class JsonHandler(CraftHandler):
     """Provides JSON-formatted content to be displayed in the UI."""
+    page = 'json'
 
     def get(self):
-      ui = self.settings['ui']
-      if not ui.Authenticate(self):
+      if self.TryProxy():
         return
-      print 'GET json data'
+      self.auth = 'any'
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       jsonstring = ui.GetData()
       self.set_header('Content-Type', 'application/json')
       self.write(jsonstring)
       self.finish()
 
     def post(self):
-      ui = self.settings['ui']
-      if not ui.AuthenticateAdmin(self):
+      if self.TryProxy():
         return
-      print 'POST JSON data for craft page'
+      self.auth = 'admin'
+      if not self.Authenticated():
+        return
+      ui = self.settings['ui']
+      print '%s %s page (%s)' % (self.request.method, self.page, ui.sim)
       request = self.request.body
       result = {}
       result['error'] = 0
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
index 5fc11ce..677f719 100755
--- a/craftui/craftui_test.sh
+++ b/craftui/craftui_test.sh
@@ -52,7 +52,7 @@
 
 onexit() {
   testname "process not running at exit"
-  kill -0 $pid
+  kill -0 $pid1
   check_failure
 
   testname "end of script reached"
@@ -86,15 +86,19 @@
 
   # add a signed cert (using our fake CA)
   sh HOW.cert
-  chmod 750 sim/tmp/ssl/*
-  cp tmp-certs/localhost.pem sim/tmp/ssl/certs/craftui.pem
-  cp tmp-certs/localhost.key sim/tmp/ssl/private/craftui.key
+  for n in sim1 sim2; do
+    chmod 750 $n/tmp/ssl/*
+    cp tmp-certs/localhost.pem $n/tmp/ssl/certs/craftui.pem
+    cp tmp-certs/localhost.key $n/tmp/ssl/private/craftui.key
+  done
 
   ./craftui &
-  pid=$!
+  pid1=$!
+  ./craftui -2 &
+  pid2=$!
 
   testname "process running"
-  kill -0 $pid
+  kill -0 $pid1
   check_success
 
   sleep 1
@@ -225,15 +229,36 @@
     $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
     check_success
 
+    testname proxy read from peer
+    $curl $admin_auth $url/content.json'?peer=1' |& grep '"platform": "GFCH100"'
+    check_success
+
+    testname proxy write to peer
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $admin_auth -d $d $url/content.json'?peer=1' |& grep '"error": 0}'
+    check_success
+
+  done
+
+  # verify insecure message is hidden on https and not on http
+  for peer in '' '?peer=1'; do
+    testname http warning $peer
+    $curl http://localhost:$http$peer |& grep 'hidden_on_https value=""'
+    check_success
+
+    testname no https warning $peer
+    $curl https://localhost:$https$peer |& grep 'hidden_on_https value="hidden"'
+    check_success
   done
 
   testname "process still running at end of test sequence"
-  kill -0 $pid
+  kill -0 $pid1
   check_success
 
   # cleanup
   t0=$(date +%s)
-  kill $pid
+  kill $pid1
+  kill $pid2
   wait
   t1=$(date +%s)
   dt=$((t1 - t0))
diff --git a/craftui/sim.tgz b/craftui/sim.tgz
deleted file mode 100644
index d50297f..0000000
--- a/craftui/sim.tgz
+++ /dev/null
Binary files differ
diff --git a/craftui/sim1.tgz b/craftui/sim1.tgz
new file mode 100644
index 0000000..04f1d51
--- /dev/null
+++ b/craftui/sim1.tgz
Binary files differ
diff --git a/craftui/sim2.tgz b/craftui/sim2.tgz
new file mode 100644
index 0000000..14e5547
--- /dev/null
+++ b/craftui/sim2.tgz
Binary files differ
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index 1227ad0..7d95d8a 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -14,16 +14,23 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/{{peer}}>Welcome</a></li>
-          <li ><a href=/status{{peer}}>Status</a></li>
-          <li class=active><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li ><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li ><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li class=active><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
-
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index fe7d9bc..7b539ba 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -64,6 +64,7 @@
   if (CraftUI.am_sending) {
     return;
   }
+  var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var xhr = new XMLHttpRequest();
   xhr.onreadystatechange = function() {
     self.unhandled = '';
@@ -74,20 +75,18 @@
     CraftUI.updateField('unhandled', self.unhandled);
     CraftUI.am_sending = false
   };
-  var payload = [];
-  payload.push('checksum=' + encodeURIComponent(CraftUI.info.checksum));
-  payload.push('_=' + encodeURIComponent((new Date()).getTime()));
-  xhr.open('get', 'content.json?' + payload.join('&'), true);
+  xhr.open('get', '/content.json' + peer_arg_on_peer, true);
   CraftUI.am_sending = true
   xhr.send();
 };
 
 CraftUI.config = function(key, activate, is_password) {
   // POST as json
+  var peer_arg_on_peer = document.getElementById("peer_arg_on_peer").value;
   var el = document.getElementById(key);
   var xhr = new XMLHttpRequest();
   var action = "Configured";
-  xhr.open('post', 'content.json');
+  xhr.open('post', '/content.json' + peer_arg_on_peer);
   xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
   var data;
   if (is_password) {
diff --git a/craftui/www/status.thtml b/craftui/www/status.thtml
index 28b5027..adb4673 100644
--- a/craftui/www/status.thtml
+++ b/craftui/www/status.thtml
@@ -14,15 +14,23 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/?{{peer}}>Welcome</a></li>
-          <li class=active><a href=/status{{peer}}>Status</a></li>
-          <li ><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li ><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li class=active><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li ><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
diff --git a/craftui/www/welcome.thtml b/craftui/www/welcome.thtml
index de7c098..43966ff 100644
--- a/craftui/www/welcome.thtml
+++ b/craftui/www/welcome.thtml
@@ -14,21 +14,29 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li class=active><a href=/{{peer}}>Welcome</a></li>
-          <li ><a href=/status{{peer}}>Status</a></li>
-          <li ><a href=/config{{peer}}>Configuration</a></li>
-          <li ><a {{hidepeer}} href="/?peer=1" target=_blank>Peer</a></li>
+          <li class=active><a href=/{{peer_arg_on_peer}}>Welcome</a></li>
+          <li ><a href=/status{{peer_arg_on_peer}}>Status</a></li>
+          <li ><a href=/config{{peer_arg_on_peer}}>Configuration</a></li>
+          <li ><a {{hidden_on_peer}} href="/{{peer_arg}}" target=_blank>Peer</a></li>
         </ul>
       </nav>
     </section>
   </header>
   <br>
+  <div hidden>
+    <input id=hidden_on_https value="{{hidden_on_https}}">
+    <input id=hidden_on_peer value="{{hidden_on_peer}}">
+    <input id=shown_on_peer value="{{shown_on_peer}}">
+    <input id=peer_arg value="{{peer_arg}}">
+    <input id=peer_arg_on_peer value="{{peer_arg_on_peer}}">
+  </div>
+  <div {{shown_on_peer}}><font color="red"><b>This is the Peer</b></font></div>
   <div class="tabs">
     <div class="tab">
       <input type="radio" id="tab-1" name="tab-group-1" checked>
       <label for="tab-1">Authorized Use Only</label>
       <div class="content">
-        <div {{hidehttps}}>
+        <div {{hidden_on_https}}>
           <b>
             Warning: You are not connected securely.  Consider https://...
             <br>
@@ -40,9 +48,6 @@
           <p>
           Unauthorized access to this system is forbidden and will be
           prosecuted by law.
-          <br>
-          By accessing this system, you agree that your actions may be
-          monitored if unauthorized usage is suspected.
       </div>
     </div>
   </div>