何がしたかったのか
以下のように、のぞき見に耐性をつけるために、入力中のパスワードを非表示にすることがある。
$ ./command #何らかのコマンド
Enter password: # P@ssw0rd を入力
Your password is 'P@ssw0rd'
これをGo言語で実装したい。
ということで、調べてみると意外とすぐに見つかる。
おそらくすぐに出てくるのは、以下のコード。
package main
import (
"fmt"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func main() {
fmt.Print("Enter password: ")
pwd, err := terminal.ReadPassword(syscall.Stdin) // ここ一行で実装可能
if err != nil {
fmt.Println(err)
return
}
fmt.Println()
fmt.Println(string(pwd))
}
すごくシンプルで簡単だが、一つ罠がある。
パスワード入力画面で Ctrl + C
などで中断した際に、その後の ターミナル上での入力がすべて非表示状態になってしまう 。
$ ./command # 何らかのコマンド
Enter password: # 入力途中で Ctrl+C
^Csignal: interrupt
$ # 何を打っても表示されない
なのでこれを解決しよう。
解決策
解決方法としては、Ctrl+Cのシグナルをキャプチャして、シグナルを受信した場合、ターミナルの非表示状態を解除すればよい。
実装すると以下のコードになる。
package main
import (
"os"
"os/signal"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func ReadPassword() ([]byte, error) {
// Ctrl+Cのシグナルをキャプチャする
signalChan := make(chan os.Signal)
signal.Notify(signalChan, os.Interrupt)
defer signal.Stop(signalChan)
// 現在のターミナルの状態をコピーしておく
currentState, err := terminal.GetState(int(syscall.Stdin))
if err != nil {
return nil, err
}
go func() {
<-signalChan
// Ctrl+Cを受信後、ターミナルの状態を先ほどのコピーを用いて元に戻す
terminal.Restore(int(syscall.Stdin), currentStates)
os.Exit(1)
}()
return terminal.ReadPassword(syscall.Stdin)
}
たったこれだけ。考えると単純だが、最初詰まった時は困った。