|
|
@ -39,13 +39,6 @@ type ConfMailer struct { |
|
|
|
From string `yaml:"from,omitempty"` |
|
|
|
} |
|
|
|
|
|
|
|
type tcpUser struct { |
|
|
|
bufConn bufferedConn |
|
|
|
userCount chan int |
|
|
|
email string |
|
|
|
newMsg chan string |
|
|
|
} |
|
|
|
|
|
|
|
// So we can peek at net.Conn, which we can't do natively
|
|
|
|
// https://stackoverflow.com/questions/51472020/how-to-get-the-size-of-available-tcp-data
|
|
|
|
type bufferedConn struct { |
|
|
@ -80,28 +73,29 @@ type chatMsg struct { |
|
|
|
Channel string `json:"channel"` |
|
|
|
User string `json:"user"` |
|
|
|
} |
|
|
|
type JsonMsg struct { |
|
|
|
Messages []*chatMsg `json:"messages"` |
|
|
|
} |
|
|
|
|
|
|
|
// Poor-Man's container/ring (circular buffer)
|
|
|
|
type chatHist struct { |
|
|
|
msgs []*chatMsg |
|
|
|
i int |
|
|
|
c int |
|
|
|
i int // current index
|
|
|
|
c int // current count (number of elements)
|
|
|
|
} |
|
|
|
|
|
|
|
// Multi-use
|
|
|
|
var config Conf |
|
|
|
var virginConns chan net.Conn |
|
|
|
var gotClientHello chan bufferedConn |
|
|
|
var myChatHist chatHist |
|
|
|
var broadcastMsg chan chatMsg |
|
|
|
|
|
|
|
var virginConns chan net.Conn |
|
|
|
// Telnet
|
|
|
|
var wantsServerHello chan bufferedConn |
|
|
|
var authTelnet chan tcpUser |
|
|
|
var cleanTelnet chan tcpUser |
|
|
|
var gotClientHello chan bufferedConn |
|
|
|
var authTelnet chan telnetUser |
|
|
|
var cleanTelnet chan telnetUser |
|
|
|
|
|
|
|
// HTTP
|
|
|
|
var demuxHttpClient chan bufferedConn |
|
|
|
var newAuthReqs chan authReq |
|
|
|
var authChallenge chan authReq |
|
|
|
var valAuthReqs chan authReq |
|
|
|
var delAuthReqs chan authReq |
|
|
|
|
|
|
@ -184,7 +178,7 @@ func muxTcp(conn bufferedConn) { |
|
|
|
demuxHttpClient <- conn |
|
|
|
} |
|
|
|
|
|
|
|
func handleConnection(netConn net.Conn) { |
|
|
|
func testForHello(netConn net.Conn) { |
|
|
|
ts := time.Now() |
|
|
|
fmt.Fprintf(os.Stdout, "[New Connection] (%s) welcome %s\n", ts, netConn.RemoteAddr().String()) |
|
|
|
|
|
|
@ -193,13 +187,11 @@ func handleConnection(netConn net.Conn) { |
|
|
|
|
|
|
|
bufConn := newBufferedConn(netConn) |
|
|
|
go func() { |
|
|
|
// Handle First Packet
|
|
|
|
// Cause first packet to be loaded into buffer
|
|
|
|
_, err := bufConn.Peek(1) |
|
|
|
//fmsg, err := bufConn.Peek(1)
|
|
|
|
if nil != err { |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
//fmt.Fprintf(os.Stdout, "[First Byte] %s\n", fmsg)
|
|
|
|
|
|
|
|
m.Lock() |
|
|
|
if virgin { |
|
|
@ -211,14 +203,17 @@ func handleConnection(netConn net.Conn) { |
|
|
|
m.Unlock() |
|
|
|
}() |
|
|
|
|
|
|
|
// Wait for a hello packet of some sort from the client
|
|
|
|
// (obviously this wouldn't work in extremely high latency situations)
|
|
|
|
time.Sleep(250 * 1000000) |
|
|
|
|
|
|
|
// If we still haven't received data from the client
|
|
|
|
// assume that the client must be expecting a welcome from us
|
|
|
|
m.Lock() |
|
|
|
if virgin { |
|
|
|
virgin = false |
|
|
|
// don't block for this
|
|
|
|
// let it be handled after the unlock
|
|
|
|
// Defer as to not block and prolonging the mutex
|
|
|
|
// (not that those few cycles much matter...)
|
|
|
|
defer fmt.Fprintf(netConn, |
|
|
|
"\n\nWelcome to Sample Chat! You appear to be using Telnet (http is also available on this port)."+ |
|
|
|
"\nYou must authenticate via email to participate\n\nEmail: ") |
|
|
@ -251,7 +246,7 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { |
|
|
|
if nil != err { |
|
|
|
return "", err |
|
|
|
} |
|
|
|
//req.PostForm = form
|
|
|
|
//req.PostForm = form ??
|
|
|
|
req.Header.Add("User-Agent", "golang http.Client - Sample Chat App Authenticator") |
|
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") |
|
|
|
req.SetBasicAuth("api", cnf.ApiKey) |
|
|
@ -276,8 +271,6 @@ func sendAuthCode(cnf ConfMailer, to string) (string, error) { |
|
|
|
return code, nil |
|
|
|
} |
|
|
|
|
|
|
|
var config Conf |
|
|
|
|
|
|
|
func main() { |
|
|
|
flag.Usage = usage |
|
|
|
port := flag.Uint("port", 0, "tcp telnet chat port") |
|
|
@ -295,7 +288,8 @@ func main() { |
|
|
|
config = Conf{} |
|
|
|
} |
|
|
|
if "" == config.RootPath { |
|
|
|
// TODO Embed the public dir at the default
|
|
|
|
// TODO Maybe embed the public dir into the binary
|
|
|
|
// (and provide a flag with path for override - like gitea)
|
|
|
|
config.RootPath = "./public" |
|
|
|
} |
|
|
|
|
|
|
@ -303,28 +297,24 @@ func main() { |
|
|
|
virginConns = make(chan net.Conn, 128) |
|
|
|
|
|
|
|
// TCP & Authentication
|
|
|
|
telnetConns := make(map[bufferedConn]tcpUser) |
|
|
|
telnetConns := make(map[bufferedConn]telnetUser) |
|
|
|
wantsServerHello = make(chan bufferedConn, 128) |
|
|
|
authTelnet = make(chan tcpUser, 128) |
|
|
|
authTelnet = make(chan telnetUser, 128) |
|
|
|
|
|
|
|
// HTTP & Authentication
|
|
|
|
myAuthReqs := make(map[string]authReq) |
|
|
|
newAuthReqs = make(chan authReq, 128) |
|
|
|
authReqs = make(chan authReq, 128) |
|
|
|
valAuthReqs = make(chan authReq, 128) |
|
|
|
delAuthReqs = make(chan authReq, 128) |
|
|
|
gotClientHello = make(chan bufferedConn, 128) |
|
|
|
demuxHttpClient = make(chan bufferedConn, 128) |
|
|
|
|
|
|
|
// cruft to delete
|
|
|
|
//myRooms = make(map[string](chan chatMsg))
|
|
|
|
|
|
|
|
//myRooms["general"] = make(chan chatMsg, 128)
|
|
|
|
// Note: I had considered dynamically select on channels for rooms.
|
|
|
|
// https://stackoverflow.com/questions/19992334/how-to-listen-to-n-channels-dynamic-select-statement
|
|
|
|
// I don't think that's actually the best approach, but I just wanted to save the link
|
|
|
|
|
|
|
|
broadcastMsg = make(chan chatMsg, 128) |
|
|
|
// Poor-Man's container/ring (circular buffer)
|
|
|
|
myChatHist.msgs = make([]*chatMsg, 128) |
|
|
|
|
|
|
|
var addr string |
|
|
@ -391,7 +381,7 @@ func main() { |
|
|
|
select { |
|
|
|
case conn := <-virginConns: |
|
|
|
// This is short lived
|
|
|
|
go handleConnection(conn) |
|
|
|
go testForHello(conn) |
|
|
|
case u := <-authTelnet: |
|
|
|
// allow to receive messages
|
|
|
|
// (and be counted among the users)
|
|
|
@ -405,7 +395,7 @@ func main() { |
|
|
|
Channel: "general", |
|
|
|
User: "system", |
|
|
|
} |
|
|
|
case ar := <-newAuthReqs: |
|
|
|
case ar := <-authReqs: |
|
|
|
myAuthReqs[ar.Cid] = ar |
|
|
|
case ar := <-valAuthReqs: |
|
|
|
// TODO In this case it's probably more conventional (and efficient) to
|
|
|
@ -455,7 +445,7 @@ func main() { |
|
|
|
sender = "system" |
|
|
|
} |
|
|
|
// Tangential thought:
|
|
|
|
// I wonder if we could use IP detection to get the client's tz
|
|
|
|
// I wonder if we could use IP detection to get a Telnet client's tz
|
|
|
|
// ... could probably make time for this in the authentication loop
|
|
|
|
zone, _ := msg.ReceivedAt.Zone() |
|
|
|
fmt.Fprintf(os.Stdout, tf+" [%s] (%s): %s\r\n", |
|
|
@ -484,23 +474,6 @@ func main() { |
|
|
|
// It can reconnect.
|
|
|
|
cleanTelnet <- u |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
// 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
|
|
|
|
// TODONE maybe use a chan to send to the socket's event loop
|
|
|
|
// (left this in to remind myself to ask questions)
|
|
|
|
go func(conn bufferedConn) { |
|
|
|
// Protect against malicious clients to prevent DoS
|
|
|
|
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
|
|
|
|
timeoutDuration := 2 * time.Second |
|
|
|
conn.SetWriteDeadline(time.Now().Add(timeoutDuration)) |
|
|
|
_, err := fmt.Fprintf(conn, msg) |
|
|
|
if nil != err { |
|
|
|
cleanTelnet <- u |
|
|
|
} |
|
|
|
}(conn) |
|
|
|
*/ |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|