mirror of
https://github.com/gregtwallace/apc-p15-tool.git
synced 2025-01-22 16:14:09 +00:00
106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
|
package app
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"path"
|
||
|
"time"
|
||
|
|
||
|
"golang.org/x/crypto/ssh"
|
||
|
)
|
||
|
|
||
|
// APC UPS won't except Go's SSH "Run()" command as the format isn't quite
|
||
|
// the same. Therefore, write a custom implementation to send the desired
|
||
|
// command instead of relying on something like github.com/bramvdbogaerde/go-scp
|
||
|
|
||
|
const (
|
||
|
scpP15Destination = "/ssl/defaultcert.p15"
|
||
|
scpP15PermissionsStr = "0600"
|
||
|
|
||
|
scpTimeout = 90 * time.Second
|
||
|
)
|
||
|
|
||
|
// scpSendFileToUPS sends the p15File to the APC UPS via the SCP protocol. it is
|
||
|
// automatically placed in the correct directory and will overwrite any existing
|
||
|
// file
|
||
|
func scpSendFileToUPS(client *ssh.Client, p15File []byte) error {
|
||
|
// make session to use for SCP
|
||
|
session, err := client.NewSession()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("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 -v -t %s", scpP15Destination))
|
||
|
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("scp: failed to execute scp cmd (%w)", err)
|
||
|
}
|
||
|
if !ok {
|
||
|
return errors.New("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("scp: failed to send scp cmd (bad remote response) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// just file name (without path)
|
||
|
filename := path.Base(scpP15Destination)
|
||
|
|
||
|
// send file header
|
||
|
_, err = fmt.Fprintln(w, "C"+scpP15PermissionsStr, len(p15File), filename)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("scp: failed to send file info (%w)", err)
|
||
|
}
|
||
|
|
||
|
err = scpCheckResponse(out)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("scp: failed to send file info (bad remote response) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// send actual file
|
||
|
_, err = io.Copy(w, bytes.NewReader(p15File))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("scp: failed to send file(%w)", err)
|
||
|
}
|
||
|
|
||
|
// send file end
|
||
|
_, err = fmt.Fprint(w, "\x00")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("scp: failed to send final 00 byte (%w)", err)
|
||
|
}
|
||
|
|
||
|
err = scpCheckResponse(out)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("scp: failed to send file (bad remote response) (%w)", err)
|
||
|
}
|
||
|
|
||
|
// done
|
||
|
return nil
|
||
|
}
|