func (beacon *Beacon) keepAlive(conn ssh.Conn) (<-chan error, chan<- struct{}) { logger := beacon.Logger.Session("keepalive") errs := make(chan error, 1) kas := time.NewTicker(5 * time.Second) cancel := make(chan struct{}) go func() { for { // ignore reply; server may just not have handled it, since there's no // standard keepalive request name _, _, err := conn.SendRequest("keepalive", true, []byte("sup")) if err != nil { logger.Error("failed", err) errs <- err return } logger.Debug("ok") select { case <-kas.C: case <-cancel: errs <- nil return } } }() return errs, cancel }
//DialClient returns two channels where one returns a ssh.Client and the other and error func DialClient(dial, expire time.Duration, ip string, conf *ssh.ClientConfig, retry <-chan struct{}) (*ssh.Client, error) { flux.Report(nil, fmt.Sprintf("MakeDial for %s for dailing at %+s and expiring in %+s", conf.User, dial, expire)) cons := make(chan *ssh.Client) errs := make(chan error) var con net.Conn var sc ssh.Conn var chans <-chan ssh.NewChannel var req <-chan *ssh.Request var err error flux.GoDefer("MakeDial", func() { con, err = net.DialTimeout("tcp", ip, dial) if err != nil { flux.Report(err, fmt.Sprintf("MakeDial:Before for %s net.DailTimeout", ip)) errs <- err return } sc, chans, req, err = ssh.NewClientConn(con, ip, conf) if err != nil { flux.Report(err, fmt.Sprintf("MakeDial:After for %s ssh.NewClientConn", ip)) errs <- err return } flux.Report(nil, fmt.Sprintf("MakeDial initiating NewClient for %s", ip)) cons <- ssh.NewClient(sc, chans, req) return }) expiration := threshold(expire) go func() { for _ = range retry { expiration = threshold(expire) } }() select { case err := <-errs: flux.Report(err, fmt.Sprintf("NewClient Ending!")) return nil, err case som := <-cons: flux.Report(nil, fmt.Sprintf("NewClient Created!")) expiration = nil return som, nil case <-expiration: flux.Report(nil, fmt.Sprintf("MakeDial Expired for %s!", ip)) defer con.Close() if sc != nil { sc.Close() } return nil, ErrTimeout } }
func OpenStream(conn ssh.Conn, remote string) (io.ReadWriteCloser, error) { stream, reqs, err := conn.OpenChannel("chisel", []byte(remote)) if err != nil { return nil, err } go ssh.DiscardRequests(reqs) return stream, nil }
func keepalive(conn ssh.Conn, ticker *time.Ticker, stopCh chan struct{}) { for { select { case <-ticker.C: _, _, _ = conn.SendRequest("*****@*****.**", true, nil) case <-stopCh: ticker.Stop() return } } }
func ProxyChannels(logger lager.Logger, conn ssh.Conn, channels <-chan ssh.NewChannel) { logger = logger.Session("proxy-channels") logger.Info("started") defer logger.Info("completed") defer conn.Close() for newChannel := range channels { logger.Info("new-channel", lager.Data{ "channelType": newChannel.ChannelType(), "extraData": newChannel.ExtraData(), }) targetChan, targetReqs, err := conn.OpenChannel(newChannel.ChannelType(), newChannel.ExtraData()) if err != nil { logger.Error("failed-to-open-channel", err) if openErr, ok := err.(*ssh.OpenChannelError); ok { newChannel.Reject(openErr.Reason, openErr.Message) } else { newChannel.Reject(ssh.ConnectionFailed, err.Error()) } continue } sourceChan, sourceReqs, err := newChannel.Accept() if err != nil { targetChan.Close() continue } toTargetLogger := logger.Session("to-target") toSourceLogger := logger.Session("to-source") go func() { helpers.Copy(toTargetLogger, nil, targetChan, sourceChan) targetChan.CloseWrite() }() go func() { helpers.Copy(toSourceLogger, nil, sourceChan, targetChan) sourceChan.CloseWrite() }() go ProxyRequests(toTargetLogger, newChannel.ChannelType(), sourceReqs, targetChan) go ProxyRequests(toSourceLogger, newChannel.ChannelType(), targetReqs, sourceChan) } }
/* handleConnRequests handles proxying requests read from reqs to the SSH connection sc. info is used for logging */ func handleConnRequests( reqs <-chan *ssh.Request, c ssh.Conn, info string, ) { for r := range reqs { go handleRequest( r, func( name string, wantReply bool, payload []byte, ) (bool, []byte, error) { return c.SendRequest(name, wantReply, payload) }, func() error { return c.Close() }, info, ) } }
func ProxyGlobalRequests(logger lager.Logger, conn ssh.Conn, reqs <-chan *ssh.Request) { logger = logger.Session("proxy-global-requests") logger.Info("started") defer logger.Info("completed") for req := range reqs { logger.Info("request", lager.Data{ "type": req.Type, "wantReply": req.WantReply, "payload": req.Payload, }) success, reply, err := conn.SendRequest(req.Type, req.WantReply, req.Payload) if err != nil { logger.Error("send-request-failed", err) continue } if req.WantReply { req.Reply(success, reply) } } }
func (c *comm) reconnect() (err error) { if c.conn != nil { c.conn.Close() } // Set the conn and client to nil since we'll recreate it c.conn = nil c.client = nil log.Printf("reconnecting to TCP connection for SSH") c.conn, err = c.config.Connection() if err != nil { // Explicitly set this to the REAL nil. Connection() can return // a nil implementation of net.Conn which will make the // "if c.conn == nil" check fail above. Read here for more information // on this psychotic language feature: // // http://golang.org/doc/faq#nil_error c.conn = nil log.Printf("reconnection error: %s", err) return } log.Printf("handshaking with SSH") // Default timeout to 1 minute if it wasn't specified (zero value). For // when you need to handshake from low orbit. var duration time.Duration if c.config.HandshakeTimeout == 0 { duration = 1 * time.Minute } else { duration = c.config.HandshakeTimeout } connectionEstablished := make(chan struct{}, 1) var sshConn ssh.Conn var sshChan <-chan ssh.NewChannel var req <-chan *ssh.Request go func() { sshConn, sshChan, req, err = ssh.NewClientConn(c.conn, c.address, c.config.SSHConfig) close(connectionEstablished) }() select { case <-connectionEstablished: // We don't need to do anything here. We just want select to block until // we connect or timeout. case <-time.After(duration): if c.conn != nil { c.conn.Close() } if sshConn != nil { sshConn.Close() } return ErrHandshakeTimeout } if err != nil { log.Printf("handshake error: %s", err) return } log.Printf("handshake complete!") if sshConn != nil { c.client = ssh.NewClient(sshConn, sshChan, req) } c.connectToAgent() return }
/* handleChan handles a single channel request from sc, proxying it to the client. General logging messages will be written to lg, and channel-specific data and messages will be written to a new file in ldir. */ func handleChan( nc ssh.NewChannel, client ssh.Conn, ldir string, lg *log.Logger, direction string, ) { /* Log the channel request */ crl := fmt.Sprintf( "Type:%q Data:%q Direction:%q", nc.ChannelType(), nc.ExtraData(), direction, ) /* Pass to server */ cc, creqs, err := client.OpenChannel( nc.ChannelType(), nc.ExtraData(), ) if nil != err { go rejectChannel(err, crl, nc, lg) return } defer cc.Close() /* Make channel to attacker, defer close */ ac, areqs, err := nc.Accept() if nil != err { lg.Printf( "Unable to accept channel request of type %q: %v", nc.ChannelType(), err, ) return } defer ac.Close() /* Channel worked, make a logger for it */ clg, lf, clgn, err := logChannel(ldir, nc) if nil != err { lg.Printf( "Unable to open log file for channel of type %q:%v", nc.ChannelType(), err, ) return } defer lf.Close() clg.Printf("Start of log") /* Proxy requests on channels */ go handleReqs(areqs, Channel{oc: cc}, clg, "attacker->server") go handleReqs(creqs, Channel{oc: ac}, clg, "server->attacker") /* Log the channel */ lg.Printf("Channel %s Log:%q", crl, clgn) /* Proxy comms */ wg := make(chan int, 4) go ProxyChannel( ac, cc, clg, "server->attacker", wg, 1, ) go ProxyChannel( cc, ac, clg, "attacker->server", wg, 1, ) go ProxyChannel( cc.Stderr(), ac.Stderr(), clg, "attacker-(err)->server", wg, 0, ) go ProxyChannel( ac.Stderr(), cc.Stderr(), clg, "server-(err)->attacker", wg, 0, ) sum := 0 for i := range wg { sum += i if 2 <= sum { break } } /* TODO: Proxy comms */ }
/* handleChannel proxies a channel request command or shell to the ssh connection sc. */ func handleNewChannel(cr ssh.NewChannel, sc ssh.Conn, info string) { log.Printf( "%v Type:%q Data:%q NewChannel", info, cr.ChannelType(), cr.ExtraData(), ) /* Make the same request to the other side */ och, oreqs, err := sc.OpenChannel(cr.ChannelType(), cr.ExtraData()) if nil != err { /* If we can't log it, and reject the client */ oe, ok := err.(*ssh.OpenChannelError) var ( reason ssh.RejectionReason message string ) if !ok { log.Printf( "%v Type:%q Data:%q Unable to open channel: "+ "%v", info, cr.ChannelType(), cr.ExtraData(), err, ) reason = ssh.ConnectionFailed message = "Fail" message = err.Error() } else { log.Printf( "%v Type:%q Data:%q Reason:%q Message:%q "+ "Unable to open channel", info, cr.ChannelType(), cr.ExtraData(), oe.Reason.String(), oe.Message, ) reason = oe.Reason message = oe.Message } if err := cr.Reject(reason, message); nil != err { log.Printf( "%v Unable to pass on channel rejecton "+ "request: %v", info, err, ) } return } defer och.Close() /* Accept the channel request from the requestor */ rch, rreqs, err := cr.Accept() if nil != err { log.Printf( "%v Unable to accept request for a channel of type "+ "%q: %v", cr.ChannelType(), info, err, ) return } defer rch.Close() /* Handle passing requests between channels */ hcrinfo := fmt.Sprintf(" %v ChannelType:%q", info, cr.ChannelType()) go handleChannelRequests( rreqs, och, hcrinfo+" ReqDir:AsDirection", ) go handleChannelRequests( oreqs, rch, hcrinfo+" ReqDir:AgainstDirection", ) log.Printf( "%v Type:%q Data:%q Opened", info, cr.ChannelType(), cr.ExtraData(), ) /* For now, print out read data */ done := make(chan struct{}, 4) go copyOut(och, rch, done) go copyOut(rch, och, done) go copyOut(och.Stderr(), rch.Stderr(), done) go copyOut(rch.Stderr(), och.Stderr(), done) /* Wait for a pipe to break */ <-done fmt.Printf("\nDone.\n") }
/* waitChan puts an empty struct in wc when c's Wait method returns. */ func waitChan(c ssh.Conn, wc chan<- struct{}) { c.Wait() wc <- struct{}{} }