Newer
Older
zardoz / classifier.go
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"regexp"
	"strings"
)

//Zexpressions is the set of regexp being used by zardoz
var Zexpressions = []string{
	`[[:alpha:]]{4,32}`,                              // alpha digit token
	`[ ]([A-Za-z0-9-_]{4,}\.)+\w+`,                   // domain name
	`[ ]/[A-Za-z0-9-_/.]{4,}[ ]`,                     // URI path (also partial)
	`[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`, // IP address
	`[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}`, // UUID
}

func passAndLearn(resp *http.Response) error {

	ProxyFlow.response = resp
	ProxyFlow.seniority++
	req := ProxyFlow.request

	switch {
	case isAuth(resp):
		log.Println("401: We don't want to store credentials")
	case isError(resp):
		buf := bytes.NewBufferString(BlockMessage)
		resp.Body = ioutil.NopCloser(buf)
		resp.Status = "403 Forbidden"
		resp.StatusCode = 403
		resp.Header["Content-Length"] = []string{fmt.Sprint(buf.Len())}
		resp.Header.Set("Content-Encoding", "none")
		resp.Header.Set("Cache-Control", "no-cache, no-store")
		log.Println("Filing inside bad class")
		feedRequest(req, "BAD")
		ControPlane.StatsTokens <- "DOWNGRADE"
	case isSuccess(resp):
		log.Println("Filing inside Good Class: ", resp.StatusCode)
		feedRequest(req, "GOOD")
	}

	return nil
}

func blockAndlearn(resp *http.Response) error {

	ProxyFlow.response = resp
	ProxyFlow.seniority++
	req := ProxyFlow.request

	buf := bytes.NewBufferString(BlockMessage)
	resp.Body = ioutil.NopCloser(buf)
	resp.Status = "403 Forbidden"
	resp.StatusCode = 403
	resp.Header["Content-Length"] = []string{fmt.Sprint(buf.Len())}
	resp.Header.Set("Content-Encoding", "none")
	resp.Header.Set("Cache-Control", "no-cache, no-store")

	switch {
	case isAuth(resp):
		log.Println("401: We don't want to store credentials")
	case isError(resp):
		log.Println("Filing inside bad class")
		feedRequest(req, "BAD")
	case isSuccess(resp):
		log.Println("Filing inside Good Class: ", resp.StatusCode)
		ControPlane.StatsTokens <- "UPGRADED"
		feedRequest(req, "GOOD")
	}

	return nil

}

func sanitizeHeaders(s string) string {

	var collect []string
	ss := strings.ToLower(s)

	for _, zregexp := range Zexpressions {

		re, rerr := regexp.Compile(zregexp)
		if rerr != nil {
			log.Println("Error Compiling regular expression: ", zregexp)
		}

		matched := re.FindAllString(ss, -1)
		if matched == nil {
			matched = []string{"null"}
		}

		collect = append(collect, matched...)

	}

	uMatched := Unique(collect)

	log.Println("Matched: ", uMatched)

	return strings.Join(uMatched, " ")

}

func feedRequest(req *http.Request, class string) {

	feed := formatRequest(req)

	if class == "BAD" {

		log.Println("Feeding BAD token: ", feed)

		ControPlane.BadTokens <- sanitizeHeaders(feed)

	}

	if class == "GOOD" {

		log.Println("Feeding GOOD Token:", feed)

		ControPlane.GoodTokens <- sanitizeHeaders(feed)

	}

}

func formatRequest(req *http.Request) string {

	requestDump, err := httputil.DumpRequest(req, false)
	if err != nil {
		fmt.Println(err)
	}

	return fmt.Sprintf("%s\n", requestDump)

}

//Unique returns unique elements in the string
func Unique(slice []string) []string {
	// create a map with all the values as key
	uniqMap := make(map[string]struct{})
	for _, v := range slice {
		uniqMap[v] = struct{}{}
	}

	// turn the map keys into a slice
	uniqSlice := make([]string, 0, len(uniqMap))
	for v := range uniqMap {
		uniqSlice = append(uniqSlice, v)
	}
	return uniqSlice
}

func isSuccess(resp *http.Response) bool {
	return resp.StatusCode <= 299
}

func isAuth(resp *http.Response) bool {
	return resp.StatusCode == 401
}

func isError(resp *http.Response) bool {
	return resp.StatusCode >= 400 && resp.StatusCode != 401
}