// handle streaming event // this function should send only func (self *nntpConnection) handleStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) (err error) { for err == nil { ev := <-self.stream log.Println(self.name, ev) if ValidMessageID(ev.MessageID()) { cmd, msgid := ev.Command(), ev.MessageID() if cmd == "TAKETHIS" { fname := daemon.store.GetFilename(msgid) if CheckFile(fname) { f, err := os.Open(fname) if err == nil { err = conn.PrintfLine("%s", ev) // time to send dw := conn.DotWriter() _, err = io.Copy(dw, f) err = dw.Close() f.Close() } } else { log.Println(self.name, "didn't send", msgid, "we don't have it locally") } } else if cmd == "CHECK" { conn.PrintfLine("%s", ev) } else { log.Println("invalid stream command", ev) } } } return }
func handleList(args []string, s *session, c *textproto.Conn) error { ltype := "active" if len(args) > 0 { ltype = strings.ToLower(args[0]) } if ltype == "overview.fmt" { dw := c.DotWriter() defer dw.Close() return handleListOverviewFmt(dw, c) } groups, err := s.backend.ListGroups(-1) if err != nil { return err } c.PrintfLine("215 list of newsgroups follows") dw := c.DotWriter() defer dw.Close() for _, g := range groups { switch ltype { case "active": fmt.Fprintf(dw, "%s %d %d %v\r\n", g.Name, g.High, g.Low, g.Posting) case "newsgroups": fmt.Fprintf(dw, "%s %s\r\n", g.Name, g.Description) } } return nil }
// handle streaming event // this function should send only func (self nntpConnection) handleStreaming(daemon NNTPDaemon, reader bool, conn *textproto.Conn) (err error) { select { case msgid := <-self.check: log.Println(self.name, "CHECK", msgid) err = conn.PrintfLine("CHECK %s", msgid) case msgid := <-self.take: // send a file via TAKETHIS if ValidMessageID(msgid) { fname := daemon.store.GetFilename(msgid) if CheckFile(fname) { f, err := os.Open(fname) if err == nil { // time to send err = conn.PrintfLine("TAKETHIS %s", msgid) dw := conn.DotWriter() _, err = io.Copy(dw, f) err = dw.Close() f.Close() } } else { log.Println(self.name, "didn't send", msgid, "we don't have it locally") } } } return }
// handle sending 1 stream event func (self *nntpConnection) handleStreamEvent(ev nntpStreamEvent, daemon *NNTPDaemon, conn *textproto.Conn) (err error) { if ValidMessageID(ev.MessageID()) { cmd, msgid := ev.Command(), ev.MessageID() if cmd == "TAKETHIS" { // open message for reading var rc io.ReadCloser rc, err = daemon.store.OpenMessage(msgid) if err == nil { err = conn.PrintfLine("%s", ev) // time to send dw := conn.DotWriter() _, err = io.Copy(dw, rc) err = dw.Close() rc.Close() self.messageSetPendingState(msgid, "sent") } else { log.Println(self.name, "didn't send", msgid, err) self.messageSetProcessed(msgid) // ignore this error err = nil } } else if cmd == "CHECK" { conn.PrintfLine("%s", ev) } else { log.Println("invalid stream command", ev) } } return }
func handleBody(args []string, s *session, c *textproto.Conn) error { article, err := s.getArticle(args) if err != nil { return err } c.PrintfLine("222 1 %s", article.MessageId()) dw := c.DotWriter() defer dw.Close() _, err = io.Copy(dw, article.Body) return err }
func handleHead(args []string, s *session, c *textproto.Conn) error { article, err := s.getArticle(args) if err != nil { return err } c.PrintfLine("221 1 %s", article.MessageId()) dw := c.DotWriter() defer dw.Close() for k, v := range article.Header { fmt.Fprintf(dw, "%s: %s\r\n", k, v[0]) } return nil }
func handleCap(args []string, s *session, c *textproto.Conn) error { c.PrintfLine("101 Capability list:") dw := c.DotWriter() defer dw.Close() fmt.Fprintf(dw, "VERSION 2\n") fmt.Fprintf(dw, "READER\n") if s.backend.AllowPost() { fmt.Fprintf(dw, "POST\n") fmt.Fprintf(dw, "IHAVE\n") } fmt.Fprintf(dw, "OVER\n") fmt.Fprintf(dw, "XOVER\n") fmt.Fprintf(dw, "LIST ACTIVE NEWSGROUPS OVERVIEW.FMT\n") return nil }
func handleListOverviewFmt(c *textproto.Conn) error { err := c.PrintfLine("215 Order of fields in overview database.") if err != nil { return err } dw := c.DotWriter() defer dw.Close() _, err = fmt.Fprintln(dw, `Subject: From: Date: Message-ID: References: :bytes :lines`) return err }
func handleOver(args []string, s *session, c *textproto.Conn) error { if s.group == nil { return NoGroupSelected } from, to := parseRange(args[0]) articles, err := s.backend.GetArticles(s.group, from, to) if err != nil { return err } c.PrintfLine("224 here it comes") dw := c.DotWriter() defer dw.Close() for _, a := range articles { fmt.Fprintf(dw, "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%d\n", a.Num, a.Article.Header.Get("Subject"), a.Article.Header.Get("From"), a.Article.Header.Get("Date"), a.Article.Header.Get("Message-Id"), a.Article.Header.Get("References"), a.Article.Bytes, a.Article.Lines) } return nil }
// run the mainloop for this connection // stream if true means they support streaming mode // reader if true means they support reader mode func (self nntpConnection) runConnection(daemon NNTPDaemon, inbound, stream, reader bool, preferMode string, conn *textproto.Conn) { var err error var line string var success bool for err == nil { if self.mode == "" { if inbound { // no mode and inbound line, err = conn.ReadLine() log.Println(self.name, line) parts := strings.Split(line, " ") cmd := parts[0] if cmd == "CAPABILITIES" { // write capabilities conn.PrintfLine("101 i support to the following:") dw := conn.DotWriter() caps := []string{"VERSION 2", "READER", "STREAMING", "IMPLEMENTATION srndv2"} for _, cap := range caps { io.WriteString(dw, cap) io.WriteString(dw, "\n") } dw.Close() log.Println(self.name, "sent Capabilities") } else if cmd == "MODE" { if len(parts) == 2 { if parts[1] == "READER" { // set reader mode self.mode = "READER" // posting is not permitted with reader mode conn.PrintfLine("201 Posting not permitted") } else if parts[1] == "STREAM" { // set streaming mode conn.PrintfLine("203 Stream it brah") self.mode = "STREAM" log.Println(self.name, "streaming enabled") go self.startStreaming(daemon, reader, conn) } } } else { // handle a it as a command, we don't have a mode set parts := strings.Split(line, " ") var code64 int64 code64, err = strconv.ParseInt(parts[0], 10, 32) if err == nil { err = self.handleLine(daemon, int(code64), line[4:], conn) } else { err = self.handleLine(daemon, 0, line, conn) } } } else { // no mode and outbound if preferMode == "stream" { // try outbound streaming if stream { success, err = self.modeSwitch("STREAM", conn) self.mode = "STREAM" if success { // start outbound streaming in background go self.startStreaming(daemon, reader, conn) } } } else if reader { // try reader mode success, err = self.modeSwitch("READER", conn) if success { self.mode = "READER" self.startReader(daemon, conn) } } if success { log.Println(self.name, "mode set to", self.mode) } else { // bullshit // we can't do anything so we quit log.Println(self.name, "can't stream or read, wtf?") conn.PrintfLine("QUIT") conn.Close() return } } } else { // we have our mode set line, err = conn.ReadLine() if err == nil { parts := strings.Split(line, " ") var code64 int64 code64, err = strconv.ParseInt(parts[0], 10, 32) if err == nil { err = self.handleLine(daemon, int(code64), line[4:], conn) } else { err = self.handleLine(daemon, 0, line, conn) } } } } log.Println(self.name, "got error", err) if !inbound { // send quit on outbound conn.PrintfLine("QUIT") } conn.Close() }
func (self nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) { parts := strings.Split(line, " ") var msgid string if code == 0 && len(parts) > 1 { msgid = parts[1] } else { msgid = parts[0] } if code == 238 { if ValidMessageID(msgid) { log.Println("sending", msgid, "to", self.name) // send the article to us self.take <- msgid } } else if code == 239 { // successful TAKETHIS log.Println(msgid, "sent via", self.name) // TODO: remember success } else if code == 431 { // CHECK said we would like this article later log.Println("defer sending", msgid, "to", self.name) go self.articleDefer(msgid) } else if code == 439 { // TAKETHIS failed log.Println(msgid, "was not sent to", self.name, "denied:", line) // TODO: remember denial } else if code == 438 { // they don't want the article // TODO: remeber rejection } else { // handle command parts := strings.Split(line, " ") if len(parts) == 2 { cmd := parts[0] if cmd == "MODE" { if parts[1] == "READER" { // reader mode self.mode = "READER" log.Println(self.name, "switched to reader mode") conn.PrintfLine("201 No posting Permitted") } else if parts[1] == "STREAM" { // wut? we're already in streaming mode log.Println(self.name, "already in streaming mode") conn.PrintfLine("203 Streaming enabled brah") } else { // invalid log.Println(self.name, "got invalid mode request", parts[1]) conn.PrintfLine("501 invalid mode variant:", parts[1]) } } else if cmd == "QUIT" { // quit command conn.PrintfLine("") // close our connection and return conn.Close() return } else if cmd == "CHECK" { // handle check command msgid := parts[1] // have we seen this article? if daemon.database.HasArticle(msgid) { // yeh don't want it conn.PrintfLine("438 %s", msgid) } else if daemon.database.ArticleBanned(msgid) { // it's banned we don't want it conn.PrintfLine("438 %s", msgid) } else { // yes we do want it and we don't have it conn.PrintfLine("238 %s", msgid) } } else if cmd == "TAKETHIS" { // handle takethis command var hdr textproto.MIMEHeader var reason string // read the article header hdr, err = conn.ReadMIMEHeader() if err == nil { // check the header reason, err = self.checkMIMEHeader(daemon, hdr) dr := conn.DotReader() if len(reason) > 0 { // discard, we do not want code = 439 log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, dr) err = daemon.database.BanArticle(msgid, reason) } else { // check if we don't have the rootpost reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") daemon.ask_for_article <- ArticleEntry{reference, newsgroup} } f := daemon.store.CreateTempFile(msgid) if f == nil { log.Println(self.name, "discarding", msgid, "we are already loading it") // discard io.Copy(ioutil.Discard, dr) } else { // write header err = writeMIMEHeader(f, hdr) // write body _, err = io.Copy(f, dr) if err == nil || err == io.EOF { f.Close() // we gud, tell daemon daemon.infeed_load <- msgid } else { log.Println(self.name, "error reading message", err) } } code = 239 reason = "gotten" } } else { log.Println(self.name, "error reading mime header:", err) code = 439 reason = "error reading mime header" } conn.PrintfLine("%d %s %s", code, msgid, reason) } else if cmd == "ARTICLE" { if ValidMessageID(msgid) { if daemon.store.HasArticle(msgid) { // we have it yeh f, err := os.Open(daemon.store.GetFilename(msgid)) if err == nil { conn.PrintfLine("220 %s", msgid) dw := conn.DotWriter() _, err = io.Copy(dw, f) dw.Close() f.Close() } else { // wtf?! conn.PrintfLine("503 idkwtf happened: %s", err.Error()) } } else { // we dont got it conn.PrintfLine("430 %s", msgid) } } else { // invalid id conn.PrintfLine("500 Syntax error") } } } } return }
func (self *nntpConnection) handleLine(daemon NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) { parts := strings.Split(line, " ") var msgid string if code == 0 && len(parts) > 1 { msgid = parts[1] } else { msgid = parts[0] } if code == 238 { if ValidMessageID(msgid) { self.stream <- nntpTAKETHIS(msgid) } return } else if code == 239 { // successful TAKETHIS log.Println(msgid, "sent via", self.name) return // TODO: remember success } else if code == 431 { // CHECK said we would like this article later log.Println("defer sending", msgid, "to", self.name) go self.articleDefer(msgid) } else if code == 439 { // TAKETHIS failed log.Println(msgid, "was not sent to", self.name, "denied:", line) // TODO: remember denial } else if code == 438 { // they don't want the article // TODO: remeber rejection } else { // handle command parts := strings.Split(line, " ") if len(parts) > 1 { cmd := parts[0] if cmd == "MODE" { if parts[1] == "READER" { // reader mode self.mode = "READER" log.Println(self.name, "switched to reader mode") conn.PrintfLine("201 No posting Permitted") } else if parts[1] == "STREAM" { // wut? we're already in streaming mode log.Println(self.name, "already in streaming mode") conn.PrintfLine("203 Streaming enabled brah") } else { // invalid log.Println(self.name, "got invalid mode request", parts[1]) conn.PrintfLine("501 invalid mode variant:", parts[1]) } } else if cmd == "QUIT" { // quit command conn.PrintfLine("") // close our connection and return conn.Close() return } else if cmd == "CHECK" { // handle check command msgid := parts[1] // have we seen this article? if daemon.database.HasArticle(msgid) { // yeh don't want it conn.PrintfLine("438 %s", msgid) } else if daemon.database.ArticleBanned(msgid) { // it's banned we don't want it conn.PrintfLine("438 %s", msgid) } else { // yes we do want it and we don't have it conn.PrintfLine("238 %s", msgid) } } else if cmd == "TAKETHIS" { // handle takethis command var hdr textproto.MIMEHeader var reason string // read the article header hdr, err = conn.ReadMIMEHeader() if err == nil { // check the header reason, err = self.checkMIMEHeader(daemon, hdr) dr := conn.DotReader() if len(reason) > 0 { // discard, we do not want code = 439 log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, dr) err = daemon.database.BanArticle(msgid, reason) } else { // check if we don't have the rootpost reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") daemon.ask_for_article <- ArticleEntry{reference, newsgroup} } f := daemon.store.CreateTempFile(msgid) if f == nil { log.Println(self.name, "discarding", msgid, "we are already loading it") // discard io.Copy(ioutil.Discard, dr) } else { // write header err = writeMIMEHeader(f, hdr) // write body _, err = io.Copy(f, dr) if err == nil || err == io.EOF { f.Close() // we gud, tell daemon daemon.infeed_load <- msgid } else { log.Println(self.name, "error reading message", err) } } code = 239 reason = "gotten" } } else { log.Println(self.name, "error reading mime header:", err) code = 439 reason = "error reading mime header" } conn.PrintfLine("%d %s %s", code, msgid, reason) } else if cmd == "ARTICLE" { if ValidMessageID(msgid) { if daemon.store.HasArticle(msgid) { // we have it yeh f, err := os.Open(daemon.store.GetFilename(msgid)) if err == nil { conn.PrintfLine("220 %s", msgid) dw := conn.DotWriter() _, err = io.Copy(dw, f) dw.Close() f.Close() } else { // wtf?! conn.PrintfLine("503 idkwtf happened: %s", err.Error()) } } else { // we dont got it conn.PrintfLine("430 %s", msgid) } } else { // invalid id conn.PrintfLine("500 Syntax error") } } else if cmd == "POST" { // handle POST command conn.PrintfLine("340 Post it nigguh; end with <CR-LF>.<CR-LF>") hdr, err := conn.ReadMIMEHeader() var success bool if err == nil { hdr["Message-ID"] = []string{genMessageID(daemon.instance_name)} reason, err := self.checkMIMEHeader(daemon, hdr) success = reason == "" && err == nil if success { dr := conn.DotReader() reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") daemon.ask_for_article <- ArticleEntry{reference, newsgroup} } f := daemon.store.CreateTempFile(msgid) if f == nil { log.Println(self.name, "discarding", msgid, "we are already loading it") // discard io.Copy(ioutil.Discard, dr) } else { // write header err = writeMIMEHeader(f, hdr) // write body _, err = io.Copy(f, dr) if err == nil || err == io.EOF { f.Close() // we gud, tell daemon daemon.infeed_load <- msgid } else { log.Println(self.name, "error reading message", err) } } } } if success && err == nil { // all gud conn.PrintfLine("240 We got it, thnkxbai") } else { // failed posting if err != nil { log.Println(self.name, "failed nntp POST", err) } conn.PrintfLine("441 Posting Failed") } } else if cmd == "IHAVE" { // handle IHAVE command msgid := parts[1] if daemon.database.HasArticleLocal(msgid) || daemon.database.HasArticle(msgid) || daemon.database.ArticleBanned(msgid) { // we don't want it conn.PrintfLine("435 Article Not Wanted") } else { // gib we want conn.PrintfLine("335 Send it plz") hdr, err := conn.ReadMIMEHeader() if err == nil { // check the header var reason string reason, err = self.checkMIMEHeader(daemon, hdr) dr := conn.DotReader() if len(reason) > 0 { // discard, we do not want log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, dr) // ignore this _ = daemon.database.BanArticle(msgid, reason) conn.PrintfLine("437 Rejected do not send again bro") } else { // check if we don't have the rootpost reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") daemon.ask_for_article <- ArticleEntry{reference, newsgroup} } f := daemon.store.CreateTempFile(msgid) if f == nil { log.Println(self.name, "discarding", msgid, "we are already loading it") // discard io.Copy(ioutil.Discard, dr) } else { // write header err = writeMIMEHeader(f, hdr) // write body _, err = io.Copy(f, dr) if err == nil || err == io.EOF { f.Close() // we gud, tell daemon daemon.infeed_load <- msgid } else { log.Println(self.name, "error reading message", err) } } conn.PrintfLine("235 We got it") } } else { // error here conn.PrintfLine("436 Transfer failed: " + err.Error()) } } } else if cmd == "NEWSGROUPS" { // handle NEWSGROUPS conn.PrintfLine("231 List of newsgroups follow") dw := conn.DotWriter() // get a list of every newsgroup groups := daemon.database.GetAllNewsgroups() // for each group for _, group := range groups { // get low/high water mark lo, hi, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { // XXX: we ignore errors here :\ _, _ = io.WriteString(dw, fmt.Sprintf("%s %d %d y\n", group, lo, hi)) } else { log.Println(self.name, "could not get low/high water mark for", group, err) } } // flush dotwriter dw.Close() } else if cmd == "XOVER" { // handle XOVER if self.group == "" { conn.PrintfLine("412 No newsgroup selected") } else { // handle xover command // right now it's every article in group models, err := daemon.database.GetPostsInGroup(self.group) if err == nil { conn.PrintfLine("224 Overview information follows") dw := conn.DotWriter() for idx, model := range models { io.WriteString(dw, fmt.Sprintf("%.6d\t%s\t\"%s\" <%s@%s>\t%s\t%s\t%s\r\n", idx+1, model.Subject(), model.Name(), model.Name(), model.Frontend(), model.Date(), model.MessageID(), model.Reference())) } dw.Close() } else { log.Println(self.name, "error when getting posts in", self.group, err) conn.PrintfLine("500 error, %s", err.Error()) } } } else if cmd == "GROUP" { // handle GROUP command group := parts[1] // check for newsgroup if daemon.database.HasNewsgroup(group) { // we have the group self.group = group // count posts number := daemon.database.CountPostsInGroup(group, 0) // get hi/low water marks low, hi, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { // we gud conn.PrintfLine("211 %d %d %d %s", number, low, hi, group) } else { // wtf error log.Println(self.name, "error in GROUP command", err) // still have to reply, send it bogus low/hi conn.PrintfLine("211 %d 0 1 %s", number, group) } } else { // no such group conn.PrintfLine("411 No Such Newsgroup") } } else { log.Println(self.name, "invalid command recv'd", cmd) conn.PrintfLine("500 Invalid command: %s", cmd) } } } return }
func (self *nntpConnection) handleLine(daemon *NNTPDaemon, code int, line string, conn *textproto.Conn) (err error) { parts := strings.Split(line, " ") var msgid string if code == 0 && len(parts) > 1 { msgid = parts[1] } else { msgid = parts[0] } if code == 238 { if ValidMessageID(msgid) { self.messageSetPendingState(msgid, "takethis") // they want this article self.takethis <- msgid } return } else if code == 239 { // successful TAKETHIS log.Println(msgid, "sent via", self.name) self.messageSetProcessed(msgid) return // TODO: remember success } else if code == 431 { // CHECK said we would like this article later self.messageSetProcessed(msgid) } else if code == 439 { // TAKETHIS failed log.Println(msgid, "was not sent to", self.name, "denied:", line) self.messageSetProcessed(msgid) // TODO: remember denial } else if code == 438 { // they don't want the article // TODO: remeber rejection self.messageSetProcessed(msgid) } else { // handle command parts := strings.Split(line, " ") if len(parts) > 1 { cmd := strings.ToUpper(parts[0]) if cmd == "MODE" { mode := strings.ToUpper(parts[1]) if mode == "READER" { // reader mode self.mode = "READER" log.Println(self.name, "switched to reader mode") if self.authenticated { conn.PrintfLine("200 Posting Permitted") } else { conn.PrintfLine("201 No posting Permitted") } } else if mode == "STREAM" && self.authenticated { // wut? we're already in streaming mode log.Println(self.name, "already in streaming mode") conn.PrintfLine("203 Streaming enabled brah") } else { // invalid log.Println(self.name, "got invalid mode request", parts[1]) conn.PrintfLine("501 invalid mode variant:", parts[1]) } } else if cmd == "QUIT" { // quit command conn.PrintfLine("") // close our connection and return conn.Close() return } else if cmd == "AUTHINFO" { if len(parts) > 1 { auth_cmd := strings.ToUpper(parts[1]) if auth_cmd == "USER" { // first part self.username = parts[2] // next phase is PASS conn.PrintfLine("381 Password required") } else if auth_cmd == "PASS" { if len(self.username) == 0 { conn.PrintfLine("482 Authentication commands issued out of sequence") } else { // try login var valid bool valid, err = daemon.database.CheckNNTPUserExists(self.username) if valid { valid, err = daemon.database.CheckNNTPLogin(self.username, line[14:]) } if valid { // valid login self.authenticated = true conn.PrintfLine("281 Authentication accepted") } else if err == nil { // invalid login conn.PrintfLine("481 Authentication rejected") } else { // there was an error // logit log.Println(self.name, "error while logging in as", self.username, err) conn.PrintfLine("501 error while logging in") } } } } else { // wut ? // wrong legnth of parametrs } } else if cmd == "CHECK" { // handle check command msgid := parts[1] if self.mode != "STREAM" { // we can't we are not in streaming mode conn.PrintfLine("431 %s", msgid) return } // have we seen this article? if daemon.database.HasArticle(msgid) { // yeh don't want it conn.PrintfLine("438 %s", msgid) } else if daemon.database.ArticleBanned(msgid) { // it's banned we don't want it conn.PrintfLine("438 %s", msgid) } else { // yes we do want it and we don't have it conn.PrintfLine("238 %s", msgid) } } else if cmd == "TAKETHIS" { // handle takethis command var hdr textproto.MIMEHeader var reason string var ban bool // read the article header r := bufio.NewReader(conn.DotReader()) hdr, err = readMIMEHeader(r) if err == nil { // check the header reason, ban, err = self.checkMIMEHeader(daemon, hdr) if len(reason) > 0 { // discard, we do not want code = 439 log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, r) if ban { err = daemon.database.BanArticle(msgid, reason) } } else if err == nil { // check if we don't have the rootpost reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") go daemon.askForArticle(ArticleEntry{reference, newsgroup}) } // store message err = self.storeMessage(daemon, hdr, r) if err == nil { code = 239 reason = "gotten" } else { code = 439 reason = err.Error() } } else { // error? // discard, we do not want code = 439 log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, r) if ban { err = daemon.database.BanArticle(msgid, reason) } } } else { log.Println(self.name, "error reading mime header:", err) code = 439 reason = "error reading mime header" } conn.PrintfLine("%d %s %s", code, msgid, reason) } else if cmd == "ARTICLE" { if !ValidMessageID(msgid) { if len(self.group) > 0 { n, err := strconv.Atoi(msgid) if err == nil { msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, int64(n)) } } } if ValidMessageID(msgid) && daemon.store.HasArticle(msgid) { // we have it yeh f, err := daemon.store.OpenMessage(msgid) if err == nil { conn.PrintfLine("220 %s", msgid) dw := conn.DotWriter() _, err = io.Copy(dw, f) dw.Close() f.Close() } else { // wtf?! conn.PrintfLine("503 idkwtf happened: %s", err.Error()) } } else { // we dont got it conn.PrintfLine("430 %s", msgid) } } else if cmd == "IHAVE" { if !self.authenticated { conn.PrintfLine("483 You have not authenticated") } else { // handle IHAVE command msgid := parts[1] if daemon.database.HasArticleLocal(msgid) || daemon.database.HasArticle(msgid) || daemon.database.ArticleBanned(msgid) { // we don't want it conn.PrintfLine("435 Article Not Wanted") } else { // gib we want conn.PrintfLine("335 Send it plz") r := bufio.NewReader(conn.DotReader()) hdr, err := readMIMEHeader(r) if err == nil { // check the header var reason string var ban bool reason, ban, err = self.checkMIMEHeader(daemon, hdr) if len(reason) > 0 { // discard, we do not want log.Println(self.name, "rejected", msgid, reason) _, err = io.Copy(ioutil.Discard, r) if ban { _ = daemon.database.BanArticle(msgid, reason) } conn.PrintfLine("437 Rejected do not send again bro") } else { // check if we don't have the rootpost reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) && !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") go daemon.askForArticle(ArticleEntry{reference, newsgroup}) } err = self.storeMessage(daemon, hdr, r) if err == nil { conn.PrintfLine("235 We got it") } else { conn.PrintfLine("437 Transfer Failed %s", err.Error()) } } } else { // error here conn.PrintfLine("436 Transfer failed: " + err.Error()) } } } } else if cmd == "LISTGROUP" { // handle LISTGROUP var group string if len(parts) > 1 { // parameters group = parts[1] } else { group = self.group } if len(group) > 0 && newsgroupValidFormat(group) { if daemon.database.HasNewsgroup(group) { // we has newsgroup var hi, lo int64 count, err := daemon.database.CountAllArticlesInGroup(group) if err == nil { hi, lo, err = daemon.database.GetLastAndFirstForGroup(group) if err == nil { conn.PrintfLine("211 %d %d %d %s list follows", count, lo, hi, group) dw := conn.DotWriter() idx := lo for idx <= hi { fmt.Fprintf(dw, "%d\r\n", idx) idx++ } dw.Close() } } if err != nil { log.Println("LISTGROUP fail", err) conn.PrintfLine("500 error in LISTGROUP: %s", err.Error()) } } else { // don't has newsgroup conn.PrintfLine("411 no such newsgroup") } } else { conn.PrintfLine("412 no newsgroup selected") } } else if cmd == "NEWSGROUPS" { // handle NEWSGROUPS conn.PrintfLine("231 List of newsgroups follow") dw := conn.DotWriter() // get a list of every newsgroup groups := daemon.database.GetAllNewsgroups() // for each group for _, group := range groups { // get low/high water mark lo, hi, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { // XXX: we ignore errors here :\ _, _ = io.WriteString(dw, fmt.Sprintf("%s %d %d y\n", group, lo, hi)) } else { log.Println(self.name, "could not get low/high water mark for", group, err) } } // flush dotwriter dw.Close() } else if cmd == "XOVER" { // handle XOVER if self.group == "" { conn.PrintfLine("412 No newsgroup selected") } else { // handle xover command // right now it's every article in group models, err := daemon.database.GetPostsInGroup(self.group) if err == nil { conn.PrintfLine("224 Overview information follows") dw := conn.DotWriter() for idx, model := range models { io.WriteString(dw, fmt.Sprintf("%.6d\t%s\t\"%s\" <%s@%s>\t%s\t%s\t%s\r\n", idx+1, model.Subject(), model.Name(), model.Name(), model.Frontend(), model.Date(), model.MessageID(), model.Reference())) } dw.Close() } else { log.Println(self.name, "error when getting posts in", self.group, err) conn.PrintfLine("500 error, %s", err.Error()) } } } else if cmd == "HEAD" { if len(self.group) == 0 { // no group selected conn.PrintfLine("412 No newsgroup slected") } else { // newsgroup is selected // handle HEAD command if len(parts) == 0 { // we have no parameters if len(self.selected_article) > 0 { // we have a selected article } else { // no selected article conn.PrintfLine("420 current article number is invalid") } } else { // head command has 1 or more paramters var n int64 var msgid string var has bool var code int n, err = strconv.ParseInt(parts[1], 10, 64) if err == nil { // is a number msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, n) if err == nil && len(msgid) > 0 { has = daemon.database.HasArticleLocal(msgid) } if !has { code = 423 } } else if ValidMessageID(parts[1]) { msgid = parts[1] has = daemon.database.HasArticleLocal(msgid) if has { n, err = daemon.database.GetNNTPIDForMessageID(self.group, parts[1]) } else { code = 430 } } if err == nil { if has { // we has hdrs := daemon.store.GetHeaders(msgid) if hdrs == nil { // wtf can't load? conn.PrintfLine("500 cannot load headers") } else { // headers loaded, send them conn.PrintfLine("221 %d %s", n, msgid) dw := conn.DotWriter() err = writeMIMEHeader(dw, hdrs) dw.Close() hdrs = nil } } else if code > 0 { // don't has conn.PrintfLine("%d don't have article", code) } else { // invalid state conn.PrintfLine("500 invalid state in HEAD, should have article but we don't") } } else { // error occured conn.PrintfLine("500 error in HEAD: %s", err.Error()) } } } } else if cmd == "GROUP" { // handle GROUP command group := parts[1] // check for newsgroup if daemon.database.HasNewsgroup(group) { // we have the group self.group = group // count posts number := daemon.database.CountPostsInGroup(group, 0) // get hi/low water marks hi, low, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { // we gud conn.PrintfLine("211 %d %d %d %s", number, low, hi, group) } else { // wtf error log.Println(self.name, "error in GROUP command", err) // still have to reply, send it bogus low/hi conn.PrintfLine("211 %d 0 1 %s", number, group) } } else { // no such group conn.PrintfLine("411 No Such Newsgroup") } } else if cmd == "LIST" && parts[1] == "NEWSGROUPS" { conn.PrintfLine("215 list of newsgroups follows") // handle list command groups := daemon.database.GetAllNewsgroups() dw := conn.DotWriter() for _, group := range groups { last, first, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { io.WriteString(dw, fmt.Sprintf("%s %d %d y\r\n", group, first, last)) } else { log.Println("cannot get last/first ids for group", group, err) } } dw.Close() } else if cmd == "STAT" { if len(self.group) == 0 { if len(parts) == 2 { // parameter given msgid := parts[1] // check for article if ValidMessageID(msgid) && daemon.database.HasArticleLocal(msgid) { // valid message id var n int64 n, err = daemon.database.GetNNTPIDForMessageID(self.group, msgid) // exists conn.PrintfLine("223 %d %s", n, msgid) err = nil } else { conn.PrintfLine("430 No article with that message-id") } } else { conn.PrintfLine("412 No newsgroup selected") } } else if daemon.database.HasNewsgroup(self.group) { // group specified if len(parts) == 2 { // parameter specified var msgid string var n int64 n, err = strconv.ParseInt(parts[1], 10, 64) if err == nil { msgid, err = daemon.database.GetMessageIDForNNTPID(self.group, n) if err != nil { // error getting id conn.PrintfLine("500 error getting nntp article id: %s", err.Error()) return } } else { // message id msgid = parts[1] } if ValidMessageID(msgid) && daemon.database.HasArticleLocal(msgid) { conn.PrintfLine("223 %d %s", n, msgid) } else if n == 0 { // was a message id conn.PrintfLine("430 no such article") } else { // was an article number conn.PrintfLine("423 no article with that number") } } else { conn.PrintfLine("420 Current article number is invalid") } } else { conn.PrintfLine("500 invalid daemon state, got STAT with group set but we don't have that group now?") } } else { log.Println(self.name, "invalid command recv'd", cmd) conn.PrintfLine("500 Invalid command: %s", cmd) } } else { if line == "LIST" { conn.PrintfLine("215 list of newsgroups follows") // handle list command groups := daemon.database.GetAllNewsgroups() dw := conn.DotWriter() for _, group := range groups { last, first, err := daemon.database.GetLastAndFirstForGroup(group) if err == nil { io.WriteString(dw, fmt.Sprintf("%s %d %d y\r\n", group, first, last)) } else { log.Println("cannot get last/first ids for group", group, err) } } dw.Close() } else if line == "POST" { if !self.authenticated { // needs tls to work if not logged in conn.PrintfLine("440 Posting Not Allowed") } else { // handle POST command conn.PrintfLine("340 Yeeeh postit yo; end with <CR-LF>.<CR-LF>") var hdr textproto.MIMEHeader hdr, err = readMIMEHeader(conn.R) var success, gotten bool var reason string if err == nil { if getMessageID(hdr) == "" { hdr.Set("Message-ID", genMessageID(daemon.instance_name)) } msgid = getMessageID(hdr) hdr.Set("Date", timeNowStr()) ipaddr, _, _ := net.SplitHostPort(self.addr.String()) if len(ipaddr) > 0 { // inject encrypted ip for poster encaddr, err := daemon.database.GetEncAddress(ipaddr) if err == nil { hdr.Set("X-Encrypted-Ip", encaddr) } } reason, _, err = self.checkMIMEHeader(daemon, hdr) success = reason == "" && err == nil if success { r := bufio.NewReader(conn.DotReader()) reference := hdr.Get("References") newsgroup := hdr.Get("Newsgroups") if reference != "" && ValidMessageID(reference) { if !daemon.store.HasArticle(reference) && !daemon.database.IsExpired(reference) { log.Println(self.name, "got reply to", reference, "but we don't have it") go daemon.askForArticle(ArticleEntry{reference, newsgroup}) } } else if reference != "" { // bad message id reason = "cannot reply with invalid reference, maybe you are replying to a reply?" success = false } if success && daemon.database.HasNewsgroup(newsgroup) { err = self.storeMessage(daemon, hdr, r) } } } if success && gotten && err == nil { // all gud conn.PrintfLine("240 We got it, thnkxbai") } else { // failed posting if err != nil { log.Println(self.name, "failed nntp POST", err) } conn.PrintfLine("441 Posting Failed %s", reason) } } } else { conn.PrintfLine("500 wut?") } } } return }
// run the mainloop for this connection // stream if true means they support streaming mode // reader if true means they support reader mode func (self *nntpConnection) runConnection(daemon *NNTPDaemon, inbound, stream, reader, use_tls bool, preferMode string, nconn net.Conn, conf *FeedConfig) { self.addr = nconn.RemoteAddr() var err error var line string var success bool var conn *textproto.Conn if (conf != nil && !conf.tls_off) && use_tls && daemon.CanTLS() && !inbound { log.Println(self.name, "STARTTLS with", self.hostname) _conn, state, err := SendStartTLS(nconn, daemon.GetTLSConfig(self.hostname)) if err == nil { // we upgraded conn = _conn self.authenticated = state.HandshakeComplete log.Println(self.name, "tls auth", self.authenticated) } else { log.Println(self.name, err) // we didn't upgrade, fall back conn = textproto.NewConn(nconn) } } else { // we are authenticated if we are don't need tls conn = textproto.NewConn(nconn) } if !inbound { if preferMode == "stream" { // try outbound streaming if stream { success, err = self.modeSwitch("STREAM", conn) if success { self.mode = "STREAM" // start outbound streaming in background go self.startStreaming(daemon, reader, conn) } } } else if reader { // try reader mode success, err = self.modeSwitch("READER", conn) if success { self.mode = "READER" self.startReader(daemon, conn) return } } if success { log.Println(self.name, "mode set to", self.mode) } else { // bullshit // we can't do anything so we quit log.Println(self.name, "can't stream or read, wtf?") conn.PrintfLine("QUIT") conn.Close() return } } for err == nil { line, err = conn.ReadLine() if self.mode == "" { if inbound { if len(line) == 0 { conn.Close() return } else if line == "QUIT" { conn.PrintfLine("205 bai") conn.Close() return } parts := strings.Split(line, " ") cmd := parts[0] if cmd == "STARTTLS" { _conn, state, err := HandleStartTLS(nconn, daemon.GetOurTLSConfig()) if err == nil { // we are now tls conn = _conn self.tls_state = state self.authenticated = state.HandshakeComplete log.Println(self.name, "TLS initiated", self.authenticated) } else { log.Println("STARTTLS failed:", err) } } else if cmd == "CAPABILITIES" { // write capabilities conn.PrintfLine("101 i support to the following:") dw := conn.DotWriter() caps := []string{"VERSION 2", "READER", "STREAMING", "IMPLEMENTATION srndv2", "POST", "IHAVE", "AUTHINFO"} if daemon.CanTLS() { caps = append(caps, "STARTTLS") } for _, cap := range caps { io.WriteString(dw, cap) io.WriteString(dw, "\n") } dw.Close() log.Println(self.name, "sent Capabilities") } else if cmd == "MODE" { if len(parts) == 2 { mode := strings.ToUpper(parts[1]) if mode == "READER" { // set reader mode self.mode = "READER" // we'll allow posting for reader conn.PrintfLine("200 Posting is Permitted awee yeh") } else if mode == "STREAM" { if !self.authenticated { conn.PrintfLine("483 Streaming Denied") } else { // set streaming mode conn.PrintfLine("203 Stream it brah") self.mode = "STREAM" log.Println(self.name, "streaming enabled") go self.startStreaming(daemon, reader, conn) } } } } else { // handle a it as a command, we don't have a mode set parts := strings.Split(line, " ") cmd := parts[0] if cmd == "STARTTLS" { _conn, state, err := HandleStartTLS(nconn, daemon.GetOurTLSConfig()) if err == nil { // we are now tls conn = _conn self.tls_state = state self.authenticated = state.HandshakeComplete log.Println("TLS initiated") } else { log.Println("STARTTLS failed:", err) nconn.Close() return } } var code64 int64 code64, err = strconv.ParseInt(parts[0], 10, 32) if err == nil { err = self.handleLine(daemon, int(code64), line[4:], conn) } else { err = self.handleLine(daemon, 0, line, conn) } } } } else { if err == nil { parts := strings.Split(line, " ") var code64 int64 code64, err = strconv.ParseInt(parts[0], 10, 32) if err == nil { err = self.handleLine(daemon, int(code64), line[4:], conn) } else { err = self.handleLine(daemon, 0, line, conn) } } } } if err != io.EOF { log.Println(self.name, "got error", err) if !inbound && conn != nil { // send quit on outbound conn.PrintfLine("QUIT") } } nconn.Close() }