package session import ( "bufio" "bytes" "encoding/binary" "errors" "fmt" "golang.org/x/crypto/ssh" "io" "log" "net" "strconv" "time" "tunnel_pls/proto" ) func (s *Session) handleGlobalRequest() { for { select { case req := <-s.GlobalRequest: if req == nil { return } if req.Type == "tcpip-forward" { log.Println("Port forwarding request detected") reader := bytes.NewReader(req.Payload) addr, err := readSSHString(reader) if err != nil { log.Println("Failed to read address from payload:", err) req.Reply(false, nil) continue } var portToBind uint32 if err := binary.Read(reader, binary.BigEndian, &portToBind); err != nil { log.Println("Failed to read port from payload:", err) req.Reply(false, nil) continue } if portToBind == 80 || portToBind == 443 { s.TunnelType = HTTP Clients["test"] = s // TODO: dont forward traffic to the listener below buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, uint32(portToBind)) log.Printf("Forwarding approved on port: %d", portToBind) req.Reply(true, buf.Bytes()) } else { s.TunnelType = TCP log.Printf("Requested forwarding on %s:%d", addr, portToBind) listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", portToBind)) if err != nil { log.Printf("Failed to bind to port %d: %v", portToBind, err) req.Reply(false, nil) continue } s.Listener = listener go func() { for { conn, err := listener.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { return } log.Printf("Error accepting connection: %v", err) continue } fmt.Println("ini bind : ", portToBind) go s.HandleForwardedConnection(conn, s.Connection, portToBind) } }() buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, uint32(portToBind)) log.Printf("Forwarding approved on port: %d", portToBind) req.Reply(true, buf.Bytes()) } } else { req.Reply(false, nil) } case <-s.Done: break } } } func (s *Session) HandleSessionChannel(newChannel ssh.NewChannel) { connection, requests, err := newChannel.Accept() s.ConnChannels = append(s.ConnChannels, connection) if err != nil { log.Printf("Could not accept channel: %s", err) return } go func() { var commandBuffer bytes.Buffer buf := make([]byte, 1) for { n, err := connection.Read(buf) if n > 0 { char := buf[0] connection.Write(buf[:n]) if char == 8 || char == 127 { if commandBuffer.Len() > 0 { commandBuffer.Truncate(commandBuffer.Len() - 1) connection.Write([]byte("\b \b")) } continue } if char == '/' { commandBuffer.Reset() commandBuffer.WriteByte(char) continue } if commandBuffer.Len() > 0 { if char == 13 { command := commandBuffer.String() fmt.Println("User entered command:", command, "<>") if command == "/bye" { fmt.Println("Closing connection...") s.Close() break } else if command == "/help" { connection.Write([]byte("Available commands: /bye, /help, /clear")) } else if command == "/clear" { connection.Write([]byte("\033[H\033[2J")) } else { connection.Write([]byte("Unknown command")) } commandBuffer.Reset() continue } commandBuffer.WriteByte(char) continue } } if err != nil { if err != io.EOF { log.Printf("Error reading from client: %s", err) } break } } }() go func() { asciiArt := []string{ ` _______ _ _____ _ `, `|__ __| | | | __ \| | `, ` | |_ _ _ __ _ __ ___| | | |__) | |___ `, ` | | | | | '_ \| '_ \ / _ \ | | ___/| / __|`, ` | | |_| | | | | | | | __/ | | | | \__ \`, ` |_|\__,_|_| |_|_| |_|\___|_| |_| |_|___/`, ``, ` "Tunnel Pls" - Project by Bagas`, ` https://fossy.my.id`, ``, ` Welcome to Tunnel! Available commands:`, ` - '/bye' : Exit the tunnel`, ` - '/help' : Show this help message`, ` - '/clear' : Clear the current line`, } connection.Write([]byte("\033[H\033[2J")) for _, line := range asciiArt { connection.Write([]byte("\r\n" + line)) } connection.Write([]byte("\r\n\r\n")) for req := range requests { switch req.Type { case "shell", "pty-req", "window-change": req.Reply(true, nil) default: fmt.Println("Unknown request type") req.Reply(false, nil) } } }() } func (s *Session) HandleForwardedConnection(conn net.Conn, sshConn *ssh.ServerConn, port uint32) { defer conn.Close() log.Printf("Handling new forwarded connection from %s", conn.RemoteAddr()) payload := createForwardedTCPIPPayload(conn, port) channel, reqs, err := sshConn.OpenChannel("forwarded-tcpip", payload) if err != nil { log.Printf("Failed to open forwarded-tcpip channel: %v", err) return } defer channel.Close() conn.SetReadDeadline(time.Now().Add(2 * time.Second)) connReader := bufio.NewReader(conn) var isHttp bool header, err := connReader.Peek(7) if err != nil { isHttp = false } else { isHttp = proto.IsHttpRequest(header) } conn.SetReadDeadline(time.Time{}) go io.Copy(channel, connReader) reader := bufio.NewReader(channel) _, err = reader.Peek(1) if err == io.EOF { if isHttp { io.Copy(conn, bytes.NewReader([]byte("HTTP/1.1 502 Bad Gateway\r\nContent-Length: 11\r\nContent-Type: text/plain\r\n\r\nBad Gateway"))) } else { conn.Write([]byte("Could not forward request to the tunnel addr\r\n")) } s.ConnChannels[0].Write([]byte("Could not forward request to the tunnel addr\r\n")) return } else { //if isHttp { // response, err := http.ReadResponse(reader, nil) // if err != nil { // return // } // fmt.Println(response) //} io.Copy(conn, reader) } go func() { for req := range reqs { req.Reply(false, nil) } }() } func writeSSHString(buffer *bytes.Buffer, str string) { binary.Write(buffer, binary.BigEndian, uint32(len(str))) buffer.WriteString(str) } func parseAddr(addr string) (string, int) { host, portStr, err := net.SplitHostPort(addr) if err != nil { log.Println("Failed to parse origin address:", err) return "0.0.0.0", 0 } port, _ := strconv.Atoi(portStr) return host, port } func createForwardedTCPIPPayload(conn net.Conn, port uint32) []byte { var buf bytes.Buffer host, originPort := parseAddr(conn.RemoteAddr().String()) writeSSHString(&buf, "localhost") binary.Write(&buf, binary.BigEndian, uint32(port)) writeSSHString(&buf, host) binary.Write(&buf, binary.BigEndian, uint32(originPort)) return buf.Bytes() } func readSSHString(reader *bytes.Reader) (string, error) { var length uint32 if err := binary.Read(reader, binary.BigEndian, &length); err != nil { return "", err } strBytes := make([]byte, length) if _, err := reader.Read(strBytes); err != nil { return "", err } return string(strBytes), nil }