func (sshClient *sshClient) handleNewPortForwardChannel(newChannel ssh.NewChannel) { defer sshClient.channelHandlerWaitGroup.Done() // http://tools.ietf.org/html/rfc4254#section-7.2 var directTcpipExtraData struct { HostToConnect string PortToConnect uint32 OriginatorIPAddress string OriginatorPort uint32 } err := ssh.Unmarshal(newChannel.ExtraData(), &directTcpipExtraData) if err != nil { sshClient.rejectNewChannel(newChannel, ssh.Prohibited, "invalid extra data") return } // Intercept TCP port forwards to a specified udpgw server and handle directly. // TODO: also support UDP explicitly, e.g. with a custom "direct-udp" channel type? isUDPChannel := sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress != "" && sshClient.sshServer.support.Config.UDPInterceptUdpgwServerAddress == fmt.Sprintf("%s:%d", directTcpipExtraData.HostToConnect, directTcpipExtraData.PortToConnect) if isUDPChannel { sshClient.handleUDPChannel(newChannel) } else { sshClient.handleTCPChannel( directTcpipExtraData.HostToConnect, int(directTcpipExtraData.PortToConnect), newChannel) } }
func (handler *DirectTcpipChannelHandler) HandleNewChannel(logger lager.Logger, newChannel ssh.NewChannel) { type channelOpenDirectTcpipMsg struct { TargetAddr string TargetPort uint32 OriginAddr string OriginPort uint32 } var directTcpipMessage channelOpenDirectTcpipMsg err := ssh.Unmarshal(newChannel.ExtraData(), &directTcpipMessage) if err != nil { newChannel.Reject(ssh.ConnectionFailed, "Failed to parse open channel message") return } destination := fmt.Sprintf("%s:%d", directTcpipMessage.TargetAddr, directTcpipMessage.TargetPort) conn, err := handler.dialer.Dial("tcp", destination) if err != nil { newChannel.Reject(ssh.ConnectionFailed, err.Error()) return } channel, requests, err := newChannel.Accept() go ssh.DiscardRequests(requests) wg := &sync.WaitGroup{} wg.Add(2) go helpers.CopyAndClose(logger.Session("to-target"), wg, conn, channel) go helpers.CopyAndClose(logger.Session("to-channel"), wg, channel, conn) wg.Wait() }
func (s *Session) HandleDirectChannel(newChannel ssh.NewChannel) (bool, ssh.RejectionReason) { data, err := UnmarshalTunnelData(newChannel.ExtraData()) if err != nil { return false, ssh.UnknownChannelType } // look up session by name session, host, port := s.Gateway().LookupSessionService(data.Host, uint16(data.Port)) if session == nil { return false, ssh.ConnectionFailed } // found the service, attempt to open a channel data.Host = host data.Port = uint32(port) c2, err := session.OpenChannel("forwarded-tcpip", MarshalTunnelData(data)) if err != nil { return false, ssh.ConnectionFailed } defer func() { if c2 != nil { c2.Close() } }() // accept the channel channel, requests, err := newChannel.Accept() if err != nil { return false, ssh.ResourceShortage } // cannot return false from this point on // also need to accepted close the channel defer func() { if channel != nil { if err := channel.Close(); err != nil { glog.Warningf("failed to close accepted channel: %s", err) } } }() c, err := NewChannel(s, channel, newChannel.ChannelType(), newChannel.ExtraData()) if err != nil { glog.Errorf("failed to create accepted channel: %s", err) return true, 0 } s.AddChannel(c) // no failure go c.HandleRequests(requests) go c.HandleTunnelChannel(c2) // do not close channel on exit channel = nil c2 = nil return true, 0 }
// ChannelForward establishes a secure channel forward (ssh -W) to the server // requested by the user, assuming it is a permitted host. func (s *Server) ChannelForward(session *Session, newChannel ssh.NewChannel) { var msg channelOpenDirectMsg ssh.Unmarshal(newChannel.ExtraData(), &msg) address := fmt.Sprintf("%s:%d", msg.RAddr, msg.RPort) permitted := false for _, remote := range session.Remotes { if remote == address { permitted = true break } } if !permitted { log.Printf("Disallowed access to %s for user %s", address, session.User.Name) newChannel.Reject(ssh.Prohibited, "remote host access denied for user") return } // Log the selection if s.Selected != nil { if err := s.Selected(session, address); err != nil { newChannel.Reject(ssh.Prohibited, "access denied") return } } conn, err := net.Dial("tcp", address) if err != nil { newChannel.Reject(ssh.ConnectionFailed, fmt.Sprintf("error: %v", err)) return } channel, reqs, err := newChannel.Accept() go ssh.DiscardRequests(reqs) var closer sync.Once closeFunc := func() { channel.Close() conn.Close() } go func() { io.Copy(channel, conn) closer.Do(closeFunc) }() go func() { io.Copy(conn, channel) closer.Do(closeFunc) }() }
func (s *Session) HandleChannel(newChannel ssh.NewChannel) { glog.V(9).Infof("new channel: type = %s, data = %v", newChannel.ChannelType(), newChannel.ExtraData()) ok := false rejection := ssh.UnknownChannelType switch newChannel.ChannelType() { case "session": ok, rejection = s.HandleSessionChannel(newChannel) case "direct-tcpip": ok, rejection = s.HandleDirectChannel(newChannel) } if !ok { // reject the channel if err := newChannel.Reject(rejection, ""); err != nil { glog.Warningf("failed to reject channel: %s", err) } } }
func (s *Session) HandleSessionChannel(newChannel ssh.NewChannel) (bool, ssh.RejectionReason) { if len(newChannel.ExtraData()) > 0 { // do not accept extra data in session channel request return false, ssh.Prohibited } // accept the channel channel, requests, err := newChannel.Accept() if err != nil { return false, ssh.ResourceShortage } // cannot return false from this point on // also need to accepted close the channel defer func() { if channel != nil { if err := channel.Close(); err != nil { glog.Warningf("failed to close accepted channel: %s", err) } } }() c, err := NewChannel(s, channel, newChannel.ChannelType(), newChannel.ExtraData()) if err != nil { glog.Errorf("failed to create accepted channel: %s", err) return true, 0 } s.AddChannel(c) // no failure go c.HandleRequests(requests) go c.HandleSessionChannel() // do not close channel on exit channel = nil return true, 0 }
// SessionForward performs a regular forward, providing the user with an // interactive remote host selection if necessary. This forwarding type // requires agent forwarding in order to work. func (s *Server) SessionForward(session *Session, newChannel ssh.NewChannel, chans <-chan ssh.NewChannel) { // Okay, we're handling this as a regular session sesschan, sessReqs, err := newChannel.Accept() if err != nil { return } stderr := sesschan.Stderr() remote := "" switch len(session.Remotes) { case 0: sesschan.Close() return case 1: remote = session.Remotes[0] default: comm := rw{Reader: sesschan, Writer: stderr} if s.Interactive == nil { remote, err = DefaultInteractive(comm, session) } else { remote, err = s.Interactive(comm, session) } if err != nil { sesschan.Close() return } } fmt.Fprintf(stderr, "Connecting to %s\r\n", remote) // Set up the agent agentChan, agentReqs, err := session.Conn.OpenChannel("*****@*****.**", nil) if err != nil { fmt.Fprintf(stderr, "\r\n====== sshmux ======\r\n") fmt.Fprintf(stderr, "sshmux requires either agent forwarding or secure channel forwarding.\r\n") fmt.Fprintf(stderr, "Either enable agent forwarding (-A), or use a ssh -W proxy command.\r\n") fmt.Fprintf(stderr, "For more info, see the sshmux wiki.\r\n") sesschan.Close() return } defer agentChan.Close() go ssh.DiscardRequests(agentReqs) // Set up the client ag := agent.NewClient(agentChan) clientConfig := &ssh.ClientConfig{ User: session.Conn.User(), Auth: []ssh.AuthMethod{ ssh.PublicKeysCallback(ag.Signers), }, } client, err := ssh.Dial("tcp", remote, clientConfig) if err != nil { fmt.Fprintf(stderr, "Connect failed: %v\r\n", err) sesschan.Close() return } // Handle all incoming channel requests go func() { for newChannel = range chans { if newChannel == nil { return } channel2, reqs2, err := client.OpenChannel(newChannel.ChannelType(), newChannel.ExtraData()) if err != nil { x, ok := err.(*ssh.OpenChannelError) if ok { newChannel.Reject(x.Reason, x.Message) } else { newChannel.Reject(ssh.Prohibited, "remote server denied channel request") } continue } channel, reqs, err := newChannel.Accept() if err != nil { channel2.Close() continue } go proxy(reqs, reqs2, channel, channel2) } }() // Forward the session channel channel2, reqs2, err := client.OpenChannel("session", []byte{}) if err != nil { fmt.Fprintf(stderr, "Remote session setup failed: %v\r\n", err) sesschan.Close() return } // Proxy the channel and its requests maskedReqs := make(chan *ssh.Request, 1) go func() { for req := range sessReqs { if req.Type == "*****@*****.**" { continue } maskedReqs <- req } }() proxy(maskedReqs, reqs2, sesschan, channel2) }
func (svr *sshServer) handleChanReq(chanReq ssh.NewChannel) { fmt.Fprintf(sshServerDebugStream, "channel request: %v, extra: '%v'\n", chanReq.ChannelType(), hex.EncodeToString(chanReq.ExtraData())) switch chanReq.ChannelType() { case "session": if ch, reqs, err := chanReq.Accept(); err != nil { fmt.Fprintf(sshServerDebugStream, "fail to accept channel request: %v\n", err) chanReq.Reject(ssh.ResourceShortage, "channel accept failure") } else { chsvr := &sshSessionChannelServer{ sshChannelServer: &sshChannelServer{svr, chanReq, ch, reqs}, env: append([]string{}, os.Environ()...), } chsvr.handle() } default: chanReq.Reject(ssh.UnknownChannelType, "channel type is not a session") } }
/* 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 */ }
func proxyChannel(c *ConnInsight, mcha ssh.Channel, mreq <-chan *ssh.Request, master ssh.NewChannel, client *SSHClient, killer <-chan struct{}) error { do := new(sync.Once) cochan, coreq, err := client.OpenChannel(master.ChannelType(), master.ExtraData()) checkError(err, fmt.Sprintf("Creating Client Channel for %s", client.RemoteAddr().String())) if err != nil { return err } stop := make(chan struct{}) endClose := func() { close(stop) } flux.GoDefer("proxyChannelCopy", func() { defer cochan.Close() defer mcha.Close() func() { ploop: for { select { case <-stop: break ploop case <-killer: break ploop case slx, ok := <-coreq: if !ok { return } Reply(slx, mcha, c) switch slx.Type { case "exit-status": break ploop } case mlx, ok := <-mreq: if !ok { return } Reply(mlx, cochan, c) switch mlx.Type { case "exit-status": break ploop } } } }() }) mastercloser := io.ReadCloser(mcha) slavecloser := io.ReadCloser(cochan) wrapmaster := io.MultiWriter(mcha, c.Out()) wrapsl := io.MultiWriter(cochan, c.In()) flux.GoDefer("CopyToSlave", func() { defer do.Do(endClose) io.Copy(wrapsl, mastercloser) }) flux.GoDefer("CopyToMaster", func() { defer do.Do(endClose) io.Copy(wrapmaster, slavecloser) }) flux.GoDefer("CopyCloser", func() { defer c.Close() <-stop mx := mastercloser.Close() checkError(mx, "Master Writer Closer") sx := slavecloser.Close() checkError(sx, "Slave Writer Closer") ex := client.Close() checkError(ex, "Client Writer Closer") }) return nil }
func (sshClient *sshClient) handleNewDirectTcpipChannel(newChannel ssh.NewChannel) { // http://tools.ietf.org/html/rfc4254#section-7.2 var directTcpipExtraData struct { HostToConnect string PortToConnect uint32 OriginatorIPAddress string OriginatorPort uint32 } err := ssh.Unmarshal(newChannel.ExtraData(), &directTcpipExtraData) if err != nil { sshClient.rejectNewChannel(newChannel, ssh.Prohibited, "invalid extra data") return } targetAddr := fmt.Sprintf("%s:%d", directTcpipExtraData.HostToConnect, directTcpipExtraData.PortToConnect) log.WithContextFields(LogFields{"target": targetAddr}).Debug("dialing") // TODO: port forward dial timeout // TODO: report ssh.ResourceShortage when appropriate // TODO: IPv6 support fwdConn, err := net.Dial("tcp4", targetAddr) if err != nil { sshClient.rejectNewChannel(newChannel, ssh.ConnectionFailed, err.Error()) return } defer fwdConn.Close() fwdChannel, requests, err := newChannel.Accept() if err != nil { log.WithContextFields(LogFields{"error": err}).Warning("accept new channel failed") return } sshClient.Lock() sshClient.portForwardCount += 1 sshClient.concurrentPortForwardCount += 1 if sshClient.concurrentPortForwardCount > sshClient.peakConcurrentPortForwardCount { sshClient.peakConcurrentPortForwardCount = sshClient.concurrentPortForwardCount } sshClient.Unlock() log.WithContextFields(LogFields{"target": targetAddr}).Debug("relaying") go ssh.DiscardRequests(requests) defer fwdChannel.Close() // When idle port forward traffic rules are in place, wrap fwdConn // in an IdleTimeoutConn configured to reset idle on writes as well // as read. This ensures the port forward idle timeout only happens // when both upstream and downstream directions are are idle. if sshClient.trafficRules.IdlePortForwardTimeoutMilliseconds > 0 { fwdConn = psiphon.NewIdleTimeoutConn( fwdConn, time.Duration(sshClient.trafficRules.IdlePortForwardTimeoutMilliseconds)*time.Millisecond, true) } // relay channel to forwarded connection // TODO: relay errors to fwdChannel.Stderr()? var bytesUp, bytesDown int64 relayWaitGroup := new(sync.WaitGroup) relayWaitGroup.Add(1) go func() { defer relayWaitGroup.Done() var err error bytesUp, err = copyWithThrottle( fwdConn, fwdChannel, sshClient.trafficRules.ThrottleUpstreamSleepMilliseconds) if err != nil { log.WithContextFields(LogFields{"error": err}).Warning("upstream relay failed") } }() bytesDown, err = copyWithThrottle( fwdChannel, fwdConn, sshClient.trafficRules.ThrottleDownstreamSleepMilliseconds) if err != nil { log.WithContextFields(LogFields{"error": err}).Warning("downstream relay failed") } fwdChannel.CloseWrite() relayWaitGroup.Wait() sshClient.Lock() sshClient.concurrentPortForwardCount -= 1 sshClient.bytesUp += bytesUp sshClient.bytesDown += bytesDown sshClient.Unlock() log.WithContextFields(LogFields{"target": targetAddr}).Debug("exiting") }
/* 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") }
channelResponse := make([]byte, 7) channel.Read(channelResponse) Expect(string(channelResponse)).To(Equal("goodbye")) channel.Close() serverConn.Close() } }) It("gets forwarded to the client", func() { var newChannel ssh.NewChannel Eventually(clientChannels).Should(Receive(&newChannel)) Expect(newChannel.ChannelType()).To(Equal("test-channel")) Expect(newChannel.ExtraData()).To(Equal([]byte("extra-data"))) channel, requests, err := newChannel.Accept() Expect(err).NotTo(HaveOccurred()) Expect(channel).NotTo(BeNil()) Expect(requests).NotTo(BeClosed()) channelRequest := make([]byte, 5) channel.Read(channelRequest) Expect(string(channelRequest)).To(Equal("hello")) channel.Write([]byte("goodbye")) channel.Close() }) }) })