// PostMessage emails the given message to the email address owned by // the node with the given IP. It requires a correct and non-expired // CAPTCHA pair be given. func (*Api) PostMessage(ctx *jas.Context) { // Because this is a somewhat sensitive endpoint, require a token. RequireToken(ctx) // Ensure that the given CAPTCHA pair is correct. If it is not, // then return the explanation. This is bypassed if the request // comes from an admin address. if !IsAdmin(ctx.Request) { err := VerifyCAPTCHA(ctx.Request) if err != nil { ctx.Error = jas.NewRequestError(err.Error()) return } } // Next, retrieve the IP of the node the user is attempting to // contact. ip := IP(net.ParseIP(ctx.RequireStringLen(0, 40, "address"))) if ip == nil { // If the address is invalid, return that error. ctx.Error = jas.NewRequestError("addressInvalid") return } // Find the appropriate variables. If any of these are not // found, JAS will return a request error. replyto := ctx.RequireStringMatch(EmailRegexp, "from") message := ctx.RequireStringLen(0, 1000, "message") // Retrieve the appropriate node from the database. node, err := Db.GetNode(ip) if err != nil { // If we encounter an error here, it was a database error. ctx.Error = jas.NewInternalError(err) l.Err("Error getting node %q: %s", ip, err) return } else if node == nil { // If the IP wasn't found, explain that there was no node with // that IP. ctx.Error = jas.NewRequestError("address unknown") return } else if len(node.OwnerEmail) == 0 { // If there was no email on the node, that probably means that // it was cached. ctx.Error = jas.NewRequestError("address belongs to cached node") return } // Create and send an email. Log any errors. e := &Email{ To: node.OwnerEmail, From: Conf.SMTP.EmailAddress, Subject: "Message via " + Conf.Name, } e.Data = map[string]interface{}{ "ReplyTo": replyto, "Message": template.HTML(message), "Name": Conf.Name, "Link": template.HTML(Conf.Web.Hostname + Conf.Web.Prefix + "/node/" + ip.String()), "AdminContact": Conf.AdminContact, // Generate a random number for use as a boundary marker in the // multipart/alternative email. "Boundary": rand.Int31(), } err = e.Send("message.txt") if err != nil { ctx.Error = jas.NewInternalError(err) l.Errf("Error messaging %q from %q: %s", node.OwnerEmail, replyto, err) return } // Even if there is no error, log the to and from info, in case it // is abusive or spam. l.Noticef("IP %q sent a message to %q from %q", ctx.Request.RemoteAddr, node.OwnerEmail, replyto) }
// PostNode creates a *Node from the submitted form and queues it for // addition with a positive 64 bit integer as an ID. func (*Api) PostNode(ctx *jas.Context) { if Db.ReadOnly { // If the database is readonly, set that as the error and // return. ctx.Error = ReadOnlyError return } var err error // Require a token, because this is mildly sensitive. RequireToken(ctx) // Initialize the node and retrieve fields. node := new(Node) ip := IP(net.ParseIP(ctx.RequireStringLen(0, 40, "address"))) if ip == nil { // If the address is invalid, return that error. ctx.Error = jas.NewRequestError("addressInvalid") return } node.Addr = ip node.Latitude = ctx.RequireFloat("latitude") node.Longitude = ctx.RequireFloat("longitude") node.OwnerName = html.EscapeString(ctx.RequireString("name")) node.OwnerEmail = ctx.RequireStringMatch(EmailRegexp, "email") node.Contact, _ = ctx.FindString("contact") node.Contact = html.EscapeString(node.Contact) node.Details, _ = ctx.FindString("details") node.Details = html.EscapeString(node.Details) // If Contact, Details, or OwnerName are too long to fit in // the database, produce an error. if len(node.Contact) > 255 { ctx.Error = jas.NewRequestError("contactTooLong") return } if len(node.Details) > 255 { ctx.Error = jas.NewRequestError("detailsTooLong") return } if len(node.OwnerName) > 255 { ctx.Error = jas.NewRequestError("ownerNameTooLong") return } // Validate the PGP ID, if given. It can be an lowercase hex // string of length 0, 8, or 16. pgpstr, _ := ctx.FindStringMatch(PGPRegexp, "pgp") if node.PGP, err = DecodePGPID([]byte(pgpstr)); err != nil { ctx.Error = jas.NewRequestError("pgpInvalid") return } status, _ := ctx.FindPositiveInt("status") node.Status = uint32(status) // Ensure that the node is correct and usable. if err = Db.VerifyRegistrant(node); err != nil { ctx.Error = jas.NewRequestError(err.Error()) return } // TODO(DuoNoxSol): Authenticate/limit node registration. // If SMTP is missing from the config, we cannot continue. if Conf.SMTP == nil { ctx.Error = jas.NewInternalError(SMTPDisabledError) l.Err(SMTPDisabledError) return } // If SMTP verification is not explicitly disabled, and the // connecting address is not an admin, send an email. if !Conf.SMTP.VerifyDisabled && !IsAdmin(ctx.Request) { id := rand.Int63() // Pseudo-random positive int64 emailsent := true if err := SendVerificationEmail(id, node.OwnerEmail); err != nil { // If the sending of the email fails, set the internal // error and log it, then set a bool so that email can be // resent. If email continues failing to send, it will // eventually expire and be removed from the database. ctx.Error = jas.NewInternalError(err) l.Err(err) emailsent = false // Note that we do *not* return here. } // Once we have attempted to send the email, queue the node // for verification. If the email has not been sent, it will // be recorded in the database. if err := Db.QueueNode(id, emailsent, Conf.VerificationExpiration, node); err != nil { // If there is a database failure, report it as an // internal error. ctx.Error = jas.NewInternalError(err) l.Err(err) return } // If the email could be sent successfully, report // it. Otherwise, report that it is in the queue, and the // email will be resent. if emailsent { ctx.Data = "verification email sent" l.Infof("Node %q entered, waiting for verification", ip) } else { ctx.Data = "verification email will be resent" l.Infof("Node %q entered, verification email will be resent", ip) } } else { err := Db.AddNode(node) if err != nil { // If there was an error, log it and report the failure. ctx.Error = jas.NewInternalError(err) l.Err(err) return } // Add the new node to the RSS feed. AddNodeToRSS(node, time.Now()) ctx.Data = "node registered" l.Infof("Node %q registered\n", ip) } }