Capabilities now per-connection thing, new connection structure and proper MODE READER stub.

This commit is contained in:
Stanislav Nikitin 2019-09-10 01:07:36 +05:00
parent 28ddc32368
commit f5b1f9d9f9
No known key found for this signature in database
GPG Key ID: 106900B32F8192EE
3 changed files with 133 additions and 39 deletions

View File

@ -6,7 +6,6 @@ import (
// local // local
"develop.pztrn.name/gonews/gonews/eventer" "develop.pztrn.name/gonews/gonews/eventer"
"develop.pztrn.name/gonews/gonews/networker"
) )
var capabilities = []string{ var capabilities = []string{
@ -16,13 +15,14 @@ var capabilities = []string{
func Initialize() { func Initialize() {
log.Println("Initializing capabilities command...") log.Println("Initializing capabilities command...")
// Global capabilities adder.
eventer.AddEventHandler(&eventer.EventHandler{ eventer.AddEventHandler(&eventer.EventHandler{
Command: "internal/capability_add", Command: "internal/capability_add",
Handler: addCapability, Handler: addCapability,
}) })
eventer.AddEventHandler(&eventer.EventHandler{ eventer.AddEventHandler(&eventer.EventHandler{
Command: "commands/capabilities", Command: "internal/capabilities",
Handler: handler, Handler: handler,
}) })
} }
@ -33,12 +33,18 @@ func addCapability(data interface{}) interface{} {
return nil return nil
} }
func handler(data interface{}) interface{} { //func handler(data interface{}) interface{} {
dataToReturn := "Capability list:\r\n" // dataToReturn := "Capability list:\r\n"
//
// for _, cap := range capabilities {
// dataToReturn += cap + "\r\n"
// }
// dataToReturn += ".\r\n"
// return &networker.Reply{Code: "101", Data: dataToReturn}
//}
for _, cap := range capabilities { func handler(data interface{}) interface{} {
dataToReturn += cap + "\r\n" caps := make([]string, len(capabilities))
} copy(caps, capabilities)
dataToReturn += ".\r\n" return caps
return &networker.Reply{Code: "101", Data: dataToReturn}
} }

View File

@ -11,34 +11,73 @@ import (
"develop.pztrn.name/gonews/gonews/eventer" "develop.pztrn.name/gonews/gonews/eventer"
) )
// This function is a connection worker. // This structure represents single NNTP client connection.
func connectionWorker(conn net.Conn) { type connection struct {
remoteAddr := conn.RemoteAddr() // Connection details and handlers.
conn net.Conn
remoteAddr net.Addr
// Read and write buffers
reader *bufio.Reader
writer *bufio.Writer
// Scanner that helps us to read incoming data.
readScanner *bufio.Scanner
// Connection flags.
// Are we in READER or MODE-READER (transit) mode?
// Right now transit mode isn't implemented. Implementation will
// require start using two goroutines for handling connections,
// one for writing and one for reading.
transit bool
// Connection capabilites.
capabilities []string
}
// Initialize initializes necessary things.
func (c *connection) Initialize(conn net.Conn) {
c.conn = conn
c.remoteAddr = c.conn.RemoteAddr()
log.Printf("accepted connection from %v\n", conn.RemoteAddr()) log.Printf("accepted connection from %v\n", conn.RemoteAddr())
defer func() {
err := conn.Close()
if err != nil {
log.Println("Failed to close connection from " + remoteAddr.String() + ": " + err.Error())
}
log.Println("Connection from " + remoteAddr.String() + " closed")
}()
// Create buffers. // Create buffers.
r := bufio.NewReader(conn) c.reader = bufio.NewReader(conn)
w := bufio.NewWriter(conn) c.writer = bufio.NewWriter(conn)
scanr := bufio.NewScanner(r) c.readScanner = bufio.NewScanner(c.reader)
// Get capabilities for this connection.
caps, _ := eventer.LaunchEvent("internal/capabilities", nil)
c.capabilities = caps.([]string)
// Set transit mode by default, according to RFC.
c.transit = true
}
// Start starts working with connection. Should be launched in separate
// goroutine.
// It will send greeting and then falls into infinite loop for working
// with connection until the end.
// Right now it implements only READER mode, no transit (which is used
// by server-to-server peering extensively).
func (c *connection) Start() {
defer func() {
err := c.conn.Close()
if err != nil {
log.Println("Failed to close connection from " + c.remoteAddr.String() + ": " + err.Error())
}
log.Println("Connection from " + c.remoteAddr.String() + " closed")
}()
// Send greeting. // Send greeting.
greetingData, _ := eventer.LaunchEvent("internal/greeting", nil) greetingData, _ := eventer.LaunchEvent("internal/greeting", nil)
greetingReply := greetingData.(*Reply) greetingReply := greetingData.(*Reply)
_, err := w.WriteString(greetingReply.Code + " " + greetingReply.Data) _, err := c.writer.WriteString(greetingReply.Code + " " + greetingReply.Data)
if err != nil { if err != nil {
log.Println("Failed to write greeting for " + remoteAddr.String() + ": " + err.Error()) log.Println("Failed to write greeting for " + c.remoteAddr.String() + ": " + err.Error())
return return
} }
w.Flush() c.writer.Flush()
// Start reading for commands. // Start reading for commands.
// Every command can be represented as slice where first element // Every command can be represented as slice where first element
@ -46,48 +85,87 @@ func connectionWorker(conn net.Conn) {
// By default we read only one line per iteration. // By default we read only one line per iteration.
// ToDo: multiline data parser for posting. // ToDo: multiline data parser for posting.
for { for {
dataAppeared := scanr.Scan() dataAppeared := c.readScanner.Scan()
if !dataAppeared { if !dataAppeared {
log.Println("Failed to read data from " + remoteAddr.String() + ": " + scanr.Err().Error()) log.Println("Failed to read data from " + c.remoteAddr.String() + ": " + c.readScanner.Err().Error())
break break
} }
log.Println("Got data: " + scanr.Text()) log.Println("Got data: " + c.readScanner.Text())
// ToDo: what if we'll upload binary data here? // ToDo: what if we'll upload binary data here?
// Not supported yet. // Not supported yet.
data := strings.Split(scanr.Text(), " ") data := strings.Split(c.readScanner.Text(), " ")
// Separate capabilities worker.
if strings.ToLower(data[0]) == "capabilities" {
dataToWrite := "Capability list:\r\n"
for _, cap := range c.capabilities {
dataToWrite += cap + "\r\n"
}
// We're also mode-switching server (in future), so we should
// also be aware of mode reader things. Writing to client will
// depend on c.transit variable.
// We will announce MODE-READER capability after initial
// connection (because we're in transit mode by default, according
// to RFC), and when client issue "MODE READER" command we will
// stop announcing MODE-READER and will start announce READER
// capability.
if c.transit {
dataToWrite += "MODE-READER\r\n"
} else {
dataToWrite += "READER\r\n"
}
dataToWrite += ".\r\n"
c.writer.WriteString(dataToWrite)
c.writer.Flush()
continue
}
// Mode worker. Reader only now.
if strings.ToLower(data[0]) == "mode" && strings.ToLower(data[1]) == "reader" {
c.transit = false
// In any case we'll require user authentication for posting.
c.writer.WriteString("201 Posting prohibited\r\n")
c.writer.Flush()
continue
}
// Execute passed command.
replyRaw, err := eventer.LaunchEvent("commands/"+strings.ToLower(data[0]), data[1:]) replyRaw, err := eventer.LaunchEvent("commands/"+strings.ToLower(data[0]), data[1:])
if err != nil { if err != nil {
// We won't break here as this is just logging of appeared error. // We won't break here as this is just logging of appeared error.
log.Println("Error appeared while processing command '" + data[0] + "' for " + remoteAddr.String() + ": " + err.Error()) log.Println("Error appeared while processing command '" + data[0] + "' for " + c.remoteAddr.String() + ": " + err.Error())
} }
// We might have nil in reply, so we'll assume that passed command // We might have nil in reply, so we'll assume that passed command
// is unknown to us. // is unknown to us.
if replyRaw == nil { if replyRaw == nil {
_, err := w.WriteString(unknownCommandErrorCode + " " + unknownCommandErrorText + "\r\n") _, err := c.writer.WriteString(unknownCommandErrorCode + " " + unknownCommandErrorText + "\r\n")
if err != nil { if err != nil {
log.Println("Failed to write string to socket for " + remoteAddr.String() + ": " + err.Error()) log.Println("Failed to write string to socket for " + c.remoteAddr.String() + ": " + err.Error())
break break
} }
w.Flush() c.writer.Flush()
continue continue
} }
// Every reply will be a reply struct. // Every reply will be a reply struct.
reply := replyRaw.(*Reply) reply := replyRaw.(*Reply)
_, err1 := w.WriteString(reply.Code + " " + reply.Data) _, err1 := c.writer.WriteString(reply.Code + " " + reply.Data)
if err1 != nil { if err1 != nil {
log.Println("Failed to write string to socket for " + remoteAddr.String() + ": " + err1.Error()) log.Println("Failed to write string to socket for " + c.remoteAddr.String() + ": " + err1.Error())
break break
} }
w.Flush() c.writer.Flush()
// Check for QUIT command. // Check for QUIT command.
if strings.ToLower(data[0]) == "quit" { if strings.ToLower(data[0]) == "quit" {
log.Println("QUIT command received, closing connection to " + remoteAddr.String()) log.Println("QUIT command received, closing connection to " + c.remoteAddr.String())
break break
} }
} }

View File

@ -9,11 +9,17 @@ import (
"develop.pztrn.name/gonews/gonews/configuration" "develop.pztrn.name/gonews/gonews/configuration"
) )
var connections map[string]*connection
// This function responsible for accepting incoming connections for // This function responsible for accepting incoming connections for
// each address configuration. // each address configuration.
func startServer(config configuration.Network) { func startServer(config configuration.Network) {
log.Println("Starting server on " + config.Address + " (type: " + config.Type + ")") log.Println("Starting server on " + config.Address + " (type: " + config.Type + ")")
if connections == nil {
connections = make(map[string]*connection)
}
l, err := net.Listen("tcp", config.Address) l, err := net.Listen("tcp", config.Address)
if err != nil { if err != nil {
log.Fatalln("Failed to start TCP server on " + config.Address + ": " + err.Error()) log.Fatalln("Failed to start TCP server on " + config.Address + ": " + err.Error())
@ -32,6 +38,10 @@ func startServer(config configuration.Network) {
continue continue
} }
go connectionWorker(conn) c := &connection{}
c.Initialize(conn)
connections[conn.RemoteAddr().String()] = c
go c.Start()
} }
} }