// given a tripcode after the # // make a seed byteslice func parseTripcodeSecret(str string) []byte { // try decoding hex raw := unhex(str) keylen := nacl.CryptoSignSeedLen() if raw == nil || len(raw) != keylen { // treat this as a "regular" chan tripcode // decode as bytes then pad the rest with 0s if it doesn't fit raw = make([]byte, keylen) str_bytes := []byte(str) if len(str_bytes) > keylen { copy(raw, str_bytes[:keylen]) } else { copy(raw, str_bytes) } } return raw }
// turn a post request into an nntp article write it to temp dir and tell daemon func (self *httpFrontend) handle_postRequest(pr *postRequest, b bannedFunc, e errorFunc, s successFunc, createGroup bool) { var err error if len(pr.Attachments) > self.attachmentLimit { err = errors.New("too many attachments") e(err) return } nntp := new(nntpArticle) defer nntp.Reset() var banned bool nntp.headers = make(ArticleHeaders) address := pr.IpAddress // check for banned if len(address) > 0 { banned, err = self.daemon.database.CheckIPBanned(address) if err == nil { if banned { b() return } } else { e(err) return } } if len(address) == 0 { address = "Tor" } if !strings.HasPrefix(address, "127.") { // set the ip address of the poster to be put into article headers // if we cannot determine it, i.e. we are on Tor/i2p, this value is not set if address == "Tor" { nntp.headers.Set("X-Tor-Poster", "1") } else { address, err = self.daemon.database.GetEncAddress(address) if err == nil { nntp.headers.Set("X-Encrypted-IP", address) } else { e(err) return } // TODO: add x-tor-poster header for tor exits } } // always lower case newsgroups board := strings.ToLower(pr.Group) // post fail message banned, err = self.daemon.database.NewsgroupBanned(board) if banned { e(errors.New("newsgroup banned ")) return } if err != nil { e(err) } if !createGroup && !self.daemon.database.HasNewsgroup(board) { e(errors.New("we don't have this newsgroup " + board)) return } // if we don't have an address for the poster try checking for i2p httpd headers if len(pr.Destination) == i2pDestHashLen() { nntp.headers.Set("X-I2P-DestHash", pr.Destination) } ref := pr.Reference if len(ref) > 0 { if ValidMessageID(ref) { if self.daemon.database.HasArticleLocal(ref) { nntp.headers.Set("References", ref) } else { e(errors.New("article referenced not locally available")) return } } else { e(errors.New("invalid reference")) return } } // set newsgroup nntp.headers.Set("Newsgroups", pr.Group) // check message size if len(pr.Attachments) == 0 && len(pr.Message) == 0 { e(errors.New("no message")) return } // TODO: make configurable if len(pr.Message) > 1024*1024 { e(errors.New("your message is too big")) return } if len(pr.Frontend) == 0 { // :-DDD pr.Frontend = "mongo.db.is.web.scale" } else if len(pr.Frontend) > 128 { e(errors.New("frontend name is too long")) return } subject := pr.Subject // set subject if len(subject) == 0 { subject = "None" } else if len(subject) > 256 { // subject too big e(errors.New("Subject is too long")) return } nntp.headers.Set("Subject", subject) if isSage(subject) { nntp.headers.Set("X-Sage", "1") } name := pr.Name var tripcode_privkey []byte // set name if len(name) == 0 { name = "Anonymous" } else { idx := strings.Index(name, "#") // tripcode if idx >= 0 { tripcode_privkey = parseTripcodeSecret(name[idx+1:]) name = strings.Trim(name[:idx], "\t ") if name == "" { name = "Anonymous" } } } if len(name) > 128 { // name too long e(errors.New("name too long")) return } msgid := genMessageID(pr.Frontend) // roll until dubs if desired for pr.Dubs && !MessageIDWillDoDubs(msgid) { msgid = genMessageID(pr.Frontend) } nntp.headers.Set("From", nntpSanitize(fmt.Sprintf("%s <poster@%s>", name, pr.Frontend))) nntp.headers.Set("Message-ID", msgid) // set message nntp.message = createPlaintextAttachment([]byte(pr.Message)) // set date nntp.headers.Set("Date", timeNowStr()) // append path from frontend nntp.AppendPath(pr.Frontend) // add extra headers if needed if pr.ExtraHeaders != nil { for name, val := range pr.ExtraHeaders { // don't overwrite existing headers if nntp.headers.Get(name, "") == "" { nntp.headers.Set(name, val) } } } if self.attachments { var delfiles []string for _, att := range pr.Attachments { // add attachment if len(att.Filedata) > 0 { a := createAttachment(att.Filetype, att.Filename, strings.NewReader(att.Filedata)) nntp.Attach(a) err = a.Save(self.daemon.store.AttachmentDir()) if err == nil { delfiles = append(delfiles, a.Filepath()) // check if we need to thumbnail it if !CheckFile(self.daemon.store.ThumbnailFilepath(a.Filepath())) { err = self.daemon.store.GenerateThumbnail(a.Filepath()) } if err == nil { delfiles = append(delfiles, self.daemon.store.ThumbnailFilepath(a.Filepath())) } } if err != nil { break } } } if err != nil { // nuke files for _, fname := range delfiles { DelFile(fname) } e(err) return } } // pack it before sending so that the article is well formed nntp.Pack() // sign if needed if len(tripcode_privkey) == nacl.CryptoSignSeedLen() { err = self.daemon.store.RegisterPost(nntp) if err != nil { e(err) return } nntp, err = signArticle(nntp, tripcode_privkey) if err != nil { // error signing e(err) return } if err == nil { err = self.daemon.store.RegisterSigned(nntp.MessageID(), nntp.Pubkey()) } } else { err = self.daemon.store.RegisterPost(nntp) } if err != nil { e(err) return } // have daemon sign message // DON'T Wrap sign yet // wrapped := self.daemon.WrapSign(nntp) // save it f := self.daemon.store.CreateFile(nntp.MessageID()) if f == nil { e(errors.New("failed to store article")) return } else { err = nntp.WriteTo(f) f.Close() if err == nil { self.daemon.loadFromInfeed(nntp.MessageID()) s(nntp) return } // clean up DelFile(self.daemon.store.GetFilename(nntp.MessageID())) e(err) } }