From 990bc2505ac148a8b32f23e5e353b7b0ef60a77c Mon Sep 17 00:00:00 2001
From: Safin Singh <safin.singh@gmail.com>
Date: Sun, 29 Oct 2023 23:31:32 -0700
Subject: [PATCH] POC aeaconf integration with obfuscation and sample functions

---
 checks.go       |  89 +++++++++++++++++----------------
 checks_linux.go |   8 +--
 configs.go      | 129 +++++++++++++++++++++++-------------------------
 go.mod          |  14 ++++--
 go.sum          |  36 +++++++++-----
 score.go        |   5 +-
 utility.go      |  10 ++++
 7 files changed, 158 insertions(+), 133 deletions(-)

diff --git a/checks.go b/checks.go
index e676430..7aa5181 100644
--- a/checks.go
+++ b/checks.go
@@ -12,35 +12,21 @@ import (
 	"reflect"
 	"regexp"
 	"strings"
+
+	"github.com/safinsingh/aeaconf2"
 )
 
-// check is the smallest unit that can show up on a scoring report. It holds all
-// the conditions for a check, and its message and points (autogenerated or
-// otherwise).
-type check struct {
-	Message string
-	Hint    string
-	Points  int
-
-	Fail         []cond
-	Pass         []cond
-	PassOverride []cond
-}
+var funcRegistry map[string]reflect.Type
 
-// cond, or condition, is the parameters for a given test within a check.
-type cond struct {
-	Hint string
-	Type string
+func init() {
+	funcRegistry = make(map[string]reflect.Type)
 
-	Path  string
-	Cmd   string
-	User  string
-	Group string
-	Name  string
-	Key   string
-	Value string
-	After string
-	regex bool
+	funcRegistry["AutoCheckUpdatesEnabled"] = reflect.TypeOf(AutoCheckUpdatesEnabled{})
+	funcRegistry["DirContains"] = reflect.TypeOf(DirContains{})
+	funcRegistry["FileContains"] = reflect.TypeOf(FileContains{})
+	funcRegistry["PathExists"] = reflect.TypeOf(PathExists{})
+
+	aeaconf2.CheckFunctionRegistry(funcRegistry)
 }
 
 // requireArgs is a convenience function that prints a warning if any required
@@ -104,11 +90,11 @@ func handleReflectPanic(condFunc string) {
 }
 
 // runCheck executes a single condition check.
-func runCheck(cond cond) bool {
-	if err := deobfuscateCond(&cond); err != nil {
+func runCheck(cond aeaconf2.Condition) bool {
+	if err := deobfuscateCond(cond); err != nil {
 		fail(err.Error())
 	}
-	defer obfuscateCond(&cond)
+	defer obfuscateCond(cond)
 	debug("Running condition:\n", cond)
 
 	not := "Not"
@@ -178,11 +164,15 @@ func (c cond) CommandOutput() (bool, error) {
 }
 
 // DirContains returns true if any file in the directory contains the string value provided.
-func (c cond) DirContains() (bool, error) {
-	c.requireArgs("Path", "Value")
-	result, err := cond{
-		Path: c.Path,
-	}.PathExists()
+type DirContains struct {
+	aeaconf2.BaseCondition
+	Path  string
+	Value string
+	Regex bool
+}
+
+func (d *DirContains) Score() (bool, error) {
+	result, err := (&PathExists{Path: d.Path}).Score()
 	if err != nil {
 		return false, err
 	}
@@ -191,7 +181,7 @@ func (c cond) DirContains() (bool, error) {
 	}
 
 	var files []string
-	err = filepath.Walk(c.Path, func(path string, info os.FileInfo, err error) error {
+	err = filepath.Walk(d.Path, func(path string, info os.FileInfo, err error) error {
 		if !info.IsDir() {
 			files = append(files, path)
 		}
@@ -206,8 +196,7 @@ func (c cond) DirContains() (bool, error) {
 	}
 
 	for _, file := range files {
-		c.Path = file
-		result, err := c.FileContains()
+		result, err := (&FileContains{Path: file, Value: d.Value}).Score()
 		if os.IsPermission(err) {
 			return false, err
 		}
@@ -222,21 +211,27 @@ func (c cond) DirContains() (bool, error) {
 //
 // Newlines in regex may not work as expected, especially on Windows. It's
 // best to not use these (ex. ^ and $).
-func (c cond) FileContains() (bool, error) {
-	c.requireArgs("Path", "Value")
-	fileContent, err := readFile(c.Path)
+type FileContains struct {
+	aeaconf2.BaseCondition
+	Path  string
+	Value string
+	Regex bool
+}
+
+func (f *FileContains) Score() (bool, error) {
+	fileContent, err := readFile(f.Path)
 	if err != nil {
 		return false, err
 	}
 	found := false
 	for _, line := range strings.Split(fileContent, "\n") {
-		if c.regex {
-			found, err = regexp.Match(c.Value, []byte(line))
+		if f.Regex {
+			found, err = regexp.Match(f.Value, []byte(line))
 			if err != nil {
 				return false, err
 			}
 		} else {
-			found = strings.Contains(line, c.Value)
+			found = strings.Contains(line, f.Value)
 		}
 		if found {
 			break
@@ -264,9 +259,13 @@ func (c cond) FileEquals() (bool, error) {
 
 // PathExists is a wrapper around os.Stat and os.IsNotExist, and determines
 // whether a file or folder exists.
-func (c cond) PathExists() (bool, error) {
-	c.requireArgs("Path")
-	_, err := os.Stat(c.Path)
+type PathExists struct {
+	aeaconf2.BaseCondition
+	Path string
+}
+
+func (p *PathExists) Score() (bool, error) {
+	_, err := os.Stat(p.Path)
 	if err != nil && os.IsNotExist(err) {
 		return false, nil
 	} else if err != nil {
diff --git a/checks_linux.go b/checks_linux.go
index 519a046..239e091 100644
--- a/checks_linux.go
+++ b/checks_linux.go
@@ -9,12 +9,14 @@ import (
 	"syscall"
 )
 
-func (c cond) AutoCheckUpdatesEnabled() (bool, error) {
-	result, err := cond{
+type AutoCheckUpdatesEnabled struct{}
+
+func (a *AutoCheckUpdatesEnabled) Score() (bool, error) {
+	result, err := DirContains{
 		Path:  "/etc/apt/apt.conf.d/",
 		Value: `(?i)^\s*APT::Periodic::Update-Package-Lists\s+"1"\s*;\s*$`,
 		regex: true,
-	}.DirContains()
+	}.Score()
 	// If /etc/apt/ does not exist, try dnf (RHEL)
 	if err != nil {
 		autoConf, err := cond{
diff --git a/configs.go b/configs.go
index 4d1dcab..15becdd 100644
--- a/configs.go
+++ b/configs.go
@@ -8,6 +8,8 @@ import (
 	"reflect"
 
 	"github.com/BurntSushi/toml"
+	"github.com/safinsingh/aeaconf2"
+	"github.com/safinsingh/aeaconf2/compat"
 )
 
 // parseConfig takes the config content as a string and attempts to parse it
@@ -17,17 +19,26 @@ func parseConfig(configContent string) {
 		fail("Configuration is empty!")
 		os.Exit(1)
 	}
-	md, err := toml.Decode(configContent, &conf)
+
+	headerRaw, checksRaw, err := compat.SeparateConfig([]byte(configContent))
 	if err != nil {
-		fail("Error decoding TOML: " + err.Error())
+		fail("error separating config file: " + err.Error())
 		os.Exit(1)
 	}
-	if verboseEnabled {
-		for _, undecoded := range md.Undecoded() {
-			warn("Undecoded scoring configuration key \"" + undecoded.String() + "\" will not be used.")
-		}
+
+	cfg := new(config)
+	err = toml.Unmarshal(headerRaw, cfg)
+	if err != nil {
+		fail("error parsing config file header: " + err.Error())
+		os.Exit(1)
 	}
 
+	ab := aeaconf2.DefaultAeaconfBuilder(checksRaw, funcRegistry).
+		SetLineOffset(countLines(headerRaw)).
+		SetMaxPoints(cfg.MaxPoints)
+
+	cfg.Checks = ab.GetChecks()
+
 	// If there's no remote, local must be enabled.
 	if conf.Remote == "" {
 		conf.Local = true
@@ -62,19 +73,6 @@ func parseConfig(configContent string) {
 		info("Consider updating your config to include:")
 		info("    version = '" + version + "'")
 	}
-
-	// Print warnings for impossible checks and undefined check types.
-	for i, check := range conf.Check {
-		if len(check.Pass) == 0 && len(check.PassOverride) == 0 {
-			warn("Check " + fmt.Sprintf("%d", i+1) + " does not define any possible ways to pass!")
-		}
-		allConditions := append(append(append([]cond{}, check.Pass[:]...), check.Fail[:]...), check.PassOverride[:]...)
-		for j, cond := range allConditions {
-			if cond.Type == "" {
-				warn("Check " + fmt.Sprintf("%d condition %d", i+1, j+1) + " does not have a check type!")
-			}
-		}
-	}
 }
 
 // writeConfig writes the in-memory config to disk as the an encrypted
@@ -157,21 +155,8 @@ func printConfig() {
 	if conf.EndDate != "" {
 		pass("End Date:", conf.EndDate)
 	}
-	for i, check := range conf.Check {
-		green("CHCK", fmt.Sprintf("Check %d (%d points):", i+1, check.Points))
-		fmt.Println("Message:", check.Message)
-		for _, c := range check.Pass {
-			fmt.Println("Pass Condition:")
-			fmt.Print(c)
-		}
-		for _, c := range check.PassOverride {
-			fmt.Println("PassOverride Condition:")
-			fmt.Print(c)
-		}
-		for _, c := range check.Fail {
-			fmt.Println("Fail Condition:")
-			fmt.Print(c)
-		}
+	for i, check := range conf.Checks {
+		green("CHCK", fmt.Sprintf("Check %d: %s", i+1, check.Debug()))
 	}
 }
 
@@ -182,64 +167,72 @@ func obfuscateConfig() {
 	if err := obfuscateData(&conf.Password); err != nil {
 		fail(err.Error())
 	}
-	for i, check := range conf.Check {
-		if err := obfuscateData(&conf.Check[i].Message); err != nil {
+	for i := range conf.Checks {
+		if err := obfuscateData(&conf.Checks[i].Message); err != nil {
 			fail(err.Error())
 		}
-		if conf.Check[i].Hint != "" {
-			if err := obfuscateData(&conf.Check[i].Hint); err != nil {
-				fail(err.Error())
-			}
-		}
-		for j := range check.Pass {
-			if err := obfuscateCond(&conf.Check[i].Pass[j]); err != nil {
+		if conf.Checks[i].Hint != "" {
+			if err := obfuscateData(&conf.Checks[i].Hint); err != nil {
 				fail(err.Error())
 			}
 		}
-		for j := range check.PassOverride {
-			if err := obfuscateCond(&conf.Check[i].PassOverride[j]); err != nil {
-				fail(err.Error())
-			}
-		}
-		for j := range check.Fail {
-			if err := obfuscateCond(&conf.Check[i].Fail[j]); err != nil {
-				fail(err.Error())
-			}
+		if err := obfuscateCond(conf.Checks[i].Condition); err != nil {
+			fail(err.Error())
 		}
 	}
 }
 
 // obfuscateCond is a convenience function to obfuscate all string fields of a
 // struct using reflection. It assumes all struct fields are strings.
-func obfuscateCond(c *cond) error {
+
+// ummmmmm
+func obfuscateCond(c aeaconf2.Condition) error {
 	s := reflect.ValueOf(c).Elem()
+	t := s.Type()
+
 	for i := 0; i < s.NumField(); i++ {
-		if s.Type().Field(i).Name == "regex" {
-			continue
-		}
-		datum := s.Field(i).String()
-		if err := obfuscateData(&datum); err != nil {
-			return err
+		field := s.Field(i)
+		fieldType := t.Field(i).Type
+
+		if fieldType.Kind() == reflect.String {
+			datum := field.String()
+			if err := obfuscateData(&datum); err != nil {
+				return err
+			}
+			field.SetString(datum)
+		} else if fieldType.Implements(reflect.TypeOf((*aeaconf2.Condition)(nil)).Elem()) {
+			if err := obfuscateCond(field.Addr().Interface().(aeaconf2.Condition)); err != nil {
+				return err
+			}
 		}
-		s.Field(i).SetString(datum)
 	}
+
 	return nil
 }
 
 // deobfuscateCond is a convenience function to deobfuscate all string fields
 // of a struct using reflection.
-func deobfuscateCond(c *cond) error {
+func deobfuscateCond(c aeaconf2.Condition) error {
 	s := reflect.ValueOf(c).Elem()
+	t := s.Type()
+
 	for i := 0; i < s.NumField(); i++ {
-		if s.Type().Field(i).Name == "regex" {
-			continue
-		}
-		datum := s.Field(i).String()
-		if err := deobfuscateData(&datum); err != nil {
-			return err
+		field := s.Field(i)
+		fieldType := t.Field(i).Type
+
+		if fieldType.Kind() == reflect.String {
+			datum := field.String()
+			if err := deobfuscateData(&datum); err != nil {
+				return err
+			}
+			field.SetString(datum)
+		} else if fieldType.Implements(reflect.TypeOf((*aeaconf2.Condition)(nil)).Elem()) {
+			if err := deobfuscateCond(field.Addr().Interface().(aeaconf2.Condition)); err != nil {
+				return err
+			}
 		}
-		s.Field(i).SetString(datum)
 	}
+
 	return nil
 }
 
diff --git a/go.mod b/go.mod
index 24ad882..276515f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,15 @@
 module github.com/elysium-suite/aeacus
 
-go 1.19
+go 1.21.1
+
+toolchain go1.21.3
 
 require (
 	github.com/ActiveState/termtest/conpty v0.5.0
 	github.com/BurntSushi/toml v1.2.0
 	github.com/DataDog/datadog-agent/pkg/util/winutil v0.36.1
 	github.com/creack/pty v1.1.18
-	github.com/fatih/color v1.13.0
+	github.com/fatih/color v1.15.0
 	github.com/gen2brain/beeep v0.0.0-20220518085355-d7852edf42fc
 	github.com/gorilla/websocket v1.5.0
 	github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
@@ -16,7 +18,7 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/stretchr/testify v1.8.0
 	github.com/urfave/cli/v2 v2.14.0
-	golang.org/x/sys v0.0.0-20220829200755-d48e67d00261
+	golang.org/x/sys v0.13.0
 	golang.org/x/text v0.3.8
 )
 
@@ -32,13 +34,15 @@ require (
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/google/cabbie v1.0.2 // indirect
 	github.com/google/glazier v0.0.0-20211029225403-9f766cca891d // indirect
-	github.com/mattn/go-colorable v0.1.9 // indirect
-	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/safinsingh/aeaconf2 v0.1.1
 	github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e // indirect
 	github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 895110b..c2ceca8 100644
--- a/go.sum
+++ b/go.sum
@@ -24,8 +24,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
+github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gen2brain/beeep v0.0.0-20220518085355-d7852edf42fc h1:6ZZLxG+lB+Qbg+chtzAEeetwqjlPnY0BXbhL3lQWYOg=
@@ -75,12 +75,16 @@ github.com/iamacarpet/go-win64api v0.0.0-20220720120512-241a9064deec/go.mod h1:B
 github.com/judwhite/go-svc v1.2.1 h1:a7fsJzYUa33sfDJRF2N/WXhA+LonCEEY8BJb1tuS5tA=
 github.com/judwhite/go-svc v1.2.1/go.mod h1:mo/P2JNX8C07ywpP9YtO2gnBgnUiFTHqtsZekJrUuTk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -97,6 +101,12 @@ github.com/rickb777/plural v1.2.2/go.mod h1:xyHbelv4YvJE51gjMnHvk+U2e9zIysg6lTnS
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/safinsingh/aeaconf2 v0.0.0-20231030002551-24373810902e h1:1SrqGvirvZudah4mNNslglGwzuNT/Sn09AkNfZ7cOVo=
+github.com/safinsingh/aeaconf2 v0.0.0-20231030002551-24373810902e/go.mod h1:U/mGBz9itT5mjn3rDYNckvnrIHNRxzKIoWE8ImDUiFk=
+github.com/safinsingh/aeaconf2 v0.1.0 h1:xXcZIOtK/GIzcKO+LWVINCjLgpBv2G3fAUSgjj57goQ=
+github.com/safinsingh/aeaconf2 v0.1.0/go.mod h1:U/mGBz9itT5mjn3rDYNckvnrIHNRxzKIoWE8ImDUiFk=
+github.com/safinsingh/aeaconf2 v0.1.1 h1:9d5IGHjdh4C4U9WNYKHr66IPS4mos2WRq5AyPRDkZ3g=
+github.com/safinsingh/aeaconf2 v0.1.1/go.mod h1:U/mGBz9itT5mjn3rDYNckvnrIHNRxzKIoWE8ImDUiFk=
 github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e h1:+/AzLkOdIXEPrAQtwAeWOBnPQ0BnYlBW0aCZmSb47u4=
 github.com/scjalliance/comshim v0.0.0-20190308082608-cf06d2532c4e/go.mod h1:9Tc1SKnfACJb9N7cw2eyuI6xzy845G7uZONBsi5uPEA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -126,8 +136,6 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200622182413-4b0db7f3f76b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -140,8 +148,11 @@ golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
-golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -159,7 +170,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/score.go b/score.go
index 5005939..bd3d272 100644
--- a/score.go
+++ b/score.go
@@ -6,6 +6,8 @@ import (
 	"log"
 	"os"
 	"strconv"
+
+	"github.com/safinsingh/aeaconf2"
 )
 
 var (
@@ -74,7 +76,8 @@ type config struct {
 	Title                   string
 	User                    string
 	Version                 string
-	Check                   []check
+	MaxPoints               int
+	Checks                  []*aeaconf2.Check
 }
 
 // statusRes is to parse a JSON response from the remote server.
diff --git a/utility.go b/utility.go
index 89cdf68..50f2517 100644
--- a/utility.go
+++ b/utility.go
@@ -127,6 +127,16 @@ func shellCommandOutput(commandGiven string) (string, error) {
 	return string(out), err
 }
 
+func countLines(source []byte) int {
+	ret := 0
+	for _, b := range source {
+		if b == '\n' {
+			ret++
+		}
+	}
+	return ret + 1
+}
+
 // assignPoints is used to automatically assign points to checks that don't
 // have a hardcoded points value.
 func assignPoints() {