Merge "veyron/services/mgmt/device/impl,veyron/lib/unixfd: more close-on-exec stuff"
diff --git a/lib/stats/sysstats/sysstats.go b/lib/stats/sysstats/sysstats.go
index 75c0c17..9579280 100644
--- a/lib/stats/sysstats/sysstats.go
+++ b/lib/stats/sysstats/sysstats.go
@@ -21,6 +21,7 @@
 	stats.NewInteger("system/num-cpu").Set(int64(runtime.NumCPU()))
 	stats.NewIntegerFunc("system/num-goroutine", func() int64 { return int64(runtime.NumGoroutine()) })
 	stats.NewString("system/version").Set(runtime.Version())
+	stats.NewInteger("system/pid").Set(int64(os.Getpid()))
 	if hostname, err := os.Hostname(); err == nil {
 		stats.NewString("system/hostname").Set(hostname)
 	}
diff --git a/lib/stats/sysstats/sysstats_test.go b/lib/stats/sysstats/sysstats_test.go
index ee2f835..d157b5e 100644
--- a/lib/stats/sysstats/sysstats_test.go
+++ b/lib/stats/sysstats/sysstats_test.go
@@ -31,3 +31,14 @@
 		t.Errorf("unexpected Alloc value. Got %v, want != 0", v)
 	}
 }
+
+func TestPid(t *testing.T) {
+	obj, err := stats.GetStatsObject("system/pid")
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	expected := int64(os.Getpid())
+	if got := obj.Value(); got != expected {
+		t.Errorf("unexpected result. Got %q, want %q", got, expected)
+	}
+}
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index de89583..c0d41df 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -79,6 +79,9 @@
 	errAuthNoPatternMatch = verror.Register(pkgPath+".authNoPatternMatch",
 		verror.NoRetry, "server blessings {3} do not match pattern {4}")
 
+	errAuthServerNotAllowed = verror.Register(pkgPath+".authServerNotAllowed",
+		verror.NoRetry, "set of allowed servers {3} not matched by server blessings {4}")
+
 	errDefaultAuthDenied = verror.Register(pkgPath+".defaultAuthDenied", verror.NoRetry, "default authorization precludes talking to server with blessings{:3}")
 
 	errBlessingGrant = verror.Register(pkgPath+".blessingGrantFailed", verror.NoRetry, "failed to grant blessing to server with blessings {3}{:4}")
@@ -122,6 +125,13 @@
 	encrypted bool
 }
 
+// PreferredProtocols instructs the Runtime implementation to select
+// endpoints with the specified protocols and to order them in the
+// specified order.
+type PreferredProtocols []string
+
+func (PreferredProtocols) IPCClientOpt() {}
+
 func InternalNewClient(streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ClientOpt) (ipc.Client, error) {
 	c := &client{
 		streamMgr: streamMgr,
@@ -135,7 +145,7 @@
 		switch v := opt.(type) {
 		case stream.VCOpt:
 			c.vcOpts = append(c.vcOpts, v)
-		case options.PreferredProtocols:
+		case PreferredProtocols:
 			c.preferredProtocols = v
 		}
 	}
@@ -654,6 +664,17 @@
 	}
 	for _, o := range opts {
 		switch v := o.(type) {
+		case options.AllowedServersPolicy:
+			allowed := false
+			for _, p := range v {
+				if p.MatchedBy(serverBlessings...) {
+					allowed = true
+					break
+				}
+			}
+			if !allowed {
+				return nil, nil, verror.Make(errAuthServerNotAllowed, ctx, v, serverBlessings)
+			}
 		case ipc.Granter:
 			if b, err := v.Grant(flow.RemoteBlessings()); err != nil {
 				return nil, nil, verror.Make(errBlessingGrant, ctx, serverBlessings, err)
diff --git a/runtimes/google/ipc/debug_test.go b/runtimes/google/ipc/debug_test.go
index 7290cd5..547d350 100644
--- a/runtimes/google/ipc/debug_test.go
+++ b/runtimes/google/ipc/debug_test.go
@@ -36,7 +36,7 @@
 	defer sm.Shutdown()
 	ns := tnaming.NewSimpleNamespace()
 	ctx := testContext()
-	server, err := InternalNewServer(ctx, sm, ns, nil, options.ReservedNameDispatcher{debugDisp}, vc.LocalPrincipal{pserver})
+	server, err := InternalNewServer(ctx, sm, ns, nil, ReservedNameDispatcher{debugDisp}, vc.LocalPrincipal{pserver})
 	if err != nil {
 		t.Fatalf("InternalNewServer failed: %v", err)
 	}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 45f3a61..f46b0b7 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -6,7 +6,6 @@
 	"fmt"
 	"io"
 	"net"
-	"os"
 	"path/filepath"
 	"reflect"
 	"runtime"
@@ -352,10 +351,8 @@
 }
 
 func matchesErrorPattern(err error, id verror.IDAction, pattern string) bool {
-	if len(pattern) > 0 && err != nil {
-		if strings.Index(err.Error(), pattern) < 0 {
-			fmt.Fprintf(os.Stderr, "got error msg: %q, expected: %q\n", err, pattern)
-		}
+	if len(pattern) > 0 && err != nil && strings.Index(err.Error(), pattern) < 0 {
+		return false
 	}
 	if err == nil && id.ID == "" {
 		return true
@@ -427,73 +424,91 @@
 
 func TestRPCServerAuthorization(t *testing.T) {
 	const (
-		vcErr   = "VC handshake failed"
-		nameErr = "do not match pattern"
+		vcErr      = "VC handshake failed"
+		nameErr    = "do not match pattern"
+		allowedErr = "set of allowed servers"
 	)
 	var (
 		pprovider, pclient, pserver = tsecurity.NewPrincipal("root"), tsecurity.NewPrincipal(), tsecurity.NewPrincipal()
 		pdischarger                 = pprovider
-
-		now = time.Now()
-
-		serverName          = "mountpoint/server"
-		dischargeServerName = "mountpoint/dischargeserver"
+		now                         = time.Now()
+		noErrID                     verror.IDAction
 
 		// Third-party caveats on blessings presented by server.
-		cavTPValid   = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.ExpiryCaveat(now.Add(24*time.Hour))))
-		cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.ExpiryCaveat(now.Add(-1*time.Second))))
+		cavTPValid   = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.ExpiryCaveat(now.Add(24*time.Hour))))
+		cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.ExpiryCaveat(now.Add(-1*time.Second))))
 
 		// Server blessings.
 		bServer          = bless(pprovider, pserver, "server")
 		bServerExpired   = bless(pprovider, pserver, "server", mkCaveat(security.ExpiryCaveat(time.Now().Add(-1*time.Second))))
 		bServerTPValid   = bless(pprovider, pserver, "serverWithTPCaveats", cavTPValid)
 		bServerTPExpired = bless(pprovider, pserver, "serverWithTPCaveats", cavTPExpired)
+		bTwoBlessings, _ = security.UnionOfBlessings(bServer, bServerTPValid)
 
 		mgr   = imanager.InternalNew(naming.FixedRoutingID(0x1111111))
 		ns    = tnaming.NewSimpleNamespace()
 		tests = []struct {
-			server  security.Blessings       // blessings presented by the server to the client.
-			pattern security.BlessingPattern // pattern on the server identity expected by the client.
+			server  security.Blessings           // blessings presented by the server to the client.
+			name    string                       // name provided by the client to StartCall
+			allowed options.AllowedServersPolicy // option provided to StartCall.
 			errID   verror.IDAction
 			err     string
 		}{
-			// Client accepts talking to the server only if the server's blessings match the provided pattern
-			{bServer, security.AllPrincipals, verror.IDAction{}, ""},
-			{bServer, "root/server", verror.IDAction{}, ""},
-			{bServer, "root/otherserver", verror.NotTrusted, nameErr},
-			{bServer, "otherroot/server", verror.NotTrusted, nameErr},
+			// Client accepts talking to the server only if the
+			// server's blessings match the provided pattern
+			{bServer, "mountpoint/server", nil, noErrID, ""},
+			{bServer, "[root/server]mountpoint/server", nil, noErrID, ""},
+			{bServer, "[root/otherserver]mountpoint/server", nil, verror.NotTrusted, nameErr},
+			{bServer, "[otherroot/server]mountpoint/server", nil, verror.NotTrusted, nameErr},
 
-			// and, if the server's blessing has third-party caveats then the server provides
-			// appropriate discharges.
-			{bServerTPValid, security.AllPrincipals, verror.IDAction{}, ""},
-			{bServerTPValid, "root/serverWithTPCaveats", verror.IDAction{}, ""},
-			{bServerTPValid, "root/otherserver", verror.NotTrusted, nameErr},
-			{bServerTPValid, "otherroot/server", verror.NotTrusted, nameErr},
+			// and, if the server's blessing has third-party
+			// caveats then the server provides appropriate
+			// discharges.
+			{bServerTPValid, "mountpoint/server", nil, noErrID, ""},
+			{bServerTPValid, "[root/serverWithTPCaveats]mountpoint/server", nil, noErrID, ""},
+			{bServerTPValid, "[root/otherserver]mountpoint/server", nil, verror.NotTrusted, nameErr},
+			{bServerTPValid, "[otherroot/server]mountpoint/server", nil, verror.NotTrusted, nameErr},
 
-			// Client does not talk to a server that presents expired blessings.
-			{bServerExpired, security.AllPrincipals, verror.NotTrusted, vcErr},
+			// Client does not talk to a server that presents
+			// expired blessings (because the blessing store is
+			// configured to only talk to root/...).
+			{bServerExpired, "mountpoint/server", nil, verror.NotTrusted, vcErr},
 
-			// Client does not talk to a server that fails to provide discharges for
-			// third-party caveats on the blessings presented by it.
-			{bServerTPExpired, security.AllPrincipals, verror.NotTrusted, vcErr},
+			// Client does not talk to a server that fails to
+			// provide discharges for third-party caveats on the
+			// blessings presented by it.
+			{bServerTPExpired, "mountpoint/server", nil, verror.NotTrusted, vcErr},
+
+			// Testing the AllowedServersPolicy option.
+			{bServer, "mountpoint/server", options.AllowedServersPolicy{"otherroot/..."}, verror.NotTrusted, allowedErr},
+			{bServer, "[root/server]mountpoint/server", options.AllowedServersPolicy{"otherroot/..."}, verror.NotTrusted, allowedErr},
+			{bServer, "[otherroot/server]mountpoint/server", options.AllowedServersPolicy{"root/server"}, verror.NotTrusted, nameErr},
+			{bServer, "[root/server]mountpoint/server", options.AllowedServersPolicy{"root/..."}, noErrID, ""},
+			// Server presents two blessings: One that satisfies
+			// the pattern provided to StartCall and one that
+			// satisfies the AllowedServersPolicy, so the server is
+			// authorized.
+			{bTwoBlessings, "[root/serverWithTPCaveats]mountpoint/server", options.AllowedServersPolicy{"root/server"}, noErrID, ""},
 		}
 	)
 
-	_, server := startServer(t, pserver, mgr, ns, serverName, testServerDisp{&testServer{}})
-	defer stopServer(t, server, ns, serverName)
+	_, server := startServer(t, pserver, mgr, ns, "mountpoint/server", testServerDisp{&testServer{}})
+	defer stopServer(t, server, ns, "mountpoint/server")
 
 	// Start the discharge server.
-	_, dischargeServer := startServer(t, pdischarger, mgr, ns, dischargeServerName, testutil.LeafDispatcher(&dischargeServer{}, &acceptAllAuthorizer{}))
-	defer stopServer(t, dischargeServer, ns, dischargeServerName)
+	_, dischargeServer := startServer(t, pdischarger, mgr, ns, "mountpoint/dischargeserver", testutil.LeafDispatcher(&dischargeServer{}, &acceptAllAuthorizer{}))
+	defer stopServer(t, dischargeServer, ns, "mountpoint/dischargeserver")
 
-	// Make the client and server principals trust root certificates from pprovider
+	// Make the client and server principals trust root certificates from
+	// pprovider
 	pclient.AddToRoots(pprovider.BlessingStore().Default())
 	pserver.AddToRoots(pprovider.BlessingStore().Default())
-	// Set a blessing that the client is willing to share with servers with blessings
-	// from pprovider.
+	// Set a blessing that the client is willing to share with servers with
+	// blessings from pprovider.
 	pclient.BlessingStore().Set(bless(pprovider, pclient, "client"), "root/...")
+
 	for i, test := range tests {
-		name := fmt.Sprintf("(%q@%q)", test.pattern, test.server)
+		name := fmt.Sprintf("(Name:%q, Server:%q, Allowed:%v)", test.name, test.server, test.allowed)
 		if err := pserver.BlessingStore().SetDefault(test.server); err != nil {
 			t.Fatalf("SetDefault failed on server's BlessingStore: %v", err)
 		}
@@ -506,9 +521,12 @@
 			t.Errorf("%s: failed to create client: %v", name, err)
 			continue
 		}
