From 6a69a658603bfc312555017df5dea1a5f6bb4083 Mon Sep 17 00:00:00 2001 From: Niels Freier Date: Mon, 4 May 2015 09:47:26 +0400 Subject: [PATCH 1/6] WebSocket layer to be able to connect "web" client Currently working with a mini irc js implem, the flow: * PASS * NICK * USER * JOIN * PRIVMSG works and the ping/pong timeout keep the communication open. --- ergonomadic.conf | 3 ++- irc/config.go | 1 + irc/server.go | 36 +++++++++++++++++++++++++++++++ irc/websocket.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 irc/websocket.go diff --git a/ergonomadic.conf b/ergonomadic.conf index fd08218f..fb9b7d2a 100644 --- a/ergonomadic.conf +++ b/ergonomadic.conf @@ -1,8 +1,9 @@ [server] -name = "irc.example.com" ; required, usually a hostname +name = "localhost" ; required, usually a hostname database = "ergonomadic.db" ; path relative to this file listen = "localhost:6667" ; see `net.Listen` for examples listen = "[::1]:6667" ; multiple `listen`s are allowed. +wslisten = ":8080" ; websocket listen log = "debug" ; error, warn, info, debug motd = "motd.txt" ; path relative to this file password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" ; 'test' diff --git a/irc/config.go b/irc/config.go index 5a3c2e44..f3db3ea9 100644 --- a/irc/config.go +++ b/irc/config.go @@ -23,6 +23,7 @@ type Config struct { PassConfig Database string Listen []string + Wslisten string Log string MOTD string Name string diff --git a/irc/server.go b/irc/server.go index f21812dd..7152bf0b 100644 --- a/irc/server.go +++ b/irc/server.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net" + "net/http" "os" "os/signal" "strings" @@ -72,6 +73,10 @@ func NewServer(config *Config) *Server { server.listen(addr) } + if config.Server.Wslisten != "" { + server.wslisten(config.Server.Wslisten) + } + signal.Notify(server.signals, SERVER_SIGNALS...) return server @@ -203,6 +208,37 @@ func (s *Server) listen(addr string) { }() } +// +// websocket listen goroutine +// + +func (s *Server) wslisten(addr string) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + Log.error.Printf("%s method not allowed", s) + return + } + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + Log.error.Printf("%s websocket upgrade error: %s", s, err) + return + } + + wsc := WSContainer{ + conn: ws, + } + + s.newConns <- wsc + }) + go func() { + Log.info.Printf("%s listening on %s", s, addr) + err := http.ListenAndServe(addr, nil) + if err != nil { + Log.error.Printf("%s listenAndServe error: %s", s, err) + } + }() +} + // // server functionality // diff --git a/irc/websocket.go b/irc/websocket.go new file mode 100644 index 00000000..e385ca08 --- /dev/null +++ b/irc/websocket.go @@ -0,0 +1,56 @@ +package irc + +import ( + "github.com/gorilla/websocket" + "net" + "net/http" + "time" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { return true }, +} + +type WSContainer struct { + conn *websocket.Conn +} + +func (this WSContainer) Close() error { + return this.conn.Close() +} + +func (this WSContainer) LocalAddr() net.Addr { + return this.conn.LocalAddr() +} + +func (this WSContainer) RemoteAddr() net.Addr { + return this.conn.RemoteAddr() +} + +func (this WSContainer) Read(msg []byte) (int, error) { + _, tmp, err := this.conn.ReadMessage() + str := (string)(tmp) + n := copy(msg, ([]byte)(str+CRLF+CRLF)) + return n, err +} + +func (this WSContainer) Write(msg []byte) (int, error) { + err := this.conn.WriteMessage(1, msg) + return len(msg), err +} + +func (this WSContainer) SetDeadline(t time.Time) error { + err := this.conn.SetWriteDeadline(t) + err = this.conn.SetReadDeadline(t) + return err +} + +func (this WSContainer) SetReadDeadline(t time.Time) error { + return this.conn.SetReadDeadline(t) +} + +func (this WSContainer) SetWriteDeadline(t time.Time) error { + return this.conn.SetWriteDeadline(t) +} From 0d1c63396bee5a9c17022e4e4119726da7b5277c Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Sat, 6 Jun 2015 13:47:56 -0700 Subject: [PATCH 2/6] undo unnecessary change --- ergonomadic.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ergonomadic.conf b/ergonomadic.conf index fb9b7d2a..7225b98b 100644 --- a/ergonomadic.conf +++ b/ergonomadic.conf @@ -1,5 +1,5 @@ [server] -name = "localhost" ; required, usually a hostname +name = "irc.example.com" ; required, usually a hostname database = "ergonomadic.db" ; path relative to this file listen = "localhost:6667" ; see `net.Listen` for examples listen = "[::1]:6667" ; multiple `listen`s are allowed. From 23d7c81684a82fa02190628845bcb6859a48378d Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Sat, 6 Jun 2015 14:05:29 -0700 Subject: [PATCH 3/6] comment for CheckOrigin --- irc/websocket.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/irc/websocket.go b/irc/websocket.go index e385ca08..74e21c5d 100644 --- a/irc/websocket.go +++ b/irc/websocket.go @@ -10,6 +10,15 @@ import ( var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, + /* If a WS session contains sensitive information, and you choose to use + cookies for authentication (during the HTTP(S) upgrade request), then + you should check that Origin is a domain under your control. If it + isn't, then it is possible for users of your site, visiting a naughty + Origin, to have a WS opened using their credentials. See + http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html#main. + We don't care about Origin because the (IRC) authentication is contained + in the WS stream -- the WS session is not privileged when it is opened. + */ CheckOrigin: func(r *http.Request) bool { return true }, } From 62302ec92ededd472df1af25e80f68adc80f76b9 Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Sat, 6 Jun 2015 15:11:59 -0700 Subject: [PATCH 4/6] fail WebSockets with subprotocols here, because gorilla leaves it up for us to deal with subprotocols --- irc/server.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/irc/server.go b/irc/server.go index 7152bf0b..9b05ba2f 100644 --- a/irc/server.go +++ b/irc/server.go @@ -218,6 +218,14 @@ func (s *Server) wslisten(addr string) { Log.error.Printf("%s method not allowed", s) return } + + // We don't have any subprotocols, so if someone attempts to `new + // WebSocket(server, "subprotocol")` they'll break here, instead of + // getting the default, ambiguous, response from gorilla. + if v, ok := r.Header["Sec-Websocket-Protocol"]; ok { + http.Error(w, fmt.Sprintf("WebSocket subprocotols (e.g. %s) not supported", v), 400) + } + ws, err := upgrader.Upgrade(w, r, nil) if err != nil { Log.error.Printf("%s websocket upgrade error: %s", s, err) From fce54343ea6ce797a98c48d5646dd3170ae3bd47 Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Sat, 6 Jun 2015 16:11:06 -0700 Subject: [PATCH 5/6] touchups to irc/websocket.go per review comments --- irc/server.go | 6 +---- irc/websocket.go | 57 ++++++++++++++++-------------------------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/irc/server.go b/irc/server.go index 9b05ba2f..33fa2fb7 100644 --- a/irc/server.go +++ b/irc/server.go @@ -232,11 +232,7 @@ func (s *Server) wslisten(addr string) { return } - wsc := WSContainer{ - conn: ws, - } - - s.newConns <- wsc + s.newConns <- WSContainer{ws} }) go func() { Log.info.Printf("%s listening on %s", s, addr) diff --git a/irc/websocket.go b/irc/websocket.go index 74e21c5d..6bf38f64 100644 --- a/irc/websocket.go +++ b/irc/websocket.go @@ -2,7 +2,6 @@ package irc import ( "github.com/gorilla/websocket" - "net" "net/http" "time" ) @@ -10,56 +9,38 @@ import ( var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, - /* If a WS session contains sensitive information, and you choose to use - cookies for authentication (during the HTTP(S) upgrade request), then - you should check that Origin is a domain under your control. If it - isn't, then it is possible for users of your site, visiting a naughty - Origin, to have a WS opened using their credentials. See - http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html#main. - We don't care about Origin because the (IRC) authentication is contained - in the WS stream -- the WS session is not privileged when it is opened. - */ + // If a WS session contains sensitive information, and you choose to use + // cookies for authentication (during the HTTP(S) upgrade request), then + // you should check that Origin is a domain under your control. If it + // isn't, then it is possible for users of your site, visiting a naughty + // Origin, to have a WS opened using their credentials. See + // http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html#main. + // We don't care about Origin because the (IRC) authentication is contained + // in the WS stream -- the WS session is not privileged when it is opened. CheckOrigin: func(r *http.Request) bool { return true }, } type WSContainer struct { - conn *websocket.Conn -} - -func (this WSContainer) Close() error { - return this.conn.Close() -} - -func (this WSContainer) LocalAddr() net.Addr { - return this.conn.LocalAddr() -} - -func (this WSContainer) RemoteAddr() net.Addr { - return this.conn.RemoteAddr() + *websocket.Conn } func (this WSContainer) Read(msg []byte) (int, error) { - _, tmp, err := this.conn.ReadMessage() - str := (string)(tmp) - n := copy(msg, ([]byte)(str+CRLF+CRLF)) - return n, err + ty, bytes, err := this.ReadMessage() + if ty == websocket.TextMessage { + n := copy(msg, []byte(string(bytes)+CRLF+CRLF)) + return n, err + } + // Binary, and other kinds of messages, are thrown away. + return 0, nil } func (this WSContainer) Write(msg []byte) (int, error) { - err := this.conn.WriteMessage(1, msg) + err := this.WriteMessage(websocket.TextMessage, msg) return len(msg), err } func (this WSContainer) SetDeadline(t time.Time) error { - err := this.conn.SetWriteDeadline(t) - err = this.conn.SetReadDeadline(t) + err := this.SetWriteDeadline(t) + err = this.SetReadDeadline(t) return err } - -func (this WSContainer) SetReadDeadline(t time.Time) error { - return this.conn.SetReadDeadline(t) -} - -func (this WSContainer) SetWriteDeadline(t time.Time) error { - return this.conn.SetWriteDeadline(t) -} From a16cc84e41602fa36ca2b9b373a105d9309d20a7 Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Sat, 6 Jun 2015 16:15:09 -0700 Subject: [PATCH 6/6] don't shadow the second error --- irc/websocket.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/irc/websocket.go b/irc/websocket.go index 6bf38f64..72a1c1c8 100644 --- a/irc/websocket.go +++ b/irc/websocket.go @@ -40,7 +40,8 @@ func (this WSContainer) Write(msg []byte) (int, error) { } func (this WSContainer) SetDeadline(t time.Time) error { - err := this.SetWriteDeadline(t) - err = this.SetReadDeadline(t) - return err + if err := this.SetWriteDeadline(t); err != nil { + return err + } + return this.SetReadDeadline(t) }