Files
signal-cli-rest-api/src/client/cli.go
Robert Schäfer 7aa1fddcd8 fix: include $stdin in server response on error
Motivation
----------
The way how I fixed this is that both `Stdout` and `Stderr` are responded back to the client.

I don't think it's good practice to discard `$stderr` on success and to discard `$stdout` on error.

Let me know what you think. I'm still very new to Golang.

How to test
-----------
1. First of all you must be able to reproduce "CAPTCHA proof required" error (I guess you need to send a lot of messages to the same number)
2. Execute:
```
curl -X POST -H "Content-Type: application/json" 'http://localhost:8080/v2/send' \
       -d '{"message": "Test via Signal API!", "number": "<SENDER_PHONE_NUMBER>", "recipients": [ "<RECIPIENT_PHONE_NUMBER>" ]}
'
```
3. See in the JSON response:
```
{"error":"Failed to send (some) messages:\n+49176xxxxxxxx: CAPTCHA proof required for sending to \"+49176xxxxxxxx\", available options \"RECAPTCHA, PUSH_CHALLENGE\" with challenge token \"1f209ee0-d487-4efc-xxxx-xxxxxxxxxxxx\", or wait \"86400\" seconds.\nTo get the captcha token, go to https://signalcaptchas.org/challenge/generate.html\nCheck the developer tools (F12) console for a failed redirect to signalcaptcha://\nEverything after signalcaptcha:// is the captcha token.\nUse the following command to submit the captcha token:\nsignal-cli submitRateLimitChallenge --challenge CHALLENGE_TOKEN --captcha CAPTCHA_TOKEN\nxxxxxxxxxxxxx\nFailed to send message\n"}
```

fix #403
2023-09-04 16:13:04 +02:00

125 lines
3.0 KiB
Go

package client
import (
"strings"
"errors"
"os/exec"
"bytes"
"time"
"bufio"
log "github.com/sirupsen/logrus"
utils "github.com/bbernhard/signal-cli-rest-api/utils"
)
type CliClient struct {
signalCliMode SignalCliMode
signalCliApiConfig *utils.SignalCliApiConfig
}
func NewCliClient(signalCliMode SignalCliMode, signalCliApiConfig *utils.SignalCliApiConfig) *CliClient {
return &CliClient {
signalCliMode: signalCliMode,
signalCliApiConfig: signalCliApiConfig,
}
}
func (s *CliClient) Execute(wait bool, args []string, stdin string) (string, error) {
containerId, err := getContainerId()
log.Debug("If you want to run this command manually, run the following steps on your host system:")
if err == nil {
log.Debug("*) docker exec -it ", containerId, " /bin/bash")
} else {
log.Debug("*) docker exec -it <container id> /bin/bash")
}
signalCliBinary := ""
if s.signalCliMode == Normal {
signalCliBinary = "signal-cli"
} else if s.signalCliMode == Native {
signalCliBinary = "signal-cli-native"
} else {
return "", errors.New("Invalid signal-cli mode")
}
//check if args contain number
trustModeStr := ""
for i, arg := range args {
if (arg == "-a" || arg == "--account") && (((i+1) < len(args)) && (utils.IsPhoneNumber(args[i+1]))) {
number := args[i+1]
trustMode, err := s.signalCliApiConfig.GetTrustModeForNumber(number)
if err == nil {
trustModeStr, err = utils.TrustModeToString(trustMode)
if err != nil {
trustModeStr = ""
log.Error("Invalid trust mode: ", trustModeStr)
}
}
break
}
}
if trustModeStr != "" {
args = append([]string{"--trust-new-identities", trustModeStr}, args...)
}
fullCmd := ""
if stdin != "" {
fullCmd += "echo '" + stdin + "' | "
}
fullCmd += signalCliBinary + " " + strings.Join(args, " ")
log.Debug("*) su signal-api")
log.Debug("*) ", fullCmd)
cmdTimeout, err := utils.GetIntEnv("SIGNAL_CLI_CMD_TIMEOUT", 120)
if err != nil {
log.Error("Env variable 'SIGNAL_CLI_CMD_TIMEOUT' contains an invalid timeout...falling back to default timeout (120 seconds)")
cmdTimeout = 120
}
cmd := exec.Command(signalCliBinary, args...)
if stdin != "" {
cmd.Stdin = strings.NewReader(stdin)
}
if wait {
var combinedOutput bytes.Buffer
cmd.Stdout = &combinedOutput
cmd.Stderr = &combinedOutput
err := cmd.Start()
if err != nil {
return "", err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(time.Duration(cmdTimeout) * time.Second):
err := cmd.Process.Kill()
if err != nil {
return "", err
}
return "", errors.New("process killed as timeout reached")
case err := <-done:
if err != nil {
return "", errors.New(combinedOutput.String())
}
}
return combinedOutput.String(), nil
} else {
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
cmd.Start()
buf := bufio.NewReader(stdout) // Notice that this is not in a loop
line, _, _ := buf.ReadLine()
return string(line), nil
}
}