示例#1
0
文件: ca.go 项目: kevinawalsh/taoca
func SubmitAndInstall(keys *tao.Keys, csr *CSR) {
	verbose.Printf("Obtaining certificate from CA (may take a while)\n")
	resp, err := Submit(keys, csr)
	options.FailIf(err, "can't obtain X509 certificate from CA")
	if len(resp) == 0 {
		options.Fail(nil, "no x509 certificates returned from CA")
	}
	// Add the certs to our keys...
	keys.Cert["default"] = resp[0]
	for i, c := range resp {
		name := "ca"
		if i > 0 {
			name = fmt.Sprintf("ca-%d", i)
		}
		keys.Cert[name] = c
	}
	if keys.X509Path("default") != "" {
		err = keys.SaveCerts()
	}
	options.FailIf(err, "can't save X509 certificates")

	chain := keys.CertChain("default")
	verbose.Printf("Obtained certfificate chain of length %d:\n", len(chain))
	for i, cert := range chain {
		verbose.Printf("  Cert[%d] Subject: %s\n", i, x509txt.RDNString(cert.Subject))
	}
	if Warn {
		fmt.Println("Note: You may need to install root CA's key into the browser.")
	}
}
示例#2
0
func handle(sig os.Signal, pid int) todo {
	switch sig {
	case syscall.SIGTSTP:
		send(pid, syscall.SIGTSTP)
		verbose.Printf("[stopping]\n")
		syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
		time.Sleep(moment)
		verbose.Printf("[resuming]\n")
		send(pid, syscall.SIGCONT)
		return resumed
	case syscall.SIGHUP: // tty hangup (e.g. via disown)
		verbose.Set(false)
		os.Stdin.Close()
		os.Stdout.Close()
		os.Stderr.Close()
		send(pid, sig.(syscall.Signal))
		// Our tty is gone, so there is little left to do. We could hang
		// around proxying signals (e.g. those sent via kill). But those
		// could be just as easily sent directly to the hosted program,
		// so let's not bother.
		return done
	default:
		send(pid, sig.(syscall.Signal))
	}
	return cont
}
示例#3
0
func startHost(domain *tao.Domain) {

	if *options.Bool["daemon"] && *options.Bool["foreground"] {
		options.Usage("Can supply only one of -daemon and -foreground")
	}
	if *options.Bool["daemon"] {
		daemonize()
	}

	cfg := configureFromFile()
	configureFromOptions(cfg)
	host, err := loadHost(domain, cfg)
	options.FailIf(err, "Can't create host")

	sockPath := path.Join(hostPath(), "admin_socket")
	// Set the socketPath directory go+rx so tao_launch can access sockPath and
	// connect to this linux host, even when tao_launch is run as non-root.
	err = os.Chmod(path.Dir(sockPath), 0755)
	options.FailIf(err, "Can't change permissions")
	uaddr, err := net.ResolveUnixAddr("unix", sockPath)
	options.FailIf(err, "Can't resolve unix socket")
	sock, err := net.ListenUnix("unix", uaddr)
	options.FailIf(err, "Can't create admin socket")
	defer sock.Close()
	err = os.Chmod(sockPath, 0666)
	if err != nil {
		sock.Close()
		options.Fail(err, "Can't change permissions on admin socket")
	}

	go func() {
		verbose.Printf("Linux Tao Service (%s) started and waiting for requests\n", host.HostName())
		err = tao.NewLinuxHostAdminServer(host).Serve(sock)
		verbose.Printf("Linux Tao Service finished\n")
		sock.Close()
		options.FailIf(err, "Error serving admin requests")
		os.Exit(0)
	}()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
	<-c
	verbose.Printf("Linux Tao Service shutting down\n")
	err = shutdown()
	if err != nil {
		sock.Close()
		options.Fail(err, "Can't shut down admin socket")
	}

	// The above goroutine will normally end by calling os.Exit(), so we
	// can block here indefinitely. But if we get a second kill signal,
	// let's abort.
	verbose.Printf("Waiting for shutdown....\n")
	<-c
	options.Fail(nil, "Could not shut down linux_host")
}
示例#4
0
文件: ca.go 项目: kevinawalsh/taoca
// LoadKeys loads and https key and cert from a directory. This is meant to be
// called from user-facing apps.
func LoadKeys(kdir string) *tao.Keys {
	// TODO(kwalsh) merge x509 load/save code into keys.go
	keys, err := tao.LoadOnDiskTaoSealedKeys(tao.Signing, tao.Parent(), kdir, tao.SealPolicyDefault)
	options.FailIf(err, "can't load tao-sealed HTTPS/TLS keys")

	chain := keys.CertChain("default")
	verbose.Printf("Using existing certfificate chain of length %d:\n", len(chain))
	for i, cert := range chain {
		verbose.Printf("  Cert[%d] Subject: %s\n", i, x509txt.RDNString(cert.Subject))
	}

	return keys
}
示例#5
0
func doError(ms util.MessageStream, err error, status rendezvous.ResponseStatus, detail string) {
	if err != nil {
		fmt.Printf("error handling request: %s\n", err)
	}
	verbose.Printf("sending error response: status=%s detail=%q\n", status, detail)
	resp := &rendezvous.Response{
		Status:      &status,
		ErrorDetail: proto.String(detail),
	}
	sendResponse(ms, resp)
}
示例#6
0
func doResponse(conn *tao.Conn) {
	defer conn.Close()

	if conn.Peer() == nil {
		verbose.Printf("netlog: connection from anonymous\n")
	} else {
		verbose.Printf("netlog: connection from peer %s\n", *conn.Peer())
	}

	for {
		req, err := conn.ReadString()
		if err == io.EOF {
			fmt.Fprintf(os.Stderr, "netlog: connection closed\n")
			break
		}
		if err != nil {
			fmt.Fprintf(os.Stderr, "netlog: can't read: %s\n", err)
			break
		}

		verbose.Printf("netlog: got %s request\n", req)

		if req == "POST" {
			if conn.Peer() == nil {
				conn.WriteString("DENIED")
				break
			}
			verbose.Printf("netlog: peer is %s\n", *conn.Peer())
			msg, err := conn.ReadString()
			if err != nil {
				conn.WriteString("BAD")
				break
			}
			e := &netlog.LogEntry{Prin: *conn.Peer(), Msg: msg}
			lock.Lock()
			log = append(log, e)
			lock.Unlock()
			conn.WriteString("OK")
		} else if req == "GET" {
			lock.RLock()
			t := log
			lock.RUnlock()
			conn.WriteString("OK")
			conn.WriteInt(len(t))
			for _, e := range t {
				conn.WriteString(e.Prin.String())
				conn.WriteString(e.Msg)
			}
		} else {
			conn.WriteString("BAD")
			break
		}
	}

	if conn.Peer() == nil {
		verbose.Printf("netlog: connection closed from anonymous\n")
	} else {
		verbose.Printf("netlog: connection closed from peer %s\n", *conn.Peer())
	}
}
示例#7
0
func retractExecute(path, host string, domain *tao.Domain) {
	prin := makeHostPrin(host)
	subprin, err := makeProgramSubPrin(path)
	if err == nil {
		prog := prin.MakeSubprincipal(subprin)
		verbose.Printf("Retracting program authorization to execute:\n"+
			"  path: %s\n"+
			"  host: %s\n"+
			"  name: %s\n", path, prin, subprin)
		err := domain.Guard.Retract(prog, "Execute", nil)
		options.FailIf(err, "Can't retract program authorization from domain")
	}
}
示例#8
0
func doResponses(conn *tao.Conn) {
	defer conn.Close()

	var peer *string
	if conn.Peer() != nil {
		peer = proto.String(conn.Peer().String())
		verbose.Printf("Processing connection requests for peer %s\n", *peer)
		netlog.Log("rendezvous: connection from peer %s", *peer)
	} else {
		verbose.Printf("Processing connection requests for anonymous peer\n")
		netlog.Log("rendezvous: connection from anonymous")
	}

	for {
		var req rendezvous.Request
		if err := conn.ReadMessage(&req); err != nil {
			if err != io.EOF {
				doError(conn, err, rendezvous.ResponseStatus_RENDEZVOUS_BAD_REQUEST, "failed to read request")
			}
			break
		}
		doResponse(&req, conn, peer)
	}
	lock.Lock()
	for k, v := range bindings {
		if v.expiration.IsZero() && v.conn == conn {
			delete(bindings, k)
			verbose.Printf("Expired binding upon close: %s\n", k)
		}
	}
	lock.Unlock()
	verbose.Println("Done processing connection requests")

	if peer == nil {
		netlog.Log("rendezvous: connection closed from anonymous")
	} else {
		netlog.Log("rendezvous: connection closed from peer %s", *peer)
	}
}
示例#9
0
func expire(now time.Time) {
	for k, v := range bindings {
		v.Age = proto.Uint64(uint64(now.Sub(v.added)))
		if !v.expiration.IsZero() {
			ttl := int64(v.expiration.Sub(now))
			if ttl <= 0 {
				delete(bindings, k)
				verbose.Printf("Expired binding: %s\n", k)
			} else {
				v.Ttl = proto.Uint64(uint64(ttl))
			}
		}
	}
}
示例#10
0
func daemonize() {
	// For our purposes, "daemon" means being a session leader.
	sid, _, errno := syscall.Syscall(syscall.SYS_GETSID, 0, 0, 0)
	var err error
	if errno != 0 {
		err = errno
	}
	options.FailIf(err, "Can't get process SID")
	if int(sid) != syscall.Getpid() {
		// Go does not support daemonize(), and we can't simply call setsid
		// because PID may be equal to GID. Using exec.Cmd with the Setsid=true
		// will fork, ensuring that PID differs from GID, then call setsid, then
		// exec ourself again in the new session.
		path, err := os.Readlink("/proc/self/exe")
		options.FailIf(err, "Can't get path to self executable")
		// special case: keep stderr if -logtostderr or -alsologtostderr
		stderr := os.Stderr
		if !isBoolFlagSet("logtostderr") && !isBoolFlagSet("alsologtostderr") {
			stderr = nil
		}
		spa := &syscall.SysProcAttr{
			Setsid: true, // Create session.
		}
		daemon := exec.Cmd{
			Path:        path,
			Args:        os.Args,
			Stderr:      stderr,
			SysProcAttr: spa,
		}
		err = daemon.Start()
		options.FailIf(err, "Can't become daemon")
		verbose.Printf("Linux Tao Host running as daemon\n")
		os.Exit(0)
	} else {
		verbose.Printf("Already a session leader?\n")
	}
}
示例#11
0
func addExecute(path, host string, domain *tao.Domain) {
	prin := makeHostPrin(host)
	subprin, err := makeProgramSubPrin(path)
	if err == nil {
		prog := prin.MakeSubprincipal(subprin)
		verbose.Printf("Authorizing program to execute:\n"+
			"  path: %s\n"+
			"  host: %s\n"+
			"  name: %s\n", path, prin, subprin)
		err := domain.Guard.Authorize(prog, "Execute", nil)
		options.FailIf(err, "Can't authorize program in domain")
		err = domain.Save()
		options.FailIf(err, "Can't save domain")
	}
}
示例#12
0
func isCtty(fd int) bool {
	// One would hope there was a simple way to figure out what our controlling
	// tty is. Or at least check if stdin is coming from our controlling tty (as
	// opposed to just any old terminal). Alas, the infuriating morass that
	// passes for job control provides no such ability. Of no help:
	// stat(/dev/tty), readlink(/dev/fd/0), open(/dev/stdin), open(/dev/tty),
	// cat(/proc/self/fdinfo/0), stat(/proc/self/fd/0), ioctl, tty_ioctl, TIOC*,
	// anything to do with sid, pgrp, pgid, /dev/console, the tty command, $TTY,
	// $SSH_TTY. Since I am on a mission, I delved into the source for /bin/ps
	// to discover /proc/self/stat contains the major/minor numbers for the
	// controlling tty. And stat(stdin).Rdev provides the same info. If they
	// differ, I'm going to conclude -- oh so tentatively -- that stdin is NOT
	// our controlling tty. If they match, or anything goes wrong, we will
	// assume that stdin, if it is a terminal, is our ctty.
	if !terminal.IsTerminal(fd) {
		return false
	}
	var s syscall.Stat_t
	err := syscall.Fstat(fd, &s)
	if err != nil {
		verbose.Printf("[warning: fstat(%d) failed: %v]\n", fd, err)
		return true
	}
	name := "/proc/self/stat"
	f, err := os.Open(name)
	if err != nil {
		verbose.Printf("[warning: open(%q) failed: %v]\n", name, err)
		return true
	}
	b, err := ioutil.ReadAll(f)
	if err != nil {
		verbose.Printf("[warning: read(%q) failed: %v]\n", name, err)
		return true
	}
	a := strings.Split(string(b), " ")
	tty_nr := 6
	if len(a) <= tty_nr {
		verbose.Printf("[warning: read(%q) borked: only %d fields]\n", name, len(a))
		return true
	}
	ctty, err := strconv.Atoi(a[tty_nr])
	if err != nil {
		verbose.Printf("[warning: read(%q) borked: tty_nr = %v]\n", name, a[tty_nr])
		return true
	}
	if uint64(ctty) != s.Rdev {
		verbose.Printf("[warning: stdin is a tty, but not ctty]\n")
		return false
	}
	return true
}
示例#13
0
func runHosted(client *tao.LinuxHostAdminClient, args []string) {
	var err error

	if len(args) == 0 {
		options.Usage("Missing program path and arguments")
	}

	spec := new(tao.HostedProgramSpec)

	spec.ContainerType = "process"
	spec.Path = args[0]
	for _, prefix := range []string{"process", "docker", "kvm_coreos", "kvm_coreos_linuxhost"} {
		if strings.HasPrefix(spec.Path, prefix+":") {
			spec.ContainerType = prefix
			spec.Path = strings.TrimPrefix(spec.Path, prefix+":")
		}
	}

	switch spec.ContainerType {
	case "process":
		dirs := util.LiberalSearchPath()
		binary := util.FindExecutable(args[0], dirs)
		if binary == "" {
			options.Fail(nil, "Can't find `%s` on path '%s'", args[0], strings.Join(dirs, ":"))
		}
		spec.ContainerArgs = []string{spec.Path}
		spec.Args = args[1:]
		spec.Path = binary
	case "kvm_coreos":
		spec.ContainerArgs, spec.Args = split(args[1:], "--")
		// TODO(kwalsh) Put memory, other qemu args into container args.
	case "docker", "kvm_coreos_linuxhost":
		// args contains [ "docker:argv0", docker_args..., "--", prog_args... ]
		spec.ContainerArgs, spec.Args = split(args, "--")
		// Replace docker arg 0 with valid image name constructed from // base(argv[0]).
		r, _ := regexp.Compile("[^a-zA-Z0-9_.]+")
		spec.ContainerArgs[0] = r.ReplaceAllLiteralString(path.Base(spec.Path), "_")
	}

	spec.Dir, err = os.Getwd()
	options.FailIf(err, "Can't get working directory")

	if !path.IsAbs(spec.Path) {
		spec.Path = path.Join(spec.Dir, spec.Path)
	}

	pidfile := *options.String["pidfile"]
	var pidOut *os.File
	if pidfile == "-" {
		pidOut = os.Stdout
	} else if pidfile != "" {
		pidOut, err = os.Create(pidfile)
		options.FailIf(err, "Can't open pid file")
	}

	namefile := *options.String["namefile"]
	var nameOut *os.File
	if namefile == "-" {
		nameOut = os.Stdout
	} else if namefile != "" {
		nameOut, err = os.Create(namefile)
		options.FailIf(err, "Can't open name file")
	}

	daemon := *options.Bool["daemon"]
	disown := *options.Bool["disown"]

	var pr, pw *os.File
	proxying := false
	tty := isCtty(int(os.Stdin.Fd()))
	if daemon {
		// stdio is nil
	} else if disown {
		// We are assuming that if stdin is a terminal, it is our controlling
		// terminal. I don't know any way to verify it, but it seems likely.
		if tty {
			// stdin is nil, else they would steal input from tty
		} else {
			spec.Stdin = os.Stdin
		}
		spec.Stdout = os.Stdout
		spec.Stderr = os.Stderr
	} else {
		// interactive
		proxying = tty
		if proxying {
			pr, pw, err = os.Pipe()
			options.FailIf(err, "Can't pipe")
			spec.Stdin = pr
		} else {
			spec.Stdin = os.Stdin
		}
		spec.Stdout = os.Stdout
		spec.Stderr = os.Stderr
		verbose.Printf("[proxying stdin]\n")
	}

	// Start catching signals early, buffering a few, so we don't miss any. We
	// don't proxy SIGTTIN. However, we do catch it and stop ourselves, rather
	// than letting the OS stop us. This is necessary so that we can send
	// SIGCONT to the child at the right times.
	// Here is the easy case
	//   we start in background
	//   we fork (output starts going)
	//   we are background, so leave SIGTTIN handling at the default
	//   we read and get SIGTTIN, so are stopped
	//   child is not stopped, it keeps outputting, as desired
	//   upon fg, we get SIGCONT, start dropping SIGTTIN and looping for input and signals
	// Here is the tricky case:
	//   we start in foreground
	//   we fork (output starts going)
	//   we are foreground, so catch and drop SIGTTIN (we use SIGTSTP instead)
	//   we get SIGTSTP via ctrl-z
	//   we send child SIGTSTP, so it stops
	//      [we are still dropping SIGTTIN]
	//   we send ourselves SIGSTOP, so we stop
	//   we get SIGCONT via either bg or fg
	//      [if bg, now furiously catching and dropping SIGTTIN]
	//      [if fg, dropping too, but there should not be any SIGTTIN]
	//   send child the SIGCONT
	//   if we are foreground, so go back to top of loop
	//   if we are background, reset SIGTTIN which causes us to stop
	//
	// The basic invariant we are trying to maintain is that when we are
	// foreground we catch and drop SIGTTIN, allowing us to properly handle
	// SIGTSTP events. There shouldn't be any SIGTTIN anyway, except for the
	// brief moments when we are transitioning to stopped.
	// And when the child is supposed to be running in the background, we should
	// leave the default SIGTTIN behavior, so that the OS will stop our read
	// loop.

	signals := make(chan os.Signal, 10) // some buffering
	signal.Notify(signals,
		syscall.SIGINT,  // Ctrl-C
		syscall.SIGTERM, // SIGINT wannabe (e.g. via kill)
		syscall.SIGQUIT, // Ctrl-\
		syscall.SIGTSTP, // Ctrl-Z
		syscall.SIGHUP,  // tty hangup (e.g. via disown)
		syscall.SIGABRT, // abort (e.g. via kill)
		syscall.SIGUSR1, // user-defined (e.g. via kill)
		syscall.SIGUSR2, // user-defined (e.g. via kill)
	)

	// Start the hosted program
	subprin, pid, err := client.StartHostedProgram(spec)
	options.FailIf(err, "Can't start hosted program")
	verbose.Printf("[started hosted program with pid %d]\n", pid)
	verbose.Printf("[subprin is %v]\n", subprin)

	if pidOut != nil {
		fmt.Fprintln(pidOut, pid)
		pidOut.Close()
	}

	if nameOut != nil {
		fmt.Fprintln(nameOut, subprin)
		nameOut.Close()
	}

	if disown || daemon {
		return
	}

	// Listen for exit status from host
	status := make(chan int, 1)
	go func() {
		s, _ := client.WaitHostedProgram(pid, subprin)
		// For short programs, we often lose the race, so
		// we get a "no such hosted program" error. Ignore it.
		status <- s
	}()

	wasForeground := false
	if proxying && isForeground() {
		verbose.Printf("[in foreground]\n")
		dropSIGTTIN()
		wasForeground = true
	}

	// Proxy stdin, if needed
	if proxying {
		pr.Close()
		go func() {
			_, err := io.Copy(pw, os.Stdin)
			options.WarnIf(err, "Can't copy from stdin to pipe")
			pw.Close()
		}()
	}

	// If we are proxying and (were) background, we should probably
	// have done a read() by now and gotten SIGTTIN and stopped. Let's
	// pause a moment to be sure the read() happens.
	time.Sleep(moment)

	// By this point, if we had been foreground, we might still be. Or, we might
	// have been foreground but just gotten SIGTSTP and are now madly dropping
	// SIGTTIN until we get into the loop below to handle the SIGTSTP.
	//
	// Alternatively, if we had been background, we would have been stopped by
	// the default SIGTTIN, so the only way we would be here is if we later got
	// pulled foreground via fg. We want to be dropping SIGTTIN in case we get a
	// SIGTSTP.
	if proxying && !wasForeground {
		dropSIGTTIN()
	}

	next := cont
	for next != done {
		select {
		case s := <-status:
			verbose.Printf("[hosted program exited, %s]\n", exitCode(s))
			next = done
		case sig := <-signals:
			next = handle(sig, pid)
		}
		if next == resumed && proxying && !isForeground() {
			// Need to toggle SIGTTIN handling and block (that's the only way to
			// stop spinning on SIGTTIN), but only after handling all pending
			// signals (e.g. SIGCONT then SIGHUP, or SIGCONT then SIGTERM).
			for next == resumed {
				select {
				case s := <-status:
					verbose.Printf("[hosted program exited, %s]\n", exitCode(s))
					next = done
				case sig := <-signals:
					next = handle(sig, pid)
					if next == cont {
						next = resumed
					}
				default:
					next = cont
					defaultSIGTTIN()
					time.Sleep(moment)
					dropSIGTTIN()
				}
			}
		}
	}
	signal.Stop(signals)
}
示例#14
0
func defaultSIGTTIN() {
	verbose.Printf("[default SIGTTIN handling]\n")
	signal.Stop(discard)
}
示例#15
0
func dropSIGTTIN() {
	verbose.Printf("[dropping SIGTTIN]\n")
	signal.Notify(discard, syscall.SIGTTIN)
}
示例#16
0
// Note: There is a slight race here. If pids are reused very quickly, we might
// end up sending a signal to the wrong child just after the hosted program
// exits. That seems unlikely. To fix it, we would have to coordinate with
// linux_host, e.g. have linux_host send the signal on our behalf.
func send(pid int, sig syscall.Signal) {
	verbose.Printf("[sending %s to hosted program]\n", sigName(sig))
	syscall.Kill(pid, sig)
}
示例#17
0
func managePolicy() {

	// Handle queries first
	if query := *options.String["query"]; query != "" {
		queryGuard(query)
		return
	}

	// Load domain
	pwd := getKey("domain policy key password", "pass")
	domain, err := tao.LoadDomain(configPath(), pwd)
	options.FailIf(err, "Can't load domain")

	// Clear all the policy stored by the Guard.
	if *options.Bool["clear"] {
		domain.Guard.Clear()
		err := domain.Save()
		options.FailIf(err, "Can't save domain")
	}

	// Clear all the policy stored by the Guard.
	if *options.Bool["show"] {
		n := domain.Guard.RuleCount()
		fmt.Printf("Domain guard enforces %d rules:\n", n)
		for i := 0; i < n; i++ {
			fmt.Printf("rule %d: %s\n", i, domain.Guard.RuleDebugString(i))
		}
	}

	// Add permissions
	if canExecute := *options.String["canexecute"]; canExecute != "" {
		host := template().GetHostName()
		addExecute(canExecute, host, domain)
	}
	if add := *options.String["add"]; add != "" {
		verbose.Printf("Adding policy rule: %s\n", add)
		err := domain.Guard.AddRule(add)
		options.FailIf(err, "Can't add rule to domain")
		err = domain.Save()
		options.FailIf(err, "Can't save domain")
	}
	if *options.Bool["add_programs"] {
		host := template().GetHostName()
		addProgramRules(host, domain)
	}
	if *options.Bool["add_containers"] {
		host := template().GetHostName()
		addContainerRules(host, domain)
	}
	if domain.Config.DomainInfo.GetGuardType() == "Datalog" {
		if *options.Bool["add_vms"] {
			addVMRules(domain)
		}
		if *options.Bool["add_linux_host"] {
			addLinuxHostRules(domain)
		}
		if *options.Bool["add_host"] {
			host := template().GetHostName()
			addHostRules(host, domain)
		}
		if *options.Bool["add_guard"] {
			addGuardRules(domain)
		}
		if *options.Bool["add_tpm"] {
			addTPMRules(domain)
		}
	}

	// Retract permissions
	if retract := *options.String["retract"]; retract != "" {
		verbose.Printf("Retracting policy rule: %s\n", retract)
		err := domain.Guard.RetractRule(retract)
		options.FailIf(err, "Can't retract rule from domain")
		err = domain.Save()
		options.FailIf(err, "Can't save domain")
	}
	if retractCanExecute := *options.String["retractcanexecute"]; retractCanExecute != "" {
		host := template().GetHostName()
		retractExecute(retractCanExecute, host, domain)
	}
}