fix func by passing conn, go fmt (oops), start http detection
This commit is contained in:
parent
64425b41d2
commit
9c507ae68e
126
chatserver.go
126
chatserver.go
|
@ -5,6 +5,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"flag"
|
"flag"
|
||||||
|
@ -35,7 +36,6 @@ type ConfMailer struct {
|
||||||
From string `yaml:"from,omitempty"`
|
From string `yaml:"from,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type tcpUser struct {
|
type tcpUser struct {
|
||||||
bufConn bufferedConn
|
bufConn bufferedConn
|
||||||
userCount chan int
|
userCount chan int
|
||||||
|
@ -58,7 +58,7 @@ func (b bufferedConn) Peek(n int) ([]byte, error) {
|
||||||
return b.r.Peek(n)
|
return b.r.Peek(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bufferedConn) Buffered() (int) {
|
func (b bufferedConn) Buffered() int {
|
||||||
return b.r.Buffered()
|
return b.r.Buffered()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,19 +80,22 @@ type myMsg struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstMsgs chan myMsg
|
var firstMsgs chan myMsg
|
||||||
|
|
||||||
//var myRooms map[string](chan myMsg)
|
//var myRooms map[string](chan myMsg)
|
||||||
var myMsgs chan myMsg
|
var myMsgs chan myMsg
|
||||||
|
|
||||||
//var myUnsortedConns map[net.Conn]bool
|
//var myUnsortedConns map[net.Conn]bool
|
||||||
var newConns chan net.Conn
|
var newConns chan net.Conn
|
||||||
var newTcpChat chan bufferedConn
|
var newTcpChat chan bufferedConn
|
||||||
var authTcpChat chan tcpUser
|
var authTcpChat chan tcpUser
|
||||||
var delTcpChat chan bufferedConn
|
var delTcpChat chan bufferedConn
|
||||||
var newHttpChat chan bufferedConn
|
var newHttpChat chan bufferedConn
|
||||||
|
var newHttpClient chan bufferedConn
|
||||||
var delHttpChat chan bufferedConn
|
var delHttpChat chan bufferedConn
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n")
|
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n")
|
||||||
flag.PrintDefaults();
|
flag.PrintDefaults()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -124,7 +127,7 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
// Handle all subsequent packets
|
// Handle all subsequent packets
|
||||||
buffer := make([]byte, 1024)
|
buffer := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n");
|
//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n")
|
||||||
count, err := bufConn.Read(buffer)
|
count, err := bufConn.Read(buffer)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
if io.EOF != err {
|
if io.EOF != err {
|
||||||
|
@ -172,13 +175,13 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
const msg = "Mailing auth code..."
|
const msg = "Mailing auth code..."
|
||||||
for _, r := range msg {
|
for _, r := range msg {
|
||||||
time.Sleep(20 * 1000000)
|
time.Sleep(20 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, string(r))
|
fmt.Fprintf(bufConn, string(r))
|
||||||
}
|
}
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
if "" != config.Mailer.ApiKey {
|
if "" != config.Mailer.ApiKey {
|
||||||
|
@ -200,9 +203,9 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
}
|
}
|
||||||
// so I don't have to actually go check my email
|
// so I don't have to actually go check my email
|
||||||
fmt.Fprintf(os.Stdout, "\n== AUTHORIZATION ==\n[cheat code for %s]: %s\n", email, code)
|
fmt.Fprintf(os.Stdout, "\n== AUTHORIZATION ==\n[cheat code for %s]: %s\n", email, code)
|
||||||
time.Sleep(150 * 1000000)
|
time.Sleep(150 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, " done\n")
|
fmt.Fprintf(bufConn, " done\n")
|
||||||
time.Sleep(150 * 1000000)
|
time.Sleep(150 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "Auth Code: ")
|
fmt.Fprintf(bufConn, "Auth Code: ")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -211,7 +214,7 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
fmt.Fprintf(bufConn, "Incorrect Code\nAuth Code: ")
|
fmt.Fprintf(bufConn, "Incorrect Code\nAuth Code: ")
|
||||||
} else {
|
} else {
|
||||||
authn = true
|
authn = true
|
||||||
time.Sleep(150 * 1000000)
|
time.Sleep(150 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "\n")
|
fmt.Fprintf(bufConn, "\n")
|
||||||
u := tcpUser{
|
u := tcpUser{
|
||||||
bufConn: bufConn,
|
bufConn: bufConn,
|
||||||
|
@ -221,18 +224,18 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
authTcpChat <- u
|
authTcpChat <- u
|
||||||
// prevent data race on len(myRawConns)
|
// prevent data race on len(myRawConns)
|
||||||
// XXX (there can't be a race between these two lines, right?)
|
// XXX (there can't be a race between these two lines, right?)
|
||||||
count := <- u.userCount
|
count := <-u.userCount
|
||||||
u.userCount = nil
|
u.userCount = nil
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "\n")
|
fmt.Fprintf(bufConn, "\n")
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "Welcome to #general (%d users)!", count)
|
fmt.Fprintf(bufConn, "Welcome to #general (%d users)!", count)
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "\n")
|
fmt.Fprintf(bufConn, "\n")
|
||||||
time.Sleep(50 * 1000000)
|
time.Sleep(50 * time.Millisecond)
|
||||||
// TODO /help /join <room> /users /channels /block <user> /upgrade <http/ws>
|
// TODO /help /join <room> /users /channels /block <user> /upgrade <http/ws>
|
||||||
//fmt.Fprintf(bufConn, "(TODO `/help' for list of commands)")
|
//fmt.Fprintf(bufConn, "(TODO `/help' for list of commands)")
|
||||||
time.Sleep(100 * 1000000)
|
time.Sleep(100 * time.Millisecond)
|
||||||
fmt.Fprintf(bufConn, "\n")
|
fmt.Fprintf(bufConn, "\n")
|
||||||
|
|
||||||
// this would be cool, but won't work since other messages will come
|
// this would be cool, but won't work since other messages will come
|
||||||
|
@ -242,7 +245,7 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Fprintf(os.Stdout, "Queing message...\n");
|
//fmt.Fprintf(os.Stdout, "Queing message...\n")
|
||||||
//myRooms["general"] <- myMsg{
|
//myRooms["general"] <- myMsg{
|
||||||
myMsgs <- myMsg{
|
myMsgs <- myMsg{
|
||||||
receivedAt: time.Now(),
|
receivedAt: time.Now(),
|
||||||
|
@ -258,6 +261,7 @@ func handleRaw(bufConn bufferedConn) {
|
||||||
func handleSorted(conn bufferedConn) {
|
func handleSorted(conn bufferedConn) {
|
||||||
// Wish List for protocol detection
|
// Wish List for protocol detection
|
||||||
// * PROXY protocol (and loop)
|
// * PROXY protocol (and loop)
|
||||||
|
// * HTTP CONNECT (proxy) (and loop)
|
||||||
// * tls (and loop) https://github.com/polvi/sni
|
// * tls (and loop) https://github.com/polvi/sni
|
||||||
// * http/ws
|
// * http/ws
|
||||||
// * irc
|
// * irc
|
||||||
|
@ -269,11 +273,50 @@ func handleSorted(conn bufferedConn) {
|
||||||
// Note: Realistically no tls/http/irc client is going to send so few bytes
|
// Note: Realistically no tls/http/irc client is going to send so few bytes
|
||||||
// (and no router is going to chunk so small)
|
// (and no router is going to chunk so small)
|
||||||
// that it cannot reasonably detect the protocol in the first packet
|
// that it cannot reasonably detect the protocol in the first packet
|
||||||
|
// However, typical MTU is 1,500 and HTTP can have a 2k URL
|
||||||
|
// so expecting to find the "HTTP/1.1" in the Peek is not always reasonable
|
||||||
n := conn.Buffered()
|
n := conn.Buffered()
|
||||||
firstMsg, err := conn.Peek(n)
|
firstMsg, err := conn.Peek(n)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
conn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
var protocol string
|
||||||
|
// between A and z
|
||||||
|
if firstMsg[0] >= 65 && firstMsg[0] <= 122 {
|
||||||
|
i := bytes.Index(firstMsg, []byte(" /"))
|
||||||
|
if -1 != i {
|
||||||
|
protocol = "HTTP"
|
||||||
|
// very likely HTTP
|
||||||
|
j := bytes.IndexAny(firstMsg, "\r\n")
|
||||||
|
if -1 != j {
|
||||||
|
k := bytes.Index(bytes.ToLower(firstMsg[:j]), []byte("HTTP/1"))
|
||||||
|
if -1 != k {
|
||||||
|
// positively HTTP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if 0x16 /*22*/ == firstMsg[0] {
|
||||||
|
// Because I don't always remember off the top of my head what the first byte is
|
||||||
|
// http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session
|
||||||
|
// https://tlseminar.github.io/first-few-milliseconds/
|
||||||
|
// TODO I want to learn about ALPN
|
||||||
|
protocol = "TLS"
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" == protocol {
|
||||||
|
fmt.Fprintf(conn, "\n\nWelcome to Sample Chat! You're not an HTTP client, assuming Telnet.\nYou must authenticate via email to participate\n\nEmail: ")
|
||||||
|
newTcpChat <- conn
|
||||||
|
return
|
||||||
|
} else if "HTTP" != protocol {
|
||||||
|
defer conn.Close()
|
||||||
|
fmt.Fprintf(conn, "\n\nNot yet supported. Try HTTP or Telnet\n\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newHttpClient <- conn
|
||||||
|
|
||||||
|
/*
|
||||||
firstMsgs <- myMsg{
|
firstMsgs <- myMsg{
|
||||||
receivedAt: time.Now(),
|
receivedAt: time.Now(),
|
||||||
sender: conn,
|
sender: conn,
|
||||||
|
@ -289,7 +332,7 @@ func handleSorted(conn bufferedConn) {
|
||||||
// Handle all subsequent packets
|
// Handle all subsequent packets
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n");
|
fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n")
|
||||||
count, err := conn.Read(buf)
|
count, err := conn.Read(buf)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
if io.EOF != err {
|
if io.EOF != err {
|
||||||
|
@ -312,6 +355,7 @@ func handleSorted(conn bufferedConn) {
|
||||||
channel: "general",
|
channel: "general",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(netConn net.Conn) {
|
func handleConnection(netConn net.Conn) {
|
||||||
|
@ -337,15 +381,14 @@ func handleConnection(netConn net.Conn) {
|
||||||
}
|
}
|
||||||
//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg)
|
//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg)
|
||||||
|
|
||||||
m.Lock();
|
m.Lock()
|
||||||
if virgin {
|
if virgin {
|
||||||
virgin = false
|
virgin = false
|
||||||
newHttpChat <- bufConn
|
newHttpChat <- bufConn
|
||||||
} else {
|
} else {
|
||||||
// TODO probably needs to go into a channel
|
|
||||||
newTcpChat <- bufConn
|
newTcpChat <- bufConn
|
||||||
}
|
}
|
||||||
m.Unlock();
|
m.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(250 * 1000000)
|
time.Sleep(250 * 1000000)
|
||||||
|
@ -378,7 +421,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) {
|
||||||
form := url.Values{}
|
form := url.Values{}
|
||||||
form.Add("from", cnf.From)
|
form.Add("from", cnf.From)
|
||||||
form.Add("to", to)
|
form.Add("to", to)
|
||||||
form.Add("subject", "Sample Chat Auth Code: " + code)
|
form.Add("subject", "Sample Chat Auth Code: "+code)
|
||||||
form.Add("text", text)
|
form.Add("text", text)
|
||||||
form.Add("html", html)
|
form.Add("html", html)
|
||||||
|
|
||||||
|
@ -414,6 +457,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var config Conf
|
var config Conf
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = usage
|
flag.Usage = usage
|
||||||
port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
|
port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
|
||||||
|
@ -438,6 +482,7 @@ func main() {
|
||||||
authTcpChat = make(chan tcpUser, 128)
|
authTcpChat = make(chan tcpUser, 128)
|
||||||
newTcpChat = make(chan bufferedConn, 128)
|
newTcpChat = make(chan bufferedConn, 128)
|
||||||
newHttpChat = make(chan bufferedConn, 128)
|
newHttpChat = make(chan bufferedConn, 128)
|
||||||
|
newHttpClient = make(chan bufferedConn, 128)
|
||||||
//myUnsortedConns = make(map[net.Conn]bool)
|
//myUnsortedConns = make(map[net.Conn]bool)
|
||||||
|
|
||||||
// TODO dynamically select on channels?
|
// TODO dynamically select on channels?
|
||||||
|
@ -458,7 +503,7 @@ func main() {
|
||||||
fmt.Fprintf(os.Stderr, "Couldn't bind to TCP socket %q: %s\n", addr, err)
|
fmt.Fprintf(os.Stderr, "Couldn't bind to TCP socket %q: %s\n", addr, err)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
fmt.Println("Listening on", addr);
|
fmt.Println("Listening on", addr)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
@ -471,14 +516,20 @@ func main() {
|
||||||
newConns <- conn
|
newConns <- conn
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
/*
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: &myHandler{},
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Main event loop handling most access to shared data
|
// Main event loop handling most access to shared data
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case conn := <- newConns:
|
case conn := <-newConns:
|
||||||
// This is short lived
|
// This is short lived
|
||||||
go handleConnection(conn)
|
go handleConnection(conn)
|
||||||
case u := <- authTcpChat:
|
case u := <-authTcpChat:
|
||||||
// allow to receive messages
|
// allow to receive messages
|
||||||
// (and be counted among the users)
|
// (and be counted among the users)
|
||||||
myRawConns[u.bufConn] = true
|
myRawConns[u.bufConn] = true
|
||||||
|
@ -492,17 +543,19 @@ func main() {
|
||||||
channel: "general",
|
channel: "general",
|
||||||
email: "system",
|
email: "system",
|
||||||
}
|
}
|
||||||
case bufConn := <- newTcpChat:
|
case bufConn := <-newTcpChat:
|
||||||
go handleRaw(bufConn)
|
go handleRaw(bufConn)
|
||||||
case bufConn := <- delTcpChat:
|
case bufConn := <-delTcpChat:
|
||||||
// we can safely ignore this error
|
// we can safely ignore this error
|
||||||
bufConn.Close()
|
bufConn.Close()
|
||||||
delete(myRawConns, bufConn)
|
delete(myRawConns, bufConn)
|
||||||
case bufConn := <- newHttpChat:
|
case bufConn := <-newHttpChat:
|
||||||
go handleSorted(bufConn)
|
go handleSorted(bufConn)
|
||||||
//case msg := <- myRooms["general"]:
|
//case msg := <- myRooms["general"]:
|
||||||
//delete(myRooms["general"], bufConn)
|
//delete(myRooms["general"], bufConn)
|
||||||
case msg := <- myMsgs:
|
//case bufConn := <-newHttpClient:
|
||||||
|
//server.Serve(bufConn)
|
||||||
|
case msg := <-myMsgs:
|
||||||
t := msg.receivedAt
|
t := msg.receivedAt
|
||||||
tf := "%d-%02d-%02d %02d:%02d:%02d (%s)"
|
tf := "%d-%02d-%02d %02d:%02d:%02d (%s)"
|
||||||
var sender string
|
var sender string
|
||||||
|
@ -516,7 +569,7 @@ func main() {
|
||||||
zone, _ := msg.receivedAt.Zone()
|
zone, _ := msg.receivedAt.Zone()
|
||||||
|
|
||||||
//ts, err := msg.receivedAt.MarshalJSON()
|
//ts, err := msg.receivedAt.MarshalJSON()
|
||||||
fmt.Fprintf(os.Stdout, tf + " [%s] (%s):\n\t%s",
|
fmt.Fprintf(os.Stdout, tf+" [%s] (%s):\n\t%s",
|
||||||
t.Year(), t.Month(), t.Day(),
|
t.Year(), t.Month(), t.Day(),
|
||||||
t.Hour(), t.Minute(), t.Second(), zone,
|
t.Hour(), t.Minute(), t.Second(), zone,
|
||||||
sender,
|
sender,
|
||||||
|
@ -528,23 +581,24 @@ func main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To ask: Why do I have to pass in conn to prevent a data race? Is it garbage collection?
|
||||||
// Don't block the rest of the loop
|
// Don't block the rest of the loop
|
||||||
// TODO maybe use a chan to send to the socket's event loop
|
// TODO maybe use a chan to send to the socket's event loop
|
||||||
go func() {
|
go func(conn bufferedConn) {
|
||||||
// Protect against malicious clients to prevent DoS
|
// Protect against malicious clients to prevent DoS
|
||||||
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
|
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
|
||||||
timeoutDuration := 5 * time.Second
|
timeoutDuration := 5 * time.Second
|
||||||
conn.SetWriteDeadline(time.Now().Add(timeoutDuration))
|
conn.SetWriteDeadline(time.Now().Add(timeoutDuration))
|
||||||
_, err := fmt.Fprintf(conn, tf + " [%s]: %s",
|
_, err := fmt.Fprintf(conn, tf+" [%s]: %s",
|
||||||
t.Year(), t.Month(), t.Day(),
|
t.Year(), t.Month(), t.Day(),
|
||||||
t.Hour(), t.Minute(), t.Second(), zone,
|
t.Hour(), t.Minute(), t.Second(), zone,
|
||||||
msg.email, msg.bytes)
|
msg.email, msg.bytes)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
delTcpChat <- conn
|
delTcpChat <- conn
|
||||||
}
|
}
|
||||||
}()
|
}(conn)
|
||||||
}
|
}
|
||||||
case msg := <- firstMsgs:
|
case msg := <-firstMsgs:
|
||||||
fmt.Fprintf(os.Stdout, "f [First Message]\n")
|
fmt.Fprintf(os.Stdout, "f [First Message]\n")
|
||||||
ts, err := msg.receivedAt.MarshalJSON()
|
ts, err := msg.receivedAt.MarshalJSON()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
@ -552,7 +606,7 @@ func main() {
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stdout, "f [Timestamp] %s\n", ts)
|
fmt.Fprintf(os.Stdout, "f [Timestamp] %s\n", ts)
|
||||||
fmt.Fprintf(os.Stdout, "f [Remote] %s\n", msg.sender.RemoteAddr().String())
|
fmt.Fprintf(os.Stdout, "f [Remote] %s\n", msg.sender.RemoteAddr().String())
|
||||||
fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes);
|
fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue