// read environment variables for data for http client construction
func InitializeAppSensorRestClient() {

	RestUrl, urlExists = os.LookupEnv("APPSENSOR_REST_ENGINE_URL")
	RestHeaderName, nameExists = os.LookupEnv("APPSENSOR_CLIENT_APPLICATION_ID_HEADER_NAME")
	RestHeaderValue, valueExists = os.LookupEnv("APPSENSOR_CLIENT_APPLICATION_ID_HEADER_VALUE")
	ClientIp, ipExists = os.LookupEnv("APPSENSOR_CLIENT_APPLICATION_IP_ADDRESS")

	missingFields := make([]string, 0)

	if !urlExists {
		missingFields = append(missingFields, "APPSENSOR_REST_ENGINE_URL")
	}

	if !nameExists {
		glog.Info("The APPSENSOR_CLIENT_APPLICATION_ID_HEADER_NAME env var not set, using default value")
		RestHeaderName = "X-Appsensor-Client-Application-Name"
	}

	if !valueExists {
		missingFields = append(missingFields, "APPSENSOR_CLIENT_APPLICATION_ID_HEADER_VALUE")
	}

	if !ipExists {
		missingFields = append(missingFields, "APPSENSOR_CLIENT_APPLICATION_ID_HEADER_VALUE")
	}

	if len(missingFields) > 0 {
		log.Fatal("The following environment variable(s) must be populated: [ " + strings.Join(missingFields, " , ") + " ]")
	}

	glog.Info("Rest client information configured properly.")

}
func evaluateInvalidVerbs(r *http.Request) {

	if !isValidVerb(r.Method) {
		glog.Info("Invalid HTTP verb seen, creating event.")
		go ids.AddEvent("Request", "RE2", r)
	}

}
func evaluateUnexpectedVerbs(r *http.Request) {

	if config.EnableGlobalPreflightRequests && r.Method == "OPTIONS" {
		// using OPTIONS always allowed in preflight mode
		return
	}

	if staticPaths.Exists(r.URL.Path) {
		for _, verb := range config.Resources[r.URL.Path] {
			if verb == r.Method {
				// found matching verb .. bail
				return
			}
		}

		glog.Info("Invalid verb was found for static path, creating event.")
		go ids.AddEvent("Request", "RE1", r)
		return
	}

	for _, re := range regexPaths {
		if re.MatchString(r.URL.Path) {

			for _, verb := range config.Resources["REGEX|"+r.URL.Path] {
				if verb == r.Method {
					// found matching verb .. bail
					return
				}
			}

			glog.Info("Invalid verb was found for regex path, creating event.")
			go ids.AddEvent("Request", "RE1", r)
			return
		}
	}

	if config.EvaluateUnlistedResources {
		glog.Info("No resource listing matched, creating event.")
		go ids.AddEvent("Request", "RE1", r)
	}
}
func ExpireBlocks() {
	glog.Info("Evaluating blocks for expiration.")

	var removableBlocks []string

	for _, element := range StoredBlocks.Flatten() {

		var block Block

		//glog.Info(element)
		if err := json.Unmarshal([]byte(element.(string)), &block); err != nil {
			panic(err)
		}

		t, err := unixToTime(block.Endtime)
		if err != nil {
			glog.Fatal(err)
		}

		if time.Now().After(t) {
			glog.Info("Expiring block: ", block)
			removableBlocks = append(removableBlocks, element.(string))
		} else {
			glog.Infof("Not expiring block:")
			glog.Infof("\tnow: ", time.Now())
			glog.Infof("\tt: ", t)
			glog.Infof("\tnow unix: ", time.Now().Unix())
			glog.Infof("\tt unix: ", t.Unix())
		}

	}

	if len(removableBlocks) > 0 {
		for _, removableBlock := range removableBlocks {
			StoredBlocks.Remove(removableBlock)
		}
	}

}
func PopulateExpectedVerbs(verbsYamlFile *string) {
	//var config VerbsConfig

	source, err := ioutil.ReadFile(*verbsYamlFile)
	if err != nil {
		panic(err)
	}

	err = yaml.Unmarshal(source, &config)
	if err != nil {
		panic(err)
	}

	for key, _ := range config.Resources {

		if strings.HasPrefix(key, "REGEX|") {
			trimmed := key[6:len(key)]

			// regex
			glog.Info("Mapped regex route = ", trimmed)

			r, _ := regexp.Compile(trimmed)

			regexResourcePaths = append(regexResourcePaths, r)
		} else {
			// static route
			glog.Info("Mapped static route = ", key)

			staticResourcePaths.Add(key)
		}

	}

	glog.Info("All regex resources: ", regexResourcePaths)
	glog.Info("All static resources: ", staticResourcePaths)

	glog.Info("parsed out config ", config)
}
func Block(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		ip := connections.FindIp(r)
		resource := r.URL.Path

		shouldBlock := false

		for _, element := range blocks.StoredBlocks.Flatten() {

			var block blocks.Block

			if err := json.Unmarshal([]byte(element.(string)), &block); err != nil {
				panic(err)
			}

			if block.Applies(ip, resource, time.Now()) {
				shouldBlock = true
				glog.Info("Found a matching block - denying request: ", block)
				break
			}

		}

		if shouldBlock {

			// deny access
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte("Access Denied"))

		} else {
			next.ServeHTTP(w, r)
		}

	})
}
func parseFlags() (*bool, *string, *string, *string, http.Handler) {

	const (
		defaultPort                     = ":8080"
		defaultPortUsage                = "default server port, ':80', ':8080'..."
		defaultTarget                   = "http://127.0.0.1:8090"
		defaultTargetUsage              = "default redirect url, 'http://127.0.0.1:8080'"
		defaultBooleanUsage             = "true or false"
		defaultRefreshUsage             = "number of seconds between block refreshes"
		defaultExpireUsage              = "number of seconds between expiration sweeps"
		defaultBlocksRefreshUrl         = "block refresh url, 'http://localhost:8090/api/v1.0/blocks'"
		defaultCertFile                 = "cert file (e.g. cert.pem)"
		defaultKeyFile                  = "key file (e.g. key.pem)"
		defaultResourceVerbsMappingFile = "resource verbs mapping file (e.g. resource-verbs-mapping.yml)"
		defaultResourcesFile            = "resources file (e.g. resources.yml)"
	)

	// flags
	port := flag.String("port", defaultPort, defaultPortUsage)
	url := flag.String("url", defaultTarget, defaultTargetUsage)

	// for running in TLS mode
	enableTls := flag.Bool("enable-tls", false, defaultBooleanUsage)
	certFile := flag.String("cert-file", "cert.pem", defaultCertFile)
	keyFile := flag.String("key-file", "key.pem", defaultKeyFile)

	// related to trend detection
	enableTrends := flag.Bool("enable-trend-tracking", false, defaultBooleanUsage)

	// related to blocking
	enableBlocking := flag.Bool("enable-blocking", false, defaultBooleanUsage)
	refreshSeconds := flag.Int("blocking-refresh-rate-seconds", 30, defaultRefreshUsage)
	expireSeconds := flag.Int("blocking-expire-rate-seconds", 30, defaultExpireUsage)
	blocksRefreshUrl := flag.String("blocking-blocks-refresh-url", "http://localhost", defaultBlocksRefreshUrl)

	// for redis, if used
	redisAddress := flag.String("redis-address", ":6379", "Address to the Redis server")
	maxRedisConnections := flag.Int("max-redis-connections", 10, "Max connections to Redis")

	// related to invalid http verbs
	// (instead of GET, POST, HEAD ... you receive DOG, SHOE, FOREST, etc.)
	enableInvalidVerbs := flag.Bool("enable-invalid-verb-checking", false, defaultBooleanUsage)

	// related to unexpected http verbs
	// requires config file
	// config file states this file reachable over GET or HEAD, and you get a POST
	enableUnexpectedVerbs := flag.Bool("enable-unexpected-verb-checking", false, defaultBooleanUsage)
	resourceVerbsMappingFile := flag.String("resource-verbs-mapping-file", "resource-verbs-mapping.yml", defaultResourceVerbsMappingFile)

	// related to unexpected resources
	// requires config file
	// config file states all resources expected in the app (think site-map)
	// this analysis should help detect scanners probing for non-existent resources
	enableUnexpectedResources := flag.Bool("enable-unexpected-resource-checking", false, defaultBooleanUsage)
	resourcesFile := flag.String("resources-file", "resources.yml", defaultResourcesFile)

	flag.Parse()

	glog.Info("------------------------------------------------")
	glog.Infof("Settings:")
	glog.Infof("\tServer port %s", *port)
	glog.Infof("\tProxy url: %s", *url)
	glog.Infof("\tEnable TLS: %t", *enableTls)
	glog.Infof("\t\tCert File: %s", *certFile)
	glog.Infof("\t\tKey File: %s", *keyFile)
	glog.Infof("\tEnable trend tracking: %t", *enableTrends)
	glog.Infof("\tEnable blocking: %t", *enableBlocking)
	glog.Infof("\t\tRefresh rate (seconds): %d", *refreshSeconds)
	glog.Infof("\t\tExpire rate (seconds): %d", *expireSeconds)
	glog.Infof("\t\tBlock refresh url: %s", *blocksRefreshUrl)
	glog.Infof("\tEnable invalid verbs: %t", *enableInvalidVerbs)
	glog.Infof("\tEnable unexpected verbs: %t", *enableUnexpectedVerbs)
	glog.Infof("\t\tResource verbs mapping file: %s", *resourceVerbsMappingFile)
	glog.Infof("\tEnable unexpected resources: %t", *enableUnexpectedResources)
	glog.Infof("\t\tResources file: %s", *resourcesFile)
	glog.Info("------------------------------------------------")

	// proxy
	proxy := New(*url)

	proxyHandler := http.HandlerFunc(proxy.handle)

	// chain default handlers (clear context, recovery, and log execution time)
	chain := alice.New(
		context.ClearHandler,
		middleware.Recovery,
		middleware.LogExecutionTime)

	// if blocking is enabled, add it to alice chain
	if *enableBlocking {
		chain = chain.Append(middleware.Block)
		go doEvery((time.Duration(*refreshSeconds) * time.Second), func() { blocks.RefreshBlocks(blocksRefreshUrl) })
		go doEvery((time.Duration(*expireSeconds) * time.Second), blocks.ExpireBlocks)
	}

	// if trending is enabled, add it to alice chain
	if *enableTrends {
		chain = chain.Append(middleware.Trend)
	}

	// if invalid verb checking is enabled, add it to alice chain
	if *enableInvalidVerbs {
		chain = chain.Append(middleware.InvalidVerbs)
	}

	// if unexpected verb checking is enabled, add it to alice chain
	if *enableUnexpectedVerbs {
		middleware.PopulateExpectedVerbs(resourceVerbsMappingFile)
		chain = chain.Append(middleware.UnexpectedVerbs)
	}

	// if unexpected resource checking is enabled, add it to alice chain
	if *enableUnexpectedResources {
		middleware.PopulateExpectedResources(resourcesFile)
		chain = chain.Append(middleware.UnexpectedResources)
	}

	// if any of these settings are enabled, connect to redis
	if *enableTrends {
		connections.ConnectRedis(redisAddress, maxRedisConnections)
	}

	// if any of these settings are enabled, setup rest client to appsensor backend
	if *enableInvalidVerbs || *enableTrends ||
		*enableUnexpectedVerbs || *enableUnexpectedResources {
		ids.InitializeAppSensorRestClient()
	}

	// always use reverse proxy as chain target (final)
	chainHandler := chain.Then(proxyHandler)

	return enableTls, port, certFile, keyFile, chainHandler
}
func (p *Prox) handle(w http.ResponseWriter, r *http.Request) {
	glog.Info("saw a request for ", r.URL)

	p.proxy.ServeHTTP(w, r)
}