コード例 #1
0
ファイル: addpeer.go プロジェクト: inhies/cjdcmd
func addPeer(data []string) {
	if len(data) == 0 {
		fmt.Println("You must enter the peering details surrounded by single qoutes '<peer details>'")
		return
	}
	input := data[0]

	// Strip comments, just in case, and surround with {} to make it valid JSON
	raw, err := stripComments([]byte("{" + input + "}"))
	if err != nil {
		fmt.Println("Comment errors: ", err)
		return
	}

	// Convert from JSON to an object
	var object map[string]interface{}
	err = json.Unmarshal(raw, &object)
	if err != nil {
		fmt.Println("JSON Error:", err)
		return
	}

	// Load the config file
	if File == "" {
		cjdAdmin, err := loadCjdnsadmin()
		if err != nil {
			fmt.Println("Unable to load configuration file:", err)
			return
		}
		File = cjdAdmin.Config
		if File == "" {
			fmt.Println("Please specify the configuration file in your .cjdnsadmin file or pass the --file flag.")
			return
		}
	}
	fmt.Printf("Loading configuration from: %v... ", File)
	conf, err := config.LoadExtConfig(File)
	if err != nil {
		fmt.Println("Error loading config:", err)
		return
	}
	fmt.Printf("Loaded\n")

	if _, ok := conf["interfaces"]; !ok {
		fmt.Println("Your configuration file does not contain an 'interfaces' section")
		return
	}

	is := conf["interfaces"].(map[string]interface{})
	if len(is) == 0 {
		fmt.Println("No valid interfaces found!")
		return
	} else if len(is) > 1 {
		fmt.Println("You have multiple interfaces to choose from, enter yes or no, or press enter for the default option:")
	}

	var useIface string
	var i []interface{}

selectIF:
	for {
		for key, _ := range is {
			if len(is) > 1 {
				fmt.Printf("Add peer to '%v' [Y/n]: ", key)
				if gotYes(true) {
					i = is[key].([]interface{})
					useIface = key
					break selectIF
				}
			} else if len(is) == 1 {
				i = is[key].([]interface{})
				useIface = key
			}
		}
		if useIface == "" {
			fmt.Println("You must select an interface to add to!")
			continue
		}
		break
	}
	var iX map[string]interface{}
	if len(i) > 1 {
		fmt.Printf("You have multiple '%v' options to choose from, enter yes or no, or press enter for the default option\n", useIface)
	selectIF2:
		for _, iFace := range i {
			temp := iFace.(map[string]interface{})
			fmt.Printf("Add peer to '%v %v' [Y/n]: ", useIface, temp["bind"])
			if gotYes(true) {
				iX = iFace.(map[string]interface{})
				break
			}
		}
		if iX == nil {
			fmt.Println("You must select an interface to add to!")
			goto selectIF2
		}
	} else if len(i) == 1 {
		iX = i[0].(map[string]interface{})
	} else {
		fmt.Printf("No valid settings for '%v' found!\n", useIface)
		return
	}

	peers := iX["connectTo"].(map[string]interface{})

	for key, data := range object {
		var peer map[string]interface{}
		if peers[key] != nil {
			peer = peers[key].(map[string]interface{})
			fmt.Printf("Peer '%v' exists with the following information:\n", key)
			for f, v := range peer {
				fmt.Printf("\t\"%v\":\"%v\"\n", f, v)
			}

			fmt.Printf("Update peer with new information? [Y/n]: ")
			if gotYes(true) {
				peer = data.(map[string]interface{})
				fmt.Printf("Updating peer '%v'\n", key)
			} else {
				fmt.Printf("Skipped updating peer '%v'\n", key)
				continue
			}
		} else {
			fmt.Printf("Adding new peer '%v'\n", key)
			peer = data.(map[string]interface{})
		}

		// Optionally add meta information
		for {
			r := bufio.NewReader(os.Stdin)
			fmt.Printf("Enter a field name for any extra information, or press enter to skip: ")
			fName, _ := r.ReadString('\n')
			fName = strings.TrimSpace(fName)
			if len(fName) == 0 {
				break
			}

			fmt.Printf("Enter a the content for field '%v' or press enter to cancel: ", fName)
			fData, _ := r.ReadString('\n')
			fData = strings.TrimSpace(fData)
			if len(fData) == 0 {
				continue
			}

			peer[fName] = fData
			continue
		}

		fmt.Println("Peer information:")

		for f, v := range peer {
			fmt.Printf("\t\"%v\":\"%v\"\n", f, v)
		}

		fmt.Printf("Add this peer? [Y/n]: ")
		if gotYes(true) {
			peers[key] = peer
			fmt.Println("Peer added")
		} else {
			fmt.Println("Skipped adding peer")
		}

	}

	// Get the permissions from the input file
	stats, err := os.Stat(File)
	if err != nil {
		fmt.Println("Error getting permissions for original file:", err)
		return
	}

	if File != "" && OutFile == "" {
		OutFile = File
	}

	// Check if the output file exists and prompt befoer overwriting
	if _, err := os.Stat(OutFile); err == nil {
		fmt.Printf("Overwrite %v? [y/N]: ", OutFile)
		if !gotYes(false) {
			return
		}
	}

	fmt.Printf("Saving configuration to: %v... ", OutFile)
	err = config.SaveConfig(OutFile, conf, stats.Mode())
	if err != nil {
		fmt.Println("Error saving config:", err)
		return
	}
	fmt.Printf("Saved\n")
}
コード例 #2
0
ファイル: addpeer.go プロジェクト: inhies/cjdcmd
func addPassword(data []string) {
	// Load the config file
	if File == "" {
		var cjdnsAdmin *admin.CjdnsAdminConfig
		var err error
		if !userSpecifiedCjdnsadmin {
			cjdnsAdmin, err = loadCjdnsadmin()
			if err != nil {
				fmt.Println("Unable to load configuration file:", err)
				return
			}
		} else {
			cjdnsAdmin, err = readCjdnsadmin(userCjdnsadmin)
			if err != nil {
				fmt.Println("Error loading cjdnsadmin file:", err)
				return
			}
		}
		File = cjdnsAdmin.Config
		if File == "" {
			fmt.Println("Please specify the configuration file in your .cjdnsadmin file or pass the --file flag.")
			return
		}
	}
	fmt.Printf("Loading configuration from: %v... ", File)
	conf, err := config.LoadExtConfig(File)
	if err != nil {
		fmt.Println("Error loading config:", err)
		return
	}
	fmt.Printf("Loaded\n")

	var input string
	if len(data) == 0 {
		fmt.Printf("You didnt supply a password, should I generate one for you? [Y/n]: ")
		if gotYes(true) {
			for {
				input = randString(15, 50)
				fmt.Printf("Generated: '%v' Accept? [Y/n]: ", input)
				if gotYes(true) {
					break
				}
			}
		} else {
			// No password supplied, not going to generate one, I quit!
			return
		}
	} else {
		input = data[0]
	}

	if _, ok := conf["authorizedPasswords"]; !ok {
		conf["authorizedPasswords"] = make([]interface{}, 0)
		fmt.Println("Your configuration file does not contain an 'authorizedPasswords' section, so one was created for you")
	}
	passwords := conf["authorizedPasswords"].([]interface{})

	for loc, p := range passwords {
		x := p.(map[string]interface{})
		if x["password"] == input {
			fmt.Printf("Password '%v' exists with the following information:\n", input)
			for f, v := range x {
				fmt.Printf("\t\"%v\":\"%v\"\n", f, v)
			}

			fmt.Printf("Update password with new information? [Y/n]: ")
			if gotYes(true) {
				// Remove the entry we are replacing
				passwords = append(passwords[:loc], passwords[loc+1:]...)
				break
			} else {
				return
			}
		}
	}

	pass := make(map[string]interface{})
	pass["password"] = input

	is := conf["interfaces"].(map[string]interface{})
	if len(is) == 0 {
		fmt.Println("No valid interfaces found!")
		return
	} else if len(is) > 1 {
		fmt.Println("You have multiple interfaces to choose from, enter yes or no, or press enter for the default option:")
	}

	var useIface string
	var i []interface{}

selectIF:
	for {
		for key, _ := range is {
			if len(is) > 1 {
				fmt.Printf("Add password to '%v' [Y/n]: ", key)
				if gotYes(true) {
					i = is[key].([]interface{})
					useIface = key
					break selectIF
				}
			} else if len(is) == 1 {
				i = is[key].([]interface{})
				useIface = key
			}
		}
		if useIface == "" {
			fmt.Println("You must select an interface to add to!")
			continue
		}

		break
	}
	var iX map[string]interface{}
	if len(i) > 1 {
		fmt.Printf("You have multiple '%v' options to choose from, enter yes or no, or press enter for the default option\n", useIface)
	selectIF2:
		for _, iFace := range i {
			temp := iFace.(map[string]interface{})
			fmt.Printf("Add peer to '%v %v' [Y/n]: ", useIface, temp["bind"])
			if gotYes(true) {
				iX = iFace.(map[string]interface{})
				break
			}
		}
		if iX == nil {
			fmt.Println("You must select an interface to add to!")
			goto selectIF2
		}
	} else if len(i) == 1 {
		iX = i[0].(map[string]interface{})
	} else {
		fmt.Printf("No valid settings for '%v' found!\n", useIface)
		return
	}

	// Optionally add meta information
	for {
		r := bufio.NewReader(os.Stdin)
		fmt.Printf("Enter a field name for any extra information, or press enter to skip: ")
		fName, _ := r.ReadString('\n')
		fName = strings.TrimSpace(fName)
		if len(fName) == 0 {
			break
		}

		fmt.Printf("Enter a the content for field '%v' or press enter to cancel: ", fName)
		fData, _ := r.ReadString('\n')
		fData = strings.TrimSpace(fData)
		if len(fData) == 0 {
			continue
		}

		pass[fName] = fData
		continue
	}

	fmt.Println("Password information:")

	for f, v := range pass {
		fmt.Printf("\t\"%v\":\"%v\"\n", f, v)
	}

	fmt.Printf("Add this password? [Y/n]: ")
	if gotYes(true) {
		conf["authorizedPasswords"] = append(passwords, pass)
		fmt.Println("Password added")
	} else {
		fmt.Println("Cancelled adding password")
		return
	}

	// Get the permissions from the input file
	stats, err := os.Stat(File)
	if err != nil {
		fmt.Println("Error getting permissions for original file:", err)
		return
	}

	if File != "" && OutFile == "" {
		OutFile = File
	}

	// Check if the output file exists and prompt befoer overwriting
	if _, err := os.Stat(OutFile); err == nil {
		fmt.Printf("Overwrite %v? [y/N]: ", OutFile)
		if !gotYes(false) {
			return
		}
	}

	fmt.Printf("Saving configuration to: %v... ", OutFile)
	err = config.SaveConfig(OutFile, conf, stats.Mode())
	if err != nil {
		fmt.Println("\nError saving config:", err)
		return
	}
	fmt.Printf("Saved\n")
	var bind string
	if strings.ToLower(useIface) == "ethinterface" {
		iFace, err := net.InterfaceByName(iX["bind"].(string))
		if err != nil {
			fmt.Println("Unable to get interface's MAC address, you'll have to enter it yourself")
			bind = "UNKNOWN"
		} else {
			bind = iFace.HardwareAddr.String()
		}
	} else {
		bind = iX["bind"].(string)
	}
	fmt.Println("Here are the details to be shared with your new peer:")
	fmt.Printf("\"%v\":{\n", bind)
	fmt.Printf("\t\"password\":\"%v\",\n", pass["password"].(string))
	fmt.Printf("\t\"publicKey\":\"%v\"\n", conf["publicKey"].(string))
	fmt.Printf("}\n")

}
コード例 #3
0
ファイル: cjdcmd.go プロジェクト: rossdylan/cjdcmd
func main() {
	//Define the flags, and parse them
	//Clearly a hack but it works for now
	//TODO(inhies): Re-implement flag parsing so flags can have multiple meanings based on the base command (ping, route, etc)
	if len(os.Args) <= 1 {
		usage()
		return
	} else if len(os.Args) == 2 {
		if string(os.Args[1]) == "--help" {
			fs.PrintDefaults()
			return
		}
	} else {
		fs.Parse(os.Args[2:])
	}

	//TODO(inhies): check argv[0] for trailing commands.
	//For example, to run ctraceroute:
	//ln -s /path/to/cjdcmd /usr/bin/ctraceroute like things
	command := os.Args[1]

	arguments := fs.Args()
	data := arguments[fs.NFlag()-fs.NFlag():]

	//Setup variables now so that if the program is killed we can still finish what we're doing
	ping := &Ping{}

	globalData := &Data{&admin.Admin{}, ""}
	var err error
	if File != "" {
		File, err = filepath.Abs(File)
		if err != nil {
			fmt.Println(err)
			return
		}
	}
	if OutFile != "" {
		OutFile, err = filepath.Abs(OutFile)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	// Check to see if the user specified a cjdnsadmin file to use instead of
	// the default
	if userCjdnsadmin != "" {
		userSpecifiedCjdnsadmin = true
	}

	// capture ctrl+c (actually any kind of kill signal...)
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			fmt.Printf("\n")
			if command == "log" {
				// Unsubscribe from logging
				_, err := admin.AdminLog_unsubscribe(globalData.User, globalData.LoggingStreamID)
				if err != nil {
					fmt.Printf("%v\n", err)
					return
				}
			}
			if command == "ping" {
				//stop pinging and print results
				outputPing(ping)
			}
			// Close all the channels
			for _, c := range globalData.User.Channels {
				close(c)
			}

			// If we have an open connection, close it
			if globalData.User.Conn != nil {
				globalData.User.Conn.Close()
			}

			// Exit with no error
			os.Exit(0)
		}
	}()

	switch command {
	// Generates a .cjdnsadmin file
	case cjdnsadminCmd:
		if File == "" {
			var cjdnsAdmin *CjdnsAdmin
			if !userSpecifiedCjdnsadmin {
				cjdnsAdmin, err = loadCjdnsadmin()
				if err != nil {
					fmt.Println("Unable to load configuration file:", err)
					return
				}
			} else {
				cjdnsAdmin, err = readCjdnsadmin(userCjdnsadmin)
				if err != nil {
					fmt.Println("Error loading cjdnsadmin file:", err)
					return
				}
			}
			File = cjdnsAdmin.Config
			if File == "" {
				fmt.Println("Please specify the configuration file in your .cjdnsadmin file or pass the --file flag.")
				return
			}
		}

		fmt.Printf("Loading configuration from: %v... ", File)
		conf, err := readConfig()
		if err != nil {
			fmt.Println("Error loading config:", err)
			return
		}
		fmt.Printf("Loaded\n")

		split := strings.LastIndex(conf.Admin.Bind, ":")
		addr := conf.Admin.Bind[:split]
		port := conf.Admin.Bind[split+1:]
		portInt, err := strconv.Atoi(port)
		if err != nil {
			fmt.Println("Error with cjdns admin bind settings")
			return
		}

		adminOut := CjdnsAdmin{
			Address:  addr,
			Port:     portInt,
			Password: conf.Admin.Password,
			Config:   File,
		}

		jsonout, err := json.MarshalIndent(adminOut, "", "\t")
		if err != nil {
			fmt.Println("Unable to create JSON for .cjdnsadmin")
			return
		}

		if OutFile == "" {
			tUser, err := user.Current()
			if err != nil {
				fmt.Println("I was unable to get your home directory, please manually specify where to save the file with --outfile")
				return
			}
			OutFile = tUser.HomeDir + "/.cjdnsadmin"
		}

		// Check if the output file exists and prompt befoer overwriting
		if _, err := os.Stat(OutFile); err == nil {
			fmt.Printf("Overwrite %v? [y/N]: ", OutFile)
			if !gotYes(false) {
				return
			}
		} else {
			fmt.Println("Saving to", OutFile)
		}

		ioutil.WriteFile(OutFile, jsonout, 0600)

	case cleanCfgCmd:
		// Load the config file
		if File == "" {
			var cjdnsAdmin *CjdnsAdmin
			if !userSpecifiedCjdnsadmin {
				cjdnsAdmin, err = loadCjdnsadmin()
				if err != nil {
					fmt.Println("Unable to load configuration file:", err)
					return
				}
			} else {
				cjdnsAdmin, err = readCjdnsadmin(userCjdnsadmin)
				if err != nil {
					fmt.Println("Error loading cjdnsadmin file:", err)
					return
				}
			}
			File = cjdnsAdmin.Config
			if File == "" {
				fmt.Println("Please specify the configuration file in your .cjdnsadmin file or pass the --file flag.")
				return
			}
		}

		fmt.Printf("Loading configuration from: %v... ", File)
		conf, err := config.LoadExtConfig(File)
		if err != nil {
			fmt.Println("Error loading config:", err)
			return
		}
		fmt.Printf("Loaded\n")

		// Get the permissions from the input file
		stats, err := os.Stat(File)
		if err != nil {
			fmt.Println("Error getting permissions for original file:", err)
			return
		}

		if File != "" && OutFile == "" {
			OutFile = File
		}

		// Check if the output file exists and prompt befoer overwriting
		if _, err := os.Stat(OutFile); err == nil {
			fmt.Printf("Overwrite %v? [y/N]: ", OutFile)
			if !gotYes(false) {
				return
			}
		}

		fmt.Printf("Saving configuration to: %v... ", OutFile)
		err = config.SaveConfig(OutFile, conf, stats.Mode())
		if err != nil {
			fmt.Println("Error saving config:", err)
			return
		}
		fmt.Printf("Saved\n")
	case addPassCmd:
		addPassword(data)

	case addPeerCmd:
		addPeer(data)

	case hostNameCmd:
		if len(data) == 0 {
			setHypeDNS("")
			return
		}
		if len(data) == 1 {
			setHypeDNS(data[0])
			return
		}
		if len(data) > 1 {
			fmt.Println("Too many arguments.")
			return
		}
		return

	case hostCmd:
		if len(data) == 0 {
			fmt.Println("Invalid hostname or IPv6 address specified")
			return
		}
		input := data[0]
		validIP, _ := regexp.MatchString(ipRegex, input)
		validHost, _ := regexp.MatchString(hostRegex, input)

		if validIP {
			hostname, err := resolveIP(input)
			if err != nil {
				fmt.Printf("Error: %v\n", err)
				return
			}
			fmt.Printf("%v\n", hostname)
		} else if validHost {
			ips, err := resolveHost(input)
			if err != nil {
				fmt.Printf("Error: %v\n", err)
				return
			}
			for _, addr := range ips {
				fmt.Printf("%v has IPv6 address %v\n", data[0], addr)
			}
		} else {
			fmt.Println("Invalid hostname or IPv6 address specified")
			return
		}

	case passGenCmd:
		// TODO(inies): Make more good
		fmt.Println(randString(15, 50))

	case pubKeyToIPcmd:
		var ip []byte
		if len(data) > 0 {
			if len(data[0]) == 52 || len(data[0]) == 54 {
				ip = []byte(data[0])
			} else {
				fmt.Println("Invalid public key")
				return
			}
		} else {
			fmt.Println("Invalid public key")
			return
		}
		parsed, err := admin.PubKeyToIP(ip)
		if err != nil {
			fmt.Println(err)
			return
		}
		var tText string
		hostname, _ := resolveIP(string(parsed))
		if hostname != "" {
			tText = string(parsed) + " (" + hostname + ")"
		} else {
			tText = string(parsed)
		}
		fmt.Printf("%v\n", tText)

	case traceCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		globalData.User = user
		target, err := setTarget(data, true)
		if err != nil {
			fmt.Println("Error:", err)
			return
		}
		doTraceroute(globalData.User, target)

	case routeCmd:

		target, err := setTarget(data, true)
		if err != nil {
			fmt.Println(err)
			return
		}
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		var tText string
		hostname, _ := resolveIP(target.Target)
		if hostname != "" {
			tText = target.Target + " (" + hostname + ")"
		} else {
			tText = target.Target
		}
		fmt.Printf("Showing all routes to %v\n", tText)
		globalData.User = user
		table := getTable(globalData.User)

		sort.Sort(ByQuality{table})
		count := 0
		for _, v := range table {
			if v.IP == target.Target || v.Path == target.Target {
				if v.Link > 1 {
					fmt.Printf("IP: %v -- Version: %d -- Path: %s -- Link: %.0f\n", v.IP, v.Version, v.Path, v.Link)
					count++
				}
			}
		}
		fmt.Println("Found", count, "routes")

	case pingCmd:
		// TODO: allow pinging of entire routing table
		target, err := setTarget(data, true)
		if err != nil {
			fmt.Println(err)
			return
		}
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user
		ping.Target = target.Target

		var tText string

		// If we were given an IP then try to resolve the hostname
		if validIP(target.Supplied) {
			hostname, _ := resolveIP(target.Target)
			if hostname != "" {
				tText = target.Supplied + " (" + hostname + ")"
			} else {
				tText = target.Supplied
			}
			// If we were given a path, resolve the IP
		} else if validPath(target.Supplied) {
			tText = target.Supplied
			table := getTable(globalData.User)
			for _, v := range table {
				if v.Path == target.Supplied {
					// We have the IP now
					tText = target.Supplied + " (" + v.IP + ")"

					// Try to get the hostname
					hostname, _ := resolveIP(v.IP)
					if hostname != "" {
						tText = target.Supplied + " (" + v.IP + " (" + hostname + "))"
					}
				}
			}
			// We were given a hostname, everything is already done for us!
		} else if validHost(target.Supplied) {
			tText = target.Supplied + " (" + target.Target + ")"
		}
		fmt.Printf("PING %v \n", tText)

		if PingCount != defaultPingCount {
			// ping only as much as the user asked for
			for i := 1; i <= PingCount; i++ {
				start := time.Duration(time.Now().UTC().UnixNano())
				err := pingNode(globalData.User, ping)
				if err != nil {
					if err.Error() != "Socket closed" {
						fmt.Println(err)
					}
					return
				}
				fmt.Println(ping.Response)
				// Send 1 ping per second
				now := time.Duration(time.Now().UTC().UnixNano())
				time.Sleep(start + (time.Duration(PingInterval) * time.Second) - now)

			}
		} else {
			// ping until we're told otherwise
			for {
				start := time.Duration(time.Now().UTC().UnixNano())
				err := pingNode(globalData.User, ping)
				if err != nil {
					// Ignore these errors, as they are returned when we kill an in-progress ping
					if err.Error() != "Socket closed" && err.Error() != "use of closed network connection" {
						fmt.Println("ermagherd:", err)
					}
					return
				}
				fmt.Println(ping.Response)
				// Send 1 ping per second
				now := time.Duration(time.Now().UTC().UnixNano())
				time.Sleep(start + (time.Duration(PingInterval) * time.Second) - now)

			}
		}
		outputPing(ping)

	case logCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user
		var response chan map[string]interface{}
		response, globalData.LoggingStreamID, err = admin.AdminLog_subscribe(globalData.User, LogFile, LogLevel, LogFileLine)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			return
		}
		format := "%d %d %s %s:%d %s\n" // TODO: add user formatted output
		counter := 1

		// Spawn a routine to ping cjdns every 10 seconds to keep the connection alive
		go func() {
			for {
				timeout := 10 * time.Second
				time.Sleep(timeout)
				ok, err := admin.SendPing(globalData.User, 1000)

				if err != nil {
					fmt.Println("Error sending periodic ping to cjdns:", err)
					return
				} else if !ok {
					fmt.Println("Cjdns did not respond to the periodic ping.")
					return
				}
			}
		}()
		for {
			input, ok := <-response
			if !ok {
				fmt.Println("Error reading log response from cjdns.")
				return
			}
			fmt.Printf(format, counter, input["time"], input["level"], input["file"], input["line"], input["message"])
			counter++
		}

	case peerCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}

		// no target specified, use ourselves
		if len(data) == 0 {
			data = append(data, "0000.0000.0000.0001")
		}

		target, err := setTarget(data, true)
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user
		doPeers(user, target)

	case versionCmd:
		// TODO(inhies): Ping a specific node and return it's cjdns version, or
		// ping all nodes in the routing table and get their versions
		// git log -1 --date=iso --pretty=format:"%ad" <hash>

	case killCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user
		_, err = admin.Core_exit(globalData.User)
		if err != nil {
			fmt.Printf("%v\n", err)
			return
		}
		alive := true
		for ; alive; alive, _ = admin.SendPing(globalData.User, 1000) {
			runtime.Gosched() //play nice
		}
		fmt.Println("cjdns is shutting down...")

	case dumpCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user
		// TODO: add flag to show zero link quality routes, by default hide them
		table := getTable(globalData.User)
		sort.Sort(ByQuality{table})
		k := 1
		for _, v := range table {
			if v.Link >= 1 {
				fmt.Printf("%d IP: %v -- Version: %d -- Path: %s -- Link: %.0f\n", k, v.IP, v.Version, v.Path, v.Link)
				k++
			}
		}
	case memoryCmd:
		user, err := adminConnect()
		if err != nil {
			fmt.Println(err)
			return
		}
		globalData.User = user

		response, err := admin.Memory(globalData.User)
		if err != nil {
			fmt.Printf("%v\n", err)
			return
		}
		fmt.Println(response, "bytes")
	default:
		fmt.Println("Invalid command", command)
		usage()
	}
}