1// Copyright (C) MongoDB, Inc. 2014-present.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
7package password
8
9import (
10	"os"
11	"syscall"
12	"unsafe"
13)
14
15// This file is a mess based primarily on
16// 		"github.com/howeyc/gopass"
17// 		"golang.org/x/crypto/ssh/terminal"
18// with extra unistd.h ripped from solaris on amd64
19//
20// TODO: get some of these changes merged into the above two packages
21
22// ioctl constants -- not defined in solaris syscall pkg
23const (
24	SYS_IOCTL = 54
25	TCGETS    = 21517
26	TCSETS    = 21518
27	ttyfd     = 0 //STDIN
28)
29
30// getTermios reads the current termios settings into the
31// given termios struct.
32func getTermios(term *syscall.Termios) error {
33	_, _, errno := syscall.Syscall(SYS_IOCTL,
34		uintptr(ttyfd), uintptr(TCGETS),
35		uintptr(unsafe.Pointer(term)))
36	if errno != 0 {
37		return os.NewSyscallError("SYS_IOCTL", errno)
38	}
39	return nil
40}
41
42// setTermios applies the supplied termios settings
43func setTermios(term *syscall.Termios) error {
44	_, _, errno := syscall.Syscall(SYS_IOCTL,
45		uintptr(ttyfd), uintptr(TCSETS),
46		uintptr(unsafe.Pointer(term)))
47	if errno != 0 {
48		return os.NewSyscallError("SYS_IOCTL", errno)
49	}
50	return nil
51}
52
53// setRaw puts the terminal into "raw" mode, which takes
54// in all key presses and does not echo them.
55func setRaw(term syscall.Termios) error {
56	termCopy := term
57	termCopy.Iflag &^= syscall.ISTRIP | syscall.INLCR |
58		syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF
59	termCopy.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG
60	return setTermios(&termCopy)
61}
62
63// isTerminal checks if we are reading from a terminal (instead of a pipe).
64func IsTerminal() bool {
65	var termios syscall.Termios
66	_, _, errno := syscall.Syscall(SYS_IOCTL,
67		uintptr(ttyfd), TCGETS,
68		uintptr(unsafe.Pointer(&termios)))
69	return errno == 0
70}
71
72// readChar safely gets one byte from stdin
73func readChar() byte {
74	var originalTerm syscall.Termios
75	if err := getTermios(&originalTerm); err != nil {
76		panic(err) // should not happen on amd64 solaris (untested on sparc)
77	}
78	if err := setRaw(originalTerm); err != nil {
79		panic(err)
80	}
81	defer func() {
82		// make sure we return the termios back to normal
83		if err := setTermios(&originalTerm); err != nil {
84			panic(err)
85		}
86	}()
87
88	// read a single byte then reset the terminal state
89	var singleChar [1]byte
90	if n, err := syscall.Read(ttyfd, singleChar[:]); n == 0 || err != nil {
91		panic(err)
92	}
93	return singleChar[0]
94}
95
96// get password from terminal
97func GetPass() string {
98	// keep reading in characters until we hit a stopping point
99	pass := []byte{}
100	for {
101		ch := readChar()
102		if ch == backspaceKey || ch == deleteKey {
103			if len(pass) > 0 {
104				pass = pass[:len(pass)-1]
105			}
106		} else if ch == carriageReturnKey || ch == newLineKey || ch == eotKey || ch == eofKey {
107			break
108		} else if ch != 0 {
109			pass = append(pass, ch)
110		}
111	}
112	return string(pass)
113}
114