mirror of
https://github.com/ChronosX88/yans.git
synced 2024-11-23 20:12:18 +00:00
Better CAPABILITIES command implementation using declared types, use textproto package for connection i/o
This commit is contained in:
parent
719005908a
commit
834298ee93
@ -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
6
internal/common/const.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServerName = "YANS"
|
||||||
|
ServerVersion = "0.0.1"
|
||||||
|
)
|
80
internal/protocol/capabilities.go
Normal file
80
internal/protocol/capabilities.go
Normal 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()
|
||||||
|
}
|
@ -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"
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user