Skip to content

Commit

Permalink
pass question to otp command
Browse files Browse the repository at this point in the history
  • Loading branch information
lonnywong committed Jul 20, 2024
1 parent d154d5b commit 0a9a0a6
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 16 deletions.
8 changes: 8 additions & 0 deletions README.cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,14 @@ trzsz-ssh ( tssh ) 设计为 ssh 客户端的直接替代品,提供与 openssh
encotp636f64653a20 77b4ce85d087b39909e563efb165659b22b9ea700a537f1258bdf56ce6fdd6ea70bc7591ea5c01918537a65433133bc0bd5ed3e4
```
- 可以自己实现获取动态密码的程序,指定 `%q` 参数可以得到问题内容,将动态密码输出到 stdout 并正常退出即可,调试信息可以输出到 stderr ( `tssh --debug` 运行时可以看到 )。配置举例(序号代表第几个问题,一般只有一个问题,只需配置 `OtpCommand1` 即可):
```
Host custom_otp_command
#!! OtpCommand1 /path/to/your_own_program %q
#!! OtpCommand2 python C:\your_python_code.py %q
```
- 如果启用了 `ControlMaster` 多路复用,或者是在 `Warp` 终端,请参考前面 `自动交互``Ctrl` 前缀来实现。
```
Expand Down
8 changes: 8 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,14 @@ trzsz-ssh ( tssh ) is an ssh client designed as a drop-in replacement for the op
encotp636f64653a20 77b4ce85d087b39909e563efb165659b22b9ea700a537f1258bdf56ce6fdd6ea70bc7591ea5c01918537a65433133bc0bd5ed3e4
```
- You can write a program to obtain the one-time password. Specify the `%q` argument if you want to get the question. Just output the one-time password to stdout and exit with 0, and the debugging information can be output to stderr (you can see it when running `tssh --debug`). Configuration example (the serial number represents the number of questions, generally there is only one question, just configure `OtpCommand1`):
```
Host custom_otp_command
#!! OtpCommand1 /path/to/your_own_program %q
#!! OtpCommand2 python C:\your_python_code.py %q
```
- If `ControlMaster` multiplexing is enabled or using `Warp` terminal, you will need to use the `Automated Interaction` mentioned earlier to achieve remembering answers.
```
Expand Down
19 changes: 10 additions & 9 deletions tssh/expect.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,19 +357,19 @@ func (e *sshExpect) wrapOutput(reader io.Reader, writer io.Writer, ch chan []byt
}
}

func (e *sshExpect) waitForPattern(pattern string, caseSends *caseSendList) error {
func (e *sshExpect) waitForPattern(pattern string, caseSends *caseSendList) (string, error) {
expr := quoteExpectPattern(pattern)
re, err := regexp.Compile(expr)
if err != nil {
warning("compile expect expr [%s] failed: %v", expr, err)
return err
return "", err
}
var builder strings.Builder
for {
var buf []byte
select {
case <-e.ctx.Done():
return e.ctx.Err()
return "", e.ctx.Err()
case buf = <-e.out:
case buf = <-e.err:
}
Expand All @@ -387,7 +387,7 @@ func (e *sshExpect) waitForPattern(pattern string, caseSends *caseSendList) erro
case <-e.out:
case <-e.err:
default:
return nil
return builder.String(), nil
}
}
} else {
Expand All @@ -396,7 +396,7 @@ func (e *sshExpect) waitForPattern(pattern string, caseSends *caseSendList) erro
}
}

func (e *sshExpect) getExpectSender(idx int) *expectSender {
func (e *sshExpect) getExpectSender(idx int, question string) *expectSender {
if pass := getExConfig(e.args.Destination, fmt.Sprintf("%sExpectSendPass%d", e.pre, idx)); pass != "" {
secret, err := decodeSecret(pass)
if err != nil {
Expand Down Expand Up @@ -425,15 +425,15 @@ func (e *sshExpect) getExpectSender(idx int) *expectSender {
warning("decode %sExpectSendEncOtp%d [%s] failed: %v", e.pre, idx, encOtp, err)
return nil
}
return newPassSender(e, getOtpCommandOutput(command))
return newPassSender(e, getOtpCommandOutput(command, question))
}

if secret := getExConfig(e.args.Destination, fmt.Sprintf("%sExpectSendTotp%d", e.pre, idx)); secret != "" {
return newPassSender(e, getTotpCode(secret))
}

if command := getExConfig(e.args.Destination, fmt.Sprintf("%sExpectSendOtp%d", e.pre, idx)); command != "" {
return newPassSender(e, getOtpCommandOutput(command))
return newPassSender(e, getOtpCommandOutput(command, question))
}

return nil
Expand All @@ -458,13 +458,14 @@ func (e *sshExpect) execInteractions(writer io.Writer, expectCount int) {
warning("Invalid ExpectCaseSendText%d: %v", idx, err)
}
}
if err := e.waitForPattern(pattern, caseSends); err != nil {
question, err := e.waitForPattern(pattern, caseSends)
if err != nil {
return
}
if e.ctx.Err() != nil {
return
}
sender := e.getExpectSender(idx)
sender := e.getExpectSender(idx, question)
if !sender.sendInput(writer, strconv.Itoa(idx)) {
return
}
Expand Down
4 changes: 2 additions & 2 deletions tssh/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ func readQuestionAnswerConfig(dest string, idx int, question string) string {
}

if command := getSecretConfig(dest, "otp"+qhex); command != "" {
if answer := getOtpCommandOutput(command); answer != "" {
if answer := getOtpCommandOutput(command, question); answer != "" {
return answer
}
}
Expand All @@ -590,7 +590,7 @@ func readQuestionAnswerConfig(dest string, idx int, question string) string {
qcmd := fmt.Sprintf("OtpCommand%d", idx)
debug("the otp command key for question '%s' is %s", question, qcmd)
if command := getSecretConfig(dest, qcmd); command != "" {
if answer := getOtpCommandOutput(command); answer != "" {
if answer := getOtpCommandOutput(command, question); answer != "" {
return answer
}
}
Expand Down
16 changes: 11 additions & 5 deletions tssh/otp.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,21 @@ import (
"github.com/pquerna/otp/totp"
)

func getOtpCommandOutput(command string) string {
func getOtpCommandOutput(command, question string) string {
argv, err := splitCommandLine(command)
if err != nil || len(argv) == 0 {
warning("split otp command failed: %v", err)
return ""
}
if enableDebugLogging {
for i, arg := range argv {
debug("otp command argv[%d] = %s", i, arg)
var args []string
for i, arg := range argv {
if i > 0 && arg == "%q" {
arg = question
}
debug("otp command argv[%d] = %s", i, arg)
args = append(args, arg)
}
cmd := exec.Command(argv[0], argv[1:]...)
cmd := exec.Command(args[0], args[1:]...)
var outBuf, errBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
Expand All @@ -56,6 +59,9 @@ func getOtpCommandOutput(command string) string {
}
return ""
}
if enableDebugLogging && errBuf.Len() > 0 {
debug("otp command stderr output: %s", errBuf.String())
}
return strings.TrimSpace(outBuf.String())
}

Expand Down

0 comments on commit 0a9a0a6

Please sign in to comment.