зеркало из https://github.com/mozilla/mig.git
307 строки
8.6 KiB
Go
307 строки
8.6 KiB
Go
// 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
|
|
}
|