Esempio n. 1
0
// 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
}
Esempio n. 2
0
// handle new post via http request for a board
func (self httpFrontend) handle_postform(wr http.ResponseWriter, r *http.Request, board string) {

	// always lower case newsgroups
	board = strings.ToLower(board)

	// post fail message
	post_fail := ""

	// post message
	msg := ""

	// the nntp message
	var nntp nntpArticle
	nntp.headers = make(ArticleHeaders)

	// tripcode private key
	var tripcode_privkey []byte

	// encrypt IP Addresses
	// when a post is recv'd from a frontend, the remote address is given its own symetric key that the local srnd uses to encrypt the address with, for privacy
	// when a mod event is fired, it includes the encrypted IP address and the symetric key that frontend used to encrypt it, thus allowing others to determine the IP address
	// each stnf will optinally comply with the mod event, banning the address from being able to post from that frontend
	// this will be done eventually but for now that requires too much infrastrucutre, let's go with regular IP Addresses for now.

	// get the "real" ip address from the request

	address, _, err := net.SplitHostPort(r.RemoteAddr)
	// TODO: have in config upstream proxy ip and check for that
	if strings.HasPrefix(address, "127.") {
		// if it's loopback check headers for reverse proxy headers
		// TODO: make sure this isn't a tor user being sneaky
		address = getRealIP(r.Header.Get("X-Real-IP"))
	}

	// check for banned
	if len(address) > 0 {
		banned, err := self.daemon.database.CheckIPBanned(address)
		if err == nil {
			if banned {
				wr.WriteHeader(403)
				// TODO: ban messages
				io.WriteString(wr, "nigguh u banned.")
				return
			}
		} else {
			wr.WriteHeader(500)
			io.WriteString(wr, "error checking for ban: ")
			io.WriteString(wr, err.Error())
			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)
			nntp.headers.Set("X-Encrypted-IP", address)
			// TODO: add x-tor-poster header for tor exits
		}
	}

	// if we don't have an address for the poster try checking for i2p httpd headers
	address = r.Header.Get("X-I2P-DestHash")
	// TODO: make sure this isn't a Tor user being sneaky
	if len(address) > 0 {
		nntp.headers.Set("X-I2P-DestHash", address)
	}

	// set newsgroup
	nntp.headers.Set("Newsgroups", board)

	// redirect url
	url := ""
	// mime part handler
	var part_buff bytes.Buffer
	mp_reader, err := r.MultipartReader()
	if err != nil {
		errmsg := fmt.Sprintf("httpfrontend post handler parse multipart POST failed: %s", err)
		log.Println(errmsg)
		wr.WriteHeader(500)
		io.WriteString(wr, errmsg)
		return
	}

	var subject, name string

	for {
		part, err := mp_reader.NextPart()
		if err == nil {
			// get the name of the part
			partname := part.FormName()

			// read part for attachment
			if partname == "attachment" && self.attachments {
				log.Println("attaching file...")
				att := readAttachmentFromMimePart(part)
				nntp = nntp.Attach(att).(nntpArticle)
				continue
			}

			io.Copy(&part_buff, part)

			// check for values we want
			if partname == "subject" {
				subject = part_buff.String()
			} else if partname == "name" {
				name = part_buff.String()
			} else if partname == "message" {
				msg = part_buff.String()
			} else if partname == "reference" {
				ref := part_buff.String()
				if len(ref) == 0 {
					url = fmt.Sprintf("%s.html", board)
				} else if ValidMessageID(ref) {
					if self.daemon.database.HasArticleLocal(ref) {
						nntp.headers.Set("References", ref)
						url = fmt.Sprintf("thread-%s.html", ShortHashMessageID(ref))
					} else {
						// no such article
						url = fmt.Sprintf("%s.html", board)
						post_fail += "we don't have "
						post_fail += ref
						post_fail += "locally, can't reply. "
					}
				} else {
					post_fail += "invalid reference: "
					post_fail += ref
					post_fail += ", not posting. "
				}

			} else if partname == "captcha" {
				captcha_solution := part_buff.String()
				s, err := self.store.Get(r, self.name)
				captcha_id, ok := s.Values["captcha_id"]
				if err == nil && ok {
					if captcha.VerifyString(captcha_id.(string), captcha_solution) {
						// captcha is valid
					} else {
						// captcha is not valid
						post_fail += "failed captcha. "
					}
				} else {
					// captcha has no cookies
					post_fail += "enable cookies. "
				}
			}
			// we done
			// reset buffer for reading parts
			part_buff.Reset()
			// close our part
			part.Close()
		} else {
			if err != io.EOF {
				errmsg := fmt.Sprintf("httpfrontend post handler error reading multipart: %s", err)
				log.Println(errmsg)
				wr.WriteHeader(500)
				io.WriteString(wr, errmsg)
				return
			}
			break
		}
	}

	// make error template param
	resp_map := make(map[string]string)
	resp_map["prefix"] = self.prefix
	// set redirect url
	if len(url) > 0 {
		// if we explicitly know the url use that
		resp_map["redirect_url"] = self.prefix + url
	} else {
		// if our referer is saying we are from /new/ page use that
		// otherwise use prefix
		if strings.HasSuffix(r.Referer(), self.prefix+"new/") {
			resp_map["redirect_url"] = self.prefix + "new/"
		} else {
			resp_map["redirect_url"] = self.prefix
		}
	}

	if len(nntp.attachments) == 0 && len(msg) == 0 {
		post_fail += "no message. "
	}

	if len(post_fail) > 0 {
		wr.WriteHeader(200)
		resp_map["reason"] = post_fail
		io.WriteString(wr, template.renderTemplate("post_fail.mustache", resp_map))
		return
	}

	// set subject
	if len(subject) == 0 {
		subject = "None"
	}
	nntp.headers.Set("Subject", subject)
	if isSage(subject) {
		nntp.headers.Set("X-Sage", "1")
	}

	// 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"
			}
		}
	}
	nntp.headers.Set("From", nntpSanitize(fmt.Sprintf("%s <anon@%s>", name, self.name)))
	nntp.headers.Set("Message-ID", genMessageID(self.name))

	// set message
	nntp.message = createPlaintextAttachment(msg)
	// set date
	nntp.headers.Set("Date", timeNowStr())
	// append path from frontend
	nntp.AppendPath(self.name)
	// send message off to daemon
	log.Printf("uploaded %d attachments", len(nntp.Attachments()))
	nntp.Pack()

	// sign if needed
	if len(tripcode_privkey) == nacl.CryptoSignSeedLen() {
		nntp, err = signArticle(nntp, tripcode_privkey)
		if err != nil {
			// wtf? error!?
			log.Println("error signing", err)
			wr.WriteHeader(500)
			io.WriteString(wr, err.Error())
			return
		}
	}
	// XXX: write it temp instead
	// self.postchan <- nntp
	f := self.daemon.store.CreateTempFile(nntp.MessageID())
	if f != nil {
		nntp.WriteTo(f, "\n")
		f.Close()
	}
	self.daemon.infeed_load <- nntp.MessageID()

	// send success reply
	wr.WriteHeader(200)
	// determine the root post so we can redirect to the thread for it
	msg_id := nntp.headers.Get("References", nntp.MessageID())
	// render response as success
	url = fmt.Sprintf("%sthread-%s.html", self.prefix, ShortHashMessageID(msg_id))
	io.WriteString(wr, template.renderTemplate("post_success.mustache", map[string]string{"prefix": self.prefix, "message_id": nntp.MessageID(), "redirect_url": url}))
}