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 ( import (
"flag" "flag"
"github.com/ChronosX88/yans/internal/common"
"github.com/ChronosX88/yans/internal/config" "github.com/ChronosX88/yans/internal/config"
"github.com/ChronosX88/yans/internal/server" "github.com/ChronosX88/yans/internal/server"
"log" "log"
@ -26,7 +27,7 @@ func main() {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
log.Println("Starting YANS...") log.Printf("Starting %s...", common.ServerName)
ns, err := server.NewNNTPServer(cfg) ns, err := server.NewNNTPServer(cfg)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -35,10 +36,11 @@ func main() {
if err := ns.Start(); err != nil { if err := ns.Start(); err != nil {
log.Fatal(err) 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 { for range c {
log.Println("Stopping YANS...") log.Printf("Stopping %s...", common.ServerName)
ns.Stop() ns.Stop()
break 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 package protocol
const (
CRLF = "\r\n"
MultilineEnding = "."
)
const ( const (
CommandCapabilities = "CAPABILITIES" CommandCapabilities = "CAPABILITIES"
CommandQuit = "QUIT" CommandQuit = "QUIT"
@ -9,9 +14,23 @@ const (
) )
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" MessageReaderModePostingProhibited = "201 Reader mode, posting prohibited"
MessageNNTPServiceExitsNormally = "205 NNTP Service exits normally" MessageNNTPServiceExitsNormally = "205 NNTP Service exits normally"
MessageUnknownCommand = "500 Unknown command" MessageUnknownCommand = "500 Unknown command"
MessageErrorHappened = "403 Failed to process command: " MessageErrorHappened = "403 Failed to process command: "
MessageListOfNewsgroupsFollows = "215 list of newsgroups follows"
) )

View File

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