mirror of
https://github.com/gregtwallace/apc-p15-tool.git
synced 2025-01-22 08:14:08 +00:00
130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
|
package apcssh
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"path"
|
||
|
|
||
|
"golang.org/x/crypto/ssh"
|
||
|
)
|
||
|
|
||
|
// UploadSCP uploads a file to the destination specified (e.g., "/ssl/file.key")
|
||
|
// containing the file content specified. An existing file at the destination
|
||
|
// will be overwritten without warning.
|
||
|
func (cli *Client) UploadSCP(destination string, fileContent []byte, filePermissions fs.FileMode) error {
|
||
|
// connect
|
||
|
sshClient, err := ssh.Dial("tcp", cli.hostname, cli.sshCfg)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to dial session (%w)", err)
|
||
|
}
|
||
|
defer sshClient.Close()
|
||
|
|
||
|
// make session to use for SCP
|
||
|
session, err := sshClient.NewSession()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to create session (%w)", err)
|
||
|
}
|
||
|
defer session.Close()
|
||
|
|
||
|
// attach pipes
|
||
|
out, err := session.StdoutPipe()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
w, err := session.StdinPipe()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer w.Close()
|
||
|
|
||
|
// send execute cmd --
|
||
|
// build cmd to send as request
|
||
|
// Go implementation sends additional 0x22 bytes when using Run() (as
|
||
|
// compared to putty's scp tool). these additional bytes seem to cause the
|
||
|
// apc ups to fail execution of the command
|
||
|
payload := []byte(fmt.Sprintf("scp -q -t %s", destination))
|
||
|
payloadLen := uint8(len(payload))
|
||
|
payload = append([]byte{0, 0, 0, payloadLen}, payload...)
|
||
|
|
||
|
ok, err := session.SendRequest("exec", true, payload)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to execute scp cmd (%w)", err)
|
||
|
}
|
||
|
if !ok {
|
||
|
return errors.New("apcssh: scp: execute scp cmd not ok")
|
||
|
}
|
||
|
|
||
|
// check remote response
|
||
|
// Note: File upload may not work if the client doesn't actually read from
|
||
|
// the remote output.
|
||
|
err = scpCheckResponse(out)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send scp cmd (bad remote response 1) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// just file name (without path)
|
||
|
filename := path.Base(destination)
|
||
|
|
||
|
// send file header
|
||
|
_, err = fmt.Fprintln(w, "C"+fmt.Sprintf("%04o", filePermissions.Perm()), len(fileContent), filename)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send file info (%w)", err)
|
||
|
}
|
||
|
|
||
|
err = scpCheckResponse(out)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send file info (bad remote response 2) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// send actual file
|
||
|
_, err = io.Copy(w, bytes.NewReader(fileContent))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send file(%w)", err)
|
||
|
}
|
||
|
|
||
|
// send file end
|
||
|
_, err = fmt.Fprint(w, "\x00")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send final 00 byte (%w)", err)
|
||
|
}
|
||
|
|
||
|
err = scpCheckResponse(out)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: scp: failed to send file (bad remote response 3) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// done
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// scpCheckResponse reads the output from the remote and returns an error
|
||
|
// if the remote output was not 0
|
||
|
func scpCheckResponse(remoteOutPipe io.Reader) error {
|
||
|
buffer := make([]uint8, 1)
|
||
|
_, err := remoteOutPipe.Read(buffer)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: failed to read output buffer (%w)", err)
|
||
|
}
|
||
|
|
||
|
responseType := buffer[0]
|
||
|
message := ""
|
||
|
if responseType > 0 {
|
||
|
bufferedReader := bufio.NewReader(remoteOutPipe)
|
||
|
message, err = bufferedReader.ReadString('\n')
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("apcssh: failed to read output buffer (%w)", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if not 0 (aka OK)
|
||
|
if responseType != 0 {
|
||
|
return fmt.Errorf("apcssh: remote returned error (%d: %s)", responseType, message)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|