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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
|
@ -35,7 +36,6 @@ type ConfMailer struct {
|
|||
From string `yaml:"from,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
type tcpUser struct {
|
||||
bufConn bufferedConn
|
||||
userCount chan int
|
||||
|
@ -58,7 +58,7 @@ func (b bufferedConn) Peek(n int) ([]byte, error) {
|
|||
return b.r.Peek(n)
|
||||
}
|
||||
|
||||
func (b bufferedConn) Buffered() (int) {
|
||||
func (b bufferedConn) Buffered() int {
|
||||
return b.r.Buffered()
|
||||
}
|
||||
|
||||
|
@ -80,19 +80,22 @@ type myMsg struct {
|
|||
}
|
||||
|
||||
var firstMsgs chan myMsg
|
||||
|
||||
//var myRooms map[string](chan myMsg)
|
||||
var myMsgs chan myMsg
|
||||
|
||||
//var myUnsortedConns map[net.Conn]bool
|
||||
var newConns chan net.Conn
|
||||
var newTcpChat chan bufferedConn
|
||||
var authTcpChat chan tcpUser
|
||||
var delTcpChat chan bufferedConn
|
||||
var newHttpChat chan bufferedConn
|
||||
var newHttpClient chan bufferedConn
|
||||
var delHttpChat chan bufferedConn
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "\nusage: go run chatserver.go\n")
|
||||
flag.PrintDefaults();
|
||||
flag.PrintDefaults()
|
||||
fmt.Println()
|
||||
|
||||
os.Exit(1)
|
||||
|
@ -124,7 +127,7 @@ func handleRaw(bufConn bufferedConn) {
|
|||
// Handle all subsequent packets
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n");
|
||||
//fmt.Fprintf(os.Stdout, "[raw] Waiting for message...\n")
|
||||
count, err := bufConn.Read(buffer)
|
||||
if nil != err {
|
||||
if io.EOF != err {
|
||||
|
@ -172,13 +175,13 @@ func handleRaw(bufConn bufferedConn) {
|
|||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
const msg = "Mailing auth code..."
|
||||
for _, r := range msg {
|
||||
time.Sleep(20 * 1000000)
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, string(r))
|
||||
}
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
wg.Done()
|
||||
}()
|
||||
if "" != config.Mailer.ApiKey {
|
||||
|
@ -200,9 +203,9 @@ func handleRaw(bufConn bufferedConn) {
|
|||
}
|
||||
// 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)
|
||||
time.Sleep(150 * 1000000)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, " done\n")
|
||||
time.Sleep(150 * 1000000)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "Auth Code: ")
|
||||
continue
|
||||
}
|
||||
|
@ -211,7 +214,7 @@ func handleRaw(bufConn bufferedConn) {
|
|||
fmt.Fprintf(bufConn, "Incorrect Code\nAuth Code: ")
|
||||
} else {
|
||||
authn = true
|
||||
time.Sleep(150 * 1000000)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "\n")
|
||||
u := tcpUser{
|
||||
bufConn: bufConn,
|
||||
|
@ -221,18 +224,18 @@ func handleRaw(bufConn bufferedConn) {
|
|||
authTcpChat <- u
|
||||
// prevent data race on len(myRawConns)
|
||||
// XXX (there can't be a race between these two lines, right?)
|
||||
count := <- u.userCount
|
||||
count := <-u.userCount
|
||||
u.userCount = nil
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "\n")
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "Welcome to #general (%d users)!", count)
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "\n")
|
||||
time.Sleep(50 * 1000000)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// TODO /help /join <room> /users /channels /block <user> /upgrade <http/ws>
|
||||
//fmt.Fprintf(bufConn, "(TODO `/help' for list of commands)")
|
||||
time.Sleep(100 * 1000000)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Fprintf(bufConn, "\n")
|
||||
|
||||
// this would be cool, but won't work since other messages will come
|
||||
|
@ -242,7 +245,7 @@ func handleRaw(bufConn bufferedConn) {
|
|||
continue
|
||||
}
|
||||
|
||||
//fmt.Fprintf(os.Stdout, "Queing message...\n");
|
||||
//fmt.Fprintf(os.Stdout, "Queing message...\n")
|
||||
//myRooms["general"] <- myMsg{
|
||||
myMsgs <- myMsg{
|
||||
receivedAt: time.Now(),
|
||||
|
@ -258,6 +261,7 @@ func handleRaw(bufConn bufferedConn) {
|
|||
func handleSorted(conn bufferedConn) {
|
||||
// Wish List for protocol detection
|
||||
// * PROXY protocol (and loop)
|
||||
// * HTTP CONNECT (proxy) (and loop)
|
||||
// * tls (and loop) https://github.com/polvi/sni
|
||||
// * http/ws
|
||||
// * irc
|
||||
|
@ -269,11 +273,50 @@ func handleSorted(conn bufferedConn) {
|
|||
// Note: Realistically no tls/http/irc client is going to send so few bytes
|
||||
// (and no router is going to chunk so small)
|
||||
// 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()
|
||||
firstMsg, err := conn.Peek(n)
|
||||
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{
|
||||
receivedAt: time.Now(),
|
||||
sender: conn,
|
||||
|
@ -289,7 +332,7 @@ func handleSorted(conn bufferedConn) {
|
|||
// Handle all subsequent packets
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n");
|
||||
fmt.Fprintf(os.Stdout, "[sortable] Waiting for message...\n")
|
||||
count, err := conn.Read(buf)
|
||||
if nil != err {
|
||||
if io.EOF != err {
|
||||
|
@ -312,6 +355,7 @@ func handleSorted(conn bufferedConn) {
|
|||
channel: "general",
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func handleConnection(netConn net.Conn) {
|
||||
|
@ -337,15 +381,14 @@ func handleConnection(netConn net.Conn) {
|
|||
}
|
||||
//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg)
|
||||
|
||||
m.Lock();
|
||||
m.Lock()
|
||||
if virgin {
|
||||
virgin = false
|
||||
newHttpChat <- bufConn
|
||||
} else {
|
||||
// TODO probably needs to go into a channel
|
||||
newTcpChat <- bufConn
|
||||
}
|
||||
m.Unlock();
|
||||
m.Unlock()
|
||||
}()
|
||||
|
||||
time.Sleep(250 * 1000000)
|
||||
|
@ -378,7 +421,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) {
|
|||
form := url.Values{}
|
||||
form.Add("from", cnf.From)
|
||||
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("html", html)
|
||||
|
||||
|
@ -414,6 +457,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) {
|
|||
}
|
||||
|
||||
var config Conf
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
port := flag.Uint("telnet-port", 0, "tcp telnet chat port")
|
||||
|
@ -438,6 +482,7 @@ func main() {
|
|||
authTcpChat = make(chan tcpUser, 128)
|
||||
newTcpChat = make(chan bufferedConn, 128)
|
||||
newHttpChat = make(chan bufferedConn, 128)
|
||||
newHttpClient = make(chan bufferedConn, 128)
|
||||
//myUnsortedConns = make(map[net.Conn]bool)
|
||||
|
||||
// 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)
|
||||
os.Exit(2)
|
||||
}
|
||||
fmt.Println("Listening on", addr);
|
||||
fmt.Println("Listening on", addr)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
|
@ -471,14 +516,20 @@ func main() {
|
|||
newConns <- conn
|
||||
}
|
||||
}()
|
||||
/*
|
||||
server := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: &myHandler{},
|
||||
}
|
||||
*/
|
||||
|
||||
// Main event loop handling most access to shared data
|
||||
for {
|
||||
select {
|
||||
case conn := <- newConns:
|
||||
case conn := <-newConns:
|
||||
// This is short lived
|
||||
go handleConnection(conn)
|
||||
case u := <- authTcpChat:
|
||||
case u := <-authTcpChat:
|
||||
// allow to receive messages
|
||||
// (and be counted among the users)
|
||||
myRawConns[u.bufConn] = true
|
||||
|
@ -492,17 +543,19 @@ func main() {
|
|||
channel: "general",
|
||||
email: "system",
|
||||
}
|
||||
case bufConn := <- newTcpChat:
|
||||
case bufConn := <-newTcpChat:
|
||||
go handleRaw(bufConn)
|
||||
case bufConn := <- delTcpChat:
|
||||
case bufConn := <-delTcpChat:
|
||||
// we can safely ignore this error
|
||||
bufConn.Close()
|
||||
delete(myRawConns, bufConn)
|
||||
case bufConn := <- newHttpChat:
|
||||
case bufConn := <-newHttpChat:
|
||||
go handleSorted(bufConn)
|
||||
//case msg := <- myRooms["general"]:
|
||||
//delete(myRooms["general"], bufConn)
|
||||
case msg := <- myMsgs:
|
||||
//case bufConn := <-newHttpClient:
|
||||
//server.Serve(bufConn)
|
||||
case msg := <-myMsgs:
|
||||
t := msg.receivedAt
|
||||
tf := "%d-%02d-%02d %02d:%02d:%02d (%s)"
|
||||
var sender string
|
||||
|
@ -516,7 +569,7 @@ func main() {
|
|||
zone, _ := msg.receivedAt.Zone()
|
||||
|
||||
//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.Hour(), t.Minute(), t.Second(), zone,
|
||||
sender,
|
||||
|
@ -528,23 +581,24 @@ func main() {
|
|||
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
|
||||
// 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
|
||||
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
|
||||
timeoutDuration := 5 * time.Second
|
||||
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.Hour(), t.Minute(), t.Second(), zone,
|
||||
msg.email, msg.bytes)
|
||||
if nil != err {
|
||||
delTcpChat <- conn
|
||||
}
|
||||
}()
|
||||
}(conn)
|
||||
}
|
||||
case msg := <- firstMsgs:
|
||||
case msg := <-firstMsgs:
|
||||
fmt.Fprintf(os.Stdout, "f [First Message]\n")
|
||||
ts, err := msg.receivedAt.MarshalJSON()
|
||||
if nil != err {
|
||||
|
@ -552,7 +606,7 @@ func main() {
|
|||
}
|
||||
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 [Message] %s\n", msg.bytes);
|
||||
fmt.Fprintf(os.Stdout, "f [Message] %s\n", msg.bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue