Better CAPABILITIES command implementation using declared types, use textproto package for connection i/o

This commit is contained in:
ChronosX88 2022-01-18 18:04:20 +03:00
parent 719005908a
commit 834298ee93
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
5 changed files with 132 additions and 14 deletions

View File

@ -2,6 +2,7 @@ package main
import (
"flag"
"github.com/ChronosX88/yans/internal/common"
"github.com/ChronosX88/yans/internal/config"
"github.com/ChronosX88/yans/internal/server"
"log"
@ -26,7 +27,7 @@ func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
log.Println("Starting YANS...")
log.Printf("Starting %s...", common.ServerName)
ns, err := server.NewNNTPServer(cfg)
if err != nil {
log.Fatal(err)
@ -35,10 +36,11 @@ func main() {
if err := ns.Start(); err != nil {
log.Fatal(err)
}
log.Println("YANS has been successfully started!")
log.Printf("%s has been successfully started!", common.ServerName)
log.Printf("Version: %s", common.ServerVersion)
for range c {
log.Println("Stopping YANS...")
log.Printf("Stopping %s...", common.ServerName)
ns.Stop()
break
}

6
internal/common/const.go Normal file
View File

@ -0,0 +1,6 @@
package common
const (
ServerName = "YANS"
ServerVersion = "0.0.1"
)

View File

@ -0,0 +1,80 @@
package protocol
import (
"fmt"
"strings"
)
type CapabilityType int
const (
VersionCapability CapabilityType = iota
ReaderCapability
IHaveCapability
PostCapability
NewNewsCapability
HdrCapability
OverCapability
ListCapability
ImplementationCapability
ModeReaderCapability
)
func (ct CapabilityType) String() string {
switch ct {
case VersionCapability:
return CapabilityNameVersion
case ReaderCapability:
return CapabilityNameReader
case IHaveCapability:
return CapabilityNameIHave
case PostCapability:
return CapabilityNamePost
case NewNewsCapability:
return CapabilityNameNewNews
case HdrCapability:
return CapabilityNameHdr
case OverCapability:
return CapabilityNameOver
case ListCapability:
return CapabilityNameList
case ImplementationCapability:
return CapabilityNameImplementation
case ModeReaderCapability:
return CapabilityNameModeReader
default:
return ""
}
}
type Capability struct {
Type CapabilityType
Params string // optional
}
type Capabilities []Capability
func (cs Capabilities) Add(c Capability) {
for _, v := range cs {
if v.Type == c.Type {
return // allowed only unique items
}
}
cs = append(cs, c)
}
func (cs Capabilities) String() string {
sb := strings.Builder{}
sb.Write([]byte("101 Capability list:" + CRLF))
for _, v := range cs {
if v.Params != "" {
sb.Write([]byte(fmt.Sprintf("%s %s%s", v.Type, v.Params, CRLF)))
} else {
sb.Write([]byte(fmt.Sprintf("%s%s", v.Type, CRLF)))
}
}
sb.Write([]byte(MultilineEnding))
return sb.String()
}

View File

@ -1,5 +1,10 @@
package protocol
const (
CRLF = "\r\n"
MultilineEnding = "."
)
const (
CommandCapabilities = "CAPABILITIES"
CommandQuit = "QUIT"
@ -9,9 +14,23 @@ const (
)
const (
MessageNNTPServiceReadyPostingProhibited = "201 YANS NNTP Service Ready, posting prohibited\n"
CapabilityNameVersion = "VERSION"
CapabilityNameReader = "READER"
CapabilityNameIHave = "IHAVE"
CapabilityNamePost = "POST"
CapabilityNameNewNews = "NEWNEWS"
CapabilityNameHdr = "HDR"
CapabilityNameOver = "OVER"
CapabilityNameList = "LIST"
CapabilityNameImplementation = "IMPLEMENTATION"
CapabilityNameModeReader = "MODE-READER"
)
const (
MessageNNTPServiceReadyPostingProhibited = "201 YANS NNTP Service Ready, posting prohibited"
MessageReaderModePostingProhibited = "201 Reader mode, posting prohibited"
MessageNNTPServiceExitsNormally = "205 NNTP Service exits normally"
MessageUnknownCommand = "500 Unknown command"
MessageErrorHappened = "403 Failed to process command: "
MessageListOfNewsgroupsFollows = "215 list of newsgroups follows"
)

View File

@ -1,10 +1,10 @@
package server
import (
"bufio"
"context"
"fmt"
"github.com/ChronosX88/yans/internal"
"github.com/ChronosX88/yans/internal/common"
"github.com/ChronosX88/yans/internal/config"
"github.com/ChronosX88/yans/internal/models"
"github.com/ChronosX88/yans/internal/protocol"
@ -14,10 +14,20 @@ import (
"io"
"log"
"net"
"net/textproto"
"strings"
"time"
)
var (
Capabilities = protocol.Capabilities{
{Type: protocol.VersionCapability, Params: "2"},
{Type: protocol.ImplementationCapability, Params: fmt.Sprintf("%s %s", common.ServerName, common.ServerVersion)},
{Type: protocol.ModeReaderCapability},
{Type: protocol.ListCapability, Params: "ACTIVE NEWSGROUPS"},
}
)
type NNTPServer struct {
ctx context.Context
cancelFunc context.CancelFunc
@ -81,19 +91,21 @@ func (ns *NNTPServer) Start() error {
}
func (ns *NNTPServer) handleNewConnection(ctx context.Context, conn net.Conn) {
_, err := conn.Write([]byte(protocol.MessageNNTPServiceReadyPostingProhibited))
_, err := conn.Write([]byte(protocol.MessageNNTPServiceReadyPostingProhibited + protocol.CRLF))
if err != nil {
log.Print(err)
conn.Close()
return
}
tconn := textproto.NewConn(conn)
for {
select {
case <-ctx.Done():
break
default:
{
message, err := bufio.NewReader(conn).ReadString('\n')
message, err := tconn.ReadLine()
if err != nil {
if err == io.EOF || err.(*net.OpError).Unwrap() == net.ErrClosed {
log.Printf("Client %s has diconnected!", conn.RemoteAddr().String())
@ -104,7 +116,7 @@ func (ns *NNTPServer) handleNewConnection(ctx context.Context, conn net.Conn) {
return
}
log.Printf("Received message from %s: %s", conn.RemoteAddr().String(), string(message))
err = ns.handleMessage(conn, message)
err = ns.handleMessage(tconn, message)
if err != nil {
log.Print(err)
conn.Close()
@ -115,8 +127,7 @@ func (ns *NNTPServer) handleNewConnection(ctx context.Context, conn net.Conn) {
}
}
func (ns *NNTPServer) handleMessage(conn net.Conn, msg string) error {
msg = strings.TrimSuffix(msg, "\r\n")
func (ns *NNTPServer) handleMessage(conn *textproto.Conn, msg string) error {
splittedMessage := strings.Split(msg, " ")
command := splittedMessage[0]
@ -126,7 +137,7 @@ func (ns *NNTPServer) handleMessage(conn net.Conn, msg string) error {
switch command {
case protocol.CommandCapabilities:
{
reply = "101 Capability list:\r\nVERSION 2\r\nIMPLEMENTATION\r\n."
reply = Capabilities.String()
break
}
case protocol.CommandDate:
@ -158,11 +169,11 @@ func (ns *NNTPServer) handleMessage(conn net.Conn, msg string) error {
log.Println(err)
}
sb := strings.Builder{}
sb.Write([]byte("215 list of newsgroups follows\n"))
sb.Write([]byte(protocol.MessageListOfNewsgroupsFollows + protocol.CRLF))
if len(splittedMessage) == 1 || splittedMessage[1] == "ACTIVE" {
for _, v := range groups {
// TODO set high/low mark and posting status to actual values
sb.Write([]byte(fmt.Sprintf("%s 0 0 n\r\n", v.GroupName)))
sb.Write([]byte(fmt.Sprintf("%s 0 0 n"+protocol.CRLF, v.GroupName)))
}
} else if splittedMessage[1] == "NEWSGROUPS" {
for _, v := range groups {
@ -189,7 +200,7 @@ func (ns *NNTPServer) handleMessage(conn net.Conn, msg string) error {
}
}
_, err := conn.Write([]byte(reply + "\r\n"))
err := conn.PrintfLine(reply)
if quit {
conn.Close()
}