-		ctx := testContextWithoutDeadline()
-		dctx, cancel := context.WithTimeout(ctx, 10*time.Second)
-		call, err := client.StartCall(dctx, fmt.Sprintf("[%s]%s/suffix", test.pattern, serverName), "Method", nil)
+		ctx, cancel := context.WithTimeout(testContextWithoutDeadline(), 10*time.Second)
+		var opts []ipc.CallOpt
+		if test.allowed != nil {
+			opts = append(opts, test.allowed)
+		}
+		call, err := client.StartCall(ctx, test.name, "Method", nil, opts...)
 		if !matchesErrorPattern(err, test.errID, test.err) {
 			t.Errorf(`%d: %s: client.StartCall: got error "%v", want to match "%v"`, i, name, err, test.err)
 		} else if call != nil {
@@ -516,8 +534,12 @@
 			if proof == nil {
 				t.Errorf("%s: Returned nil for remote blessings", name)
 			}
-			if !test.pattern.MatchedBy(blessings...) {
-				t.Errorf("%s: %q.MatchedBy(%v) failed", name, test.pattern, blessings)
+			// Currently all tests are configured so that the only
+			// blessings presented by the server that are
+			// recognized by the client match the pattern
+			// "root/..."
+			if len(blessings) < 1 || !security.BlessingPattern("root/...").MatchedBy(blessings...) {
+				t.Errorf("%s: Client sees server as %v, expected a single blessing matching root/...", name, blessings)
 			}
 		}
 		cancel()
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 26fd9f0..12eeba0 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -138,6 +138,14 @@
 
 func (PreferredServerResolveProtocols) IPCServerOpt() {}
 
+// ReservedNameDispatcher specifies the dispatcher that controls access
+// to framework managed portion of the namespace.
+type ReservedNameDispatcher struct {
+	Dispatcher ipc.Dispatcher
+}
+
+func (ReservedNameDispatcher) IPCServerOpt() {}
+
 func InternalNewServer(ctx *context.T, streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ServerOpt) (ipc.Server, error) {
 	ctx, _ = vtrace.SetNewSpan(ctx, "NewServer")
 	statsPrefix := naming.Join("ipc", "server", "routing-id", streamMgr.RoutingID().String())
@@ -170,7 +178,7 @@
 			}
 		case options.ServesMountTable:
 			s.servesMountTable = bool(opt)
-		case options.ReservedNameDispatcher:
+		case ReservedNameDispatcher:
 			s.dispReserved = opt.Dispatcher
 		case PreferredServerResolveProtocols:
 			s.preferredProtocols = []string(opt)
diff --git a/runtimes/google/ipc/server_test.go b/runtimes/google/ipc/server_test.go
index 14548b3..8502416 100644
--- a/runtimes/google/ipc/server_test.go
+++ b/runtimes/google/ipc/server_test.go
@@ -408,10 +408,19 @@
 	if err := <-progress; err != nil {
 		t.Fatalf(err.Error())
 	}
-	// Now that the the RPC is done the server should be able to stop.
-	status = server.Status()
-	if got, want := status.State, ipc.ServerStopped; got != want {
-		t.Fatalf("got %s, want %s", got, want)
+
+	// Now that the RPC is done, the server should be able to stop.
+	then = time.Now()
+	for {
+		status = server.Status()
+		if got, want := status.State, ipc.ServerStopped; got != want {
+			if time.Now().Sub(then) > time.Minute {
+				t.Fatalf("got %s, want %s", got, want)
+			}
+		} else {
+			break
+		}
+		time.Sleep(100 * time.Millisecond)
 	}
 }
 
diff --git a/runtimes/google/rt/runtime.go b/runtimes/google/rt/runtime.go
index f52b2d9..4a5d3da 100644
--- a/runtimes/google/rt/runtime.go
+++ b/runtimes/google/rt/runtime.go
@@ -15,7 +15,6 @@
 	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/ipc/stream"
 	"v.io/core/veyron2/naming"
-	"v.io/core/veyron2/options"
 	"v.io/core/veyron2/security"
 	"v.io/core/veyron2/verror2"
 	"v.io/core/veyron2/vlog"
@@ -223,7 +222,7 @@
 	otherOpts := append([]ipc.ServerOpt{}, opts...)
 	otherOpts = append(otherOpts, vc.LocalPrincipal{principal})
 	if reserved, ok := ctx.Value(reservedNameKey).(*reservedNameDispatcher); ok {
-		otherOpts = append(otherOpts, options.ReservedNameDispatcher{reserved.dispatcher})
+		otherOpts = append(otherOpts, iipc.ReservedNameDispatcher{reserved.dispatcher})
 		otherOpts = append(otherOpts, reserved.opts...)
 	}
 	if protocols, ok := ctx.Value(protocolsKey).([]string); ok {
@@ -323,7 +322,7 @@
 	otherOpts = append(otherOpts, vc.LocalPrincipal{p}, &imanager.DialTimeout{5 * time.Minute})
 
 	if protocols, ok := ctx.Value(protocolsKey).([]string); ok {
-		otherOpts = append(otherOpts, options.PreferredProtocols(protocols))
+		otherOpts = append(otherOpts, iipc.PreferredProtocols(protocols))
 	}
 
 	client, err := iipc.InternalNewClient(sm, ns, otherOpts...)
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index c42fa1d..0628c47 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -1,8 +1,29 @@
 #!/bin/bash
 
 # Test the device manager and related services and tools.
+#
+#
+# By default, this script tests the device manager in a fashion amenable
+# to automatic testing: the --single_user is passed to the device
+# manager so that all device manager components run as the same user and
+# no user input (such as an agent pass phrase) is needed.
+#
+# When this script is invoked with the --with_suid <user> flag, it
+# installs the device manager in its more secure multi-account
+# configuration where the device manager runs under the account of the
+# invoker and test apps will be executed as <user>. This mode will
+# require root permisisons to install and may require configuring an
+# agent passphrase.
+#
+# For exanple:
+#
+#   ./test.sh --with_suid vanaguest
+#
+# to test a device manager with multi-account support enabled for app
+# account vanaguest.
+#
 
-source "$(go list -f {{.Dir}} v.io/core/shell/lib)/shell_test.sh"
+ source "$(go list -f {{.Dir}} v.io/core/shell/lib)/shell_test.sh"
 
 # Run the test under the security agent.
 shell_test::enable_agent "$@"
@@ -83,6 +104,12 @@
 }
 
 main() {
+  local -r WITH_SUID="${1:-no}"
+   if [[ "${WITH_SUID}" == "--with_suid" ]]; then
+    local -r SUID_USER="$2"
+    SUDO_USER="root"
+  fi
+
   cd "${WORKDIR}"
   build
 
@@ -90,14 +117,17 @@
   cp "${AGENTD_BIN}" "${SUIDHELPER_BIN}" "${INITHELPER_BIN}" "${DEVICEMANAGER_BIN}" "${BIN_STAGING_DIR}"
   shell_test::setup_server_test
 
-  # TODO(caprita): Expose an option to turn --single_user off, so we can run
-  # test.sh by hand and exercise the code that requires root privileges.
-
   # Install and start device manager.
   DM_INSTALL_DIR=$(shell::tmp_dir)
 
   export VANADIUM_DEVICE_DIR="${DM_INSTALL_DIR}/dm"
-  "${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" --single_user -- --veyron.tcp.address=127.0.0.1:0
+
+  if [[ "${WITH_SUID}" == "--with_suid" ]]; then
+    "${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" --veyron.tcp.address=127.0.0.1:0
+  else
+      "${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" --single_user -- --veyron.tcp.address=127.0.0.1:0
+  fi
+
   "${VRUN}" "${DEVICE_SCRIPT}" start
   local -r DM_NAME=$(hostname)
   DM_EP=$(wait_for_mountentry "${NAMESPACE_BIN}" 5 "${DM_NAME}")
@@ -119,6 +149,12 @@
   # Claim the device as "alice/myworkstation".
   "${DEVICE_BIN}" claim "${DM_NAME}/device" myworkstation
 
+  if [[ "${WITH_SUID}" == "--with_suid" ]]; then
+    "${DEVICE_BIN}" associate add "${DM_NAME}/device"   "${SUID_USER}"  "alice"
+     shell_test::assert_eq   "$("${DEVICE_BIN}" associate list "${DM_NAME}/device")" \
+       "alice ${SUID_USER}" "${LINENO}"
+  fi
+
   # Verify the device's default blessing is as expected.
   shell_test::assert_eq "$("${DEBUG_BIN}" stats read "${DM_NAME}/__debug/stats/security/principal/blessingstore" | head -1 | sed -e 's/^.*Default blessings: '//)" \
     "alice/myworkstation" "${LINENO}"
@@ -173,6 +209,8 @@
   # Verify that the instance shows up when globbing the device manager.
   shell_test::assert_eq "$("${NAMESPACE_BIN}" glob "${DM_NAME}/apps/BINARYD/*/*")" "${INSTANCE_NAME}" "${LINENO}"
 
+  # TODO(rjkroege): Verify that the app is actually running as ${SUID_USER}
+
   # Verify the app's default blessing.
   shell_test::assert_eq "$("${DEBUG_BIN}" stats read "${INSTANCE_NAME}/stats/security/principal/blessingstore" | head -1 | sed -e 's/^.*Default blessings: '//)" \
     "alice/myapp/BINARYD" "${LINENO}"