func checkfile(w http.ResponseWriter, r *http.Request) { req := api.CheckFileRequest{} err := readRequest(r, &req) if err != nil { sendError(w, "Could not parse request") return } accessKey, _ := mailbox.FindKeyByName(req.AccessKeyName) if accessKey == nil { sendError(w, "Access key invalid") return } path := filepath.Join(filesPath(), req.MD5) resp := api.SimpleResponse{} if _, err := os.Stat(path); os.IsNotExist(err) { resp.Success = false } else { resp.Success = true } resp.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, resp) }
// deleteMessage is used by clients to mark messages as processed. The message // remains in the database, but is marked deleted and will no longer be // presented to polling clients. func deleteMessage(w http.ResponseWriter, r *http.Request) { var request api.DeleteMessageRequest err := readRequest(r, &request) if err != nil { sendError(w, "Could not parse json request") return } accessKey, _ := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key invalid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Signature is invalid") return } err = mailbox.DeleteMessage(request.Message) if err != nil { sendError(w, "Could not delete message") return } resp := api.DeleteMessageResponse{Message: request.Message} resp.Sign(accessKey.Name, accessKey.Secret) log.Infof("Message %s deleted", request.Message) writeResponse(&w, resp) }
func getAsset(w http.ResponseWriter, r *http.Request) { var ( request = api.GetAssetRequest{} ) err := readRequest(r, &request) if err != nil { sendError(w, err.Error()) return } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key is invalid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } fp := filepath.Join(filesPath(), request.MD5) if _, err := os.Stat(fp); os.IsNotExist(err) { sendError(w, "Asset not found on server") return } log.Infof("Serving asset to %s", accessKey.Name) http.ServeFile(w, r, fp) }
// deregister is used by administrative clients to remove mailboxes. It deletes // the mailbox and all messages from the database. func deregister(w http.ResponseWriter, r *http.Request) { var request api.RegisterRequest err := readRequest(r, &request) if err != nil { sendError(w, err.Error()) return } accessKey, _ := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key is invalid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to deregister mailboxes") return } err = mailbox.Deregister(request.Mailbox) if err != nil { sendError(w, err.Error()) return } resp := api.SimpleResponse{Success: true} resp.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, resp) }
func TestAcessCmd(t *testing.T) { accessCmd.Run(accessCmd, []string{"NAME"}) key, err := mailbox.FindKeyByName("NAME") if err != nil { t.Fatal(err) } if key == nil { t.Fatal("Key not created") } }
// register is used by administrative clients to reigster new mailboxes. func register(w http.ResponseWriter, r *http.Request) { var request api.RegisterRequest err := readRequest(r, &request) if err != nil { sendError(w, err.Error()) return } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key is invalid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to register mailboxes.") return } if mailbox.KeyExists(request.Mailbox) { sendError(w, "An access key already exists with that mailbox name") return } mb, err := mailbox.Create(request.Mailbox) if err != nil { sendError(w, err.Error()) return } mbKey := &mailbox.AccessKey{ MailboxId: mb.Id, } err = mbKey.Create() if err != nil { sendError(w, err.Error()) return } resp := api.RegisterResponse{ Mailbox: mb.Id, AccessKeyName: mbKey.Name, AccessKeySecret: mbKey.Secret, } resp.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, resp) log.Infof("Mailbox %s registered.", mb.Id) }
// clientStats reports all mailboxes and their current connection status. func clientStats(w http.ResponseWriter, r *http.Request) { var request api.SimpleRequest err := readRequest(r, &request) if err != nil { sendError(w, "Bad request") } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if err != nil || accessKey == nil { sendError(w, "Access key is invalid") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to get statistics") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } clients := []api.ClientStatus{} mbxs, err := mailbox.All() if err != nil { sendError(w, err.Error()) return } for _, mb := range mbxs { st := api.ClientStatus{ Mailbox: mb.Id, Version: mb.Version, Host: mb.Host, LastSeen: mb.LastSeen, } if _, ok := pollingChannels[mb.Id]; ok { st.Online = true } else { st.Online = false } clients = append(clients, st) } response := api.ClientStatusResponse{Clients: clients} response.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, response) }
// getMessage is used by clients to poll for mailbox messages. This method will // search the database for messages for the given mailbox. If the mailbox is // empty and long_polling is enabled it will create a channel and add it to // pollingChannels. It will then wait for a message to be pushed to that // channel. It will then continue to output that message. Messages are pushed by // the putMessage method. func getMessage(w http.ResponseWriter, r *http.Request) { if !EnableLongPolling { time.Sleep(ThrottleDelay) } var request api.GetMessageRequest err := readRequest(r, &request) if err != nil { sendError(w, err.Error()) return } mb, err := mailbox.Find(request.Mailbox) if err != nil { sendError(w, err.Error()) return } if mb == nil { log.Warnf("Could not find a mailbox named '%s'", request.Mailbox) sendError(w, fmt.Sprintf("Mailbox %s not found.", request.Mailbox)) return } log.Debugf("Message request from %s", mb.Id) accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { log.Warnf("Could not find an access key named '%s'", request.AccessKeyName) sendError(w, "Access key is invalid") return } if !request.Validate(accessKey.Secret) { log.Warnf(fmt.Sprintf("Signature for %s invalid", mb.Id)) sendError(w, "Signature is invalid") return } if !accessKey.CanGet(mb) { sendError(w, "Not allowed to get messages from this mailbox.") return } if err := mb.Checkin(r.RemoteAddr, request.Version); err != nil { sendError(w, err.Error()) return } msg, err := mb.GetMessage() if err != nil { sendError(w, err.Error()) log.Errorf("Error retrieving messages: %s", err.Error()) return } if EnableLongPolling == true && msg == nil { if _, ok := pollingChannels[mb.Id]; ok { delete(pollingChannels, mb.Id) } // Create a channel for the client. This channel has a message pushed to it // from the putMessage function. When a message gets delivered. pollingChannels[mb.Id] = make(chan *mailbox.Message) timeout := make(chan bool, 1) // This goroutine will create a timeout to close the long polling connection // and force the client to reconnect. go func() { // Randomize the polling timeout in order to stagger client reconnects. sleepTime := rand.Intn(500) + 200 time.Sleep(time.Duration(sleepTime) * time.Second) timeout <- true }() // Wait for either a timeout or a message to be sent to a channel. select { case m := <-pollingChannels[mb.Id]: msg = m case <-timeout: response := api.GetMessageResponse{} response.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, response) delete(pollingChannels, mb.Id) return } delete(pollingChannels, mb.Id) } if msg == nil { writeResponse(&w, nil) return } dep, err := msg.GetDeployment() if err != nil { sendError(w, err.Error()) return } response := api.GetMessageResponse{ Message: msg.Id, Body: msg.Body, CreatedAt: msg.CreatedAt, ReceiveCount: msg.ReceiveCount, Deployment: msg.Deployment, Asset: dep.Asset, } response.Sign(accessKey.Name, accessKey.Secret) log.Infof("Delivering message %s to %s", response.Message, mb.Id) writeResponse(&w, response) }
// deployRespond is used by scripts to "respond" with information from the // remote system. These responses can then be reported to the admin client that // deployed the original script. func deployRespond(w http.ResponseWriter, r *http.Request) { var request api.ResponseRequest err := readRequest(r, &request) if err != nil { sendError(w, "Could not parse request") return } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key is not valid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } msg, err := mailbox.FindMessage(request.Message) if err != nil { sendError(w, err.Error()) return } if msg == nil { sendError(w, "Could not find message "+request.Message) return } mb, err := mailbox.Find(msg.Mailbox) if err != nil { sendError(w, err.Error()) return } if mb == nil { sendError(w, "Mailbox not found") return } dep, err := mailbox.FindDeployment(msg.Deployment) if err != nil { sendError(w, err.Error()) return } if dep == nil { sendError(w, "Could not find deployment "+msg.Deployment) return } if !accessKey.CanGet(mb) { sendError(w, "Not allowed to respond to deploys") return } err = dep.AddResponse(msg.Mailbox, request.Response, request.Error) if err != nil { sendError(w, err.Error()) return } response := api.SimpleResponse{Success: true} response.Sign(accessKey.Name, accessKey.Secret) log.Infof("Reponse added to %s from %s", dep.Id, msg.Mailbox) writeResponse(&w, response) }
// deployInfo is used to both report a list of recent deployments and get // details and responses on a specific deployment. func deployInfo(w http.ResponseWriter, r *http.Request) { var request api.DeploymentStatsRequest err := readRequest(r, &request) if err != nil { sendError(w, "Could not parse request") return } if request.NamePattern == "" { request.NamePattern = ".*" } if request.TokenPattern == "" { request.TokenPattern = ".*" } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if err != nil || accessKey == nil { sendError(w, "Access key is invalid") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to list deployments") return } if !request.Validate(accessKey.Secret) { sendError(w, "Signature invalid") return } response := api.DeploymentStatsResponse{} if request.Deployment == "" { log.Infof("Listing all deploys for %s", accessKey.Name) deployments, err := mailbox.ListDeployments(request.NamePattern, int(request.Count), request.TokenPattern) if err != nil { sendError(w, err.Error()) return } for _, d := range deployments { dStats, err := d.Stats() if err != nil { sendError(w, err.Error()) return } statsResp := api.DeploymentStats{ Name: d.Name, Id: d.Id, PendingCount: dStats.PendingCount, MessageCount: dStats.MessageCount, ResponseCount: dStats.ResponseCount, CreatedAt: d.DeployedAt, DeployedBy: d.DeployedBy, } response.Deployments = append(response.Deployments, statsResp) } } else { dep, err := mailbox.FindDeployment(request.Deployment) if err != nil { sendError(w, err.Error()) return } if dep == nil { sendError(w, "Deployment not found") return } dStats, err := dep.Stats() if err != nil { sendError(w, err.Error()) return } deploymentResponses, err := dep.GetResponses() if err != nil { sendError(w, err.Error()) return } statsResp := api.DeploymentStats{ Name: dep.Name, Id: dep.Id, PendingCount: dStats.PendingCount, MessageCount: dStats.MessageCount, ResponseCount: dStats.ResponseCount, CreatedAt: dep.DeployedAt, Responses: []api.DeploymentResponse{}, } for _, r := range deploymentResponses { apiR := api.DeploymentResponse{ Mailbox: r.Mailbox, Response: r.Response, RespondedAt: r.RespondedAt, IsError: r.IsError, } statsResp.Responses = append(statsResp.Responses, apiR) } response.Deployments = append(response.Deployments, statsResp) } response.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, response) }
// putMessage is used to deploy messages to mailboxes, etiher by a list of // mailboxes or a pattern. Messages are organized into a deployment and // persisted to the database. If any receiptients are currently polling the // message will be forwarded to that session. func putMessage(w http.ResponseWriter, r *http.Request) { var request api.PutMessageRequest err := readRequest(r, &request) if err != nil { sendError(w, "Could not parse request") } mailboxes := []mailbox.Mailbox{} if request.Pattern != "" { results, err := mailbox.Search(request.Pattern) if err != nil { sendError(w, err.Error()) return } for _, mb := range results { mailboxes = append(mailboxes, mb) } } for _, mbId := range request.Mailboxes { mb, err := mailbox.Find(mbId) if err != nil { sendError(w, err.Error()) } if mb == nil { sendError(w, fmt.Sprintf("Mailbox not found (%s)", mbId)) return } mailboxes = append(mailboxes, *mb) } if len(mailboxes) == 0 { sendError(w, "No mailboxes specified") return } if request.Asset != "" { assetPath := filepath.Join(filesPath(), request.Asset) if _, err := os.Stat(assetPath); os.IsNotExist(err) { sendError(w, "Asset does not exist on server") return } } mbList := []string{} // dep, err := mailbox.CreateDeployment(request.DeploymentName, request.Token, // request.Body) dep := mailbox.Deployment{ Name: request.DeploymentName, DeployedBy: request.AccessKeyName, MessageBody: request.Body, Asset: request.Asset, } err = dep.Create() if err != nil { sendError(w, err.Error()) return } accessKey, err := mailbox.FindKeyByName(request.AccessKeyName) if err != nil || accessKey == nil { sendError(w, "Access key is invalid") return } if !request.Validate(accessKey.Secret) { sendError(w, "Signature not valid") return } for _, mb := range mailboxes { if !accessKey.CanPut(&mb) { sendError(w, "Not allowed to send messages to "+mb.Id) return } var msg *mailbox.Message msg, err = dep.Deploy(&mb) mbList = append(mbList, mb.Id) if err != nil { sendError(w, err.Error()) return } if pollChannel, ok := pollingChannels[mb.Id]; ok { time.Sleep(50 * time.Millisecond) pollChannel <- msg } } resp := api.PutMessageResponse{ MessageSize: r.ContentLength, Mailboxes: mbList, Deployment: dep.Id, } resp.Sign(accessKey.Name, accessKey.Secret) log.Infof("Message received for %d mailboxes from %s", len(mbList), dep.DeployedBy) writeResponse(&w, resp) }
func acceptFile(w http.ResponseWriter, r *http.Request) { var ( request = api.UploadFileRequest{} ) defer r.Body.Close() // parse form post data err := r.ParseMultipartForm(1000000) if err != nil { sendError(w, err.Error()) return } // Get reuqest data and unmarshal reqData := r.FormValue("data") err = json.Unmarshal([]byte(reqData), &request) if err != nil { sendError(w, "Could not parse request!") return } accessKey, _ := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Invalid access key") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to upload files") return } if !request.Validate(accessKey.Secret) { sendError(w, "Invalid signature") return } // Save the posted file file, _, err := r.FormFile("file") if file != nil { defer file.Close() } if err != nil { sendError(w, err.Error()) return } path := filepath.Join(filesPath(), request.MD5) out, err := os.Create(path) if out != nil { defer out.Close() } if err != nil { sendError(w, err.Error()) return } _, err = io.Copy(out, file) if err != nil { sendError(w, err.Error()) return } fileHash, err := hashFile(path) if err != nil { sendError(w, err.Error()) return } if request.MD5 != fileHash { defer os.Remove(filepath.Join(filesPath(), request.MD5)) sendError(w, fmt.Sprintf("MD5 missmatch %s != %s", request.MD5, fileHash)) return } log.Infof("File uploaded %s", request.MD5) response := api.SimpleResponse{Success: true} response.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, response) }
// systemStats is json endpoint used to retrieve overall statistics. The token // used to request the endpoint must have write priviledges. func systemStats(w http.ResponseWriter, r *http.Request) { var ( request api.SimpleRequest memStats runtime.MemStats ) err := readRequest(r, &request) if err != nil { sendError(w, "Could not get stats") return } accessKey, _ := mailbox.FindKeyByName(request.AccessKeyName) if accessKey == nil { sendError(w, "Access key is invalid") return } if !accessKey.CanAdmin() { sendError(w, "Not allowed to get statistics") return } if !request.Validate(accessKey.Secret) { sendError(w, "Could not validate signature") return } stats, err := mailbox.Stats() if err != nil { sendError(w, err.Error()) return } dbVersion, err := mailbox.GetDBVersion() if err != nil { sendError(w, err.Error()) return } var fileCount int64 = 0 var filesSize int64 = 0 files, _ := ioutil.ReadDir(filesPath()) for _, f := range files { fileCount++ filesSize += f.Size() } runtime.ReadMemStats(&memStats) response := api.SystemStatsResponse{ TotalMailboxes: stats.MailboxCount, PendingMessages: stats.PendingMessages, MessageCount: stats.MessageCount, ConnectedClients: int64(len(pollingChannels)), DBVersion: dbVersion, CPUCount: int64(runtime.NumCPU()), Threads: int64(runtime.NumGoroutine()), MemoryAllocated: memStats.Alloc, Lookups: memStats.Lookups, NextGC: memStats.NextGC, FileStoreSize: filesSize, FileStoreCount: fileCount, } response.Sign(accessKey.Name, accessKey.Secret) writeResponse(&w, response) }