mirror of
				https://gogs.blitter.com/RLabs/xs
				synced 2024-08-14 10:26:42 +00:00 
			
		
		
		
	Added hkexpasswd util; moved minimal term stuff into hkexauth.go
This commit is contained in:
		
							parent
							
								
									3ca98d364c
								
							
						
					
					
						commit
						d484ec7fd1
					
				
					 6 changed files with 400 additions and 138 deletions
				
			
		|  | @ -18,10 +18,10 @@ | |||
| 
 | ||||
| -- | ||||
| 
 | ||||
| This is a drop-in replacement for the golang/pkg/net facilities | ||||
| (net.Dial(), net.Listen(), net.Accept() and net.Conn type) using the | ||||
| Package herradurakex is a drop-in replacement for golang/pkg/net facilities | ||||
| (net.Dial(), net.Listen(), net.Accept() and the net.Conn type) using the | ||||
| experimental HerraduraKEx 'secure' key exchange algorithm, first released at | ||||
| github.com/Caume/HerraduraKEx | ||||
| (Omar Elejandro Herrera Reyna's github page)[github.com/Caume/HerraduraKEx]. | ||||
| 
 | ||||
| One can simply replace calls to net.Dial() with hkex.Dial(), and likewise | ||||
| net.Listen() with hkex.Listen(), to obtain connections (hkex.Conn) conforming | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ import ( | |||
| 
 | ||||
| 	hkex "blitter.com/herradurakex" | ||||
| 	isatty "github.com/mattn/go-isatty" | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| type cmdSpec struct { | ||||
|  | @ -69,16 +68,17 @@ func main() { | |||
| 		panic(err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
| 	// From this point on, conn is a secure encrypted channel | ||||
| 
 | ||||
| 	// Set stdin in raw mode if it's an interactive session | ||||
| 	// TODO: send flag to server side indicating this | ||||
| 	//  affects shell command used | ||||
| 	if isatty.IsTerminal(os.Stdin.Fd()) { | ||||
| 		oldState, err := MakeRaw(int(os.Stdin.Fd())) | ||||
| 		oldState, err := hkex.MakeRaw(int(os.Stdin.Fd())) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. | ||||
| 		defer func() { _ = hkex.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. | ||||
| 	} else { | ||||
| 		log.Println("NOT A TTY") | ||||
| 	} | ||||
|  | @ -108,7 +108,8 @@ func main() { | |||
| 
 | ||||
| 	if len(authCookie) == 0 { | ||||
| 		fmt.Printf("Gimme cookie:") | ||||
| 		ab, err := ReadPassword(int(os.Stdin.Fd())) | ||||
| 		ab, err := hkex.ReadPassword(int(os.Stdin.Fd())) | ||||
| 		fmt.Printf("\r\n") | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | @ -179,124 +180,3 @@ func main() { | |||
| 	// Wait until both stdin and stdout goroutines finish | ||||
| 	wg.Wait() | ||||
| } | ||||
| 
 | ||||
| /* ------------- minimal terminal APIs brought in from ssh/terminal | ||||
|  * (they have no real business being there as they aren't specific to | ||||
|  * ssh, but as of v1.10, early 2018, core go stdlib hasn't yet done | ||||
|  * the planned terminal lib reorgs.) | ||||
|  * ------------- | ||||
|  */ | ||||
| 
 | ||||
| // From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go | ||||
| const ioctlReadTermios = unix.TCGETS | ||||
| const ioctlWriteTermios = unix.TCSETS | ||||
| 
 | ||||
| // From github.com/golang/crypto/blob/master/ssh/terminal/util.go | ||||
| // State contains the state of a terminal. | ||||
| type State struct { | ||||
| 	termios unix.Termios | ||||
| } | ||||
| 
 | ||||
| // MakeRaw put the terminal connected to the given file descriptor into raw | ||||
| // mode and returns the previous state of the terminal so that it can be | ||||
| // restored. | ||||
| func MakeRaw(fd int) (*State, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	oldState := State{termios: *termios} | ||||
| 
 | ||||
| 	// This attempts to replicate the behaviour documented for cfmakeraw in | ||||
| 	// the termios(3) manpage. | ||||
| 	termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | ||||
| 	termios.Oflag &^= unix.OPOST | ||||
| 	termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN | ||||
| 	termios.Cflag &^= unix.CSIZE | unix.PARENB | ||||
| 	termios.Cflag |= unix.CS8 | ||||
| 	termios.Cc[unix.VMIN] = 1 | ||||
| 	termios.Cc[unix.VTIME] = 0 | ||||
| 	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &oldState, nil | ||||
| } | ||||
| 
 | ||||
| // GetState returns the current state of a terminal which may be useful to | ||||
| // restore the terminal after a signal. | ||||
| func GetState(fd int) (*State, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &State{termios: *termios}, nil | ||||
| } | ||||
| 
 | ||||
| // Restore restores the terminal connected to the given file descriptor to a | ||||
| // previous state. | ||||
| func Restore(fd int, state *State) error { | ||||
| 	return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) | ||||
| } | ||||
| 
 | ||||
| // ReadPassword reads a line of input from a terminal without local echo.  This | ||||
| // is commonly used for inputting passwords and other sensitive data. The slice | ||||
| // returned does not include the \n. | ||||
| func ReadPassword(fd int) ([]byte, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	newState := *termios | ||||
| 	newState.Lflag &^= unix.ECHO | ||||
| 	newState.Lflag |= unix.ICANON | unix.ISIG | ||||
| 	newState.Iflag |= unix.ICRNL | ||||
| 	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	defer func() { | ||||
| 		unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) | ||||
| 	}() | ||||
| 
 | ||||
| 	return readPasswordLine(passwordReader(fd)) | ||||
| } | ||||
| 
 | ||||
| // passwordReader is an io.Reader that reads from a specific file descriptor. | ||||
| type passwordReader int | ||||
| 
 | ||||
| func (r passwordReader) Read(buf []byte) (int, error) { | ||||
| 	return unix.Read(int(r), buf) | ||||
| } | ||||
| 
 | ||||
| // readPasswordLine reads from reader until it finds \n or io.EOF. | ||||
| // The slice returned does not include the \n. | ||||
| // readPasswordLine also ignores any \r it finds. | ||||
| func readPasswordLine(reader io.Reader) ([]byte, error) { | ||||
| 	var buf [1]byte | ||||
| 	var ret []byte | ||||
| 
 | ||||
| 	for { | ||||
| 		n, err := reader.Read(buf[:]) | ||||
| 		if n > 0 { | ||||
| 			switch buf[0] { | ||||
| 			case '\n': | ||||
| 				return ret, nil | ||||
| 			case '\r': | ||||
| 				// remove \r from passwords on Windows | ||||
| 			default: | ||||
| 				ret = append(ret, buf[0]) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF && len(ret) > 0 { | ||||
| 				return ret, nil | ||||
| 			} | ||||
| 			return ret, err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										128
									
								
								demo/hkexpasswd/hkexpasswd.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								demo/hkexpasswd/hkexpasswd.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,128 @@ | |||
| // Util to generate/store passwords for users in a file akin to /etc/passwd | ||||
| // suitable for the demo hkexsh server, using bcrypt. | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/csv" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/user" | ||||
| 
 | ||||
| 	hkex "blitter.com/herradurakex" | ||||
| 	"github.com/jameskeane/bcrypt" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	var pfName string | ||||
| 	var newpw string | ||||
| 	var confirmpw string | ||||
| 	var userName string | ||||
| 
 | ||||
| 	flag.StringVar(&userName, "u", "", "username") | ||||
| 	flag.StringVar(&pfName, "f", "/etc/hkexsh.passwd", "passwd file") | ||||
| 	flag.Parse() | ||||
| 
 | ||||
| 	var uname string | ||||
| 	if len(userName) == 0 { | ||||
| 		log.Println("specify username with -u") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := user.Lookup(userName) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Invalid user %s\n", userName) | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	uname = u.Username | ||||
| 
 | ||||
| 	fmt.Printf("New Password:") | ||||
| 	ab, err := hkex.ReadPassword(int(os.Stdin.Fd())) | ||||
| 	fmt.Printf("\r\n") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	newpw = string(ab) | ||||
| 
 | ||||
| 	fmt.Printf("Confirm:") | ||||
| 	ab, err = hkex.ReadPassword(int(os.Stdin.Fd())) | ||||
| 	fmt.Printf("\r\n") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	confirmpw = string(ab) | ||||
| 
 | ||||
| 	if confirmpw != newpw { | ||||
| 		log.Println("New passwords do not match.") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	// generate a random salt with specific rounds of complexity | ||||
| 	// (default in jameskeane/bcrypt is 12 but we'll be explicit here) | ||||
| 	salt, err := bcrypt.Salt(12) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("ERROR: bcrypt.Salt() failed.") | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 
 | ||||
| 	// hash and verify a password with explicit (random) salt | ||||
| 	hash, err := bcrypt.Hash(newpw, salt) | ||||
| 	if err != nil || !bcrypt.Match(newpw, hash) { | ||||
| 		fmt.Println("ERROR: bcrypt.Match() failed.") | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	//fmt.Println("Salt:", salt, "Hash:", hash) | ||||
| 
 | ||||
| 	b, err := ioutil.ReadFile(pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	r := csv.NewReader(bytes.NewReader(b)) | ||||
| 
 | ||||
| 	r.Comma = ':' | ||||
| 	r.Comment = '#' | ||||
| 	r.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...) | ||||
| 
 | ||||
| 	records, err := r.ReadAll() | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	for i, _ := range records { | ||||
| 		//fmt.Println(records[i]) | ||||
| 		if records[i][0] == uname { | ||||
| 			records[i][1] = salt | ||||
| 			records[i][2] = hash | ||||
| 		} | ||||
| 		//// csv lib doesn't preserve comment in record, so put it back | ||||
| 		//if records[i][0][0] == '!' { | ||||
| 		//	records[i][0] = "#" + records[i][0] | ||||
| 		//} | ||||
| 	} | ||||
| 
 | ||||
| 	outFile, err := ioutil.TempFile("", "hkexsh-passwd") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	w := csv.NewWriter(outFile) | ||||
| 	w.Comma = ':' | ||||
| 	//w.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...) | ||||
| 	w.Write([]string{"#username", "salt", "authCookie", "disallowedCmdList"}) | ||||
| 	w.WriteAll(records) | ||||
| 	if err = w.Error(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = os.Remove(pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = os.Rename(outFile.Name(), pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										128
									
								
								demo/hkexpasswd/hkexpasswd.go~
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								demo/hkexpasswd/hkexpasswd.go~
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,128 @@ | |||
| // Util to generate/store passwords for users in a file akin to /etc/passwd | ||||
| // suitable for the demo hkexsh server, using bcrypt. | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/csv" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/user" | ||||
| 
 | ||||
| 	hkex "blitter.com/herradurakex" | ||||
| 	"github.com/jameskeane/bcrypt" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	var pfName string | ||||
| 	var newpw string | ||||
| 	var confirmpw string | ||||
| 	var userName string | ||||
| 
 | ||||
| 	flag.StringVar(&userName, "u", "", "username") | ||||
| 	flag.StringVar(&pfName, "f", "/etc/hkexsh.passwd", "passwd file") | ||||
| 	flag.Parse() | ||||
| 
 | ||||
| 	var uname string | ||||
| 	if len(userName) == 0 { | ||||
| 		log.Println("specify username with -u") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := user.Lookup(userName) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Invalid user %s\n", userName) | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	uname = u.Username | ||||
| 
 | ||||
| 	fmt.Printf("New Password:") | ||||
| 	ab, err := hkex.ReadPassword(int(os.Stdin.Fd())) | ||||
| 	fmt.Printf("\r\n") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	newpw = string(ab) | ||||
| 
 | ||||
| 	fmt.Printf("Confirm:") | ||||
| 	ab, err = hkex.ReadPassword(int(os.Stdin.Fd())) | ||||
| 	fmt.Printf("\r\n") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 	confirmpw = string(ab) | ||||
| 
 | ||||
| 	if confirmpw != newpw { | ||||
| 		log.Println("New passwords do not match.") | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| 
 | ||||
| 	// generate a random salt with specific rounds of complexity | ||||
| 	// (default in jameskeane/bcrypt is 12 but we'll be explicit here) | ||||
| 	salt, err := bcrypt.Salt(12) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("ERROR: bcrypt.Salt() failed.") | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 
 | ||||
| 	// hash and verify a password with explicit (random) salt | ||||
| 	hash, err := bcrypt.Hash(newpw, salt) | ||||
| 	if err != nil || !bcrypt.Match(newpw, hash) { | ||||
| 		fmt.Println("ERROR: bcrypt.Match() failed.") | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	//fmt.Println("Salt:", salt, "Hash:", hash) | ||||
| 
 | ||||
| 	b, err := ioutil.ReadFile(pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	r := csv.NewReader(bytes.NewReader(b)) | ||||
| 
 | ||||
| 	r.Comma = ':' | ||||
| 	r.Comment = '#' | ||||
| 	r.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...) | ||||
| 
 | ||||
| 	records, err := r.ReadAll() | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	for i, _ := range records { | ||||
| 		fmt.Println(records[i]) | ||||
| 		if records[i][0] == uname { | ||||
| 			records[i][1] = salt | ||||
| 			records[i][2] = hash | ||||
| 		} | ||||
| 		//// csv lib doesn't preserve comment in record, so put it back | ||||
| 		//if records[i][0][0] == '!' { | ||||
| 		//	records[i][0] = "#" + records[i][0] | ||||
| 		//} | ||||
| 	} | ||||
| 
 | ||||
| 	outFile, err := ioutil.TempFile("", "hkexsh-passwd") | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	w := csv.NewWriter(outFile) | ||||
| 	w.Comma = ':' | ||||
| 	//w.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...) | ||||
| 	w.Write([]string{"#username", "salt", "authCookie", "disallowedCmdList"}) | ||||
| 	w.WriteAll(records) | ||||
| 	if err = w.Error(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = os.Remove(pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = os.Rename(outFile.Name(), pfName) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | @ -13,6 +13,7 @@ import ( | |||
| 	"syscall" | ||||
| 
 | ||||
| 	hkex "blitter.com/herradurakex" | ||||
| 	"blitter.com/spinsult" | ||||
| 	"github.com/kr/pty" | ||||
| ) | ||||
| 
 | ||||
|  | @ -109,8 +110,7 @@ func runShellAs(who string, cmd string, interactive bool, conn hkex.Conn) (err e | |||
| } | ||||
| 
 | ||||
| func rejectUserMsg() string { | ||||
| 		// TODO: Use Shakespeare insult generator. :p | ||||
| 		return "Invalid user\r\n" | ||||
| 	return "Begone, " + spinsult.GetSentence() + "\r\n" | ||||
| } | ||||
| 
 | ||||
| // Demo of a simple server that listens and spawns goroutines for each | ||||
|  | @ -201,9 +201,9 @@ func main() { | |||
| 
 | ||||
| 			valid, allowedCmds := hkex.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd") | ||||
| 			if !valid { | ||||
| 					log.Println("Invalid user", string(rec.who)) | ||||
| 					c.Write([]byte(rejectUserMsg())) | ||||
| 					return | ||||
| 				log.Println("Invalid user", string(rec.who)) | ||||
| 				c.Write([]byte(rejectUserMsg())) | ||||
| 				return | ||||
| 			} | ||||
| 			log.Printf("[allowedCmds:%s]\n", allowedCmds) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										136
									
								
								hkexauth.go
									
										
									
									
									
								
							
							
						
						
									
										136
									
								
								hkexauth.go
									
										
									
									
									
								
							|  | @ -10,9 +10,12 @@ import ( | |||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"runtime" | ||||
| 
 | ||||
| 	"github.com/jameskeane/bcrypt" | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
| 
 | ||||
| func AuthUser(username string, authcookie string, fname string) (valid bool, allowedCmds string) { | ||||
| func AuthUser(username string, auth string, fname string) (valid bool, allowedCmds string) { | ||||
| 	b, _ := ioutil.ReadFile(fname) | ||||
| 	r := csv.NewReader(bytes.NewReader(b)) | ||||
| 
 | ||||
|  | @ -21,7 +24,7 @@ func AuthUser(username string, authcookie string, fname string) (valid bool, all | |||
| 
 | ||||
| 	r.Comma = ':' | ||||
| 	r.Comment = '#' | ||||
| 	r.FieldsPerRecord = 3 // username:authCookie:disallowedCmdList (a,b,...) | ||||
| 	r.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...) | ||||
| 	for { | ||||
| 		record, err := r.Read() | ||||
| 		if err == io.EOF { | ||||
|  | @ -31,9 +34,11 @@ func AuthUser(username string, authcookie string, fname string) (valid bool, all | |||
| 			log.Fatal(err) | ||||
| 		} | ||||
| 
 | ||||
| 		if username == record[0] && | ||||
| 			authcookie == record[1] { | ||||
| 			valid = true | ||||
| 		if username == record[0] { | ||||
| 			tmp, _ := bcrypt.Hash(auth, record[1]) | ||||
| 			if tmp == record[2] { | ||||
| 				valid = true | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
|  | @ -41,3 +46,124 @@ func AuthUser(username string, authcookie string, fname string) (valid bool, all | |||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| /* ------------- minimal terminal APIs brought in from ssh/terminal | ||||
|  * (they have no real business being there as they aren't specific to | ||||
|  * ssh, but as of v1.10, early 2018, core go stdlib hasn't yet done | ||||
|  * the planned terminal lib reorgs.) | ||||
|  * ------------- | ||||
|  */ | ||||
| 
 | ||||
| // From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go | ||||
| const ioctlReadTermios = unix.TCGETS | ||||
| const ioctlWriteTermios = unix.TCSETS | ||||
| 
 | ||||
| // From github.com/golang/crypto/blob/master/ssh/terminal/util.go | ||||
| // State contains the state of a terminal. | ||||
| type State struct { | ||||
| 	termios unix.Termios | ||||
| } | ||||
| 
 | ||||
| // MakeRaw put the terminal connected to the given file descriptor into raw | ||||
| // mode and returns the previous state of the terminal so that it can be | ||||
| // restored. | ||||
| func MakeRaw(fd int) (*State, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	oldState := State{termios: *termios} | ||||
| 
 | ||||
| 	// This attempts to replicate the behaviour documented for cfmakeraw in | ||||
| 	// the termios(3) manpage. | ||||
| 	termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | ||||
| 	termios.Oflag &^= unix.OPOST | ||||
| 	termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN | ||||
| 	termios.Cflag &^= unix.CSIZE | unix.PARENB | ||||
| 	termios.Cflag |= unix.CS8 | ||||
| 	termios.Cc[unix.VMIN] = 1 | ||||
| 	termios.Cc[unix.VTIME] = 0 | ||||
| 	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &oldState, nil | ||||
| } | ||||
| 
 | ||||
| // GetState returns the current state of a terminal which may be useful to | ||||
| // restore the terminal after a signal. | ||||
| func GetState(fd int) (*State, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &State{termios: *termios}, nil | ||||
| } | ||||
| 
 | ||||
| // Restore restores the terminal connected to the given file descriptor to a | ||||
| // previous state. | ||||
| func Restore(fd int, state *State) error { | ||||
| 	return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) | ||||
| } | ||||
| 
 | ||||
| // ReadPassword reads a line of input from a terminal without local echo.  This | ||||
| // is commonly used for inputting passwords and other sensitive data. The slice | ||||
| // returned does not include the \n. | ||||
| func ReadPassword(fd int) ([]byte, error) { | ||||
| 	termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	newState := *termios | ||||
| 	newState.Lflag &^= unix.ECHO | ||||
| 	newState.Lflag |= unix.ICANON | unix.ISIG | ||||
| 	newState.Iflag |= unix.ICRNL | ||||
| 	if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	defer func() { | ||||
| 		unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) | ||||
| 	}() | ||||
| 
 | ||||
| 	return readPasswordLine(passwordReader(fd)) | ||||
| } | ||||
| 
 | ||||
| // passwordReader is an io.Reader that reads from a specific file descriptor. | ||||
| type passwordReader int | ||||
| 
 | ||||
| func (r passwordReader) Read(buf []byte) (int, error) { | ||||
| 	return unix.Read(int(r), buf) | ||||
| } | ||||
| 
 | ||||
| // readPasswordLine reads from reader until it finds \n or io.EOF. | ||||
| // The slice returned does not include the \n. | ||||
| // readPasswordLine also ignores any \r it finds. | ||||
| func readPasswordLine(reader io.Reader) ([]byte, error) { | ||||
| 	var buf [1]byte | ||||
| 	var ret []byte | ||||
| 
 | ||||
| 	for { | ||||
| 		n, err := reader.Read(buf[:]) | ||||
| 		if n > 0 { | ||||
| 			switch buf[0] { | ||||
| 			case '\n': | ||||
| 				return ret, nil | ||||
| 			case '\r': | ||||
| 				// remove \r from passwords on Windows | ||||
| 			default: | ||||
| 				ret = append(ret, buf[0]) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF && len(ret) > 0 { | ||||
| 				return ret, nil | ||||
| 			} | ||||
| 			return ret, err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue