14 807f644f 2024-03-28 o // Handler is a low-level protocol handler
15 807f644f 2024-03-28 o type Handler func(args []string, s *session, c *textproto.Conn) error
17 807f644f 2024-03-28 o // A NumberedArticle provides local sequence nubers to articles When
18 807f644f 2024-03-28 o // listing articles in a group.
19 807f644f 2024-03-28 o type NumberedArticle struct {
21 807f644f 2024-03-28 o Article *Article
24 807f644f 2024-03-28 o // The Backend that provides the things and does the stuff.
25 807f644f 2024-03-28 o type Backend interface {
26 807f644f 2024-03-28 o ListGroups(max int) ([]*Group, error)
27 807f644f 2024-03-28 o GetGroup(name string) (*Group, error)
28 807f644f 2024-03-28 o GetArticle(g *Group, id string) (*Article, error)
29 807f644f 2024-03-28 o GetArticles(g *Group, from, to int64) ([]NumberedArticle, error)
30 807f644f 2024-03-28 o Authorized() bool
31 807f644f 2024-03-28 o // Authenticate and optionally swap out the backend for this session.
32 807f644f 2024-03-28 o // You may return nil to continue using the same backend.
33 807f644f 2024-03-28 o Authenticate(user, pass string) (Backend, error)
34 807f644f 2024-03-28 o AllowPost() bool
35 807f644f 2024-03-28 o Post(article *Article) error
38 807f644f 2024-03-28 o type session struct {
40 807f644f 2024-03-28 o backend Backend
44 807f644f 2024-03-28 o // The Server handle.
45 807f644f 2024-03-28 o type Server struct {
46 807f644f 2024-03-28 o // Handlers are dispatched by command name.
47 807f644f 2024-03-28 o Handlers map[string]Handler
48 807f644f 2024-03-28 o // The backend (your code) that provides data
49 807f644f 2024-03-28 o Backend Backend
50 807f644f 2024-03-28 o // The currently selected group.
54 807f644f 2024-03-28 o // NewServer builds a new server handle request to a backend.
55 807f644f 2024-03-28 o func NewServer(backend Backend) *Server {
57 807f644f 2024-03-28 o Handlers: make(map[string]Handler),
58 807f644f 2024-03-28 o Backend: backend,
60 807f644f 2024-03-28 o rv.Handlers[""] = handleDefault
61 807f644f 2024-03-28 o rv.Handlers["quit"] = handleQuit
62 807f644f 2024-03-28 o rv.Handlers["group"] = handleGroup
63 807f644f 2024-03-28 o rv.Handlers["list"] = handleList
64 807f644f 2024-03-28 o rv.Handlers["head"] = handleHead
65 807f644f 2024-03-28 o rv.Handlers["body"] = handleBody
66 807f644f 2024-03-28 o rv.Handlers["article"] = handleArticle
67 807f644f 2024-03-28 o rv.Handlers["post"] = handlePost
68 807f644f 2024-03-28 o rv.Handlers["ihave"] = handleIHave
69 807f644f 2024-03-28 o rv.Handlers["capabilities"] = handleCap
70 807f644f 2024-03-28 o rv.Handlers["mode"] = handleMode
71 807f644f 2024-03-28 o rv.Handlers["authinfo"] = handleAuthInfo
72 807f644f 2024-03-28 o rv.Handlers["newgroups"] = handleNewGroups
73 807f644f 2024-03-28 o rv.Handlers["over"] = handleOver
74 807f644f 2024-03-28 o rv.Handlers["xover"] = handleOver
78 807f644f 2024-03-28 o func (s *session) dispatchCommand(cmd string, args []string,
79 807f644f 2024-03-28 o c *textproto.Conn) (err error) {
81 807f644f 2024-03-28 o handler, found := s.server.Handlers[strings.ToLower(cmd)]
83 807f644f 2024-03-28 o handler, found = s.server.Handlers[""]
85 807f644f 2024-03-28 o panic("No default handler.")
88 807f644f 2024-03-28 o return handler(args, s, c)
91 807f644f 2024-03-28 o // Process an NNTP session.
92 807f644f 2024-03-28 o func (s *Server) Process(nc net.Conn) {
93 807f644f 2024-03-28 o defer nc.Close()
94 807f644f 2024-03-28 o c := textproto.NewConn(nc)
96 807f644f 2024-03-28 o sess := &session{
98 807f644f 2024-03-28 o backend: s.Backend,
102 807f644f 2024-03-28 o c.PrintfLine("200 Hello!")
104 807f644f 2024-03-28 o l, err := c.ReadLine()
105 807f644f 2024-03-28 o if err != nil {
106 807f644f 2024-03-28 o log.Printf("Error reading from client, dropping conn: %v", err)
109 807f644f 2024-03-28 o cmd := strings.Split(l, " ")
110 807f644f 2024-03-28 o log.Printf("Got cmd: %+v", cmd)
111 807f644f 2024-03-28 o args := []string{}
112 807f644f 2024-03-28 o if len(cmd) > 1 {
113 807f644f 2024-03-28 o args = cmd[1:]
115 807f644f 2024-03-28 o err = sess.dispatchCommand(cmd[0], args, c)
116 807f644f 2024-03-28 o if err != nil {
117 807f644f 2024-03-28 o _, ok := err.(*Error)
119 807f644f 2024-03-28 o case err == io.EOF:
120 807f644f 2024-03-28 o // Drop this connection silently. They hung up
123 807f644f 2024-03-28 o c.PrintfLine(err.Error())
125 807f644f 2024-03-28 o log.Printf("Error dispatching command, dropping conn: %v", err)
132 807f644f 2024-03-28 o func parseRange(spec string) (low, high int64) {
133 807f644f 2024-03-28 o if spec == "" {
134 807f644f 2024-03-28 o return 0, math.MaxInt64
136 807f644f 2024-03-28 o parts := strings.Split(spec, "-")
137 807f644f 2024-03-28 o if len(parts) == 1 {
138 807f644f 2024-03-28 o h, err := strconv.ParseInt(parts[0], 10, 64)
139 807f644f 2024-03-28 o if err != nil {
140 807f644f 2024-03-28 o h = math.MaxInt64
144 807f644f 2024-03-28 o l, _ := strconv.ParseInt(parts[0], 10, 64)
145 807f644f 2024-03-28 o h, err := strconv.ParseInt(parts[1], 10, 64)
146 807f644f 2024-03-28 o if err != nil {
147 807f644f 2024-03-28 o h = math.MaxInt64
153 807f644f 2024-03-28 o "0" or article number (see below)
154 807f644f 2024-03-28 o Subject header content
155 807f644f 2024-03-28 o From header content
156 807f644f 2024-03-28 o Date header content
157 807f644f 2024-03-28 o Message-ID header content
158 807f644f 2024-03-28 o References header content
159 807f644f 2024-03-28 o :bytes metadata item
160 807f644f 2024-03-28 o :lines metadata item
163 807f644f 2024-03-28 o func handleOver(args []string, s *session, c *textproto.Conn) error {
164 807f644f 2024-03-28 o if s.group == nil {
165 807f644f 2024-03-28 o return ErrNoGroupSelected
167 807f644f 2024-03-28 o from, to := parseRange(args[0])
168 807f644f 2024-03-28 o articles, err := s.backend.GetArticles(s.group, from, to)
169 807f644f 2024-03-28 o if err != nil {
172 807f644f 2024-03-28 o c.PrintfLine("224 here it comes")
173 807f644f 2024-03-28 o dw := c.DotWriter()
174 807f644f 2024-03-28 o defer dw.Close()
175 807f644f 2024-03-28 o for _, a := range articles {
176 807f644f 2024-03-28 o fmt.Fprintf(dw, "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%d\n", a.Num,
177 807f644f 2024-03-28 o a.Article.Header.Get("Subject"),
178 807f644f 2024-03-28 o a.Article.Header.Get("From"),
179 807f644f 2024-03-28 o a.Article.Header.Get("Date"),
180 807f644f 2024-03-28 o a.Article.Header.Get("Message-Id"),
181 807f644f 2024-03-28 o a.Article.Header.Get("References"),
182 807f644f 2024-03-28 o a.Article.Bytes, a.Article.Lines)
187 807f644f 2024-03-28 o func handleListOverviewFmt(c *textproto.Conn) error {
188 807f644f 2024-03-28 o err := c.PrintfLine("215 Order of fields in overview database.")
189 807f644f 2024-03-28 o if err != nil {
192 807f644f 2024-03-28 o dw := c.DotWriter()
193 807f644f 2024-03-28 o defer dw.Close()
194 807f644f 2024-03-28 o _, err = fmt.Fprintln(dw, `Subject:
204 807f644f 2024-03-28 o func handleList(args []string, s *session, c *textproto.Conn) error {
205 807f644f 2024-03-28 o ltype := "active"
206 807f644f 2024-03-28 o if len(args) > 0 {
207 807f644f 2024-03-28 o ltype = strings.ToLower(args[0])
210 807f644f 2024-03-28 o if ltype == "overview.fmt" {
211 807f644f 2024-03-28 o return handleListOverviewFmt(c)
214 807f644f 2024-03-28 o groups, err := s.backend.ListGroups(-1)
215 807f644f 2024-03-28 o if err != nil {
218 807f644f 2024-03-28 o c.PrintfLine("215 list of newsgroups follows")
219 807f644f 2024-03-28 o dw := c.DotWriter()
220 807f644f 2024-03-28 o defer dw.Close()
221 807f644f 2024-03-28 o for _, g := range groups {
222 807f644f 2024-03-28 o switch ltype {
223 807f644f 2024-03-28 o case "active":
224 807f644f 2024-03-28 o fmt.Fprintf(dw, "%s %d %d %v\r\n",
225 807f644f 2024-03-28 o g.Name, g.High, g.Low, g.Posting)
226 807f644f 2024-03-28 o case "newsgroups":
227 807f644f 2024-03-28 o fmt.Fprintf(dw, "%s %s\r\n", g.Name, g.Description)
234 807f644f 2024-03-28 o func handleNewGroups(args []string, s *session, c *textproto.Conn) error {
235 807f644f 2024-03-28 o c.PrintfLine("231 list of newsgroups follows")
236 807f644f 2024-03-28 o c.PrintfLine(".")
240 807f644f 2024-03-28 o func handleDefault(args []string, s *session, c *textproto.Conn) error {
241 807f644f 2024-03-28 o return ErrUnknownCommand
244 807f644f 2024-03-28 o func handleQuit(args []string, s *session, c *textproto.Conn) error {
245 807f644f 2024-03-28 o c.PrintfLine("205 bye")
249 807f644f 2024-03-28 o func handleGroup(args []string, s *session, c *textproto.Conn) error {
250 807f644f 2024-03-28 o if len(args) < 1 {
251 807f644f 2024-03-28 o return ErrNoSuchGroup
254 807f644f 2024-03-28 o group, err := s.backend.GetGroup(args[0])
255 807f644f 2024-03-28 o if err != nil {
259 807f644f 2024-03-28 o s.group = group
261 807f644f 2024-03-28 o c.PrintfLine("211 %d %d %d %s",
262 807f644f 2024-03-28 o group.Count, group.Low, group.High, group.Name)
266 807f644f 2024-03-28 o func (s *session) getArticle(args []string) (*Article, error) {
267 807f644f 2024-03-28 o if s.group == nil {
268 807f644f 2024-03-28 o return nil, ErrNoGroupSelected
270 807f644f 2024-03-28 o return s.backend.GetArticle(s.group, args[0])
275 807f644f 2024-03-28 o HEAD message-id
280 807f644f 2024-03-28 o First form (message-id specified)
281 807f644f 2024-03-28 o 221 0|n message-id Headers follow (multi-line)
282 807f644f 2024-03-28 o 430 No article with that message-id
284 807f644f 2024-03-28 o Second form (article number specified)
285 807f644f 2024-03-28 o 221 n message-id Headers follow (multi-line)
286 807f644f 2024-03-28 o 412 No newsgroup selected
287 807f644f 2024-03-28 o 423 No article with that number
289 807f644f 2024-03-28 o Third form (current article number used)
290 807f644f 2024-03-28 o 221 n message-id Headers follow (multi-line)
291 807f644f 2024-03-28 o 412 No newsgroup selected
292 807f644f 2024-03-28 o 420 Current article number is invalid
295 807f644f 2024-03-28 o func handleHead(args []string, s *session, c *textproto.Conn) error {
296 807f644f 2024-03-28 o article, err := s.getArticle(args)
297 807f644f 2024-03-28 o if err != nil {
300 807f644f 2024-03-28 o c.PrintfLine("221 1 %s", article.MessageID())
301 807f644f 2024-03-28 o dw := c.DotWriter()
302 807f644f 2024-03-28 o defer dw.Close()
303 807f644f 2024-03-28 o for k, v := range article.Header {
304 807f644f 2024-03-28 o fmt.Fprintf(dw, "%s: %s\r\n", k, v[0])
311 807f644f 2024-03-28 o BODY message-id
317 807f644f 2024-03-28 o First form (message-id specified)
318 807f644f 2024-03-28 o 222 0|n message-id Body follows (multi-line)
319 807f644f 2024-03-28 o 430 No article with that message-id
321 807f644f 2024-03-28 o Second form (article number specified)
322 807f644f 2024-03-28 o 222 n message-id Body follows (multi-line)
323 807f644f 2024-03-28 o 412 No newsgroup selected
324 807f644f 2024-03-28 o 423 No article with that number
326 807f644f 2024-03-28 o Third form (current article number used)
327 807f644f 2024-03-28 o 222 n message-id Body follows (multi-line)
328 807f644f 2024-03-28 o 412 No newsgroup selected
329 807f644f 2024-03-28 o 420 Current article number is invalid
332 807f644f 2024-03-28 o number Requested article number
333 807f644f 2024-03-28 o n Returned article number
334 807f644f 2024-03-28 o message-id Article message-id
337 807f644f 2024-03-28 o func handleBody(args []string, s *session, c *textproto.Conn) error {
338 807f644f 2024-03-28 o article, err := s.getArticle(args)
339 807f644f 2024-03-28 o if err != nil {
342 807f644f 2024-03-28 o c.PrintfLine("222 1 %s", article.MessageID())
343 807f644f 2024-03-28 o dw := c.DotWriter()
344 807f644f 2024-03-28 o defer dw.Close()
345 807f644f 2024-03-28 o _, err = io.Copy(dw, article.Body)
351 807f644f 2024-03-28 o ARTICLE message-id
352 807f644f 2024-03-28 o ARTICLE number
357 807f644f 2024-03-28 o First form (message-id specified)
358 807f644f 2024-03-28 o 220 0|n message-id Article follows (multi-line)
359 807f644f 2024-03-28 o 430 No article with that message-id
361 807f644f 2024-03-28 o Second form (article number specified)
362 807f644f 2024-03-28 o 220 n message-id Article follows (multi-line)
363 807f644f 2024-03-28 o 412 No newsgroup selected
364 807f644f 2024-03-28 o 423 No article with that number
366 807f644f 2024-03-28 o Third form (current article number used)
367 807f644f 2024-03-28 o 220 n message-id Article follows (multi-line)
368 807f644f 2024-03-28 o 412 No newsgroup selected
369 807f644f 2024-03-28 o 420 Current article number is invalid
372 807f644f 2024-03-28 o number Requested article number
373 807f644f 2024-03-28 o n Returned article number
374 807f644f 2024-03-28 o message-id Article message-id
377 807f644f 2024-03-28 o func handleArticle(args []string, s *session, c *textproto.Conn) error {
378 807f644f 2024-03-28 o article, err := s.getArticle(args)
379 807f644f 2024-03-28 o if err != nil {
382 807f644f 2024-03-28 o c.PrintfLine("220 1 %s", article.MessageID())
383 807f644f 2024-03-28 o dw := c.DotWriter()
384 807f644f 2024-03-28 o defer dw.Close()
386 807f644f 2024-03-28 o for k, v := range article.Header {
387 807f644f 2024-03-28 o fmt.Fprintf(dw, "%s: %s\r\n", k, v[0])
390 807f644f 2024-03-28 o fmt.Fprintln(dw, "")
392 807f644f 2024-03-28 o _, err = io.Copy(dw, article.Body)
402 807f644f 2024-03-28 o Initial responses
403 807f644f 2024-03-28 o 340 Send article to be posted
404 807f644f 2024-03-28 o 440 Posting not permitted
406 807f644f 2024-03-28 o Subsequent responses
407 807f644f 2024-03-28 o 240 Article received OK
408 807f644f 2024-03-28 o 441 Posting failed
411 807f644f 2024-03-28 o func handlePost(args []string, s *session, c *textproto.Conn) error {
412 807f644f 2024-03-28 o if !s.backend.AllowPost() {
413 807f644f 2024-03-28 o return ErrPostingNotPermitted
416 807f644f 2024-03-28 o c.PrintfLine("340 Go ahead")
418 807f644f 2024-03-28 o var article Article
419 807f644f 2024-03-28 o article.Header, err = c.ReadMIMEHeader()
420 807f644f 2024-03-28 o if err != nil {
421 807f644f 2024-03-28 o return ErrPostingFailed
423 807f644f 2024-03-28 o article.Body = c.DotReader()
424 807f644f 2024-03-28 o err = s.backend.Post(&article)
425 807f644f 2024-03-28 o if err != nil {
428 807f644f 2024-03-28 o c.PrintfLine("240 article received OK")
432 807f644f 2024-03-28 o func handleIHave(args []string, s *session, c *textproto.Conn) error {
433 807f644f 2024-03-28 o if !s.backend.AllowPost() {
434 807f644f 2024-03-28 o return ErrNotWanted
437 807f644f 2024-03-28 o // XXX: See if we have it.
438 807f644f 2024-03-28 o article, err := s.backend.GetArticle(nil, args[0])
439 807f644f 2024-03-28 o if article != nil {
440 807f644f 2024-03-28 o return ErrNotWanted
443 807f644f 2024-03-28 o c.PrintfLine("335 send it")
444 807f644f 2024-03-28 o article = &Article{}
445 807f644f 2024-03-28 o article.Header, err = c.ReadMIMEHeader()
446 807f644f 2024-03-28 o if err != nil {
447 807f644f 2024-03-28 o return ErrPostingFailed
449 807f644f 2024-03-28 o article.Body = c.DotReader()
450 807f644f 2024-03-28 o err = s.backend.Post(article)
451 807f644f 2024-03-28 o if err != nil {
454 807f644f 2024-03-28 o c.PrintfLine("235 article received OK")
458 807f644f 2024-03-28 o func handleCap(args []string, s *session, c *textproto.Conn) error {
459 807f644f 2024-03-28 o c.PrintfLine("101 Capability list:")
460 807f644f 2024-03-28 o dw := c.DotWriter()
461 807f644f 2024-03-28 o defer dw.Close()
463 807f644f 2024-03-28 o fmt.Fprintf(dw, "VERSION 2\n")
464 807f644f 2024-03-28 o fmt.Fprintf(dw, "READER\n")
465 807f644f 2024-03-28 o if s.backend.AllowPost() {
466 807f644f 2024-03-28 o fmt.Fprintf(dw, "POST\n")
467 807f644f 2024-03-28 o fmt.Fprintf(dw, "IHAVE\n")
469 807f644f 2024-03-28 o fmt.Fprintf(dw, "OVER\n")
470 807f644f 2024-03-28 o fmt.Fprintf(dw, "XOVER\n")
471 807f644f 2024-03-28 o fmt.Fprintf(dw, "LIST ACTIVE NEWSGROUPS OVERVIEW.FMT\n")
475 807f644f 2024-03-28 o func handleMode(args []string, s *session, c *textproto.Conn) error {
476 807f644f 2024-03-28 o if s.backend.AllowPost() {
477 807f644f 2024-03-28 o c.PrintfLine("200 Posting allowed")
479 807f644f 2024-03-28 o c.PrintfLine("201 Posting prohibited")
484 807f644f 2024-03-28 o func handleAuthInfo(args []string, s *session, c *textproto.Conn) error {
485 807f644f 2024-03-28 o if len(args) < 2 {
486 807f644f 2024-03-28 o return ErrSyntax
488 807f644f 2024-03-28 o if strings.ToLower(args[0]) != "user" {
489 807f644f 2024-03-28 o return ErrSyntax
492 807f644f 2024-03-28 o if s.backend.Authorized() {
493 807f644f 2024-03-28 o return c.PrintfLine("250 authenticated")
496 807f644f 2024-03-28 o c.PrintfLine("350 Continue")
497 807f644f 2024-03-28 o a, err := c.ReadLine()
498 807f644f 2024-03-28 o parts := strings.SplitN(a, " ", 3)
499 807f644f 2024-03-28 o if strings.ToLower(parts[0]) != "authinfo" || strings.ToLower(parts[1]) != "pass" {
500 807f644f 2024-03-28 o return ErrSyntax
502 807f644f 2024-03-28 o b, err := s.backend.Authenticate(args[1], parts[2])
503 807f644f 2024-03-28 o if err == nil {
504 807f644f 2024-03-28 o c.PrintfLine("250 authenticated")