diff --git a/internal/news/nntpd/server.go b/internal/news/nntpd/server.go index dca10e5..4665862 100644 --- a/internal/news/nntpd/server.go +++ b/internal/news/nntpd/server.go @@ -362,6 +362,9 @@ func handleIHave(args []string, s *session, c *textproto.Conn) error { if !s.backend.AllowPost() { return ErrNotWanted } + if len(args) < 1 { + return ErrSyntax + } article, err := s.backend.GetArticle(nil, args[0]) if article != nil { return ErrNotWanted diff --git a/internal/news/nntpd/server_test.go b/internal/news/nntpd/server_test.go index eaf4445..d54ca07 100644 --- a/internal/news/nntpd/server_test.go +++ b/internal/news/nntpd/server_test.go @@ -12,7 +12,8 @@ import ( ) type whitespaceBackend struct { - group *nntp.Group + group *nntp.Group + allowPost bool } func (b *whitespaceBackend) ListGroups(max int) ([]*nntp.Group, error) { @@ -40,7 +41,7 @@ func (b *whitespaceBackend) Authenticate(user, pass string) (Backend, error) { return b, nil } -func (b *whitespaceBackend) AllowPost() bool { return false } +func (b *whitespaceBackend) AllowPost() bool { return b.allowPost } func (b *whitespaceBackend) Post(article *nntp.Article) error { return errors.New("posting disabled") @@ -85,3 +86,44 @@ func TestProcessCollapsesRepeatedCommandWhitespace(t *testing.T) { t.Fatal("server did not close after QUIT") } } + +func TestIHaveWithoutMessageIDReturnsSyntaxError(t *testing.T) { + backend := &whitespaceBackend{ + group: &nntp.Group{Name: "pfs.general", Posting: nntp.PostingPermitted}, + allowPost: true, + } + server := NewServer(backend) + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + + done := make(chan struct{}) + go func() { + server.Process(serverConn) + close(done) + }() + + client := textproto.NewConn(clientConn) + defer client.Close() + + if line, err := client.ReadLine(); err != nil || !strings.HasPrefix(line, "200 ") { + t.Fatalf("greeting = %q, %v", line, err) + } + if err := client.PrintfLine("IHAVE"); err != nil { + t.Fatalf("send IHAVE: %v", err) + } + if line, err := client.ReadLine(); err != nil || !strings.HasPrefix(line, "501 ") { + t.Fatalf("IHAVE without message-id = %q, %v; want 501", line, err) + } + if err := client.PrintfLine("QUIT"); err != nil { + t.Fatalf("send QUIT: %v", err) + } + if line, err := client.ReadLine(); err != nil || !strings.HasPrefix(line, "205 ") { + t.Fatalf("QUIT = %q, %v; want 205", line, err) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("server did not close after QUIT") + } +}