// PostUpdateNode removes a Node of a given IP from the database and // re-adds it with the supplied information. It is the equivalent of // removing a Node from the database, then invoking PostNode() with // its information, with the exception that it does not send a // verification email, and requires that the request be sent by the // Node that is being update. func (*Api) PostUpdateNode(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 a very sensitive endpoint. RequireToken(ctx) // Retrieve the given IP address, check that it's sane, and check // that it exists in the *local* database. 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, err := Db.GetNode(ip) if err != nil { ctx.Error = jas.NewInternalError(err.Error()) return } if node == nil || len(node.OwnerEmail) == 0 { ctx.Error = jas.NewRequestError("no matching local node") return } // Check to make sure that the Node is the one sending the // address, or an admin. If not, return an error. if !net.IP(ip).Equal(net.ParseIP(ctx.RemoteAddr)) && !IsAdmin(ctx.Request) { ctx.Error = jas.NewRequestError( RemoteAddressDoesNotMatchError.Error()) return } node.Addr = ip node.Latitude = ctx.RequireFloat("latitude") node.Longitude = ctx.RequireFloat("longitude") node.OwnerName = html.EscapeString(ctx.RequireString("name")) node.Contact, err = ctx.FindString("contact") if err != nil { ctx.Error = jas.NewRequestError("someError") return } node.Contact = html.EscapeString(node.Contact) node.Details, err = ctx.FindString("details") if err != nil { ctx.Error = jas.NewRequestError("someError") return } 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) // Note that we do not perform a verification step here, or send // an email. Because the Node was already verified once, we can // assume that it remains usable. // Update the Node in the database, replacing the one of matching // IP. err = Db.UpdateNode(node) if err != nil { ctx.Error = jas.NewInternalError(err) l.Errf("Error updating %q: %s", node.Addr, err) return } // If we reach this point, all was successful. ctx.Data = "successful" }
// 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) } }