// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // // Contributor: Julien Vehent jvehent@mozilla.com [:ulfr] package main import ( "fmt" "net/http" "net/url" "regexp" "strconv" "time" "github.com/jvehent/cljs" "github.com/mozilla/mig" migdbsearch "github.com/mozilla/mig/database/search" ) type pagination struct { Limit float64 `json:"limit"` Offset float64 `json:"offset"` Next string `json:"next"` } // search runs searches func search(respWriter http.ResponseWriter, request *http.Request) { var ( err error p migdbsearch.Parameters filterFound bool ) opid := getOpID(request) loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String()) resource := cljs.New(loc) defer func() { if e := recover(); e != nil { // on panic, log and return error to client, including the search parameters ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err() resource.AddItem(cljs.Item{ Href: loc, Data: []cljs.Data{{Name: "search parameters", Value: p}}, }) resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)}) if fmt.Sprintf("%v", e) == "no results found" { respond(http.StatusNotFound, resource, respWriter, request) } else { respond(http.StatusInternalServerError, resource, respWriter, request) } } ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving search()"}.Debug() }() p, filterFound, err = parseSearchParameters(request.URL.Query()) if err != nil { panic(err) } // run the search based on the type var results interface{} switch p.Type { case "action": results, err = ctx.DB.SearchActions(p) case "agent": if p.Target != "" { results, err = ctx.DB.ActiveAgentsByTarget(p.Target) } else { results, err = ctx.DB.SearchAgents(p) } case "command": results, err = ctx.DB.SearchCommands(p, filterFound) case "investigator": results, err = ctx.DB.SearchInvestigators(p) case "manifest": results, err = ctx.DB.SearchManifests(p) case "loader": results, err = ctx.DB.SearchLoaders(p) default: panic("search type is invalid") } if err != nil { panic(err) } switch p.Type { case "action": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d actions", len(results.([]mig.Action)))} if len(results.([]mig.Action)) == 0 { panic("no results found") } for i, r := range results.([]mig.Action) { err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s%s/action?actionid=%.0f", ctx.Server.Host, ctx.Server.BaseRoute, r.ID), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } case "agent": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d agents", len(results.([]mig.Agent)))} if len(results.([]mig.Agent)) == 0 { panic("no results found") } for i, r := range results.([]mig.Agent) { err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s%s/agent?agentid=%.0f", ctx.Server.Host, ctx.Server.BaseRoute, r.ID), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } case "command": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d commands", len(results.([]mig.Command)))} if len(results.([]mig.Command)) == 0 { panic("no results found") } for i, r := range results.([]mig.Command) { err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s%s/command?commandid=%.0f", ctx.Server.Host, ctx.Server.BaseRoute, r.ID), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } case "investigator": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d investigators", len(results.([]mig.Investigator)))} if len(results.([]mig.Investigator)) == 0 { panic("no results found") } for i, r := range results.([]mig.Investigator) { err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s%s/investigator?investigatorid=%.0f", ctx.Server.Host, ctx.Server.BaseRoute, r.ID), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } case "manifest": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d manifests", len(results.([]mig.ManifestRecord)))} if len(results.([]mig.ManifestRecord)) == 0 { panic("no results found") } for i, r := range results.([]mig.ManifestRecord) { err = resource.AddItem(cljs.Item{ Href: fmt.Sprintf("%s%s/manifest?manifestid=%.0f", ctx.Server.Host, ctx.Server.BaseRoute, r.ID), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } case "loader": ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d loaders", len(results.([]mig.LoaderEntry)))} if len(results.([]mig.LoaderEntry)) == 0 { panic("no results found") } for i, r := range results.([]mig.LoaderEntry) { err = resource.AddItem(cljs.Item{ // XXX This should be an Href to fetch the entry Href: fmt.Sprintf("%s%s", ctx.Server.Host, ctx.Server.BaseRoute), Data: []cljs.Data{{Name: p.Type, Value: r}}, }) if err != nil { panic(err) } if float64(i) > p.Limit { break } } } // if needed, add pagination info if p.Offset > 0 { nextP := p nextP.Offset += p.Limit page := pagination{ Limit: p.Limit, Offset: p.Offset, Next: ctx.Server.BaseURL + "/search?" + nextP.String(), } err = resource.AddItem(cljs.Item{ Href: loc, Data: []cljs.Data{{Name: "pagination", Value: page}}, }) if err != nil { panic(err) } } // add search parameters at the end of the response err = resource.AddItem(cljs.Item{ Href: loc, Data: []cljs.Data{{Name: "search parameters", Value: p}}, }) if err != nil { panic(err) } respond(http.StatusOK, resource, respWriter, request) } // truere is a case insensitive regex that matches the string 'true' var truere = regexp.MustCompile("(?i)^true$") // false is a case insensitive regex that matches the string 'false' var falsere = regexp.MustCompile("(?i)^false$") // parseSearchParameters transforms a query string into search parameters in the migdb format func parseSearchParameters(qp url.Values) (p migdbsearch.Parameters, filterFound bool, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("parseSearchParameters()-> %v", e) } }() p = migdbsearch.NewParameters() for queryParams, _ := range qp { switch queryParams { case "actionname": p.ActionName = qp["actionname"][0] case "actionid": p.ActionID = qp["actionid"][0] case "after": p.After, err = time.Parse(time.RFC3339, qp["after"][0]) if err != nil { panic("after date not in RFC3339 format") } case "agentid": p.AgentID = qp["agentid"][0] case "agentname": p.AgentName = qp["agentname"][0] case "agentversion": p.AgentVersion = qp["agentversion"][0] case "before": p.Before, err = time.Parse(time.RFC3339, qp["before"][0]) if err != nil { panic("before date not in RFC3339 format") } case "commandid": p.CommandID = qp["commandid"][0] case "foundanything": if truere.MatchString(qp["foundanything"][0]) { p.FoundAnything = true } else if falsere.MatchString(qp["foundanything"][0]) { p.FoundAnything = false } else { panic("foundanything parameter must be true or false") } filterFound = true case "investigatorid": p.InvestigatorID = qp["investigatorid"][0] case "investigatorname": p.InvestigatorName = qp["investigatorname"][0] case "limit": p.Limit, err = strconv.ParseFloat(qp["limit"][0], 64) if err != nil { panic("invalid limit parameter") } case "loadername": p.LoaderName = qp["loadername"][0] case "loaderid": p.LoaderID = qp["loaderid"][0] case "manifestname": p.ManifestName = qp["manifestname"][0] case "manifestid": p.ManifestID = qp["manifestid"][0] case "offset": p.Offset, err = strconv.ParseFloat(qp["offset"][0], 64) if err != nil { panic("invalid offset parameter") } case "status": p.Status = qp["status"][0] case "target": p.Target = qp["target"][0] case "threatfamily": p.ThreatFamily = qp["threatfamily"][0] case "type": p.Type = qp["type"][0] } } return }