mirror of
				https://github.com/amitbet/vncproxy.git
				synced 2025-10-31 00:58:48 +00:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ea8f9b5109 | ||
|  | faa9021021 | ||
|  | 1c052273de | ||
|  | f07be04814 | ||
|  | 55cf83519a | ||
|  | 92ee157817 | ||
|  | f635a73757 | ||
|  | 149e26b6f7 | ||
|  | 3d42d4fa5f | ||
|  | f66f550c77 | ||
|  | c7ac36d15f | ||
|  | 0ba10fab76 | ||
|  | 76299f1bc3 | ||
|  | a9dce0e6c6 | ||
|  | fa416220ea | ||
|  | efc5741057 | ||
|  | 82e1549c4f | ||
|  | 33be77dedc | ||
|  | 11b1d45ce9 | ||
|  | 42873297d2 | ||
|  | f00e2c33e7 | ||
|  | 9a52a433ea | ||
|  | 06e555775f | ||
|  | 3e882d6140 | ||
|  | 5a488bcd0f | 
							
								
								
									
										129
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | version: 2 # use CircleCI 2.0 | ||||||
|  | jobs: # basic units of work in a run | ||||||
|  |   build: # runs not using Workflows must have a `build` job as entry point | ||||||
|  |     docker: # run the steps with Docker | ||||||
|  |       # CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/ | ||||||
|  |       - image: circleci/golang:1.12 | ||||||
|  |       # CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/ | ||||||
|  |       - image: circleci/postgres:9.6-alpine | ||||||
|  |         environment: # environment variables for primary container | ||||||
|  |           POSTGRES_USER: circleci-demo-go | ||||||
|  |           POSTGRES_DB: circle_test | ||||||
|  |  | ||||||
|  |     parallelism: 2 | ||||||
|  |  | ||||||
|  |     environment: # environment variables for the build itself | ||||||
|  |       TEST_RESULTS: /tmp/test-results # path to where test results will be saved | ||||||
|  |  | ||||||
|  |     steps: # steps that comprise the `build` job | ||||||
|  |       - checkout # check out source code to working directory | ||||||
|  |       - run: mkdir -p $TEST_RESULTS # create the test results directory | ||||||
|  |  | ||||||
|  |       - restore_cache: # restores saved cache if no changes are detected since last run | ||||||
|  |           keys: | ||||||
|  |             - go-mod-v4-{{ checksum "go.sum" }} | ||||||
|  |  | ||||||
|  |       #  Wait for Postgres to be ready before proceeding | ||||||
|  |       - run: | ||||||
|  |           name: Waiting for Postgres to be ready | ||||||
|  |           command: dockerize -wait tcp://localhost:5432 -timeout 1m | ||||||
|  |  | ||||||
|  |       - run: | ||||||
|  |           name: Run unit tests | ||||||
|  |           environment: # environment variables for the database url and path to migration files | ||||||
|  |             CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable" | ||||||
|  |             CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations | ||||||
|  |  | ||||||
|  |           # store the results of our tests in the $TEST_RESULTS directory | ||||||
|  |           command: | | ||||||
|  |             PACKAGE_NAMES=$(go list ./... | circleci tests split --split-by=timings --timings-type=classname) | ||||||
|  |             gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES | ||||||
|  |      | ||||||
|  |       #  Build  | ||||||
|  |       - run: | ||||||
|  |           name: Build and package all OS flavors | ||||||
|  |           command: | | ||||||
|  |               #!/bin/bash | ||||||
|  |               sum="sha1sum" | ||||||
|  |  | ||||||
|  |               # VERSION=`date -u +%Y%m%d` | ||||||
|  |               VERSION="v1.11" | ||||||
|  |               LDFLAGS="-X main.VERSION=$VERSION -s -w" | ||||||
|  |               GCFLAGS="" | ||||||
|  |  | ||||||
|  |               if ! hash sha1sum 2>/dev/null; then | ||||||
|  |                 if ! hash shasum 2>/dev/null; then | ||||||
|  |                   echo "I can't see 'sha1sum' or 'shasum'" | ||||||
|  |                   echo "Please install one of them!" | ||||||
|  |                   exit | ||||||
|  |                 fi | ||||||
|  |                 sum="shasum" | ||||||
|  |               fi | ||||||
|  |  | ||||||
|  |               UPX=false | ||||||
|  |               if hash upx 2>/dev/null; then | ||||||
|  |                 UPX=true | ||||||
|  |               fi | ||||||
|  |  | ||||||
|  |  | ||||||
|  |               OSES=( linux darwin windows ) | ||||||
|  |               ARCHS=(amd64 386 ) | ||||||
|  |               for os in ${OSES[@]}; do | ||||||
|  |                 for arch in ${ARCHS[@]}; do | ||||||
|  |                   suffix="" | ||||||
|  |                   if [ "$os" == "windows" ] | ||||||
|  |                   then | ||||||
|  |                     suffix=".exe" | ||||||
|  |                   fi | ||||||
|  |  | ||||||
|  |                   env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/recorder${suffix} ./recorder/cmd | ||||||
|  |                   env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/player${suffix} ./player/cmd | ||||||
|  |                   env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o ./dist/${os}_${arch}/proxy${suffix} ./proxy/cmd | ||||||
|  |                  | ||||||
|  |                     if $UPX; then upx -9 client_${os}_${arch}${suffix} server_${os}_${arch}${suffix};fi | ||||||
|  |                   # tar -zcf ./dist/${CIRCLE_PROJECT_REPONAME}-${os}-${arch}-$VERSION.tar.gz ./dist/${os}_${arch}/proxy${suffix} ./dist/${os}_${arch}/player${suffix} ./dist/${os}_${arch}/recorder${suffix} | ||||||
|  |                       cd dist/${os}_${arch}/ | ||||||
|  |                       zip -D -q -r ../${CIRCLE_PROJECT_REPONAME}-${os}-${arch}-$VERSION.zip proxy${suffix} player${suffix} recorder${suffix} | ||||||
|  |                       cd ../.. | ||||||
|  |                       export | ||||||
|  |                     $sum ./dist/${CIRCLE_PROJECT_REPONAME}-${os}-${arch}-$VERSION.zip | ||||||
|  |                     rm -rf ./dist/${os}_${arch}/ | ||||||
|  |                 done | ||||||
|  |               done | ||||||
|  |  | ||||||
|  |       - store_artifacts: # upload test summary for display in Artifacts | ||||||
|  |           path: ./dist | ||||||
|  |           destination: release-artifacts | ||||||
|  |  | ||||||
|  |       # - run: make # pull and build dependencies for the project | ||||||
|  |  | ||||||
|  |       # - save_cache: | ||||||
|  |       #     key: go-mod-v4-{{ checksum "go.sum" }} | ||||||
|  |       #     paths: | ||||||
|  |       #       - "/go/pkg/mod" | ||||||
|  |  | ||||||
|  |       # - run: | ||||||
|  |       #     name: Start service | ||||||
|  |       #     environment: | ||||||
|  |       #       CONTACTS_DB_URL: "postgres://circleci-demo-go@localhost:5432/circle_test?sslmode=disable" | ||||||
|  |       #       CONTACTS_DB_MIGRATIONS: /home/circleci/project/db/migrations | ||||||
|  |       #     command: ./workdir/contacts | ||||||
|  |       #     background: true # keep service running and proceed to next step | ||||||
|  |  | ||||||
|  |       # - run: | ||||||
|  |       #     name: Validate service is working | ||||||
|  |       #     command: | | ||||||
|  |       #       sleep 5 | ||||||
|  |       #       curl --retry 10 --retry-delay 1 -X POST --header "Content-Type: application/json" -d '{"email":"test@example.com","name":"Test User"}' http://localhost:8080/contacts | ||||||
|  |  | ||||||
|  |       # - store_artifacts: # upload test summary for display in Artifacts | ||||||
|  |       #     path: /tmp/test-results | ||||||
|  |       #     destination: raw-test-output | ||||||
|  |  | ||||||
|  |       # - store_test_results: # upload test results for display in Test Summary | ||||||
|  |       #     path: /tmp/test-results | ||||||
|  | workflows: | ||||||
|  |   version: 2 | ||||||
|  |   build-workflow: | ||||||
|  |     jobs: | ||||||
|  |       - build | ||||||
| @@ -1,9 +1,10 @@ | |||||||
| # VncProxy | # VncProxy [](https://circleci.com/gh/amitbet/vncproxy/tree/master) [](https://raw.githubusercontent.com/CircleCI-Public/circleci-demo-go/master/LICENSE.md) | ||||||
|  |  | ||||||
| An RFB proxy, written in go that can save and replay FBS files | An RFB proxy, written in go that can save and replay FBS files | ||||||
| * Supports all modern encodings & most useful pseudo-encodings | * Supports all modern encodings & most useful pseudo-encodings | ||||||
| * Supports multiple VNC client connections & multi servers (chosen by sessionId) | * Supports multiple VNC client connections & multi servers (chosen by sessionId) | ||||||
| * Supports being a "websockify" proxy (for web clients like NoVnc) | * Supports being a "websockify" proxy (for web clients like NoVnc) | ||||||
| * Produces FBS files compatible with tightvnc player (while using tight's default 3Byte color format) | * Produces FBS files compatible with [tightvnc's rfb player](https://www.tightvnc.com/rfbplayer.php) (while using tight's default 3Byte color format) | ||||||
| * Can also be used as: | * Can also be used as: | ||||||
|     * A screen recorder vnc-client |     * A screen recorder vnc-client | ||||||
|     * A replay server to show fbs recordings to connecting clients  |     * A replay server to show fbs recordings to connecting clients  | ||||||
| @@ -15,6 +16,7 @@ An RFB proxy, written in go that can save and replay FBS files | |||||||
|     - ChickenOfTheVnc(client) |     - ChickenOfTheVnc(client) | ||||||
|     - VineVnc(server) |     - VineVnc(server) | ||||||
|     - TigerVnc(client) |     - TigerVnc(client) | ||||||
|  |     - Qemu vnc(server)  | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Executables (see releases) | ### Executables (see releases) | ||||||
| @@ -23,7 +25,7 @@ An RFB proxy, written in go that can save and replay FBS files | |||||||
| * player - a toy player that will replay a given fbs file to all incoming connections | * player - a toy player that will replay a given fbs file to all incoming connections | ||||||
|  |  | ||||||
| ## Usage: | ## Usage: | ||||||
|     recorder -recDir=./recording.rbs -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@ |     recorder -recFile=./recording.rbs -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@ | ||||||
|     player -fbsFile=./myrec.fbs -tcpPort=5905 |     player -fbsFile=./myrec.fbs -tcpPort=5905 | ||||||
|     proxy -recDir=./recordings/ -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@ -tcpPort=5903 -wsPort=5905 -vncPass=@!@!@! |     proxy -recDir=./recordings/ -targHost=192.168.0.100 -targPort=5903 -targPass=@@@@@ -tcpPort=5903 -wsPort=5905 -vncPass=@!@!@! | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								build.sh
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								build.sh
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ | |||||||
| sum="sha1sum" | sum="sha1sum" | ||||||
|  |  | ||||||
| # VERSION=`date -u +%Y%m%d` | # VERSION=`date -u +%Y%m%d` | ||||||
| VERSION="v1.02" | VERSION="v1.11" | ||||||
| LDFLAGS="-X main.VERSION=$VERSION -s -w" | LDFLAGS="-X main.VERSION=$VERSION -s -w" | ||||||
| GCFLAGS="" | GCFLAGS="" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,11 +4,11 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/amitbet/vncproxy/common" | ||||||
|  | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net" | 	"net" | ||||||
| 	"unicode" | 	"unicode" | ||||||
| 	"vncproxy/common" |  | ||||||
| 	"vncproxy/logger" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // A ServerMessage implements a message sent from the server to the client. | // A ServerMessage implements a message sent from the server to the client. | ||||||
| @@ -146,7 +146,7 @@ func (c *ClientConn) CutText(text string) error { | |||||||
|  |  | ||||||
| 	for _, char := range text { | 	for _, char := range text { | ||||||
| 		if char > unicode.MaxLatin1 { | 		if char > unicode.MaxLatin1 { | ||||||
| 			return fmt.Errorf("Character '%s' is not valid Latin-1", char) | 			return fmt.Errorf("Character '%v' is not valid Latin-1", char) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { | 		if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { | ||||||
| @@ -474,6 +474,7 @@ func (c *ClientConn) mainLoop() { | |||||||
| 		new(MsgSetColorMapEntries), | 		new(MsgSetColorMapEntries), | ||||||
| 		new(MsgBell), | 		new(MsgBell), | ||||||
| 		new(MsgServerCutText), | 		new(MsgServerCutText), | ||||||
|  | 		new(MsgServerFence), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, msg := range defaultMessages { | 	for _, msg := range defaultMessages { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func readPixelFormat(r io.Reader, result *common.PixelFormat) error { | func readPixelFormat(r io.Reader, result *common.PixelFormat) error { | ||||||
|   | |||||||
| @@ -6,9 +6,9 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // MsgFramebufferUpdate consists of a sequence of rectangles of | // MsgFramebufferUpdate consists of a sequence of rectangles of | ||||||
| @@ -129,7 +129,7 @@ type MsgSetColorMapEntries struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (fbm *MsgSetColorMapEntries) CopyTo(r io.Reader, w io.Writer, c common.IClientConn) error { | func (fbm *MsgSetColorMapEntries) CopyTo(r io.Reader, w io.Writer, c common.IClientConn) error { | ||||||
| 	reader := &common.RfbReadHelper{Reader: r} | 	reader := common.NewRfbReadHelper(r) | ||||||
| 	writeTo := &WriteTo{w, "MsgSetColorMapEntries.CopyTo"} | 	writeTo := &WriteTo{w, "MsgSetColorMapEntries.CopyTo"} | ||||||
| 	reader.Listeners.AddListener(writeTo) | 	reader.Listeners.AddListener(writeTo) | ||||||
| 	_, err := fbm.Read(c, reader) | 	_, err := fbm.Read(c, reader) | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package client | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type WriteTo struct { | type WriteTo struct { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import ( | |||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var TightMinToCompress = 12 | var TightMinToCompress = 12 | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type CopyRectEncoding struct { | type CopyRectEncoding struct { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type CoRREEncoding struct { | type CoRREEncoding struct { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"math" | 	"math" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type EncCursorPseudo struct { | type EncCursorPseudo struct { | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package encodings | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package encodings | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type EncLedStatePseudo struct { | type EncLedStatePseudo struct { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ package encodings | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type PseudoEncoding struct { | type PseudoEncoding struct { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RawEncoding is raw pixel data sent by the server. | // RawEncoding is raw pixel data sent by the server. | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type RREEncoding struct { | type RREEncoding struct { | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var TightMinToCompress int = 12 | var TightMinToCompress int = 12 | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ package encodings | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type TightPngEncoding struct { | type TightPngEncoding struct { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ZLibEncoding struct { | type ZLibEncoding struct { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ZRLEEncoding struct { | type ZRLEEncoding struct { | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | module github.com/amitbet/vncproxy | ||||||
|  |  | ||||||
|  | require golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 | ||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k= | ||||||
|  | golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| @@ -4,11 +4,11 @@ import ( | |||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"vncproxy/player" | 	"github.com/amitbet/vncproxy/player" | ||||||
| 	"vncproxy/server" | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
|   | |||||||
| @@ -5,10 +5,10 @@ import ( | |||||||
|  |  | ||||||
| 	"io" | 	"io" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/client" | 	"github.com/amitbet/vncproxy/client" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"vncproxy/server" | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type VncStreamFileReader interface { | type VncStreamFileReader interface { | ||||||
|   | |||||||
| @@ -5,9 +5,9 @@ import ( | |||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type FbsReader struct { | type FbsReader struct { | ||||||
|   | |||||||
| @@ -3,14 +3,15 @@ package player | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/common" |  | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/server" | 	"github.com/amitbet/vncproxy/logger" | ||||||
|  | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestServer(t *testing.T) { | func TestServer(t *testing.T) { | ||||||
|  | 	t.Skip("this isn't an automated test, just an entrypoint for debugging") | ||||||
| 	//chServer := make(chan common.ClientMessage) | 	//chServer := make(chan common.ClientMessage) | ||||||
| 	//chClient := make(chan common.ServerMessage) | 	//chClient := make(chan common.ServerMessage) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,13 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import "vncproxy/proxy" | import ( | ||||||
| import "flag" | 	"flag" | ||||||
| import "vncproxy/logger" | 	"os" | ||||||
| import "os" | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/amitbet/vncproxy/logger" | ||||||
|  | 	vncproxy "github.com/amitbet/vncproxy/proxy" | ||||||
|  | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| 	//create default session if required | 	//create default session if required | ||||||
| @@ -11,8 +15,9 @@ func main() { | |||||||
| 	var wsPort = flag.String("wsPort", "", "websocket port") | 	var wsPort = flag.String("wsPort", "", "websocket port") | ||||||
| 	var vncPass = flag.String("vncPass", "", "password on incoming vnc connections to the proxy, defaults to no password") | 	var vncPass = flag.String("vncPass", "", "password on incoming vnc connections to the proxy, defaults to no password") | ||||||
| 	var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD if not defined.") | 	var recordDir = flag.String("recDir", "", "path to save FBS recordings WILL NOT RECORD if not defined.") | ||||||
| 	var targetVncPort = flag.String("targPort", "", "target vnc server port") | 	var targetVnc = flag.String("target", "", "target vnc server (host:port or /path/to/unix.socket)") | ||||||
| 	var targetVncHost = flag.String("targHost", "", "target vnc server host") | 	var targetVncPort = flag.String("targPort", "", "target vnc server port (deprecated, use -target)") | ||||||
|  | 	var targetVncHost = flag.String("targHost", "", "target vnc server host (deprecated, use -target)") | ||||||
| 	var targetVncPass = flag.String("targPass", "", "target vnc password") | 	var targetVncPass = flag.String("targPass", "", "target vnc password") | ||||||
| 	var logLevel = flag.String("logLevel", "info", "change logging level") | 	var logLevel = flag.String("logLevel", "info", "change logging level") | ||||||
|  |  | ||||||
| @@ -25,8 +30,8 @@ func main() { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if *targetVncPort == "" { | 	if *targetVnc == "" && *targetVncPort == "" { | ||||||
| 		logger.Error("no target vnc server port defined") | 		logger.Error("no target vnc server host/port or socket defined") | ||||||
| 		flag.Usage() | 		flag.Usage() | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| @@ -34,30 +39,42 @@ func main() { | |||||||
| 	if *vncPass == "" { | 	if *vncPass == "" { | ||||||
| 		logger.Warn("proxy will have no password") | 		logger.Warn("proxy will have no password") | ||||||
| 	} | 	} | ||||||
| 	if *recordDir == "" { |  | ||||||
| 		logger.Warn("FBS recording is turned off") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	tcpUrl := "" | 	tcpURL := "" | ||||||
| 	if *tcpPort != "" { | 	if *tcpPort != "" { | ||||||
| 		tcpUrl = ":" + string(*tcpPort) | 		tcpURL = ":" + string(*tcpPort) | ||||||
| 	} | 	} | ||||||
|  | 	wsURL := "" | ||||||
| 	proxy := &proxy.VncProxy{ | 	if *wsPort != "" { | ||||||
| 		WsListeningUrl:   "http://0.0.0.0:" + string(*wsPort) + "/", // empty = not listening on ws | 		wsURL = "http://0.0.0.0:" + string(*wsPort) + "/" | ||||||
| 		RecordingDir:     *recordDir,                                //"/Users/amitbet/vncRec",                     // empty = no recording | 	} | ||||||
| 		TcpListeningUrl:  tcpUrl, | 	proxy := &vncproxy.VncProxy{ | ||||||
|  | 		WsListeningURL:   wsURL, // empty = not listening on ws | ||||||
|  | 		TCPListeningURL:  tcpURL, | ||||||
| 		ProxyVncPassword: *vncPass, //empty = no auth | 		ProxyVncPassword: *vncPass, //empty = no auth | ||||||
| 		SingleSession: &proxy.VncSession{ | 		SingleSession: &vncproxy.VncSession{ | ||||||
|  | 			Target:         *targetVnc, | ||||||
| 			TargetHostname: *targetVncHost, | 			TargetHostname: *targetVncHost, | ||||||
| 			TargetPort:     *targetVncPort, | 			TargetPort:     *targetVncPort, | ||||||
| 			TargetPassword: *targetVncPass, //"vncPass", | 			TargetPassword: *targetVncPass, //"vncPass", | ||||||
| 			ID:             "dummySession", | 			ID:             "dummySession", | ||||||
| 			Status:         proxy.SessionStatusInit, | 			Status:         vncproxy.SessionStatusInit, | ||||||
| 			Type:           proxy.SessionTypeRecordingProxy, | 			Type:           vncproxy.SessionTypeProxyPass, | ||||||
| 		}, // to be used when not using sessions | 		}, // to be used when not using sessions | ||||||
| 		UsingSessions: false, //false = single session - defined in the var above | 		UsingSessions: false, //false = single session - defined in the var above | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if *recordDir != "" { | ||||||
|  | 		fullPath, err := filepath.Abs(*recordDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logger.Error("bad recording path: ", err) | ||||||
|  | 		} | ||||||
|  | 		logger.Info("FBS recording is turned on, writing to dir: ", fullPath) | ||||||
|  | 		proxy.RecordingDir = fullPath | ||||||
|  | 		proxy.SingleSession.Type = vncproxy.SessionTypeRecordingProxy | ||||||
|  | 	} else { | ||||||
|  | 		logger.Info("FBS recording is turned off") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	proxy.StartListening() | 	proxy.StartListening() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package proxy | package proxy | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"vncproxy/client" | 	"github.com/amitbet/vncproxy/client" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"vncproxy/server" | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ClientUpdater struct { | type ClientUpdater struct { | ||||||
|   | |||||||
| @@ -5,18 +5,19 @@ import ( | |||||||
| 	"path" | 	"path" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/client" |  | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/client" | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/player" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	listeners "vncproxy/recorder" | 	"github.com/amitbet/vncproxy/player" | ||||||
| 	"vncproxy/server" | 	listeners "github.com/amitbet/vncproxy/recorder" | ||||||
|  | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type VncProxy struct { | type VncProxy struct { | ||||||
| 	TcpListeningUrl  string      // empty = not listening on tcp | 	TCPListeningURL  string      // empty = not listening on tcp | ||||||
| 	WsListeningUrl   string      // empty = not listening on ws | 	WsListeningURL   string      // empty = not listening on ws | ||||||
| 	RecordingDir     string      // empty = no recording | 	RecordingDir     string      // empty = no recording | ||||||
| 	ProxyVncPassword string      //empty = no auth | 	ProxyVncPassword string      //empty = no auth | ||||||
| 	SingleSession    *VncSession // to be used when not using sessions | 	SingleSession    *VncSession // to be used when not using sessions | ||||||
| @@ -24,8 +25,17 @@ type VncProxy struct { | |||||||
| 	sessionManager   *SessionManager | 	sessionManager   *SessionManager | ||||||
| } | } | ||||||
|  |  | ||||||
| func (vp *VncProxy) createClientConnection(targetServerUrl string, vncPass string) (*client.ClientConn, error) { | func (vp *VncProxy) createClientConnection(target string, vncPass string) (*client.ClientConn, error) { | ||||||
| 	nc, err := net.Dial("tcp", targetServerUrl) | 	var ( | ||||||
|  | 		nc  net.Conn | ||||||
|  | 		err error | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if target[0] == '/' { | ||||||
|  | 		nc, err = net.Dial("unix", target) | ||||||
|  | 	} else { | ||||||
|  | 		nc, err = net.Dial("tcp", target) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Errorf("error connecting to vnc server: %s", err) | 		logger.Errorf("error connecting to vnc server: %s", err) | ||||||
| @@ -85,7 +95,12 @@ func (vp *VncProxy) newServerConnHandler(cfg *server.ServerConfig, sconn *server | |||||||
|  |  | ||||||
| 	session.Status = SessionStatusInit | 	session.Status = SessionStatusInit | ||||||
| 	if session.Type == SessionTypeProxyPass || session.Type == SessionTypeRecordingProxy { | 	if session.Type == SessionTypeProxyPass || session.Type == SessionTypeRecordingProxy { | ||||||
| 		cconn, err := vp.createClientConnection(session.TargetHostname+":"+session.TargetPort, session.TargetPassword) | 		target := session.Target | ||||||
|  | 		if session.TargetHostname != "" && session.TargetPort != "" { | ||||||
|  | 			target = session.TargetHostname + ":" + session.TargetPort | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cconn, err := vp.createClientConnection(target, session.TargetPassword) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			session.Status = SessionStatusError | 			session.Status = SessionStatusError | ||||||
| 			logger.Errorf("Proxy.newServerConnHandler error creating connection: %s", err) | 			logger.Errorf("Proxy.newServerConnHandler error creating connection: %s", err) | ||||||
| @@ -171,19 +186,19 @@ func (vp *VncProxy) StartListening() { | |||||||
| 		UseDummySession:  !vp.UsingSessions, | 		UseDummySession:  !vp.UsingSessions, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if vp.TcpListeningUrl != "" && vp.WsListeningUrl != "" { | 	if vp.TCPListeningURL != "" && vp.WsListeningURL != "" { | ||||||
| 		logger.Infof("running two listeners: tcp port: %s, ws url: %s", vp.TcpListeningUrl, vp.WsListeningUrl) | 		logger.Infof("running two listeners: tcp port: %s, ws url: %s", vp.TCPListeningURL, vp.WsListeningURL) | ||||||
|  |  | ||||||
| 		go server.WsServe(vp.WsListeningUrl, cfg) | 		go server.WsServe(vp.WsListeningURL, cfg) | ||||||
| 		server.TcpServe(vp.TcpListeningUrl, cfg) | 		server.TcpServe(vp.TCPListeningURL, cfg) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if vp.WsListeningUrl != "" { | 	if vp.WsListeningURL != "" { | ||||||
| 		logger.Infof("running ws listener url: %s", vp.WsListeningUrl) | 		logger.Infof("running ws listener url: %s", vp.WsListeningURL) | ||||||
| 		server.WsServe(vp.WsListeningUrl, cfg) | 		server.WsServe(vp.WsListeningURL, cfg) | ||||||
| 	} | 	} | ||||||
| 	if vp.TcpListeningUrl != "" { | 	if vp.TCPListeningURL != "" { | ||||||
| 		logger.Infof("running tcp listener on port: %s", vp.TcpListeningUrl) | 		logger.Infof("running tcp listener on port: %s", vp.TCPListeningURL) | ||||||
| 		server.TcpServe(":"+vp.TcpListeningUrl, cfg) | 		server.TcpServe(vp.TCPListeningURL, cfg) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,11 +4,12 @@ import "testing" | |||||||
|  |  | ||||||
| func TestProxy(t *testing.T) { | func TestProxy(t *testing.T) { | ||||||
| 	//create default session if required | 	//create default session if required | ||||||
|  | 	t.Skip("this isn't an automated test, just an entrypoint for debugging") | ||||||
|  |  | ||||||
| 	proxy := &VncProxy{ | 	proxy := &VncProxy{ | ||||||
| 		WsListeningUrl:  "http://0.0.0.0:7778/", // empty = not listening on ws | 		WsListeningURL:  "http://0.0.0.0:7778/", // empty = not listening on ws | ||||||
| 		RecordingDir:    "d:\\",                 // empty = no recording | 		RecordingDir:    "d:\\",                 // empty = no recording | ||||||
| 		TcpListeningUrl: ":5904", | 		TCPListeningURL: ":5904", | ||||||
| 		//RecordingDir:          "C:\\vncRec", // empty = no recording | 		//RecordingDir:          "C:\\vncRec", // empty = no recording | ||||||
| 		ProxyVncPassword: "1234", //empty = no auth | 		ProxyVncPassword: "1234", //empty = no auth | ||||||
| 		SingleSession: &VncSession{ | 		SingleSession: &VncSession{ | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ const ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type VncSession struct { | type VncSession struct { | ||||||
|  | 	Target         string | ||||||
| 	TargetHostname string | 	TargetHostname string | ||||||
| 	TargetPort     string | 	TargetPort     string | ||||||
| 	TargetPassword string | 	TargetPassword string | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"os" | 	"os" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/client" | 	"github.com/amitbet/vncproxy/client" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"vncproxy/recorder" | 	"github.com/amitbet/vncproxy/recorder" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
|   | |||||||
| @@ -5,9 +5,9 @@ import ( | |||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"os" | 	"os" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| 	"vncproxy/server" | 	"github.com/amitbet/vncproxy/server" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Recorder struct { | type Recorder struct { | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ package recorder | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"time" | 	"time" | ||||||
| 	"vncproxy/client" | 	"github.com/amitbet/vncproxy/client" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type RfbRequester struct { | type RfbRequester struct { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package server | |||||||
| import ( | import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Key represents a VNC key press. | // Key represents a VNC key press. | ||||||
|   | |||||||
| @@ -3,10 +3,10 @@ package server | |||||||
| import ( | import ( | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
|  |  | ||||||
| 	"io" | 	"io" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ProtoVersionLength = 12 | const ProtoVersionLength = 12 | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import ( | |||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"log" | 	"log" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type SecurityType uint8 | type SecurityType uint8 | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ServerConn struct { | type ServerConn struct { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
| 	"vncproxy/common" | 	"github.com/amitbet/vncproxy/common" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var DefaultClientMessages = []common.ClientMessage{ | var DefaultClientMessages = []common.ClientMessage{ | ||||||
|   | |||||||
| @@ -3,8 +3,9 @@ package server | |||||||
| import ( | import ( | ||||||
| 	"log" | 	"log" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"vncproxy/common" |  | ||||||
| 	"vncproxy/encodings" | 	"github.com/amitbet/vncproxy/common" | ||||||
|  | 	"github.com/amitbet/vncproxy/encodings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error { | func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error { | ||||||
| @@ -13,6 +14,7 @@ func newServerConnHandler(cfg *ServerConfig, conn *ServerConn) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestServer(t *testing.T) { | func TestServer(t *testing.T) { | ||||||
|  | 	t.Skip("this isn't an automated test, just an entrypoint for debugging") | ||||||
|  |  | ||||||
| 	//chServer := make(chan common.ClientMessage) | 	//chServer := make(chan common.ClientMessage) | ||||||
| 	chClient := make(chan common.ServerMessage) | 	chClient := make(chan common.ServerMessage) | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"vncproxy/logger" | 	"github.com/amitbet/vncproxy/logger" | ||||||
|  |  | ||||||
| 	"golang.org/x/net/websocket" | 	"golang.org/x/net/websocket" | ||||||
| ) | ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user