diff --git a/ssh/sshpiper.go b/ssh/sshpiper.go index 7bf3f5a7..4011de7a 100644 --- a/ssh/sshpiper.go +++ b/ssh/sshpiper.go @@ -49,6 +49,9 @@ type pipedConn struct { downstream *downstream processAuthMsg func(msg *userAuthRequestMsg) (*userAuthRequestMsg, error) + + hookUpstreamMsg func(msg []byte) ([]byte, error) + hookDownstreamMsg func(msg []byte) ([]byte, error) } // SSHPiperConn is a piped SSH connection, linking upstream ssh server and @@ -57,11 +60,32 @@ type pipedConn struct { // AdditionalChallenge from SSHPiper. type SSHPiperConn struct { *pipedConn + + HookUpstreamMsg func(conn ConnMetadata, msg []byte) ([]byte, error) + HookDownstreamMsg func(conn ConnMetadata, msg []byte) ([]byte, error) } // Wait blocks until the piped connection has shut down, and returns the // error causing the shutdown. func (p *SSHPiperConn) Wait() error { + + p.pipedConn.hookUpstreamMsg = func(msg []byte) ([]byte, error) { + if p.HookUpstreamMsg != nil { + // api always using p.downstream as conn meta + return p.HookUpstreamMsg(p.downstream, msg) + } + + return msg, nil + } + + p.pipedConn.hookDownstreamMsg = func(msg []byte) ([]byte, error) { + if p.HookDownstreamMsg != nil { + return p.HookDownstreamMsg(p.downstream, msg) + } + + return msg, nil + } + return p.pipedConn.loop() } @@ -70,6 +94,14 @@ func (p *SSHPiperConn) Close() { p.pipedConn.Close() } +func (p *SSHPiperConn) UpstreamConnMeta() ConnMetadata { + return p.pipedConn.upstream +} + +func (p *SSHPiperConn) DownstreamConnMeta() ConnMetadata { + return p.pipedConn.downstream +} + // AddHostKey adds a private key as a SSHPiper host key. If an existing host // key exists with the same algorithm, it is overwritten. Each SSHPiper // config must have at least one host key. @@ -228,7 +260,7 @@ func NewSSHPiperConn(conn net.Conn, piper *SSHPiperConfig) (pipe *SSHPiperConn, return nil, err } - return &SSHPiperConn{p}, nil + return &SSHPiperConn{pipedConn: p}, nil } func (pipe *pipedConn) validAndAck(user string, upKey, downKey PublicKey) (*userAuthRequestMsg, error) { @@ -343,7 +375,7 @@ func parsePublicKeyMsg(userAuthReq *userAuthRequestMsg) (PublicKey, bool, *Signa return pubKey, isQuery, sig, nil } -func piping(dst, src packetConn) error { +func piping(dst, src packetConn, hooker func(msg []byte) ([]byte, error)) error { for { p, err := src.readPacket() @@ -351,6 +383,12 @@ func piping(dst, src packetConn) error { return err } + p, err = hooker(p) + + if err != nil { + return err + } + err = dst.writePacket(p) if err != nil { @@ -363,11 +401,11 @@ func (pipe *pipedConn) loop() error { c := make(chan error) go func() { - c <- piping(pipe.upstream.transport, pipe.downstream.transport) + c <- piping(pipe.upstream.transport, pipe.downstream.transport, pipe.hookDownstreamMsg) }() go func() { - c <- piping(pipe.downstream.transport, pipe.upstream.transport) + c <- piping(pipe.downstream.transport, pipe.upstream.transport, pipe.hookUpstreamMsg) }() defer pipe.Close() diff --git a/sshpiperd/audit.go b/sshpiperd/audit.go new file mode 100644 index 00000000..a02bbc66 --- /dev/null +++ b/sshpiperd/audit.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/tg123/sshpiper/ssh" +) + +const ( + msgChannelData = 94 +) + +type filePtyLogger struct { + typescript *os.File + timing *os.File + + oldtime time.Time +} + +func newFilePtyLogger(user string) (*filePtyLogger, error) { + + now := time.Now() + + filename := fmt.Sprintf("%d", now.Unix()) + + typescript, err := os.OpenFile(userSpecFile(user, fmt.Sprintf("%v.typescript", filename)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + + if err != nil { + return nil, err + } + + _, err = typescript.Write([]byte(fmt.Sprintf("Script started on %v\n", now.Format(time.ANSIC)))) + + if err != nil { + return nil, err + } + + timing, err := os.OpenFile(userSpecFile(user, fmt.Sprintf("%v.timing", filename)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + + if err != nil { + return nil, err + } + + return &filePtyLogger{ + typescript: typescript, + timing: timing, + oldtime: time.Now(), + }, nil +} + +func (l *filePtyLogger) loggingTty(conn ssh.ConnMetadata, msg []byte) ([]byte, error) { + + if msg[0] == msgChannelData { + + buf := msg[9:] + + now := time.Now() + + delta := now.Sub(l.oldtime) + + // see term-utils/script.c + fmt.Fprintf(l.timing, "%v.%06v %v\n", int64(delta/time.Second), int64(delta/time.Microsecond), len(buf)) + + l.oldtime = now + + _, err := l.typescript.Write(buf) + + if err != nil { + return msg, err + } + + } + + return msg, nil +} + +func (l *filePtyLogger) Close() (err error) { + _, err = l.typescript.Write([]byte(fmt.Sprintf("Script done on %v\n", time.Now().Format(time.ANSIC)))) + l.typescript.Close() + l.timing.Close() + + return nil // TODO +} diff --git a/sshpiperd/config.go b/sshpiperd/config.go index e88c2421..3f97aed2 100644 --- a/sshpiperd/config.go +++ b/sshpiperd/config.go @@ -33,6 +33,7 @@ var ( ShowVersion bool AllowBadUsername bool NoCheckPerm bool + RecordTypescript bool }{} out = os.Stdout @@ -85,9 +86,12 @@ func initConfig() { pflag.StringVarP(&config.WorkingDir, "working_dir", "w", "/var/sshpiper", "Working Dir") pflag.StringVarP(&config.PiperKeyFile, "server_key", "i", "/etc/ssh/ssh_host_rsa_key", "Key file for SSH Piper") pflag.StringVarP(&config.Challenger, "challenger", "c", "", "Additional challenger name, e.g. pam, emtpy for no additional challenge") + pflag.StringVar(&config.Logfile, "log", "", "Logfile path. Leave emtpy or any error occurs will fall back to stdout") - pflag.BoolVar(&config.AllowBadUsername, "allow_bad_username", false, "disable username check while search the working dir") + pflag.BoolVar(&config.AllowBadUsername, "allow_bad_username", false, "Disable username check while search the working dir") pflag.BoolVar(&config.NoCheckPerm, "no_check_perm", false, "Disable 0400 checking when using files in the working dir") + pflag.BoolVar(&config.RecordTypescript, "record_typescript", false, "record screen output into the working dir with typescript format") + pflag.BoolVarP(&config.ShowHelp, "help", "h", false, "Print help and exit") pflag.BoolVar(&config.ShowVersion, "version", false, "Print version and exit") diff --git a/sshpiperd/sshpiperd.go b/sshpiperd/sshpiperd.go index 098e6634..2e2a8322 100644 --- a/sshpiperd/sshpiperd.go +++ b/sshpiperd/sshpiperd.go @@ -93,6 +93,19 @@ func main() { return } + if config.RecordTypescript { + auditor, err := newFilePtyLogger(p.DownstreamConnMeta().User()) + + if err != nil { + logger.Printf("connection from %v failed to create auditor reason: %v", c.RemoteAddr(), err) + return + } + + defer auditor.Close() + + p.HookUpstreamMsg = auditor.loggingTty + } + err = p.Wait() logger.Printf("connection from %v closed reason: %v", c.RemoteAddr(), err) }()