Example #1
0
File: nntp.go Project: 4cdn/srndv2
// 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
}
Example #2
0
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
}
Example #3
0
// 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
}
Example #4
0
File: nntp.go Project: ZiRo-/srndv2
// 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
}
Example #5
0
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
}
Example #6
0
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
}
Example #7
0
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
}
Example #8
0
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
}
Example #9
0
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
}
Example #10
0
// 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()
}
Example #11
0
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
}
Example #12
0
File: nntp.go Project: 4cdn/srndv2
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
}
Example #13
0
File: nntp.go Project: ZiRo-/srndv2
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
}
Example #14
0
File: nntp.go Project: ZiRo-/srndv2
// 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()
}