├── .gitignore ├── pkg ├── core │ ├── doc.go │ └── crux.go ├── internal │ ├── doc.go │ ├── cmd.go │ ├── init.go │ ├── directives.go │ └── process.go ├── workshop │ ├── doc.go │ └── queue.go ├── alerts │ ├── doc.go │ ├── discord_test.go │ ├── util.go │ └── discord.go ├── shell │ ├── doc.go │ ├── basic.go │ ├── notify.go │ └── cmdwrap.go ├── scheduler │ ├── node.go │ ├── helpers.go │ └── schedule.go ├── db │ ├── backend_test.go │ ├── provider.go │ ├── interface.go │ ├── boltx │ │ ├── private.go │ │ └── bboltdb.go │ ├── db_intergration_test.go │ └── mongox │ │ ├── mongodb.go │ │ └── private.go ├── stringutils │ ├── random.go │ └── string.go ├── ioutils │ ├── helpers.go │ └── stdout.go └── shared │ ├── registry.go │ ├── settings.go │ └── share.go ├── static ├── cmdout.png ├── help.png ├── script.png ├── banner.jpeg ├── taloshelp.png ├── talosplus.png └── notification.png ├── CONTRIBUTING.md ├── cmd └── talosplus │ ├── banner.go │ ├── banner.txt │ ├── main.go │ └── options.go ├── .github └── workflows │ ├── go.yml │ ├── mongodb_tests.yml │ ├── release.yml │ ├── dependency-review.yml │ └── codeql.yml ├── .goreleaser.yaml ├── LICENSE ├── go.mod ├── SYNTAX.md ├── examples └── subenum.sh ├── test ├── shell_test │ └── simple_test.go └── core_test │ └── complex_test.go ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist/ 3 | -------------------------------------------------------------------------------- /pkg/core/doc.go: -------------------------------------------------------------------------------- 1 | // Package core is entry point and handles everything 2 | package core 3 | -------------------------------------------------------------------------------- /static/cmdout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/cmdout.png -------------------------------------------------------------------------------- /static/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/help.png -------------------------------------------------------------------------------- /static/script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/script.png -------------------------------------------------------------------------------- /static/banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/banner.jpeg -------------------------------------------------------------------------------- /static/taloshelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/taloshelp.png -------------------------------------------------------------------------------- /static/talosplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/talosplus.png -------------------------------------------------------------------------------- /static/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarunKoyalwar/talosplus/HEAD/static/notification.png -------------------------------------------------------------------------------- /pkg/internal/doc.go: -------------------------------------------------------------------------------- 1 | // Package internal deals with actual parsing of data from plaintext 2 | package internal 3 | -------------------------------------------------------------------------------- /pkg/workshop/doc.go: -------------------------------------------------------------------------------- 1 | package workshop 2 | 3 | // A Workshop for goroutines 4 | // Execute goroutines based on workers 5 | -------------------------------------------------------------------------------- /pkg/alerts/doc.go: -------------------------------------------------------------------------------- 1 | // Provides support to send notifications to discord 2 | // may be sysnotifications later 3 | package alerts 4 | -------------------------------------------------------------------------------- /pkg/shell/doc.go: -------------------------------------------------------------------------------- 1 | // Package shell provides access to exec.cmd and wrapper struct 2 | // This package provides everything that is required to run a single command 3 | package shell 4 | -------------------------------------------------------------------------------- /pkg/scheduler/node.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | // Node : Scheduler Node 4 | type Node struct { 5 | Root []*Node 6 | UID string 7 | Comment string 8 | Children []*Node 9 | } 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * Create a issue describing issue/enhancement 4 | * Fork the repo 5 | * Make your changes 6 | * Run Unit tests `go test -v ./test/...` 7 | * Issue a pull request 8 | -------------------------------------------------------------------------------- /cmd/talosplus/banner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | 6 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 7 | ) 8 | 9 | //go:embed banner.txt 10 | var banner string 11 | 12 | var Version string = "v1.1.0" 13 | 14 | func showBanner() { 15 | ioutils.Cout.Printf(banner, Version) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/db/backend_test.go: -------------------------------------------------------------------------------- 1 | package db_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/tarunKoyalwar/talosplus/pkg/db" 7 | "github.com/tarunKoyalwar/talosplus/pkg/db/boltx" 8 | "github.com/tarunKoyalwar/talosplus/pkg/db/mongox" 9 | ) 10 | 11 | func Test_MongoDBCompatibility(t *testing.T) { 12 | var _ db.Provider = (*mongox.Provider)(nil) 13 | } 14 | 15 | func Test_BBoltDBCompatibility(t *testing.T) { 16 | var _ db.Provider = (*boltx.Provider)(nil) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/talosplus/banner.txt: -------------------------------------------------------------------------------- 1 | _____ _ _ 2 | /__ \ __ _ | | ___ ___ _ __ | | _ _ ___ 3 | / /\/ / _` || | / _ \ / __|| '_ \ | || | | |/ __| 4 | / / | (_| || || (_) |\__ \| |_) || || |_| |\__ \ 5 | \/ \__,_||_| \___/ |___/| .__/ |_| \__,_||___/ 6 | |_| 7 | %s 8 | Towards Intelligent & Effortless Automation 9 | -------------------------------------------------------------------------------- /pkg/stringutils/random.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // RandomString : Generates random strings of given length with Uppercase charset 10 | func RandomString(size int) string { 11 | rand.Seed(time.Now().UnixNano()) 12 | chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 13 | var b strings.Builder 14 | for i := 0; i < size; i++ { 15 | b.WriteRune(chars[rand.Intn(len(chars))]) 16 | } 17 | str := b.String() 18 | 19 | return str 20 | } 21 | -------------------------------------------------------------------------------- /pkg/alerts/discord_test.go: -------------------------------------------------------------------------------- 1 | package alerts_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/tarunKoyalwar/talosplus/pkg/alerts" 8 | ) 9 | 10 | func Test_Alerts(t *testing.T) { 11 | id := os.Getenv("DISCORD_WID") 12 | tok := os.Getenv("DISCORD_WTOKEN") 13 | 14 | alerts.Alert = alerts.NewDiscordHook(id, tok) 15 | 16 | alerts.Alert.Title = "Testing Hook" 17 | 18 | err := alerts.Alert.SendEmbed("Just a new simple test", map[string]string{}) 19 | 20 | if err != nil { 21 | t.Errorf(err.Error()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/ioutils/helpers.go: -------------------------------------------------------------------------------- 1 | package ioutils 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/muesli/termenv" 7 | ) 8 | 9 | var ( 10 | Green = GetColor(126, 211, 33) 11 | Orange = GetColor(245, 166, 35) 12 | AquaMarine = GetColor(80, 227, 194) 13 | Azure = GetColor(74, 144, 226) 14 | Yellow = GetColor(248, 231, 28) 15 | Grey = GetColor(155, 155, 155) 16 | Red = GetColor(208, 2, 27) 17 | LightGreen = GetColor(184, 233, 134) 18 | ) 19 | 20 | var Profile termenv.Profile = termenv.ColorProfile() 21 | 22 | func GetColor(r uint8, g uint8, b uint8) termenv.Color { 23 | return Profile.FromColor(color.RGBA{R: r, G: g, B: b, A: 255}) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go Build and test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | 9 | jobs: 10 | build: 11 | name: Test Builds 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest] 17 | 18 | steps: 19 | - name: Setup Golang 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Checkout repo 25 | uses: actions/checkout@v3 26 | 27 | 28 | - name: Build talosplus 29 | run: go build . 30 | working-directory: cmd/talosplus/ 31 | 32 | - name: Unit Tests 33 | run: go test ./test/... -------------------------------------------------------------------------------- /pkg/alerts/util.go: -------------------------------------------------------------------------------- 1 | package alerts 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // use spycolor.com for decimal codes 8 | var ( 9 | ErrColor = 12538964 10 | NormColor = 5955808 11 | ) 12 | 13 | // FormatMsg : Split Msg if too long 14 | func FormatMsg(dat string) []string { 15 | arr := []string{} 16 | 17 | counter := 0 18 | temp := "" 19 | 20 | for _, v := range strings.Fields(dat) { 21 | 22 | if counter > 1800 { 23 | 24 | arr = append(arr, temp) 25 | temp = "" 26 | counter = 0 27 | 28 | } 29 | 30 | temp = temp + v + " " 31 | counter += len(v) + 1 32 | 33 | } 34 | 35 | if temp != "" { 36 | arr = append(arr, temp) 37 | } 38 | 39 | return arr 40 | 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/mongodb_tests.yml: -------------------------------------------------------------------------------- 1 | name: Run MongoDB tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | RUN_MONGODB_TEST: true 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Setup Golang 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Checkout repo 22 | uses: actions/checkout@v3 23 | 24 | - name: Start MongoDB 25 | uses: supercharge/mongodb-github-action@1.8.0 26 | 27 | - name: Build talosplus 28 | run: go build . 29 | working-directory: cmd/talosplus/ 30 | 31 | - name: Unit Tests 32 | run: go test ./test/... -------------------------------------------------------------------------------- /pkg/scheduler/helpers.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | // Distance of Node From Root 4 | func FarthestNodethru(x *Node, depth int) (*Node, int) { 5 | if len(x.Root) == 0 { 6 | return x, depth 7 | } else { 8 | if len(x.Root) == 1 { 9 | _, val := FarthestNodethru(x.Root[0], depth+1) 10 | return x, val 11 | } 12 | rvals := map[*Node]int{} 13 | for _, r := range x.Root { 14 | _, rvals[r] = FarthestNodethru(r, depth+1) 15 | } 16 | return MaxofArr(rvals) 17 | 18 | } 19 | } 20 | 21 | // MaxofArr 22 | func MaxofArr(z map[*Node]int) (*Node, int) { 23 | max := -1 24 | var reqnode *Node 25 | for k, v := range z { 26 | // fmt.Printf("%v with %v\n", k.Comment, v) 27 | if v > max { 28 | max = v 29 | reqnode = k 30 | } 31 | } 32 | 33 | return reqnode, max 34 | } 35 | -------------------------------------------------------------------------------- /pkg/db/provider.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/tarunKoyalwar/talosplus/pkg/db/boltx" 5 | "github.com/tarunKoyalwar/talosplus/pkg/db/mongox" 6 | ) 7 | 8 | // DB 9 | var DB Provider 10 | 11 | // UserMongoDB as db backend 12 | func UseMongoDB(url, dbname, collname string) error { 13 | 14 | options := mongox.Options{ 15 | URL: url, 16 | DBName: dbname, 17 | CollectionName: collname, 18 | } 19 | 20 | var err error 21 | DB, err = mongox.New(&options) 22 | 23 | return err 24 | } 25 | 26 | // UseBBoltDB as db backend 27 | func UseBBoltDB(dir, filename, bucketname string) error { 28 | options := boltx.Options{ 29 | Directory: dir, 30 | Filename: filename, 31 | BucketName: bucketname, 32 | } 33 | 34 | var err error 35 | DB, err = boltx.New(&options) 36 | 37 | return err 38 | } 39 | -------------------------------------------------------------------------------- /pkg/db/interface.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | // DBProvider : DB Provider i.e MongoDB , InMemory or bbolt(embedded) etc 4 | type DBProvider int 5 | 6 | const ( 7 | Mongo_DB DBProvider = iota 8 | BBolt_DB 9 | ) 10 | 11 | // Provider : Interface for All Providers of Database 12 | type Provider interface { 13 | // Put variable name and value to db 14 | Put(key, value string, isExplicit bool) error 15 | // Get value of a variable 16 | Get(key string) (string, error) 17 | // GetAll Variable Names 18 | GetAllVarNames() (map[string]bool, error) 19 | // GetAllExplicit Variable Names 20 | GetAllExplicit() (map[string]string, error) 21 | // GetAllRuntime Variable Names 22 | GetAllImplicit() (map[string]string, error) 23 | // // GetAllGlobal Variables 24 | // GetAllGlobal() (map[string]string, error) 25 | // ProviderName 26 | ProviderName() string 27 | // Close DB Connection 28 | Close() error 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release nestle binary 2 | 3 | on: 4 | push: 5 | # Only against tags 6 | tags: 7 | - '*' 8 | 9 | permissions: 10 | contents: write 11 | packages: write 12 | 13 | jobs: 14 | goreleaser: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Setup Git repository 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | 24 | - 25 | name: Force Fetch Tags 26 | run: git fetch --force --tags 27 | 28 | - 29 | name: Setup Go 30 | uses: actions/setup-go@v3 31 | with: 32 | go-version: 1.19 33 | 34 | - 35 | name: Setup Goreleaser 36 | uses: goreleaser/goreleaser-action@v3 37 | with: 38 | args: "release --rm-dist" 39 | version: latest 40 | 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | 8 | builds: 9 | - env: 10 | - CGO_ENABLED=0 11 | 12 | id: talosplus 13 | main: ./cmd/talosplus/ 14 | binary: talosplus 15 | 16 | goos: 17 | - linux 18 | - windows 19 | - darwin 20 | 21 | goarch: [amd64,"386",arm,arm64] 22 | ignore: 23 | - goos: darwin 24 | goarch: "386" 25 | - goos: windows 26 | goarch: arm 27 | - goos: windows 28 | goarch: arm64 29 | 30 | archives: 31 | - format: zip 32 | replacements: 33 | darwin: Darwin 34 | linux: Linux 35 | windows: Windows 36 | 386: i386 37 | amd64: x86_64 38 | 39 | checksum: 40 | algorithm: sha256 41 | name_template: 'checksums.txt' 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /pkg/db/boltx/private.go: -------------------------------------------------------------------------------- 1 | package boltx 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "go.etcd.io/bbolt" 7 | ) 8 | 9 | /* 10 | Private/ Internal Methods of Provider 11 | */ 12 | 13 | // dbEntry of BBoltDB containing Metadata 14 | type dbEntry struct { 15 | Value string `json:"value"` 16 | IsExplicit bool `json:"isExplicit"` 17 | } 18 | 19 | // getBytes 20 | func (d *dbEntry) getBytes() ([]byte, error) { 21 | bin, err := json.MarshalIndent(d, "", "\t") 22 | 23 | return bin, err 24 | } 25 | 26 | // newdbEntry 27 | func newdbEntry(bin []byte) (*dbEntry, error) { 28 | var x dbEntry 29 | 30 | err := json.Unmarshal(bin, &x) 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &x, nil 37 | } 38 | 39 | // getkvwhere : get key value where explicit is bool 40 | func (p *Provider) getkvwhere(Explicit bool) (map[string]string, error) { 41 | vars := map[string]string{} 42 | 43 | if _, er := p.validate(); er != nil { 44 | return vars, er 45 | } 46 | 47 | var erx error 48 | 49 | p.db.View(func(tx *bbolt.Tx) error { 50 | bucket := tx.Bucket([]byte(p.BBoltDB.BucketName)) 51 | 52 | erx = bucket.ForEach(func(k, v []byte) error { 53 | item, err := newdbEntry(v) 54 | if err != nil { 55 | erx = err 56 | return err 57 | } 58 | if item.IsExplicit == Explicit { 59 | vars[string(k)] = item.Value 60 | } 61 | 62 | return nil 63 | }) 64 | 65 | return nil 66 | }, 67 | ) 68 | 69 | if erx != nil { 70 | return vars, erx 71 | } 72 | 73 | return vars, nil 74 | 75 | } 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Tarun Koyalwar 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /pkg/workshop/queue.go: -------------------------------------------------------------------------------- 1 | package workshop 2 | 3 | import ( 4 | "io/ioutil" 5 | "sync" 6 | 7 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 8 | "github.com/tarunKoyalwar/talosplus/pkg/shell" 9 | ) 10 | 11 | // ExecQueue : Execute Given Queue with given concurrency 12 | func ExecQueue(q []*shell.CMDWrap, limit int, showout bool) { 13 | 14 | wg := &sync.WaitGroup{} 15 | wrec := &sync.WaitGroup{} 16 | 17 | if limit > len(q) { 18 | limit = len(q) 19 | } 20 | 21 | var z chan *shell.CMDWrap = make(chan *shell.CMDWrap, len(q)) 22 | var done chan *shell.CMDWrap = make(chan *shell.CMDWrap, limit) 23 | 24 | wrec.Add(1) 25 | go func(d chan *shell.CMDWrap) { 26 | defer wrec.Done() 27 | for { 28 | instance, ok := <-done 29 | if !ok { 30 | break 31 | } 32 | 33 | ioutils.Cout.Value("[$] %v Executed Successfully", instance.Comment) 34 | 35 | // WIll show output of commands 36 | if showout { 37 | if !instance.Ignore { 38 | ioutils.Cout.Printf("[+] %v\n", ioutils.Cout.GetColor(ioutils.Green, instance.Raw)) 39 | if instance.ExportFromFile == "" { 40 | ioutils.Cout.Printf("%v", instance.CMD.COutStream.String()) 41 | } else { 42 | dat, _ := ioutil.ReadFile(instance.ExportFromFile) 43 | ioutils.Cout.Printf("%v", string(dat)) 44 | } 45 | } 46 | } 47 | 48 | } 49 | }(done) 50 | 51 | worker := func(z chan *shell.CMDWrap) { 52 | defer wg.Done() 53 | 54 | for { 55 | d, ok := <-z 56 | if !ok { 57 | break 58 | } 59 | 60 | er := d.Execute() 61 | if er != nil { 62 | ioutils.Cout.PrintWarning(er.Error()) 63 | } 64 | done <- d 65 | } 66 | 67 | } 68 | 69 | for i := 0; i < limit; i++ { 70 | wg.Add(1) 71 | go worker(z) 72 | } 73 | 74 | //assign work 75 | for _, v := range q { 76 | z <- v 77 | } 78 | 79 | close(z) 80 | wg.Wait() 81 | close(done) 82 | wrec.Wait() 83 | 84 | } 85 | -------------------------------------------------------------------------------- /pkg/stringutils/string.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var variablereg *regexp.Regexp = regexp.MustCompile(`(([@]{1}[0-9a-zA-Z]+[{][a-z!]+[}])|([@]{1}[0-9a-zA-Z]+))`) 9 | 10 | // SplitAtSpace : Similar to strings.Feilds but only considers ' ' 11 | func SplitAtSpace(s string) []string { 12 | 13 | return Split(s, ' ') 14 | 15 | } 16 | 17 | // Split : Similar to Strings.Feilds with Custom separator 18 | func Split(s string, delim rune) []string { 19 | 20 | // Must trim the string first 21 | s = strings.TrimSpace(s) 22 | 23 | arr := []string{} 24 | 25 | var sb strings.Builder 26 | 27 | for _, v := range s { 28 | if v != delim { 29 | sb.WriteRune(v) 30 | } else { 31 | if sb.Len() != 0 { 32 | arr = append(arr, sb.String()) 33 | sb.Reset() 34 | } 35 | } 36 | } 37 | 38 | if sb.Len() != 0 { 39 | arr = append(arr, sb.String()) 40 | sb.Reset() 41 | } 42 | 43 | return arr 44 | } 45 | 46 | // UniqueArray : Get Array with unique items 47 | func UniqueArray(s ...string) []string { 48 | 49 | z := map[string]bool{} 50 | 51 | for _, v := range s { 52 | //split string 53 | tarr := strings.Fields(v) 54 | for _, elem := range tarr { 55 | z[elem] = true 56 | } 57 | } 58 | 59 | keys := make([]string, 0, len(z)) 60 | for k := range z { 61 | keys = append(keys, k) 62 | } 63 | 64 | return keys 65 | } 66 | 67 | // UniqueElements : Get Unique Elements 68 | func UniqueElements(s ...string) string { 69 | u := map[string]bool{} 70 | for _, v := range s { 71 | for _, b := range Split(v, '\n') { 72 | u[b] = true 73 | } 74 | } 75 | 76 | tmp := "" 77 | for k := range u { 78 | if tmp == "" { 79 | tmp = k 80 | } else { 81 | tmp += "\n" + k 82 | } 83 | } 84 | 85 | return tmp 86 | } 87 | 88 | // ExtractVar : Extract var name using regex 89 | func ExtractVar(raw string) string { 90 | 91 | /* 92 | Handle edge cases like printf "@somevar\n" 93 | */ 94 | 95 | return variablereg.FindString(raw) 96 | } 97 | -------------------------------------------------------------------------------- /pkg/shared/registry.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | var DefaultRegistry *Registry = NewRegistry() 4 | 5 | // Registry : Struct Containing all info that is shared b/w cmds 6 | // This includes Local Vars, Exports etc and a lot of other stuff 7 | type Registry struct { 8 | VarAddress map[string][]string // Address of commands that export data 9 | FoundVars map[string]bool // All Implicit Declarations 10 | Dependents map[string][]string // CMD's that depend on a particular variable 11 | // Dependents[Variable][cmd uid's] 12 | } 13 | 14 | // Registerdep : Registers uid and variable to registry 15 | func (r *Registry) Registerdep(uid string, variable string) { 16 | 17 | //check if variable was explicitly declared at start of script 18 | //if explicit no need to mark as dep 19 | 20 | if SharedVars.IsExplicitVar(variable) { 21 | return 22 | } 23 | 24 | if r.Dependents[variable] == nil { 25 | r.Dependents[variable] = []string{} 26 | } 27 | 28 | // and uid to dependents list 29 | r.Dependents[variable] = append(r.Dependents[variable], uid) 30 | 31 | //check if variable was declared already 32 | if !SharedVars.Exists(variable) { 33 | //if ok is false then variable is not declared 34 | if !r.FoundVars[variable] { 35 | //Also not a implicit / runtime declaration 36 | r.FoundVars[variable] = false // mark this as not declared 37 | // if found later will be marked as true then 38 | } 39 | } 40 | 41 | } 42 | 43 | // Registerexp : Registers uid as exporter of given variable 44 | func (r *Registry) RegisterExport(uid string, variable string) { 45 | 46 | // add address of variable here address is uid 47 | if r.VarAddress[variable] == nil { 48 | r.VarAddress[variable] = []string{uid} 49 | } else { 50 | r.VarAddress[variable] = append(r.VarAddress[variable], uid) 51 | } 52 | 53 | //also mark variable as implicitly declared 54 | r.FoundVars[variable] = true 55 | } 56 | 57 | func NewRegistry() *Registry { 58 | r := &Registry{ 59 | VarAddress: map[string][]string{}, 60 | FoundVars: map[string]bool{}, 61 | Dependents: map[string][]string{}, 62 | } 63 | 64 | return r 65 | 66 | } 67 | -------------------------------------------------------------------------------- /pkg/internal/cmd.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 10 | ) 11 | 12 | // Command : This struct holds raw command 13 | type Command struct { 14 | Raw string 15 | Comment string 16 | UID string 17 | } 18 | 19 | // This will generate UID's for each command 20 | func (c *Command) GenUID() { 21 | 22 | cmdarr := stringutils.SplitAtSpace(c.Raw) 23 | 24 | //sort to avoid duplicates 25 | sort.Strings(cmdarr) 26 | 27 | //Lets use # as separator 28 | suffix := strings.Join(cmdarr, "#") 29 | 30 | data := []byte(suffix) 31 | 32 | bin := md5.Sum(data) 33 | 34 | c.UID = hex.EncodeToString(bin[:]) 35 | 36 | } 37 | 38 | // Resolve : Resolve all directives from command 39 | func (c *Command) Resolve() { 40 | /* 41 | Each directive and variables in command have different meanings 42 | 43 | By resolving these directives and variables 44 | 45 | system would be able to create a dependency graph 46 | 47 | do some static analysis and stuff 48 | 49 | In this step command remains as it is directives/ variables are not replaced 50 | at this stage . that only happens at runtime 51 | 52 | 53 | */ 54 | 55 | // Remove all operators from cmd 56 | 57 | tempcmd := RemoveOperators(c.Raw) 58 | 59 | // If command has #for directive process it first 60 | 61 | if strings.Contains(tempcmd, "#for") { 62 | tempcmd = ProcessForDirective(c.UID, c.Raw) 63 | } 64 | 65 | for _, word := range stringutils.SplitAtSpace(tempcmd) { 66 | 67 | if strings.Contains(word, "#from") { 68 | ProcessFromDirective(word, c.UID) 69 | } else if strings.Contains(word, "#as") { 70 | ProcessAsDirective(word, c.UID) 71 | } else if strings.Contains(word, "@") || word == "@outfile" { 72 | sanitized := stringutils.ExtractVar(word) 73 | ProcessVariables(sanitized, c.UID) 74 | } 75 | } 76 | 77 | } 78 | 79 | func NewCommand(raw string, comment string) Command { 80 | 81 | z := Command{ 82 | Raw: raw, 83 | Comment: comment, 84 | } 85 | 86 | z.GenUID() 87 | 88 | return z 89 | } 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tarunKoyalwar/talosplus 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/disgoorg/disgo v0.10.2 7 | github.com/disgoorg/snowflake/v2 v2.0.0 8 | github.com/muesli/termenv v0.12.0 9 | github.com/spf13/cobra v1.4.0 10 | go.mongodb.org/mongo-driver v1.9.1 11 | ) 12 | 13 | require ( 14 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 15 | github.com/aymerick/douceur v0.2.0 // indirect 16 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect 17 | github.com/gorilla/css v1.0.0 // indirect 18 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 19 | github.com/microcosm-cc/bluemonday v1.0.20 // indirect 20 | github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 // indirect 21 | github.com/projectdiscovery/stringsutil v0.0.1 // indirect 22 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect 23 | github.com/spf13/pflag v1.0.5 // indirect 24 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect 25 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 26 | gopkg.in/yaml.v3 v3.0.1 // indirect 27 | ) 28 | 29 | require ( 30 | github.com/disgoorg/log v1.2.0 // indirect 31 | github.com/go-stack/stack v1.8.0 // indirect 32 | github.com/golang/snappy v0.0.1 // indirect 33 | github.com/klauspost/compress v1.13.6 // indirect 34 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 35 | github.com/mattn/go-isatty v0.0.14 // indirect 36 | github.com/mattn/go-runewidth v0.0.13 // indirect 37 | github.com/pkg/errors v0.9.1 // indirect 38 | github.com/projectdiscovery/goflags v0.1.1 39 | github.com/rivo/uniseg v0.2.0 // indirect 40 | github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect 41 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 42 | github.com/xdg-go/scram v1.0.2 // indirect 43 | github.com/xdg-go/stringprep v1.0.2 // indirect 44 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 45 | go.etcd.io/bbolt v1.3.6 46 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect 47 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 48 | golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect 49 | golang.org/x/text v0.3.7 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /SYNTAX.md: -------------------------------------------------------------------------------- 1 | 2 | ## Directives/Modules 3 | 4 | - Refer Below Table for available directives and their use 5 | 6 | | Directive | Syntax | Description | 7 | | -- | -- | -- | 8 | | **#dir** | `#dir:/path/to/directory` | Run Given Command in this directory | 9 | | **#from** | `#from:@varname` | Get Data from variable(`@varname`) and pass as stdin to cmd | 10 | | **#as**| `#as:@varname` | Export Output(Stdout Only) of Command to variable (`@varname`) | 11 | | **#for** | `#for:@arr:@i` | For each line(`@i`) in variable(`@arr`) run new command (Similar to **interlace**) | 12 | | **#ignore** | `#ignore` | Ignore Output of this command While showing Output | 13 | 14 | 15 | 16 | ## Variables 17 | 18 | Variables are like buffers/env-variable etc starting with `@` and are handled by golang and are thread-safe . All variables exported in script are saved to MongoDB thus it is possible to get output of a specific command in the middle of execution. Talosplus tries to ignore `Everything is a file` Linux Philosophy by abstracting file system and creating and deleting files at runtime based on the need. Below Table Contains Some operations that can be performed on variables. 19 | 20 | A Particular operation can be done on variable by supplying operator within `{}` 21 | 22 | |Operator| Use Case | Description | 23 | | -- | -- | -- | 24 | | **add** | `#as:@gvar{add}` | Append Output of command to `@gvar` variable | 25 | | **unique** | `#as:@gvar{unique}` | Append output of command to `@gvar` but all values are unique | 26 | | **file** | `@inscope{file}` | Create a Temp File with `@inscope` variable data and return path of that temp file | 27 | | **!file** | `@outscope{!file}` | Same as `file` but it can be empty | 28 | 29 | 30 | 31 | ## - Special Cases 32 | 33 | | Syntax | Example | Description | 34 | | -- | -- | -- | 35 | | `@outfile` | `subfinder ... -o @outfile` | Create a temp file(`@outfile`) and use content of file as output instead of stdout | 36 | | `@tempfile` | - | Create a temp file and return its path | 37 | | `@env` | `@env:DISCORD_TOKEN` | Get value of enviournment variable (Can also be done using `$`) | 38 | 39 | 40 | 41 | ## Writing Automation Scripts With Syntax 42 | To leverage all features of Talosplus like Auto Scheduling etc . It is essential the written bash script follows the syntax . Example of such bash script can be found at [subenum.sh](examples/subenum.sh) . 43 | 44 | In detail guide of how to write such scripts and using the syntax can be found at [blog](https://medium.com/@zealousme/create-your-ultimate-bug-bounty-automation-without-nerdy-bash-skills-part-2-c8cd72018922) -------------------------------------------------------------------------------- /pkg/shared/settings.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "strconv" 7 | 8 | "github.com/tarunKoyalwar/talosplus/pkg/alerts" 9 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 10 | ) 11 | 12 | // DefaultSettings : Self Explainatory 13 | var DefaultSettings *Settings = NewSettings() 14 | 15 | // Settings : All Settings and Env Variables 16 | type Settings struct { 17 | Purge bool // Skip Cached Data 18 | Limit int // Max Concurrent program 19 | ProjectExportName string // Name for directory containing all project exports 20 | CacheDIR string // Cache Directory (default : temp) 21 | ProjectName string // DIR Name where cache is saved 22 | } 23 | 24 | // ParseSettings : Parse Settings From Variables 25 | func (s *Settings) ParseSettings(m map[string]string) { 26 | for k, v := range m { 27 | 28 | if v == "" { 29 | continue 30 | } 31 | 32 | // Check for global settings 33 | switch k { 34 | case "@pname": 35 | s.ProjectName = v 36 | s.ProjectExportName = v + "exports" 37 | 38 | case "@cachedir": 39 | s.CacheDIR = v 40 | 41 | case "@purge": 42 | z, er := strconv.ParseBool(v) 43 | if er == nil { 44 | s.Purge = z 45 | } 46 | 47 | case "@notifytitle": 48 | if alerts.Alert != nil { 49 | alerts.Alert.Title = v 50 | } 51 | 52 | case "@disablenotify": 53 | z, er := strconv.ParseBool(v) 54 | if er == nil { 55 | if alerts.Alert != nil { 56 | alerts.Alert.Disabled = z 57 | } 58 | } 59 | 60 | case "@limit": 61 | z, er := strconv.Atoi(v) 62 | if er == nil { 63 | if z < 2 { 64 | z = 2 65 | } 66 | s.Limit = z 67 | } 68 | } 69 | 70 | } 71 | 72 | if s.ProjectName == "" { 73 | //warn and set as phontom 74 | ioutils.Cout.PrintWarning("Project Name Not Set Fallback: talos") 75 | s.ProjectName = "talos" 76 | s.ProjectExportName = "talosExports" 77 | 78 | } 79 | 80 | if s.CacheDIR == "" { 81 | ioutils.Cout.PrintWarning("CacheDIR Not Set Fallback: system temp directory") 82 | s.CacheDIR = os.TempDir() 83 | } 84 | 85 | } 86 | 87 | func NewSettings() *Settings { 88 | s := Settings{ 89 | Limit: 4, 90 | ProjectExportName: "talosExports", 91 | ProjectName: "talos", 92 | CacheDIR: os.TempDir(), 93 | } 94 | 95 | return &s 96 | } 97 | 98 | // CreateDirectoryIfNotExist : Creates Directory IF Not Present 99 | func (s *Settings) CreateDirectoryIfNotExist(directory string) (string, error) { 100 | wdir := path.Join(s.CacheDIR, directory) 101 | 102 | //check if cache dir exists and dir name 103 | _, err := os.Stat(wdir) 104 | 105 | if err != nil { 106 | 107 | //Create New DIrectory 108 | err := os.Mkdir(wdir, 0755) 109 | if err != nil { 110 | return "", err 111 | } 112 | } 113 | 114 | return wdir, nil 115 | } 116 | -------------------------------------------------------------------------------- /pkg/alerts/discord.go: -------------------------------------------------------------------------------- 1 | package alerts 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | 7 | "github.com/disgoorg/disgo/discord" 8 | "github.com/disgoorg/disgo/rest" 9 | "github.com/disgoorg/disgo/webhook" 10 | "github.com/disgoorg/snowflake/v2" 11 | ) 12 | 13 | var Alert *DiscordHook 14 | 15 | // DiscordHook : Struct to interact with discord hook 16 | type DiscordHook struct { 17 | Disabled bool 18 | Title string 19 | 20 | client webhook.Client 21 | 22 | iD string 23 | token string 24 | } 25 | 26 | // SendEmbed : Self Explainatory 27 | func (g *DiscordHook) SendEmbed(dat string, feilds map[string]string) error { 28 | 29 | if g.Disabled { 30 | return nil 31 | } 32 | 33 | var embed *discord.EmbedBuilder = discord.NewEmbedBuilder() 34 | 35 | if len(feilds) > 0 { 36 | for k, v := range feilds { 37 | embed.AddField(k, v, false) 38 | } 39 | } 40 | 41 | arr := FormatMsg(dat) 42 | 43 | if len(arr) < 5 { 44 | var er error = nil 45 | for _, v := range arr { 46 | 47 | e := discord.Embed{ 48 | Title: g.Title, 49 | Description: v, 50 | Type: discord.EmbedTypeArticle, 51 | 52 | Fields: embed.Fields, 53 | Color: NormColor, 54 | } 55 | 56 | _, er = g.client.CreateEmbeds([]discord.Embed{e}) 57 | 58 | } 59 | return er 60 | } else { 61 | wg := &sync.WaitGroup{} 62 | m := &sync.Mutex{} 63 | var er error 64 | 65 | for _, v := range arr { 66 | wg.Add(1) 67 | go func(x string) { 68 | defer wg.Done() 69 | e := discord.Embed{ 70 | Title: g.Title, 71 | Description: x, 72 | Type: discord.EmbedTypeArticle, 73 | 74 | Fields: embed.Fields, 75 | Color: NormColor, 76 | } 77 | 78 | _, z := g.client.CreateEmbeds([]discord.Embed{e}) 79 | if z != nil { 80 | m.Lock() 81 | er = z 82 | m.Unlock() 83 | } 84 | }(v) 85 | } 86 | 87 | wg.Wait() 88 | 89 | return er 90 | 91 | } 92 | 93 | } 94 | 95 | // SendErr : Self Explainatory 96 | func (g *DiscordHook) SendErr(er error, cmd string) error { 97 | 98 | if g.Disabled { 99 | return nil 100 | } 101 | 102 | f := discord.NewEmbedBuilder() 103 | f.AddField("CMD", cmd, false) 104 | 105 | e := discord.Embed{ 106 | Title: g.Title, 107 | Description: er.Error(), 108 | Type: discord.EmbedTypeArticle, 109 | 110 | Fields: f.Fields, 111 | Color: ErrColor, 112 | } 113 | 114 | _, er = g.client.CreateEmbeds([]discord.Embed{e}) 115 | 116 | return er 117 | 118 | } 119 | 120 | // NewDiscordHook : 121 | func NewDiscordHook(id string, token string) *DiscordHook { 122 | 123 | webid := snowflake.ID(0) 124 | 125 | no, err := strconv.Atoi(id) 126 | if err != nil { 127 | return nil 128 | } 129 | 130 | webid = snowflake.ID(no) 131 | 132 | clientx := webhook.NewClient(webid, token) 133 | 134 | g := DiscordHook{ 135 | client: clientx, 136 | token: token, 137 | iD: id, 138 | } 139 | 140 | rest.WithQueryParam("splitlines", "true") 141 | 142 | return &g 143 | } 144 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '32 12 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /pkg/shared/share.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/tarunKoyalwar/talosplus/pkg/db" 9 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 10 | ) 11 | 12 | var SharedVars *Shared = NewShared() 13 | 14 | // Shared : This contains data that is shared b/w commands 15 | type Shared struct { 16 | mutex *sync.Mutex // To avoid race conditons while sharing 17 | variables map[string]string // All variables + Runtime 18 | explicit map[string]bool // Explicitly declared Variable Names 19 | } 20 | 21 | func (e *Shared) AcquireLock() { 22 | e.mutex.Lock() 23 | } 24 | 25 | func (e *Shared) ReleaseLock() { 26 | e.mutex.Unlock() 27 | } 28 | 29 | // Exists : Check if Enviornemtn variable already exists 30 | func (e *Shared) Exists(key string) bool { 31 | _, ok := e.variables[key] 32 | return ok 33 | } 34 | 35 | // IsExplicitVar : Check If Variable Was Explicitly declared 36 | func (e *Shared) IsExplicitVar(key string) bool { 37 | _, ok := e.explicit[key] 38 | return ok 39 | } 40 | 41 | // Get : Self Explainatory 42 | func (e *Shared) Get(key string) (string, error) { 43 | if key == "" { 44 | return "", fmt.Errorf("empty key") 45 | } 46 | 47 | val := "" 48 | 49 | val = e.variables[key] 50 | 51 | if val == "" { 52 | return val, fmt.Errorf("empty value for key : %v", key) 53 | } 54 | 55 | return val, nil 56 | } 57 | 58 | // Set : Self Explainatory 59 | func (e *Shared) Set(key string, value string, explicit bool) error { 60 | 61 | if key == "" { 62 | return fmt.Errorf("empty key while setting") 63 | } 64 | 65 | // Must Trim Spaces Before setting 66 | // to avoid inconsistencies 67 | value = strings.TrimSpace(value) 68 | 69 | if value == "" { 70 | return fmt.Errorf("empty value while setting key %v", key) 71 | } 72 | 73 | e.variables[key] = value 74 | errx := db.DB.Put(key, value, explicit) 75 | if errx != nil { 76 | ioutils.Cout.PrintWarning("failed to save %v to db\n:%v", key, errx.Error()) 77 | } 78 | 79 | if explicit { 80 | e.explicit[key] = true 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // GetGlobalVars : Self Explainatory 87 | func (e *Shared) GetGlobalVars() map[string]string { 88 | 89 | z := map[string]string{} 90 | 91 | for k := range e.explicit { 92 | z[k] = e.variables[k] 93 | } 94 | 95 | return z 96 | 97 | } 98 | 99 | // AddGlobalVarsFromDB to Shared Instance 100 | func (e *Shared) AddGlobalVarsFromDB() { 101 | if db.DB == nil { 102 | return 103 | } 104 | 105 | // fmt.Printf("adding vars") 106 | 107 | z, err := db.DB.GetAllExplicit() 108 | if err != nil || len(z) == 0 { 109 | ioutils.Cout.PrintWarning("failed to add global vars %v", err) 110 | return 111 | } 112 | 113 | // fmt.Printf("total len %v\n", len(z)) 114 | for k, v := range z { 115 | e.variables[k] = v 116 | e.explicit[k] = true 117 | } 118 | 119 | } 120 | 121 | func NewShared() *Shared { 122 | 123 | s := Shared{ 124 | mutex: &sync.Mutex{}, 125 | variables: map[string]string{}, 126 | explicit: map[string]bool{}, 127 | } 128 | 129 | return &s 130 | 131 | } 132 | -------------------------------------------------------------------------------- /examples/subenum.sh: -------------------------------------------------------------------------------- 1 | // Subdomain Enumeration and Filtering 2 | 3 | // Note 4 | // This is just for demonstration 5 | // This script will probably failed because I have not released 6 | // Some of my own tools publicly yet and these filepaths are mine 7 | 8 | 9 | //Important File Paths 10 | @amasspassive = "/opt/tools/Amass/passive.ini" 11 | @resolvers = "/opt/wordlists/resolvers.txt" 12 | @dnsmicro = "/opt/wordlists/dnsbrute.txt" 13 | 14 | 15 | 16 | //Create Temporary Inscope File 17 | echo @rootsubs{file} #as:@inscopefile 18 | 19 | //Create Temporary OutofScope File 20 | echo @outscope{!file} #as:@outscopefile 21 | 22 | // ####---- Passive Subdomain Enumeration --- 23 | 24 | // assetfinder subdomains 25 | assetfinder -subs-only #from:@rootsubs #as:@passivesubs{unique} 26 | 27 | //Passive Amass Scan 28 | amass enum -df @inscopefile -blf @outscopefile -config @amasspassive -o @outfile #as:@passivesubs{unique} 29 | 30 | // Subfinder Enumeration 31 | subfinder -all -dL @inscopefile -nc -silent #as:@passivesubs{unique} 32 | 33 | // Findomain Subdomains 34 | findomain -f @inscopefile -o @outfile #as:@passivesubs{unique} 35 | 36 | // Filter inscope and outscope domains from passive enum 37 | subops filter --df @rootsubs{file} --ef @outscope{!file} #from:@passivesubs #as:@passive{unique} 38 | 39 | 40 | 41 | 42 | // ####--- Active Subdomain Enumeration 43 | 44 | // DNS BruteForce using PureDNS 45 | puredns bruteforce @dnsmicro -r @resolvers -w @outfile @z #for:@rootsubs:@z #as:@activesubs{unique} 46 | 47 | // Get Subdomains from TLS Certificates 48 | cero -p 443,4443,8443,10443 -c 1000 #from:@passive #as:@activesubs{unique} 49 | 50 | // Get Subdomains From CNAMEs 51 | dnsx -retry 3 -cname -l @passivesubs{file} -o @outfile #as:@activesubs{unique} 52 | 53 | // Filter inscope and outscope domains from active enum 54 | subops filter --df @rootsubs{file} --ef @outscope{!file} #from:@activesubs #as:@active{unique} 55 | 56 | 57 | // Merge Results From Active and Passive Sources 58 | 59 | 60 | //Check If Active Sub Enum Was Useful 61 | bninja diff @active{file} @passive{file} -1 #as:@uniqueactivesubs #notifylen{Total Subdomain Found From Active Enum but Not Passive :} 62 | 63 | //Merge passive and activesubs 64 | cat @passive{file} @active{file} | bninja uniq #as:@allsubs 65 | 66 | //Check For Subdomain Takeover If in Scope 67 | subjack -w @allsubs{file} -t 100 -timeout 30 -o @outfile -ssl 68 | 69 | 70 | // ###---- Filtering Found Subdomains 71 | 72 | //Resolve All Passive Found Subdomains 73 | rusolver -r @resolvers -i #from:@allsubs #as:@resolved 74 | 75 | //Filter Ips Pointing to Private Ip Addresses 76 | rusolverfilter -sub-only #from:@resolved #as:@filtered #notifylen{Total Resolved Subdomains: } 77 | 78 | //Subdomins Hosting Web Services API,Web Page etc 79 | httpx-pd -silent -t 100 -retries 2 #from:@filtered #as:@webraw #notifylen{Total Subdomains With Web Services :} 80 | 81 | //remove https from httpx output 82 | cut -d "/" -f 3 #from:@webraw #as:@webservices 83 | 84 | //Subdomains Not Hosting Web Services 85 | bninja diff @filtered{file} @webservices{file} -1 #as:@nonwebsubs #notifylen{Total Non Web Subs } 86 | 87 | //Port Scan All Non Web 88 | naabu -ec -ep 21,22,80,443 -verify -nmap-cli "nmap -sC -sV" -o @outfile -c 50 -list @nonwebsubs{file} #as:@nonwebportscan #notify:{Non Web Port Scan Results} 89 | 90 | -------------------------------------------------------------------------------- /pkg/shell/basic.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strings" 11 | 12 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 13 | ) 14 | 15 | // BashBuiltInCmds : Bash Interpreter Built in commands like for ,if etc 16 | var BashBuiltInCmds map[string]bool = map[string]bool{ 17 | "for": true, 18 | "if": true, 19 | } 20 | 21 | // SimpleCMD : Wrapper for std lib exec.CMD With Some other features 22 | type SimpleCMD struct { 23 | Cmdsplit []string //Command split using space 24 | CErrStream bytes.Buffer // Error Stream 25 | COutStream bytes.Buffer // Stdout Stream 26 | CPipeIn bytes.Buffer // Pass Data Using Pipe 27 | DIR string // DIR in which CMD should be run 28 | Failed bool // If Command Failed due to a reason 29 | } 30 | 31 | // CheckInstall : Check if Program Binary Exist Either in Path or DIR 32 | func (s *SimpleCMD) CheckInstall() (string, error) { 33 | 34 | //Check if command is built in shell interpreter 35 | if BashBuiltInCmds[s.Cmdsplit[0]] { 36 | return s.Cmdsplit[0], nil 37 | } 38 | 39 | //check for cmd in 40 | cpath, err := exec.LookPath(s.Cmdsplit[0]) 41 | if err == nil { 42 | return cpath, err 43 | } 44 | if s.DIR != "" { 45 | // Program Is Not Installed In Path 46 | p := path.Join(s.DIR, s.Cmdsplit[0]) 47 | if _, err := os.Stat(p); err == nil { 48 | 49 | return p, err 50 | } 51 | } 52 | 53 | return "", fmt.Errorf("%v Not Found in path or dir", s.Cmdsplit[0]) 54 | 55 | } 56 | 57 | // Run : Run Command Get Error 58 | func (s *SimpleCMD) Run() error { 59 | // ioutils.Cout.PrintWarning("running %v from cmdwrap", s.Cmdsplit) 60 | 61 | // Just a precaution to avoid FP in results 62 | _, er := s.CheckInstall() 63 | 64 | if er != nil { 65 | ioutils.Cout.PrintWarning("did not find %v", er) 66 | s.Failed = true 67 | return er 68 | } 69 | 70 | var c exec.Cmd 71 | 72 | //Its better to use sh -c Instead of Normal 73 | //Doing this allows piping and all features with it 74 | //Without that we can only run 1 command 75 | 76 | if s.DIR != "" { 77 | 78 | c = *exec.Command("/bin/sh", "-c", strings.Join(s.Cmdsplit, " ")) 79 | c.Dir = s.DIR 80 | } else { 81 | c = *exec.Command("/bin/sh", "-c", strings.Join(s.Cmdsplit, " ")) 82 | } 83 | 84 | c.Stdout = &s.COutStream 85 | c.Stderr = &s.CErrStream 86 | 87 | if s.CPipeIn.Len() > 1 { 88 | 89 | stdin, err := c.StdinPipe() 90 | if err != nil { 91 | ioutils.Cout.PrintWarning("[error] %v", err) 92 | s.Failed = true 93 | return err 94 | } 95 | func() { 96 | defer stdin.Close() 97 | io.WriteString(stdin, s.CPipeIn.String()) 98 | }() 99 | } 100 | 101 | err := c.Run() 102 | 103 | if s.COutStream.Len() > 0 || s.CErrStream.Len() > 0 { 104 | err = nil 105 | } 106 | 107 | // Identify Cases When Command returns error or panics 108 | if s.CErrStream.Len() > 0 { 109 | trimmedcout := strings.TrimSpace(s.COutStream.String()) 110 | 111 | if len(trimmedcout) == 0 { 112 | dat := s.CErrStream.String() 113 | 114 | dat = strings.TrimSpace(dat) 115 | 116 | if len(dat) > 0 { 117 | err = fmt.Errorf("%v", dat) 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | if err != nil { 125 | s.Failed = true 126 | return err 127 | } 128 | 129 | return err 130 | 131 | } 132 | 133 | // UseStdin : Uses provided string as stdin 134 | func (s *SimpleCMD) UseStdin(d string) { 135 | 136 | //skipping errs since stdin usually is safe 137 | s.CPipeIn.WriteString(d) 138 | } 139 | -------------------------------------------------------------------------------- /pkg/internal/init.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 8 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 9 | ) 10 | 11 | // ParseScript : This is entry point and all parsing is done from here 12 | func ParseScript(s string) []Command { 13 | 14 | lines := stringutils.Split(s, '\n') 15 | 16 | //convert multiline cmds to single line 17 | 18 | singlelines := parseMultiLine(lines) 19 | 20 | cmdarr := parseScriptLines(singlelines) 21 | 22 | // Parse Settings from Global Vars 23 | vars := shared.SharedVars.GetGlobalVars() 24 | shared.DefaultSettings.ParseSettings(vars) 25 | 26 | //all parsing done 27 | 28 | return cmdarr 29 | 30 | } 31 | 32 | // parseCommands : Parse Commands and Load Settings + Env Variables 33 | func parseScriptLines(arr []string) []Command { 34 | 35 | cmds := []Command{} 36 | 37 | //Get Comment Commands and Global Variables 38 | for i := 0; i < len(arr); i++ { 39 | 40 | v := arr[i] 41 | 42 | // Check if Line has any comments 43 | z := strings.Index(v, "//") 44 | 45 | if z != -1 { 46 | //remove anything after // 47 | v = strings.TrimSpace(v[:z]) 48 | 49 | // If line only had a comment skip 50 | if v == "" { 51 | continue 52 | } 53 | } 54 | 55 | //Parse Variables at top of script 56 | if strings.HasPrefix(v, "@") { 57 | splitd := stringutils.Split(v, '=') 58 | 59 | if len(splitd) == 2 { 60 | key := strings.TrimSpace(splitd[0]) 61 | value := strings.TrimSpace(splitd[1]) 62 | 63 | // remove outer quotes from value 64 | value = strings.Trim(value, "\"") 65 | 66 | // Add variable to shared vars 67 | 68 | // Malformed variables are ignored 69 | shared.SharedVars.Set(key, value, true) 70 | 71 | } 72 | continue 73 | } 74 | 75 | x := NewCommand(v, "") 76 | 77 | // Extract any comment given to command by 78 | // parsing previous line 79 | if i-1 >= 0 { 80 | 81 | lastline := strings.TrimSpace(arr[i-1]) 82 | 83 | if strings.HasPrefix(lastline, "//") { 84 | x.Comment = strings.TrimLeft(lastline, "//") 85 | } 86 | } 87 | 88 | cmds = append(cmds, x) 89 | 90 | } 91 | 92 | return cmds 93 | 94 | } 95 | 96 | // arseMutliLine : Convert MultLine Commands to single line 97 | func parseMultiLine(arr []string) []string { 98 | 99 | /* 100 | Changes a multi line command to single line 101 | 102 | Ex : 103 | 104 | for i in {1..9}; do; \ 105 | printf "no $i"\ 106 | done; 107 | 108 | for i in {1..9}; do; printf "no $i"; done; 109 | */ 110 | 111 | var sb strings.Builder 112 | 113 | newarr := []string{} 114 | 115 | for _, v := range arr { 116 | 117 | v = strings.TrimSpace(v) 118 | 119 | if strings.HasSuffix(v, `\`) { 120 | sb.WriteString(v[:len(v)-1]) 121 | } else { 122 | // if buffer is not empty 123 | if sb.Len() != 0 { 124 | newarr = append(newarr, sb.String()+" "+v) 125 | sb.Reset() 126 | } else { 127 | 128 | newarr = append(newarr, v) 129 | } 130 | } 131 | 132 | } 133 | 134 | return newarr 135 | 136 | } 137 | 138 | func GetVariableName(varname string) string { 139 | //first check if varname has anyoperation linked 140 | re := regexp.MustCompile("{.*}") 141 | 142 | matched := re.FindStringSubmatchIndex(varname) 143 | 144 | if len(matched) != 2 { 145 | 146 | return varname 147 | } 148 | 149 | // Find and process operators 150 | // Ex: add , unique etc 151 | varname = varname[:matched[0]] 152 | 153 | return varname 154 | 155 | } 156 | -------------------------------------------------------------------------------- /test/shell_test/simple_test.go: -------------------------------------------------------------------------------- 1 | package shelltest_test 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/tarunKoyalwar/talosplus/pkg/db" 9 | "github.com/tarunKoyalwar/talosplus/pkg/shell" 10 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 11 | ) 12 | 13 | func Test_Echo(t *testing.T) { 14 | 15 | g := shell.NewCMDWrap("echo 'hello world'", "") 16 | g.Process() 17 | 18 | if err := g.Execute(); err != nil { 19 | HandleErrors(err, t, "Failed to Run Command %v\n returned %v", g.Raw, err) 20 | } 21 | 22 | out := strings.TrimSpace(g.CMD.COutStream.String()) 23 | if out != "hello world" { 24 | t.Errorf("Failed to run echo\n received %v Instead of hello world", out) 25 | } else { 26 | t.Logf("Passed Echo Command Test") 27 | } 28 | } 29 | 30 | func Test_File_Export(t *testing.T) { 31 | g := shell.NewCMDWrap(" cat /etc/passwd | grep ':0:' | cut -d ':' -f 1| tee @outfile #as:@etctest ", "") 32 | g.Process() 33 | 34 | if err := g.Execute(); err != nil { 35 | HandleErrors(err, t, "Failed to run cmd %v", g.Raw) 36 | } 37 | 38 | reqout, er1 := shell.Buffers.Get("@etctest") 39 | if er1 != nil { 40 | HandleErrors(er1, t, "Failed to get Exported Value of a cmd") 41 | } 42 | 43 | if reqout != "root" { 44 | t.Errorf("Failed to test file export\n Expected root but received %v", reqout) 45 | } else { 46 | t.Logf("Passed File Export test Using `@outfile`") 47 | } 48 | 49 | } 50 | 51 | func Test_Supply_Stdin(t *testing.T) { 52 | shell.Buffers.Set("@stdincheck", "Passed to Stdin", true) 53 | g := shell.NewCMDWrap("tee /dev/null #from:@stdincheck", "") 54 | g.Process() 55 | 56 | if err := g.Execute(); err != nil { 57 | HandleErrors(err, t, "Failed to Run Command %v\n returned %v", g.Raw, err) 58 | } 59 | 60 | out := strings.TrimSpace(g.CMD.COutStream.String()) 61 | if out != "Passed to Stdin" { 62 | t.Errorf("Expected `Passed to Stdin` but received `%v`", out) 63 | } else { 64 | t.Logf("Passed Pipe Stdin") 65 | } 66 | } 67 | 68 | func Test_CMD_Export(t *testing.T) { 69 | g := shell.NewCMDWrap("echo 'hello world' #as:@echoexport", "") 70 | g.Process() 71 | 72 | if err := g.Execute(); err != nil { 73 | HandleErrors(err, t, "Failed to Run Command %v\n returned %v", g.Raw, err) 74 | } 75 | 76 | out, er1 := shell.Buffers.Get("@echoexport") 77 | if er1 != nil { 78 | HandleErrors(er1, t, "Failed to get Exported Value of a cmd") 79 | } 80 | if out != "hello world" { 81 | t.Errorf("Failed Basic_Export_Command Test received %v Instead of hello world", out) 82 | } else { 83 | t.Log("Passed Command Export Test") 84 | } 85 | } 86 | 87 | // func Test_Alerts(t *testing.T) { 88 | // id := os.Getenv("DISCORD_WID") 89 | // tok := os.Getenv("DISCORD_WTOKEN") 90 | 91 | // alerts.Alert = alerts.NewDiscordHook(id, tok) 92 | 93 | // alerts.Alert.Title = "Testing" 94 | 95 | // g := shell.NewCMDWrap("echo 'Hello Luci!!' #notify{Test Output}", "") 96 | // g.Process() 97 | 98 | // if err := g.Execute(); err != nil { 99 | // HandleErrors(err, t, "Failed to Run Command %v\n returned %v", g.Raw, err) 100 | // } 101 | 102 | // // g.Export() 103 | 104 | // } 105 | 106 | func HandleErrors(er error, t *testing.T, msg string, a ...any) { 107 | if er != nil { 108 | t.Logf(msg, a...) 109 | t.Errorf(msg, a...) 110 | t.Fatal(er) 111 | } 112 | } 113 | 114 | func TestMain(m *testing.M) { 115 | val := os.Getenv("RUN_MONGODB_TEST") 116 | 117 | if val == "" { 118 | db.UseBBoltDB(os.TempDir(), stringutils.RandomString(6)+".db", "Complex_test") 119 | } else { 120 | db.UseMongoDB("", stringutils.RandomString(6)+".db", "Complex_test") 121 | } 122 | 123 | m.Run() 124 | } 125 | -------------------------------------------------------------------------------- /pkg/shell/notify.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/tarunKoyalwar/talosplus/pkg/alerts" 9 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 10 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 11 | ) 12 | 13 | // Notifications : Sends and parses notifications for each cmd 14 | type Notifications struct { 15 | NotifyEnabled bool //to check if notification should be sent 16 | NotifyLen bool //Only Notify Len and Not Entire Text 17 | NotifyMsg string //Msg to be sent along with Notify 18 | Raw string 19 | Comment string // Comment 20 | } 21 | 22 | // Parse : Parse Notification Settings 23 | func (n *Notifications) Parse(raw string) { 24 | /* 25 | 26 | Syntax1: 27 | #notify{Some Notification Text} 28 | 29 | Response: 30 | Some Notification Text : %stdout 31 | 32 | Default: 33 | Results for %comment : %stdout 34 | 35 | 36 | Syntax2: 37 | 38 | #notifylen{Some Notification Text} 39 | 40 | Response: 41 | Some Notification Text len(%stdout) 42 | 43 | Default: 44 | Found Total for %comment : len(%stdout) 45 | 46 | 47 | #Parse Steps 48 | 1. Identify if #notifiy exists 49 | 2. Extract notify msg 50 | 3. Set Default Messages 51 | 52 | */ 53 | 54 | temp := raw 55 | 56 | if strings.Contains(temp, "#notify") { 57 | n.NotifyEnabled = true 58 | 59 | // This will extract message 60 | regex := `(#notify|#notifylen){(?P.*?)}` 61 | 62 | re := regexp.MustCompile(regex) 63 | 64 | matches := re.FindStringSubmatch(temp) 65 | 66 | if len(matches) == 3 { 67 | group1 := matches[1] 68 | n.NotifyMsg = matches[2] 69 | 70 | if group1 == "#notifylen" { 71 | n.NotifyLen = true 72 | } 73 | 74 | //remove #notify from string 75 | n.Raw = strings.ReplaceAll(temp, matches[0], "") 76 | return 77 | 78 | } else { 79 | // cmd only has #notify no msg was specified 80 | n.NotifyMsg = "" 81 | 82 | if strings.Contains(temp, "#notifylen") { 83 | n.NotifyLen = true 84 | n.Raw = strings.ReplaceAll(temp, "#notifylen", "") 85 | } else { 86 | n.Raw = strings.ReplaceAll(temp, "#notify", "") 87 | } 88 | 89 | if n.NotifyLen { 90 | n.NotifyMsg = "Found Total for\n" + n.Comment + " : " 91 | } else { 92 | n.NotifyMsg = "Results for \n" + n.Comment + "\n" 93 | } 94 | 95 | } 96 | } else { 97 | n.Raw = raw 98 | } 99 | 100 | } 101 | 102 | // Notify : Notifies completion of command 103 | func (n *Notifications) Notify(dat string) { 104 | 105 | // fmt.Printf("\n\nCalled Notify with %v\n\n", dat) 106 | 107 | if dat == "" { 108 | ioutils.Cout.PrintWarning("Empty String") 109 | return 110 | } 111 | 112 | var senderr error 113 | 114 | if !n.NotifyEnabled { 115 | return 116 | } 117 | 118 | if alerts.Alert == nil { 119 | return 120 | } 121 | 122 | if n.NotifyLen { 123 | final := fmt.Sprintf("%v %v\n", n.NotifyMsg, len(stringutils.Split(dat, '\n'))) 124 | 125 | senderr = alerts.Alert.SendEmbed(final, map[string]string{ 126 | "CMD": n.Raw, 127 | }) 128 | 129 | } else { 130 | final := fmt.Sprintf("%v\n%v\n", n.NotifyMsg, dat) 131 | 132 | senderr = alerts.Alert.SendEmbed(final, map[string]string{ 133 | "CMD": n.Raw, 134 | }) 135 | } 136 | 137 | if senderr != nil { 138 | ioutils.Cout.PrintWarning("Failed to send notification\n%v", senderr) 139 | } 140 | 141 | } 142 | 143 | // NotifyErr : Notifies Err 144 | func (n *Notifications) NotifyErr(er error) {} 145 | 146 | func NewNotification(raw string, comment string) (*Notifications, string) { 147 | z := Notifications{ 148 | Comment: comment, 149 | } 150 | 151 | z.Parse(raw) 152 | 153 | return &z, z.Raw 154 | } 155 | -------------------------------------------------------------------------------- /pkg/db/db_intergration_test.go: -------------------------------------------------------------------------------- 1 | package db_test 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "reflect" 7 | "strings" 8 | "testing" 9 | "time" 10 | 11 | "github.com/tarunKoyalwar/talosplus/pkg/db" 12 | ) 13 | 14 | func Test_DBProvider(t *testing.T) { 15 | // Check if all methods of DB Provider are working correctly 16 | mongodb := os.Getenv("USE_MONGODB") 17 | 18 | dbname := randomString(6) 19 | 20 | if mongodb == "" { 21 | // Use bbolt embedded db 22 | db.UseBBoltDB(os.TempDir(), dbname, "talosplus") 23 | t.Logf("Using BBoltDB as DB") 24 | } 25 | 26 | testcases := []struct { 27 | key string 28 | value string 29 | explicit bool 30 | }{ 31 | {"pd", "subfinder", true}, 32 | {"owasp", "amass", false}, 33 | {"portswigger", "burpsuite", true}, 34 | } 35 | 36 | // Test Database Provider call `Put` 37 | 38 | t.Logf("Loading testcases for Put Call") 39 | 40 | for _, tc := range testcases { 41 | er1 := db.DB.Put(tc.key, tc.value, tc.explicit) 42 | if er1 != nil { 43 | t.Errorf("failed to save variable to db %v for %v when %v", er1, db.DB.ProviderName(), tc) 44 | } 45 | 46 | } 47 | 48 | t.Logf("Test for Put Method Successful") 49 | 50 | // get test 51 | 52 | val, err := db.DB.Get("pd") 53 | 54 | if val != "subfinder" || err != nil { 55 | t.Errorf("failed to get value of pd expected `subfinder` but got %v with error %v", val, err) 56 | } 57 | 58 | val2, err2 := db.DB.Get("owasp") 59 | 60 | if val2 != "amass" || err2 != nil { 61 | t.Errorf("failed to get value of owasp expected `amass` but fot %v with error %v", val2, err2) 62 | } 63 | 64 | // Test for GetAllVarNames 65 | 66 | t.Logf("Loading Test for GetAllVarNames Method") 67 | 68 | expected_allvars := map[string]bool{ 69 | "pd": true, 70 | "owasp": true, 71 | "portswigger": true, 72 | } 73 | 74 | allvars, erx := db.DB.GetAllVarNames() 75 | 76 | if !reflect.DeepEqual(allvars, expected_allvars) || erx != nil { 77 | t.Errorf("failed to getallvarNames expected %v but got %v with error %v", expected_allvars, allvars, erx) 78 | } 79 | 80 | t.Logf("Test for GetAllVarNames Successful") 81 | 82 | // Test for GetAllImplicit 83 | t.Logf("Loading Test for GetAllImplicit Method") 84 | 85 | expected_implicitvars := map[string]string{ 86 | "owasp": "amass", 87 | } 88 | 89 | implicitvars, erx2 := db.DB.GetAllImplicit() 90 | 91 | if !reflect.DeepEqual(implicitvars, expected_implicitvars) || erx2 != nil { 92 | t.Errorf("failed to getimplicitvars expected %v but got %v with error %v", expected_implicitvars, implicitvars, erx2) 93 | } 94 | 95 | t.Logf("Test for GetAllImplicit Successful") 96 | 97 | // Test for GetAllExplicit 98 | 99 | t.Logf("Loading Test for GetAllExplicit Method") 100 | 101 | expected_explicitvars := map[string]string{ 102 | "pd": "subfinder", 103 | "portswigger": "burpsuite", 104 | } 105 | 106 | explicitvars, erx3 := db.DB.GetAllExplicit() 107 | 108 | if !reflect.DeepEqual(explicitvars, expected_explicitvars) || erx3 != nil { 109 | t.Errorf("failed to getallexplicitvars expected %v but got %v with error %v", expected_explicitvars, explicitvars, erx3) 110 | } 111 | 112 | t.Logf("Test for GetAllExplicit Successful") 113 | 114 | err = db.DB.Close() 115 | 116 | if err != nil { 117 | t.Errorf("failed to properly close db connection") 118 | } 119 | 120 | } 121 | 122 | // randomString : Generates random strings of given length with Uppercase charset 123 | func randomString(size int) string { 124 | rand.Seed(time.Now().UnixNano()) 125 | chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 126 | var b strings.Builder 127 | for i := 0; i < size; i++ { 128 | b.WriteRune(chars[rand.Intn(len(chars))]) 129 | } 130 | str := b.String() 131 | 132 | return str 133 | } 134 | -------------------------------------------------------------------------------- /test/core_test/complex_test.go: -------------------------------------------------------------------------------- 1 | package coretest_test 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "reflect" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/tarunKoyalwar/talosplus/pkg/core" 15 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 16 | ) 17 | 18 | // Tests related to parsing a script 19 | func Test_Script(t *testing.T) { 20 | 21 | /* 22 | basicscript is a very lightweight bash script 23 | that is expected to run on every linux 24 | intended to only check if all features of `talos` 25 | are operating correctly 26 | */ 27 | 28 | basicscript := ` 29 | @purge = true 30 | @limit = 3 31 | 32 | // run basic linux commands to test all features 33 | 34 | // get short linux kernel details 35 | cat /proc/version | cut -d " " -f 1-3 #as:@kernel 36 | 37 | // save data to file and get filename 38 | echo @kernel{file} #as:@fileaddr 39 | 40 | // add random data to file 41 | echo "talos file" >> @fileaddr 42 | 43 | // read details from file and save to buffer 44 | cat @fileaddr | tee @outfile #as:@alldata 45 | 46 | // pass data to stdin and do some filtering 47 | grep -i Linux #from:@alldata | cut -d " " -f 1 #as:@reqout 48 | 49 | // save to temp directory and filename as moriningstar 50 | cp @reqout{file} /tmp/morningstar 51 | ` 52 | 53 | /* 54 | Commands are dependent on one another 55 | if everything executes perfectly and there is a file at /tmp/morningstar 56 | with data `Linux` then script parsing , scheduling and all other functions are working 57 | correctly 58 | */ 59 | 60 | s := core.NewVolatileEngine() 61 | 62 | s.Compile(basicscript) 63 | 64 | s.Evaluate() 65 | 66 | s.Schedule() 67 | 68 | s.PrintAllCMDs() 69 | 70 | s.Execute() 71 | 72 | if _, err := os.Stat("/tmp/morningstar"); err != nil { 73 | t.Fatalf("Test for bash script failed\n any/all functions are not working") 74 | } 75 | 76 | dat, err2 := ioutil.ReadFile("/tmp/morningstar") 77 | if err2 != nil { 78 | t.Fatalf("Test for bash script failed\n any/all functions are not working %v", err2) 79 | } 80 | 81 | expected := string(bytes.TrimSpace(dat)) 82 | 83 | if expected != "Linux" { 84 | t.Fatalf("Content Mismatch\n Some Functions are not working properly\ngot `%v` expected `Linux`", expected) 85 | } else { 86 | t.Log("Core Test Passed , talos is working properly") 87 | } 88 | 89 | // if everything is fine delete the file 90 | os.Remove("/tmp/morningstar") 91 | 92 | } 93 | 94 | // Test For Loop 95 | func Test_For(t *testing.T) { 96 | 97 | s := core.NewVolatileEngine() 98 | 99 | sample := "" 100 | for i := 0; i < 10; i++ { 101 | sample = sample + strconv.Itoa(i) + "\n" 102 | } 103 | 104 | shared.SharedVars.Set("@sequence", sample, true) 105 | 106 | bscript := ` 107 | // Just Testing For Loop 108 | echo @z | tee /tmp/@z #for:@sequence:@z #as:@forout{add} 109 | ` 110 | 111 | s.Compile(bscript) 112 | 113 | s.Evaluate() 114 | 115 | s.Schedule() 116 | 117 | s.Execute() 118 | 119 | out, er := shared.SharedVars.Get("@forout") 120 | 121 | if er != nil { 122 | panic(er) 123 | } 124 | 125 | arr := strings.Fields(out) 126 | 127 | sort.Strings(arr) 128 | 129 | log.Printf("Got : %v\n", arr) 130 | 131 | orig := strings.Fields(sample) 132 | 133 | if !reflect.DeepEqual(orig, arr) { 134 | t.Errorf("Expected sequence not matched got %v", arr) 135 | } else { 136 | t.Logf("test of for directive working successful") 137 | } 138 | 139 | } 140 | 141 | // func Test_sizes(t *testing.T) { 142 | // z := shell.CMDWrap{CMD: &shell.SimpleCMD{ 143 | // COutStream: *bytes.NewBufferString("Yup tons of data"), 144 | // }} 145 | 146 | // z2 := &shell.CMDWrap{ 147 | // CMD: &shell.SimpleCMD{ 148 | // COutStream: *bytes.NewBufferString("Yup tons of data"), 149 | // }, 150 | // } 151 | 152 | // t.Logf("Size of struct is %v\n", unsafe.Sizeof(z)) 153 | 154 | // t.Logf("Size of struct is %v\n", unsafe.Sizeof(z2)) 155 | // } 156 | -------------------------------------------------------------------------------- /pkg/internal/directives.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 8 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 9 | ) 10 | 11 | //This will contain actions for each directives 12 | 13 | // ProcessForDirective : Process For Directive 14 | func ProcessForDirective(uid string, cmd string) string { 15 | /* 16 | #for 17 | For directive works similar to for each loop 18 | 19 | [+] Syntax 20 | #for:@arr:@z 21 | Here 22 | 23 | @z : Loop Variable 24 | @arr : Array to Loop Over 25 | 26 | 27 | [+] Steps 28 | 1. identify array variable(@arr) and loop variable(@z) 29 | 2. register uid as dependent of @arr in the registry in shared package 30 | 3. remove all loop variables from cmd 31 | 4. return cmd to process other directives 32 | */ 33 | 34 | returncmd := "" 35 | loopvar := "" 36 | 37 | for _, v := range stringutils.SplitAtSpace(cmd) { 38 | 39 | if strings.Contains(v, "#for") { 40 | datx := strings.TrimLeft(v, "#for:") 41 | splitfor := strings.Split(datx, ":") 42 | 43 | if len((splitfor)) == 2 { 44 | arrayvar := splitfor[0] 45 | loopvar = splitfor[1] 46 | 47 | arrayvar = GetVariableName(arrayvar) 48 | loopvar = GetVariableName(loopvar) 49 | 50 | // register uid as dependent 51 | shared.DefaultRegistry.Registerdep(uid, arrayvar) 52 | } 53 | } else { 54 | returncmd += v + " " 55 | } 56 | } 57 | 58 | // remove all loop vars 59 | returncmd = strings.ReplaceAll(returncmd, loopvar, "") 60 | 61 | return returncmd 62 | 63 | } 64 | 65 | // ProcessFromDirective : Process From Directive 66 | func ProcessFromDirective(word string, uid string) { 67 | /* 68 | #from 69 | Use given variable data as stdin for this command 70 | and mark dependency 71 | 72 | [+] Syntax 73 | #from:@gvar 74 | 75 | Here 76 | 77 | @gvar : data source for this command 78 | 79 | 80 | [+] Steps 81 | 1. Just mark this cmd as dependent of that variable 82 | 83 | */ 84 | gvar := strings.TrimLeft(word, "#from:") 85 | 86 | // register uid as dependent 87 | shared.DefaultRegistry.Registerdep(uid, gvar) 88 | } 89 | 90 | // ProcessAsDirective : Process As Directive 91 | func ProcessAsDirective(word string, uid string) { 92 | /* 93 | #as 94 | marks command as exporter of this given variable 95 | 96 | [+] Syntax 97 | #as:@somevar 98 | 99 | Here 100 | 101 | @somevar : output of this command will be exported to @somevar 102 | 103 | 104 | [+] Steps 105 | 1. Just mark this cmd as Exporter of that variable 106 | 107 | */ 108 | 109 | word = GetVariableName(word) 110 | 111 | somevar := strings.TrimLeft(word, "#as:") 112 | 113 | // register uid as exporter 114 | shared.DefaultRegistry.RegisterExport(uid, somevar) 115 | 116 | } 117 | 118 | // ProcessVariables : Process Variables @something 119 | func ProcessVariables(word string, uid string) { 120 | /* 121 | [+] Syntax 122 | @varname or @outfile 123 | 124 | Variables have two different categories 125 | 126 | Here 127 | 128 | @outfile : 129 | There are many commands that store the filtered and required output 130 | to a file instead of printing it on stdout. In such cases using 131 | @outfile will use content of file as output instead of stdout 132 | 133 | @varname : use value of the variable 134 | Ex : curl -v @exploiturl 135 | 136 | 137 | [+] Steps 138 | 1. Just mark this cmd as dependent of that variable 139 | 140 | */ 141 | word = GetVariableName(word) 142 | 143 | if word == "@outfile" { 144 | //ignore nothing to do 145 | return 146 | } 147 | 148 | // register uid as exporter 149 | shared.DefaultRegistry.Registerdep(uid, word) 150 | } 151 | 152 | // RemoveOperators : Removes Ops from variables 153 | func RemoveOperators(raw string) string { 154 | /* 155 | variables can have operators like 156 | 157 | @gvar{file} @gvar{unique} @gvar{add} 158 | 159 | 160 | remove all operators from command using regex 161 | 162 | */ 163 | 164 | re := regexp.MustCompile("{[^(}|@)]*}") 165 | 166 | return re.ReplaceAllLiteralString(raw, "") 167 | } 168 | -------------------------------------------------------------------------------- /pkg/internal/process.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io/ioutil" 5 | "path" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 10 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 11 | ) 12 | 13 | // ProcessVariable: Process Variable and get value required by processor 14 | func ProcessVariable(varname string) (string, error) { 15 | 16 | SharedVars := shared.SharedVars 17 | DefaultSettings := shared.DefaultSettings 18 | 19 | //first check if varname has anyoperation linked 20 | re := regexp.MustCompile("{.*}") 21 | 22 | matched := re.FindStringSubmatchIndex(varname) 23 | 24 | if len(matched) != 2 { 25 | 26 | got, er := SharedVars.Get(varname) 27 | return got, er 28 | 29 | } 30 | 31 | // Find and process operators 32 | // Ex: add , unique etc 33 | ops := varname[matched[0]+1 : matched[1]-1] 34 | varname = varname[:matched[0]] 35 | 36 | for _, v := range strings.Split(strings.TrimSpace(ops), ",") { 37 | if v == "file" { 38 | //save data of variable to file and get address of file 39 | addr, er1 := DefaultSettings.CreateDirectoryIfNotExist(DefaultSettings.ProjectExportName) 40 | if er1 != nil { 41 | return "", er1 42 | } 43 | reqpath := path.Join(addr, stringutils.RandomString(8)) 44 | 45 | got, err := SharedVars.Get(varname) 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | //write to file 51 | er2 := ioutil.WriteFile(reqpath, []byte(got), 0644) 52 | if er2 != nil { 53 | return reqpath, er2 54 | } 55 | return reqpath, nil 56 | 57 | //Allow Even if Input is empty 58 | } else if v == "!file" { 59 | //save data of variable to file and get address of file 60 | addr, er1 := DefaultSettings.CreateDirectoryIfNotExist(DefaultSettings.ProjectExportName) 61 | if er1 != nil { 62 | return "", er1 63 | } 64 | reqpath := path.Join(addr, stringutils.RandomString(8)) 65 | 66 | got, err := SharedVars.Get(varname) 67 | if err != nil { 68 | if err.Error() != "empty value" { 69 | return varname, err 70 | } 71 | } 72 | 73 | //write to file 74 | er2 := ioutil.WriteFile(reqpath, []byte(got), 0644) 75 | if er2 != nil { 76 | return reqpath, er2 77 | } 78 | return reqpath, nil 79 | } 80 | } 81 | 82 | return "", nil 83 | } 84 | 85 | // CompleteOperation : Update Value and Complete Operation such as {add} etc 86 | func CompleteOperation(varname string, currvalue string) { 87 | shared.SharedVars.AcquireLock() 88 | defer shared.SharedVars.ReleaseLock() 89 | 90 | // ioutils.Cout.PrintWarning("got %v and %v\n", varname, currvalue) 91 | 92 | SharedVars := shared.SharedVars 93 | 94 | //first check if varname has anyoperation linked 95 | re := regexp.MustCompile("{.*}") 96 | 97 | matched := re.FindStringSubmatchIndex(varname) 98 | 99 | if len(matched) < 2 { 100 | // SharedVars.AcquireLock() 101 | SharedVars.Set(varname, currvalue, false) 102 | // SharedVars.ReleaseLock() 103 | return 104 | } 105 | 106 | ops := varname[matched[0]+1 : matched[1]-1] 107 | varname = varname[:matched[0]] 108 | 109 | for _, v := range strings.Split(strings.TrimSpace(ops), ",") { 110 | if v == "add" { 111 | 112 | //check if value exists then add itself 113 | if !SharedVars.Exists(varname) { 114 | // no such value exists 115 | SharedVars.Set(varname, currvalue, false) 116 | 117 | } else { 118 | // SharedVars.AcquireLock() 119 | //get old value and then add new value 120 | localvalue, _ := SharedVars.Get(varname) 121 | newval := localvalue + "\n" + currvalue 122 | SharedVars.Set(varname, newval, false) 123 | // SharedVars.ReleaseLock() 124 | 125 | } 126 | 127 | } else if v == "unique" { 128 | if !SharedVars.Exists(varname) { 129 | // no such value exists 130 | SharedVars.Set(varname, stringutils.UniqueElements(currvalue), false) 131 | } else { 132 | // SharedVars.AcquireLock() 133 | localvalue, _ := SharedVars.Get(varname) 134 | SharedVars.Set(varname, stringutils.UniqueElements(localvalue, currvalue), false) 135 | // SharedVars.ReleaseLock() 136 | } 137 | } 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /pkg/db/boltx/bboltdb.go: -------------------------------------------------------------------------------- 1 | package boltx 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | 7 | "go.etcd.io/bbolt" 8 | ) 9 | 10 | // Options : BBolt DB 11 | type Options struct { 12 | Directory string 13 | Filename string 14 | BucketName string 15 | } 16 | 17 | // Provider of BBolt DB 18 | type Provider struct { 19 | BBoltDB *Options /// DB Options 20 | db *bbolt.DB // DB pointer 21 | } 22 | 23 | // New BBoltDB Instance 24 | func New(options *Options) (*Provider, error) { 25 | p := &Provider{ 26 | BBoltDB: options, 27 | } 28 | 29 | er := p.open() 30 | 31 | return p, er 32 | } 33 | 34 | // validate connection 35 | func (p *Provider) validate() (string, error) { 36 | if p.BBoltDB.BucketName == "" { 37 | return "DB Options Missing", fmt.Errorf("bboltdb bucket name not provider") 38 | } 39 | 40 | if p.db == nil { 41 | return "DB Instance Missing", fmt.Errorf("bbolt db no connection") 42 | } 43 | 44 | return "", nil 45 | } 46 | 47 | // Open DB Connection 48 | func (p *Provider) open() error { 49 | var er error 50 | var dbpath string 51 | 52 | if p.BBoltDB.Directory == "." || p.BBoltDB.Directory == "" { 53 | dbpath = p.BBoltDB.Filename 54 | } else { 55 | dbpath = path.Join(p.BBoltDB.Directory, p.BBoltDB.Filename) 56 | } 57 | 58 | p.db, er = bbolt.Open(dbpath, 0644, nil) 59 | 60 | if er != nil { 61 | return er 62 | } 63 | 64 | p.db.Update( 65 | func(tx *bbolt.Tx) error { 66 | _, er := tx.CreateBucketIfNotExists([]byte(p.BBoltDB.BucketName)) 67 | if er != nil { 68 | return er 69 | } 70 | return nil 71 | }, 72 | ) 73 | 74 | return nil 75 | } 76 | 77 | // Close DB Connection 78 | func (p *Provider) Close() error { 79 | return p.db.Close() 80 | } 81 | 82 | // Get Variable Value 83 | func (p *Provider) Get(key string) (string, error) { 84 | if _, er := p.validate(); er != nil { 85 | return "", er 86 | } 87 | 88 | var item *dbEntry 89 | var erx error 90 | 91 | p.db.View( 92 | func(tx *bbolt.Tx) error { 93 | bucket := tx.Bucket([]byte(p.BBoltDB.BucketName)) 94 | 95 | val := bucket.Get([]byte(key)) 96 | 97 | if val == nil { 98 | erx = fmt.Errorf("value of variable is missing") 99 | return nil 100 | } 101 | 102 | item, erx = newdbEntry(val) 103 | 104 | return nil 105 | }, 106 | ) 107 | 108 | if erx != nil || item == nil { 109 | return "", fmt.Errorf("bbolt db get failed value of key missing") 110 | } 111 | 112 | return item.Value, nil 113 | } 114 | 115 | // Put Variable to DB 116 | func (p *Provider) Put(key, value string, isExplicit bool) error { 117 | item := dbEntry{ 118 | Value: value, 119 | IsExplicit: isExplicit, 120 | } 121 | 122 | itembin, er := item.getBytes() 123 | if er != nil { 124 | return er 125 | } 126 | 127 | if _, er := p.validate(); er != nil { 128 | return er 129 | } 130 | 131 | var erx error 132 | 133 | p.db.Update(func(tx *bbolt.Tx) error { 134 | bucket, err := tx.CreateBucketIfNotExists([]byte(p.BBoltDB.BucketName)) 135 | if err != nil { 136 | erx = err 137 | return err 138 | } 139 | 140 | erx = bucket.Put([]byte(key), itembin) 141 | return nil 142 | }, 143 | ) 144 | 145 | return erx 146 | 147 | } 148 | 149 | // GetAllVarNames 150 | func (p *Provider) GetAllVarNames() (map[string]bool, error) { 151 | allvars := map[string]bool{} 152 | 153 | if _, er := p.validate(); er != nil { 154 | return allvars, er 155 | } 156 | 157 | var erx error 158 | 159 | p.db.View( 160 | func(tx *bbolt.Tx) error { 161 | bucket := tx.Bucket([]byte(p.BBoltDB.BucketName)) 162 | 163 | erx = bucket.ForEach(func(k, v []byte) error { 164 | allvars[string(k)] = true 165 | return nil 166 | }) 167 | return nil 168 | }, 169 | ) 170 | 171 | if erx != nil { 172 | return allvars, fmt.Errorf("bbolt db get failed value of key missing") 173 | } 174 | 175 | return allvars, nil 176 | } 177 | 178 | // GetAllExplicit Variables and their Values 179 | func (p *Provider) GetAllExplicit() (map[string]string, error) { 180 | return p.getkvwhere(true) 181 | } 182 | 183 | // GetAllImplicit Variables and Values 184 | func (p *Provider) GetAllImplicit() (map[string]string, error) { 185 | return p.getkvwhere(false) 186 | } 187 | 188 | // ProviderName i.e DB backend here (mongodb) 189 | func (m *Provider) ProviderName() string { 190 | return "BBoltDB" 191 | } 192 | -------------------------------------------------------------------------------- /pkg/ioutils/stdout.go: -------------------------------------------------------------------------------- 1 | package ioutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | 8 | "github.com/muesli/termenv" 9 | ) 10 | 11 | // Cout : Global Print Instance 12 | var Cout *Print = NewPrint() 13 | 14 | // Print : Concurrency Safe Stdout Printing 15 | type Print struct { 16 | m *sync.Mutex 17 | Verbose bool // Verbose (includes warnings) 18 | VeryVerbose bool // VeryVerbose (includes warnings+info) 19 | DisableColor bool // Disable Color Output 20 | } 21 | 22 | // Header : Stdout Header Printing Style 23 | func (p *Print) Header(format string, a ...any) { 24 | 25 | if p.DisableColor { 26 | p.Printf(format, a...) 27 | return 28 | } 29 | 30 | s := termenv.String(fmt.Sprintf(format, a...)) 31 | 32 | s = s.Bold().Foreground(Orange) 33 | 34 | p.Printf("%v", s) 35 | 36 | } 37 | 38 | // Value : Stdout Header Value Printing Style 39 | func (p *Print) Value(format string, a ...any) { 40 | if p.DisableColor { 41 | p.Printf(format, a...) 42 | return 43 | } 44 | 45 | s := termenv.String(fmt.Sprintf(format, a...)) 46 | 47 | s = s.Foreground(AquaMarine) 48 | 49 | p.Printf("%v", s) 50 | } 51 | 52 | // Seperator : Dash(-) Seperator of given length 53 | func (p *Print) Seperator(len int) { 54 | 55 | tmp := "" 56 | 57 | for i := 0; i < len; i++ { 58 | tmp += "-" 59 | } 60 | 61 | if p.DisableColor { 62 | p.m.Lock() 63 | fmt.Printf("\n%v\n%v\n", tmp, tmp) 64 | p.m.Unlock() 65 | return 66 | } 67 | 68 | s := termenv.String(fmt.Sprintf("\n%v\n%v\n", tmp, tmp)) 69 | 70 | s = s.Bold().Foreground(Azure) 71 | 72 | p.m.Lock() 73 | fmt.Printf("%v\n", s) 74 | p.m.Unlock() 75 | } 76 | 77 | // GetColor : Get Colorized text 78 | func (p *Print) GetColor(z termenv.Color, format string, a ...any) termenv.Style { 79 | s := termenv.String(fmt.Sprintf(format, a...)) 80 | if p.DisableColor { 81 | return s 82 | } 83 | 84 | s = s.Foreground(z) 85 | 86 | return s 87 | } 88 | 89 | // PrintColor : Print Colored Output 90 | func (p *Print) PrintColor(z termenv.Color, format string, a ...any) { 91 | if p.DisableColor { 92 | p.Printf(format, a...) 93 | return 94 | } 95 | 96 | s := termenv.String(fmt.Sprintf(format, a...)) 97 | 98 | s = s.Bold().Foreground(z) 99 | 100 | p.Printf("%v", s) 101 | } 102 | 103 | // PrintInfo : Print Info 104 | func (p *Print) PrintInfo(format string, a ...any) { 105 | if !p.VeryVerbose { 106 | return 107 | } 108 | 109 | if p.DisableColor { 110 | p.m.Lock() 111 | fmt.Printf("[Info] "+format+"\n", a...) 112 | p.m.Unlock() 113 | } else { 114 | z := fmt.Sprintf("%v %v\n", p.GetColor(Orange, "[Info]"), p.GetColor(Azure, format, a...)) 115 | p.m.Lock() 116 | fmt.Print(z) 117 | p.m.Unlock() 118 | } 119 | 120 | } 121 | 122 | // Fatalf : Output Followed by panic 123 | func (p *Print) Fatalf(er error, format string, a ...any) { 124 | if p.DisableColor { 125 | p.m.Lock() 126 | fmt.Printf("[Fatal] "+format+"\n", a...) 127 | p.m.Unlock() 128 | } else { 129 | z := fmt.Sprintf("%v %v\n", p.GetColor(Red, "[Fatal]"), fmt.Sprintf(format, a...)) 130 | p.m.Lock() 131 | fmt.Print(z) 132 | p.m.Unlock() 133 | } 134 | 135 | panic(er) 136 | 137 | } 138 | 139 | // ErrExit : Error Followed by exit 140 | func (p *Print) ErrExit(format string, a ...any) { 141 | if p.DisableColor { 142 | p.m.Lock() 143 | fmt.Printf("[Fatal] "+format+"\n", a...) 144 | p.m.Unlock() 145 | } else { 146 | z := fmt.Sprintf("%v %v\n", p.GetColor(Red, "[Fatal]"), fmt.Sprintf(format, a...)) 147 | p.m.Lock() 148 | fmt.Print(z) 149 | p.m.Unlock() 150 | } 151 | 152 | os.Exit(1) 153 | } 154 | 155 | // ErrColor : Colorize Error 156 | func (p *Print) ErrColor(er error) termenv.Style { 157 | s := termenv.String(er.Error()) 158 | if p.DisableColor { 159 | return s 160 | } 161 | 162 | s = s.Foreground(Red) 163 | 164 | return s 165 | } 166 | 167 | // Printf : Normal Print 168 | func (p *Print) Printf(format string, a ...any) { 169 | p.m.Lock() 170 | fmt.Printf(format+"\n", a...) 171 | p.m.Unlock() 172 | } 173 | 174 | // PrintWarning : Print Warnings 175 | func (p *Print) PrintWarning(format string, a ...any) { 176 | if !p.Verbose { 177 | return 178 | } 179 | if p.DisableColor { 180 | p.m.Lock() 181 | fmt.Printf("[Warn] "+format+"\n", a...) 182 | p.m.Unlock() 183 | } else { 184 | z := fmt.Sprintf("%v %v\n", p.GetColor(Orange, "[Warn]"), p.GetColor(LightGreen, format, a...)) 185 | p.m.Lock() 186 | fmt.Print(z) 187 | p.m.Unlock() 188 | } 189 | } 190 | 191 | // NewPrint : New Print Instance 192 | func NewPrint() *Print { 193 | x := Print{ 194 | m: &sync.Mutex{}, 195 | } 196 | 197 | return &x 198 | } 199 | -------------------------------------------------------------------------------- /pkg/db/mongox/mongodb.go: -------------------------------------------------------------------------------- 1 | package mongox 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | ) 9 | 10 | // Options : MongoDB Options 11 | type Options struct { 12 | URL string // MongoDB Connection URL 13 | DBName string 14 | CollectionName string 15 | } 16 | 17 | // Provider : MongoDB Provider 18 | type Provider struct { 19 | MongoDB *Options 20 | client *mongo.Client // MongoDB Connection Client 21 | db *mongo.Database // MongoDB Database 22 | collection *mongo.Collection // MongoDB Collection 23 | } 24 | 25 | // New Provider Instance 26 | func New(opts *Options) (*Provider, error) { 27 | p := &Provider{ 28 | MongoDB: opts, 29 | } 30 | 31 | err := p.open() 32 | 33 | return p, err 34 | } 35 | 36 | // validate DB Connection 37 | func (m *Provider) validate() (string, error) { 38 | if m.MongoDB == nil { 39 | return "No DB Options", fmt.Errorf("mongodb options missing") 40 | } 41 | if m.db == nil { 42 | return "No DB Connection", fmt.Errorf("mongodb no connection") 43 | } 44 | return "", nil 45 | } 46 | 47 | // Open DB Connection 48 | func (m *Provider) open() error { 49 | 50 | if err := m.Connect(); err != nil { 51 | return err 52 | } 53 | 54 | if m.MongoDB == nil { 55 | return fmt.Errorf("mongodb options missing") 56 | } 57 | 58 | if m.MongoDB.DBName == "" { 59 | return fmt.Errorf("db name missing") 60 | } else { 61 | m.GetDatabase(m.MongoDB.DBName) 62 | } 63 | 64 | if m.MongoDB.CollectionName == "" { 65 | return fmt.Errorf("mongoDB collection name missing") 66 | } else { 67 | m.GetCollection(m.MongoDB.CollectionName) 68 | } 69 | 70 | return nil 71 | 72 | } 73 | 74 | // Close DB Connection 75 | func (m *Provider) Close() error { 76 | return m.Disconnect() 77 | } 78 | 79 | // Get variable value 80 | func (m *Provider) Get(key string) (string, error) { 81 | 82 | if msg, err := m.validate(); err != nil { 83 | return msg, err 84 | } 85 | 86 | var x dbEntry 87 | filter := bson.M{"varname": key} 88 | 89 | err := m.FindOne(filter, &x) 90 | 91 | return x.Value, err 92 | } 93 | 94 | // Put Variable to DB 95 | func (m *Provider) Put(key, value string, isExplicit bool) error { 96 | 97 | if _, err := m.validate(); err != nil { 98 | return err 99 | } 100 | 101 | x := dbEntry{ 102 | Varname: key, 103 | Value: value, 104 | Explicit: isExplicit, 105 | } 106 | 107 | filter := bson.M{"varname": key} 108 | data := bson.M{"$set": x} 109 | 110 | _, err := m.UpdateDocument(filter, data) 111 | 112 | return err 113 | } 114 | 115 | // GetAllVarNames 116 | func (m *Provider) GetAllVarNames() (map[string]bool, error) { 117 | 118 | if _, err := m.validate(); err != nil { 119 | return map[string]bool{}, err 120 | } 121 | 122 | resp, err := m.FindWhere(bson.M{}) 123 | if err != nil { 124 | return map[string]bool{}, err 125 | } 126 | 127 | res := map[string]bool{} 128 | 129 | for _, v := range resp { 130 | 131 | var zx dbEntry 132 | bin, _ := bson.Marshal(v) 133 | 134 | bson.Unmarshal(bin, &zx) 135 | 136 | res[zx.Varname] = zx.Explicit 137 | 138 | } 139 | 140 | return res, nil 141 | } 142 | 143 | // GetAllExplicit Variables and their Values 144 | func (m *Provider) GetAllExplicit() (map[string]string, error) { 145 | 146 | if _, err := m.validate(); err != nil { 147 | return map[string]string{}, err 148 | } 149 | 150 | filter := bson.M{"explicit": true} 151 | 152 | resp, err := m.FindWhere(filter) 153 | 154 | if err != nil { 155 | return map[string]string{}, err 156 | } 157 | 158 | res := map[string]string{} 159 | 160 | for _, v := range resp { 161 | 162 | var zx dbEntry 163 | bin, _ := bson.Marshal(v) 164 | 165 | bson.Unmarshal(bin, &zx) 166 | 167 | res[zx.Varname] = zx.Value 168 | 169 | } 170 | 171 | return res, nil 172 | } 173 | 174 | // GetAllImplicit Variables and Values 175 | func (m *Provider) GetAllImplicit() (map[string]string, error) { 176 | 177 | if _, err := m.validate(); err != nil { 178 | return map[string]string{}, err 179 | } 180 | 181 | filter := bson.M{"explicit": false} 182 | 183 | resp, err := m.FindWhere(filter) 184 | 185 | if err != nil { 186 | return map[string]string{}, err 187 | } 188 | 189 | res := map[string]string{} 190 | 191 | for _, v := range resp { 192 | 193 | var zx dbEntry 194 | bin, _ := bson.Marshal(v) 195 | 196 | bson.Unmarshal(bin, &zx) 197 | 198 | res[zx.Varname] = zx.Value 199 | 200 | } 201 | 202 | return res, nil 203 | } 204 | 205 | // ProviderName i.e DB backend here (mongodb) 206 | func (m *Provider) ProviderName() string { 207 | return "MongoDB" 208 | } 209 | -------------------------------------------------------------------------------- /cmd/talosplus/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | 9 | "github.com/tarunKoyalwar/talosplus/pkg/alerts" 10 | "github.com/tarunKoyalwar/talosplus/pkg/core" 11 | "github.com/tarunKoyalwar/talosplus/pkg/db" 12 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 13 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 14 | "github.com/tarunKoyalwar/talosplus/pkg/shell" 15 | ) 16 | 17 | func main() { 18 | 19 | // Get Input 20 | opts := parseInput() 21 | 22 | if !opts.Silent { 23 | showBanner() 24 | } 25 | 26 | // Output 27 | ioutils.Cout.DisableColor = opts.NoColor 28 | ioutils.Cout.Verbose = opts.Verbose 29 | ioutils.Cout.VeryVerbose = opts.VeryVerbose 30 | if ioutils.Cout.VeryVerbose { 31 | ioutils.Cout.Verbose = true 32 | } 33 | 34 | // Configure DB Settings 35 | 36 | if opts.UseMongoDB { 37 | // using mongodb 38 | err := db.UseMongoDB(opts.DatabaseURI, opts.DBName, opts.ContextName) 39 | if err != nil { 40 | ioutils.Cout.Fatalf(err, "Connection to MongoDB Failed %v", opts.DatabaseURI) 41 | } 42 | ioutils.Cout.PrintInfo("Connected to MongoDB") 43 | } else { 44 | opts.DBName += ".db" 45 | err := db.UseBBoltDB(opts.DatabaseURI, opts.DBName, opts.ContextName) 46 | if err != nil { 47 | ioutils.Cout.Fatalf(err, "Failed to open Database at %v %v", opts.DatabaseURI, opts.DBName) 48 | } 49 | } 50 | 51 | // Tasks related to Database Client 52 | 53 | if opts.READ_VAR != "" { 54 | val, err := db.DB.Get(opts.READ_VAR) 55 | if err != nil { 56 | ioutils.Cout.Fatalf(err, "Failed to retrieve %v from db", opts.READ_VAR) 57 | } else { 58 | fmt.Println(val) 59 | } 60 | os.Exit(0) 61 | } else if opts.WRITE_VAR != "" { 62 | var data string 63 | if HasStdin() { 64 | data = GetStdin() 65 | } else if opts.FROM_FILE != "" { 66 | bin, er := os.ReadFile(opts.FROM_FILE) 67 | if er != nil { 68 | ioutils.Cout.Fatalf(er, "Failed to read file %v", opts.FROM_FILE) 69 | } else { 70 | data = string(bin) 71 | } 72 | } else { 73 | ioutils.Cout.ErrExit("Input Missing . Exiting!!") 74 | } 75 | 76 | err := db.DB.Put(opts.WRITE_VAR, data, true) 77 | if err != nil { 78 | ioutils.Cout.Fatalf(err, "Failed to Write in %v", opts.WRITE_VAR) 79 | } else { 80 | fmt.Println("Saved to DB") 81 | } 82 | os.Exit(0) 83 | } else if opts.LIST_ALL { 84 | val, err := db.DB.GetAllVarNames() 85 | if err != nil { 86 | ioutils.Cout.Fatalf(err, "Failed to get list of variables from db") 87 | } else { 88 | for k := range val { 89 | fmt.Println(k) 90 | } 91 | } 92 | os.Exit(0) 93 | } 94 | 95 | // Configure Discord Webhook 96 | 97 | if opts.DiscordWID != "" && opts.DiscordWTOKEN != "" { 98 | alerts.Alert = alerts.NewDiscordHook(opts.DiscordWID, opts.DiscordWTOKEN) 99 | } 100 | 101 | if opts.SkipNotification { 102 | alerts.Alert.Disabled = true 103 | } 104 | 105 | // Configure script args 106 | shell.Settings.CacheDIR = opts.CacheDIR 107 | shell.Settings.Limit = opts.Concurrency 108 | shell.Settings.ProjectName = opts.ContextName 109 | shell.Settings.ProjectExportName = opts.ContextName + "Exports" 110 | shell.Settings.Purge = opts.Purge 111 | 112 | // Template File Buffer 113 | var templateBuff bytes.Buffer 114 | 115 | // Configure Templates 116 | if opts.TemplateDir != "" { 117 | files, err := os.ReadDir(opts.TemplateDir) 118 | if err != nil { 119 | ioutils.Cout.Fatalf(err, "Failed to read templates from dir %v", opts.TemplateDir) 120 | } 121 | 122 | count := 0 123 | 124 | for _, v := range files { 125 | if !v.IsDir() { 126 | fbin, err := os.ReadFile(path.Join(opts.TemplateDir, v.Name())) 127 | if err != nil { 128 | ioutils.Cout.Printf("failed to read template %v got %v", opts.Template, err.Error()) 129 | } else { 130 | templateBuff.WriteString("\n\n") 131 | templateBuff.Write(fbin) 132 | count += 1 133 | } 134 | } 135 | } 136 | 137 | ioutils.Cout.PrintInfo("Successfully Loaded %v Templates", count) 138 | 139 | } else if opts.Template != "" { 140 | // Load Templates 141 | fbin, err := os.ReadFile(opts.Template) 142 | if err != nil { 143 | ioutils.Cout.Fatalf(err, "failed to read template %v", opts.Template) 144 | } 145 | templateBuff.Write(fbin) 146 | } else { 147 | ioutils.Cout.ErrExit("No Templates Found. Exiting!!") 148 | } 149 | 150 | t := core.NewEngine() 151 | t.ShowOutput = opts.ShowOutput 152 | 153 | // Load Existing Variable Values 154 | shared.SharedVars.AddGlobalVarsFromDB() 155 | 156 | // Add Blacklisted Variables 157 | if len(opts.BlacklistVars) != 0 { 158 | for _, v := range opts.BlacklistVars { 159 | t.BlackList[v] = true 160 | } 161 | } 162 | 163 | t.Compile(templateBuff.String()) 164 | 165 | t.Evaluate() 166 | 167 | t.PrintAllCMDs() 168 | 169 | t.Schedule() 170 | 171 | if opts.DryRun { 172 | os.Exit(0) 173 | } 174 | 175 | t.Execute() 176 | 177 | } 178 | -------------------------------------------------------------------------------- /pkg/scheduler/schedule.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 5 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 6 | ) 7 | 8 | // Scheduler : Schedules commands based on their dependency 9 | type Scheduler struct { 10 | AllNodes map[string]*Node 11 | ExecPyramid [][]*Node // 12 | BlackListed map[string]bool 13 | } 14 | 15 | // AddNode : Add Node to Scheduler 16 | func (s *Scheduler) AddNode(uid string, comment string) { 17 | x := Node{ 18 | Root: []*Node{}, 19 | UID: uid, 20 | Comment: comment, 21 | Children: []*Node{}, 22 | } 23 | s.AllNodes[uid] = &x 24 | } 25 | 26 | // Run : Run Scheduler and Create ExecPyramid 27 | func (s *Scheduler) Run() { 28 | //remove old assigned data or verify if parent has changed 29 | 30 | for k, v := range shared.DefaultRegistry.Dependents { 31 | addrs := shared.DefaultRegistry.VarAddress[k] 32 | requirednodes := []*Node{} 33 | 34 | for _, uid := range addrs { 35 | requirednodes = append(requirednodes, s.AllNodes[uid]) 36 | } 37 | 38 | for _, reqnode := range requirednodes { 39 | for _, b := range v { 40 | //check if uid is blacklisted 41 | 42 | if reqnode != nil { 43 | // fmt.Println(b) 44 | childnode := s.AllNodes[b] 45 | 46 | // fmt.Printf("childnode is %v\n", childnode) 47 | 48 | childnode.Root = append(childnode.Root, reqnode) 49 | reqnode.Children = append(reqnode.Children, childnode) 50 | } 51 | 52 | } 53 | } 54 | 55 | // reqnode := s.AllNodes[id] 56 | // fmt.Printf("%v requires %v\n", v, k) 57 | 58 | } 59 | 60 | // temporary patch 61 | 62 | // Identify Root Nodes 63 | roots := []*Node{} 64 | for _, v := range s.AllNodes { 65 | if len(v.Root) == 0 { 66 | roots = append(roots, v) 67 | } 68 | } 69 | 70 | s.ExecPyramid = append(s.ExecPyramid, roots) 71 | 72 | s.createExecutionPyramid(roots) 73 | 74 | // fix inconsistencies Ex: A-B-C if b dropped then A-C (All complex cases) 75 | 76 | // new pyramid 77 | npy := [][]*Node{} 78 | 79 | for _, v := range s.ExecPyramid { 80 | arr := []*Node{} 81 | for _, n := range v { 82 | if !s.BlackListed[n.UID] { 83 | arr = append(arr, n) 84 | } 85 | } 86 | 87 | if len(arr) > 0 { 88 | npy = append(npy, arr) 89 | } 90 | } 91 | 92 | //use npy as execpyramid 93 | s.ExecPyramid = npy 94 | 95 | count := 0 96 | 97 | ioutils.Cout.Header("[*] Execution Pyramid by levels (top->bottom)\n") 98 | 99 | for _, v := range s.ExecPyramid { 100 | ioutils.Cout.PrintColor(ioutils.Yellow, "Level %v : ", count) 101 | for _, b := range v { 102 | ioutils.Cout.Value("%v", b.Comment) 103 | } 104 | count += 1 105 | ioutils.Cout.Printf("") 106 | } 107 | 108 | ioutils.Cout.Seperator(60) 109 | 110 | } 111 | 112 | // createExecutionPyramid 113 | func (s *Scheduler) createExecutionPyramid(CurrNodes []*Node) { 114 | cnodes := CurrNodes 115 | 116 | for { 117 | // if there are no nodes just return 118 | if len(cnodes) < 1 { 119 | break 120 | } 121 | 122 | // If children have more than 1 root 123 | // Using SOmething Similar to DFS and give preference to farthest one 124 | // also autobalance if any node is blacklisted 125 | for _, v := range cnodes { 126 | for _, x2 := range v.Children { 127 | if len(x2.Root) > 1 { 128 | 129 | // remove parent root if it is blacklisted 130 | // add its children to level zero 131 | 132 | newroutes := []*Node{} 133 | 134 | for _, v := range x2.Root { 135 | //skip if it is blacklisted 136 | if !s.BlackListed[v.UID] { 137 | newroutes = append(newroutes, v) 138 | } 139 | } 140 | 141 | //after filtering blacklisted nodes 142 | if len(newroutes) == 0 { 143 | //change status to roots and add to top 144 | x2.Root = []*Node{} 145 | s.ExecPyramid[0] = append(s.ExecPyramid[0], x2) 146 | } else { 147 | //if not use same remaining as parents 148 | x2.Root = newroutes 149 | if len(newroutes) > 1 { 150 | rnode, _ := FarthestNodethru(x2, 0) 151 | x2.Root = []*Node{rnode} 152 | } 153 | } 154 | 155 | } 156 | } 157 | } 158 | 159 | tmpchildren := []*Node{} 160 | for _, v := range cnodes { 161 | for _, z := range v.Children { 162 | if len(z.Root) == 1 { 163 | if z.Root[0] == v { 164 | tmpchildren = append(tmpchildren, z) 165 | } 166 | } 167 | 168 | } 169 | } 170 | // levelarr = append(levelarr, tmp) 171 | if len(tmpchildren) > 0 { 172 | s.ExecPyramid = append(s.ExecPyramid, tmpchildren) 173 | } 174 | 175 | cnodes = tmpchildren 176 | 177 | if len(cnodes) < 1 { 178 | break 179 | } 180 | 181 | } 182 | 183 | // return levelarr 184 | } 185 | 186 | // NewScheduler 187 | func NewScheduler() *Scheduler { 188 | return &Scheduler{ 189 | AllNodes: map[string]*Node{}, 190 | ExecPyramid: [][]*Node{}, 191 | BlackListed: map[string]bool{}, 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /cmd/talosplus/options.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/projectdiscovery/goflags" 10 | ) 11 | 12 | type DB_CLIENT_OPTS struct { 13 | READ_VAR string 14 | WRITE_VAR string 15 | LIST_ALL bool 16 | FROM_FILE string 17 | } 18 | 19 | type Templates_OPTS struct { 20 | DryRun bool 21 | TemplateDir string 22 | Template string 23 | } 24 | 25 | type Config_OPTS struct { 26 | Purge bool 27 | SkipNotification bool 28 | Concurrency int 29 | CacheDIR string 30 | DiscordWID string 31 | DiscordWTOKEN string 32 | BlacklistVars goflags.StringSlice 33 | } 34 | 35 | type DB_OPTS struct { 36 | UseMongoDB bool 37 | DatabaseURI string 38 | DBName string 39 | ContextName string 40 | } 41 | 42 | type Options struct { 43 | Templates_OPTS 44 | DB_CLIENT_OPTS 45 | Config_OPTS 46 | DB_OPTS 47 | 48 | Silent bool // No Banner 49 | NoColor bool //Disable Color Output 50 | ShowOutput bool // Show Ouput of all commands 51 | Verbose bool // Verbose Mode 52 | VeryVerbose bool 53 | } 54 | 55 | func parseInput() Options { 56 | opts := Options{} 57 | 58 | flagset := goflags.NewFlagSet() 59 | 60 | description := "Talosplus is a template based Automation Framework that harnesses power of GoLang" 61 | 62 | flagset.SetDescription(fmt.Sprintf("%v\n%v", fmt.Sprintf(banner, Version), description)) 63 | 64 | flagset.CreateGroup("templates", "Templates", 65 | flagset.BoolVarP(&opts.DryRun, "dry-run", "n", false, "Dry/Test Run the template"), 66 | flagset.StringVarEnv(&opts.TemplateDir, "templates", "td", "", "TALOS_TEMPLATE_DIR", "Run all templates from directory [Env:TALOS_TEMPLATE_DIR]"), 67 | flagset.StringVarP(&opts.Template, "template", "t", "", "Run a single template"), 68 | ) 69 | 70 | flagset.CreateGroup("output", "Output", 71 | flagset.BoolVar(&opts.Silent, "silent", false, "Don't Print Banner"), 72 | flagset.BoolVarP(&opts.NoColor, "no-color", "nc", false, "Disable Color Output"), 73 | flagset.BoolVarP(&opts.ShowOutput, "show", "s", false, "Show Output of All Commands"), 74 | flagset.BoolVarP(&opts.Verbose, "verbose", "v", false, "Verbose Mode (Show Scheduled Tasks & Warnings)"), 75 | flagset.BoolVarP(&opts.VeryVerbose, "very-verbose", "vv", false, "Max Verbosity"), 76 | ) 77 | 78 | flagset.CreateGroup("configs", "Configurations", 79 | flagset.IntVarP(&opts.Concurrency, "limit", "c", 8, "Max Number of Concurrent Programs"), 80 | flagset.StringSliceVarP(&opts.BlacklistVars, "blacklist-vars", "b", []string{}, "Blacklist ", goflags.CommaSeparatedStringSliceOptions), 81 | flagset.BoolVarP(&opts.Purge, "purge", "p", false, "Purge Cache"), 82 | flagset.BoolVar(&opts.SkipNotification, "skip-notify", false, "Skip Sending Notification to Discord"), 83 | flagset.StringVarEnv(&opts.CacheDIR, "cache-dir", "cdir", os.TempDir(), "TALOS_CACHE_DIR", "Cache Directory [Env:TALOS_CACHE_DIR](All command outputs are saved here)"), 84 | flagset.StringVarEnv(&opts.DiscordWID, "discord-wid", "wid", "", "DISCORD_WID", "Discord Webhook ID [Env:DISCORD_WID]"), 85 | flagset.StringVarEnv(&opts.DiscordWTOKEN, "discord-wtoken", "wtoken", "", "DISCORD_WTOKEN", "Discord Webhook Token [Env:DISCORD_WTOKEN]"), 86 | ) 87 | 88 | flagset.CreateGroup("database", "Database", 89 | flagset.StringVarEnv(&opts.DatabaseURI, "uri", "u", "", "TALOS_URI", "URI [Env: TALOS_URI] (MongoDB URL/Directory)"), 90 | flagset.StringVarEnv(&opts.DBName, "database-name", "db", "talosplus", "TALOS_DBNAME", "Database Name [Env:TALOS_DBNAME]"), 91 | flagset.StringVarEnv(&opts.ContextName, "context-name", "cn", "automation", "TALOS_CN", "Similar to Table Name in SQL can be anything version,subdomain etc [Env:TALOS_CN]"), 92 | flagset.BoolVar(&opts.UseMongoDB, "mongodb", false, "Use MongoDB (default : BBolt DB)"), 93 | ) 94 | 95 | flagset.CreateGroup("dbclient", "DB Client(Similar to BBRF)", 96 | flagset.StringVarP(&opts.READ_VAR, "read-var", "get", "", "Read Variable Value from Database"), 97 | flagset.StringVarP(&opts.WRITE_VAR, "write-var", "put", "", "Save Data to Variable"), 98 | flagset.StringVarP(&opts.FROM_FILE, "file", "f", "", "Read From File"), 99 | flagset.BoolVarP(&opts.LIST_ALL, "list", "l", false, "List All Variables"), 100 | ) 101 | 102 | if err := flagset.Parse(); err != nil { 103 | log.Fatalf("Could not parse flags: %s\n", err) 104 | } 105 | 106 | return opts 107 | } 108 | 109 | // HasStdin : Check if Stdin is present 110 | func HasStdin() bool { 111 | stat, err := os.Stdin.Stat() 112 | if err != nil { 113 | return false 114 | } 115 | 116 | mode := stat.Mode() 117 | 118 | isPipedFromChrDev := (mode & os.ModeCharDevice) == 0 119 | isPipedFromFIFO := (mode & os.ModeNamedPipe) != 0 120 | 121 | return isPipedFromChrDev || isPipedFromFIFO 122 | } 123 | 124 | // GetStdin : Get all Data present on stdin 125 | func GetStdin() string { 126 | bin, _ := ioutil.ReadAll(os.Stdin) 127 | return string(bin) 128 | } 129 | -------------------------------------------------------------------------------- /pkg/db/mongox/private.go: -------------------------------------------------------------------------------- 1 | package mongox 2 | 3 | /* 4 | Private or Internal Methods for CRUD Operations 5 | */ 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "go.mongodb.org/mongo-driver/bson" 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/options" 14 | ) 15 | 16 | type dbEntry struct { 17 | Varname string `bson:"varname"` 18 | Value string `bson:"value"` 19 | Explicit bool `bson:"explicit"` 20 | } 21 | 22 | //lint:file-ignore U1000 Ignore all unused code, it's for future use only 23 | 24 | // GetDatabase : Connect to Database 25 | func (m *Provider) GetDatabase(name string) { 26 | m.db = m.client.Database(name) 27 | } 28 | 29 | // GetCollection : Get COllection 30 | func (m *Provider) GetCollection(name string) { 31 | m.collection = m.db.Collection(name) 32 | } 33 | 34 | // Isconnected : Check If Connected to Database 35 | func (m *Provider) Isconnected() bool { 36 | if m.db == nil { 37 | return false 38 | } else { 39 | return true 40 | } 41 | } 42 | 43 | // Connect : Connect to database 44 | func (m *Provider) Connect() error { 45 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 46 | defer cancel() 47 | if m.MongoDB.URL == "" { 48 | m.MongoDB.URL = "mongodb://localhost:27017" 49 | } 50 | var err error 51 | m.client, err = mongo.Connect(ctx, options.Client().ApplyURI(m.MongoDB.URL)) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | //add ping here 57 | err = m.client.Ping(ctx, nil) 58 | if err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | // PingTest : Test Successful Connection 65 | func (m *Provider) PingTest() error { 66 | if m.client == nil { 67 | er := m.Connect() 68 | if er != nil { 69 | return er 70 | } 71 | } 72 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 73 | defer cancel() 74 | err := m.client.Ping(ctx, nil) 75 | if err != nil { 76 | return err 77 | } 78 | return nil 79 | } 80 | 81 | // Disconnect : throws error when fails 82 | func (m *Provider) Disconnect() error { 83 | er := m.PingTest() 84 | if er != nil { 85 | return er 86 | } 87 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 88 | defer cancel() 89 | if err := m.client.Disconnect(ctx); err != nil { 90 | return err 91 | } 92 | 93 | return nil 94 | } 95 | 96 | // ListDatabases : List all Databases 97 | func (m *Provider) ListDatabases() ([]string, error) { 98 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 99 | defer cancel() 100 | results, err := m.client.ListDatabaseNames(ctx, bson.D{}) 101 | if err != nil { 102 | return []string{}, err 103 | } 104 | return results, nil 105 | } 106 | 107 | // ListDBCollections : List All Collections of Current Database 108 | func (m *Provider) ListDBCollections() ([]string, error) { 109 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 110 | defer cancel() 111 | results, err := m.db.ListCollectionNames(ctx, bson.D{}) 112 | if err != nil { 113 | return []string{}, err 114 | } 115 | return results, nil 116 | } 117 | 118 | // CreateCollection : Creates New Collection 119 | func (m *Provider) CreateCollection(collname string) error { 120 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 121 | defer cancel() 122 | err := m.db.CreateCollection(ctx, collname) 123 | return err 124 | } 125 | 126 | // UpdateDocument : Update Existing Document or create New One 127 | func (m *Provider) UpdateDocument(filter interface{}, dat interface{}) (*mongo.UpdateResult, error) { 128 | // fmt.Println("Update Document called") 129 | opts := options.Update().SetUpsert(true) 130 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 131 | defer cancel() 132 | result, err := m.collection.UpdateOne(ctx, filter, dat, opts) 133 | if err != nil { 134 | return result, err 135 | } 136 | // fmt.Printf("Matchedcount %v , Modified COunt %v, with upsert id %v\n ", result.MatchedCount, result.ModifiedCount, result.UpsertedID) 137 | return result, nil 138 | } 139 | 140 | // FindAll : Find All Possible Matches 141 | func (m *Provider) FindAll() ([]bson.D, error) { 142 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 143 | defer cancel() 144 | 145 | var arr []bson.D 146 | 147 | cursor, err := m.collection.Find(ctx, bson.M{}) 148 | if err != nil { 149 | return arr, err 150 | } 151 | 152 | if err = cursor.All(ctx, &arr); err != nil { 153 | return arr, err 154 | } 155 | 156 | return arr, nil 157 | } 158 | 159 | // FindWhere : Find All Possible Matches Where 160 | func (m *Provider) FindWhere(filter interface{}) ([]bson.D, error) { 161 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 162 | defer cancel() 163 | 164 | var arr []bson.D 165 | 166 | cursor, err := m.collection.Find(ctx, filter) 167 | if err != nil { 168 | return arr, err 169 | } 170 | 171 | if err = cursor.All(ctx, &arr); err != nil { 172 | return arr, err 173 | } 174 | 175 | return arr, nil 176 | } 177 | 178 | // InsertOne : Insert One Document 179 | func (m *Provider) InsertOne(dat interface{}) error { 180 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 181 | defer cancel() 182 | _, err := m.collection.InsertOne(ctx, dat) 183 | 184 | return err 185 | } 186 | 187 | // FindOne : Find One Document using Filter 188 | func (m *Provider) FindOne(filter interface{}, data interface{}) error { 189 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 190 | defer cancel() 191 | if err := m.collection.FindOne(ctx, filter).Decode(data); err != nil { 192 | return err 193 | } 194 | 195 | return nil 196 | 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 |

13 | 14 |

15 | Screenshots • 16 | Blog • 17 | Features • 18 | Installation • 19 | Usage 20 |

21 | 22 | # What is Talosplus? 23 | 24 | Talosplus is a fast and robust **template based Intelligent automation framework** that is designed to create and run automation scripts with almost no knowledge of bash scripting. However having knowledge of bash scripting allows one to create complex automation that is usually not possible with **bash alone**. 25 | 26 | Bash was written in 80's in c so it lacks many features that are required to create and run **modern Intelligent automation scripts** . Instead of creating automation in different scripting languages (python etc) or other DSLs. talosplus allows to create intelligent automation scripts by adding annotations (variables & modules) to existing bash scripts. 27 | 28 | ## How does it work?? 29 | 30 | The concept is similar to how **goroutines** work in Golang , goroutines are managed by go runtime unlike threads . In this case all heavy lifting is done by talosplus at runtime and it manages all issues related with concurrency , data sharing etc and only simplified commands are executed at low level using goroutines. 31 | 32 | 33 | 34 | # Features 35 | 36 | These are some oversimplified features that are provided by talosplus. 37 | 38 | - Parallel Execution of Commands using goroutines 39 | - Auto Scheduling & Data sharing b/w Commands at Runtime 40 | - Filesystem Abstraction 41 | - Caching 42 | - Discord Notification Support 43 | - Thread Safe 44 | - Persistent storage using MongoDB,BBoltDB(Similar to sqlite) 45 | - Easy & Lenient Syntax 46 | - Fail Safe && Condition Checks 47 | - Stop /Resume (BETA) 48 | - No Compatiblity issues with bash 49 | - Other Features Similar to `bbrf-client`,`interlace`,`rush` etc 50 | 51 | 52 | When bash script is written using **proper annotations** it barely looks like a bash script for example [sub_enum.sh](static/script.png) which is used for **subdomain enumeration** . It looks like list of commands with some annotions and comments but it is probably the **fastest and simplest automation script** available out there. 53 | 54 | ## Flow of Execution 55 | 56 | 57 | When above **template/script is executed using talosplus** . It parses and validates syntax (i.e annotations) and creates **graph** like datastructure using these annotations and creates a execution pyramid . This execution pyramid contains details like which commands can be run in parllel and details of commands that dependents on this command and lot of other stuff and provides best possible execution flow and handles all edge cases in cases of failures , missing output etc. 58 | 59 | # Screenshots 60 | 61 | 62 | - [Subdomain Enum Template](static/script.png) 63 | 64 | - [Talosplus output](static/cmdout.png) 65 | 66 | - [Custom Discord Notification](static/notification.png) 67 | 68 | - [Demo Output Video(Old)](https://asciinema.org/a/qHeRefcO6WOPrWuNAnpcuICLf.svg) 69 | 70 | # Installation Instructions 71 | 72 | - Download Binary from Releases 73 | 74 | - Build From Source . 75 | 76 | ~~~sh 77 | go install github.com/tarunKoyalwar/talosplus/cmd/talosplus@latest 78 | ~~~ 79 | 80 | 81 | Do Star the repo to show your support. 82 | Follow me on [github](https://github.com/tarunKoyalwar) / [twitter](https://twitter.com/KoyalwarTarun) to get latest updates on Talosplus. 83 | 84 | 85 | 86 | # Usage 87 | 88 | ```bash 89 | talosplus -h 90 | ``` 91 | 92 | Above Command will display help for the tool. Here are all options supported by talosplus 93 | 94 | 95 | 96 | 97 | 98 | 99 | # Resources 100 | 101 | [Create Your Ultimate Bug Bounty Automation Without Nerdy Bash Skills](https://medium.com/@zealousme/create-your-ultimate-bug-bounty-automation-without-nerdy-bash-skills-part-1-a78c2b109731) 102 | 103 | - [Part 1](https://medium.com/@zealousme/create-your-ultimate-bug-bounty-automation-without-nerdy-bash-skills-part-1-a78c2b109731) 104 | 105 | - [Part 2](https://medium.com/@zealousme/create-your-ultimate-bug-bounty-automation-without-nerdy-bash-skills-part-2-c8cd72018922) 106 | 107 | - [Part 3 (Outdated)](https://medium.com/@zealousme/create-your-ultimate-bug-bounty-automation-without-nerdy-bash-skills-part-3-7ee2b353a781) 108 | 109 | 110 | # Syntax / Annotations 111 | 112 | There are only 3 different types of annotations 113 | 114 | 1. Variables (Starts with @) 115 | 2. Modules/directives (starts with #) 116 | 3. Comments (starts with // and are above a command) 117 | 118 | Talosplus uses comments to represent a command and this comment is linked/embedded with command at runtime these comments are printed instead of commands for simplicity. 119 | 120 | Details about using these annotations can be found [here](./SYNTAX.md) 121 | 122 | 123 | # Disclaimer 124 | 125 | 1. Taloplus is just a parser tool and is not aware of bash syntax at least not in this major release 126 | 127 | 2. Each Command is sandboxed if you are using bash variables etc it won't work .It has to be variables 128 | 129 | 3. Loops & Conditional statements Will Work But they can only be in a single line or newline should be escaped using `\`. or must be enclosed within `#block{}` module to write it without any restrictions 130 | 131 | 132 | # Support 133 | 134 | If you like `talosplus` and want to see it improve furthur or want me to create intresting projects , You can buy me a coffee 135 | 136 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/B0B4CPU5V) 137 | 138 | # Acknowledgment 139 | 140 | Some Features are inspired by [@honoki/bbrf-client](https://github.com/honoki/bbrf-client) -------------------------------------------------------------------------------- /pkg/shell/cmdwrap.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "sort" 11 | "strings" 12 | 13 | "github.com/tarunKoyalwar/talosplus/pkg/internal" 14 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 15 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 16 | ) 17 | 18 | // Settings :Pointer to default settings 19 | var Settings *shared.Settings = shared.DefaultSettings 20 | 21 | // Buffers : Pointer to shared values 22 | var Buffers *shared.Shared = shared.SharedVars 23 | 24 | // CMDWrap : A Wrapper Around SimpleCMD provides 25 | // features like caching , exports etc 26 | type CMDWrap struct { 27 | CMD *SimpleCMD // access to basic cmd 28 | 29 | Comment string // Human Readable Comment 30 | Raw string //Raw Command to be passed 31 | CacheKey string // Unique Hash of Command to Cache 32 | UID string // A Temporary UID of Raw Command 33 | 34 | ExportAs string // Export as variable name 35 | ExportFromFile string // IF @outfile is used then export data from here 36 | 37 | IsForLoop bool //Does the command have for loop 38 | IsInvalid bool //Will Be Valid Only if Dependency is Not Empty 39 | Ignore bool // Ignore Flag Does not print output 40 | 41 | Alerts *Notifications // Access to Notifications if enabled 42 | 43 | CauseofFailure []string // Cause of Failure to execute command 44 | } 45 | 46 | // Process : Process command and fill values 47 | func (c *CMDWrap) Process() { 48 | rawsplit := stringutils.SplitAtSpace(c.Raw) 49 | 50 | if strings.Contains(c.Raw, "#for:") { 51 | c.IsForLoop = true 52 | return 53 | } 54 | 55 | // missed source [temp fix] [Patched] 56 | // if c.CMD == nil { 57 | // c.CMD = &SimpleCMD{} 58 | // } 59 | 60 | // cmd after replacing and processing directives 61 | filtered := []string{} 62 | 63 | for _, v := range rawsplit { 64 | // fmt.Println(v) 65 | if strings.HasPrefix(v, "#as:") { 66 | val := strings.TrimLeft(v, "#as:") 67 | c.ExportAs = val 68 | 69 | } else if strings.HasPrefix(v, "#from:") { 70 | key := strings.TrimLeft(v, "#from:") 71 | val, er1 := Buffers.Get(key) 72 | if er1 != nil { 73 | c.addReason("\tfailed to fetch env value for %v\n no reason to execute", val) 74 | } 75 | c.CMD.UseStdin(val) 76 | 77 | } else if strings.HasPrefix(v, "#dir") { 78 | val := strings.TrimLeft(v, "#dir:") 79 | c.CMD.DIR = val 80 | } else if v == "#ignore" || strings.Contains(v, "#ignore") { 81 | // do no print output of the command 82 | c.Ignore = true 83 | } else if strings.HasPrefix(v, "@env:") { 84 | val := strings.TrimLeft(v, "@env:") 85 | envalue := os.Getenv(val) 86 | if envalue == "" { 87 | c.addReason("\tfailed to fetch env value for %v\n no reason to execute", val) 88 | } 89 | filtered = append(filtered) 90 | 91 | } else if strings.HasPrefix(v, "@") { 92 | 93 | // Sanitize v 94 | v = stringutils.ExtractVar(v) 95 | 96 | if v == "@outfile" { 97 | 98 | addr, er1 := shared.DefaultSettings.CreateDirectoryIfNotExist(shared.DefaultSettings.ProjectExportName) 99 | if er1 != nil { 100 | c.addReason("\tfailed to create directory no reason to execute %v", er1) 101 | continue 102 | } 103 | c.ExportFromFile = path.Join(addr, filtered[0]+"-texport-"+stringutils.RandomString(8)) 104 | filtered = append(filtered, c.ExportFromFile) 105 | } else if v == "@tempfile" { 106 | addr, er1 := shared.DefaultSettings.CreateDirectoryIfNotExist(shared.DefaultSettings.ProjectExportName) 107 | if er1 != nil { 108 | c.addReason("\tfailed to create directory no reason to execute %v", er1) 109 | continue 110 | } 111 | tmploc := path.Join(addr, filtered[0]+"-texport-"+stringutils.RandomString(8)) 112 | filtered = append(filtered, tmploc) 113 | 114 | } else { 115 | resp, er := internal.ProcessVariable(v) 116 | if er != nil { 117 | c.addReason("\t%v", er.Error()) 118 | continue 119 | } 120 | 121 | filtered = append(filtered, resp) 122 | } 123 | 124 | } else { 125 | filtered = append(filtered, v) 126 | } 127 | 128 | } 129 | 130 | c.CMD.Cmdsplit = filtered 131 | 132 | c.genCacheKey() 133 | 134 | } 135 | 136 | // Export : Setup Export 137 | func (c *CMDWrap) Export() { 138 | // Check Export Type 139 | // Use a tempfile as output 140 | 141 | if c.ExportFromFile != "" { 142 | bin, err := ioutil.ReadFile(c.ExportFromFile) 143 | if err != nil { 144 | c.addReason("Temp File was not created by command %v\n%v", c.Raw, err) 145 | return 146 | } 147 | 148 | fileval := string(bin) 149 | 150 | internal.CompleteOperation(c.ExportAs, fileval) 151 | 152 | c.Alerts.Notify(fileval) 153 | 154 | } else { 155 | //use stdout as output 156 | // ioutils.Cout.PrintInfo("got cout as %v for %v", c.CMD.COutStream.String(), c.UID) 157 | 158 | if c.ExportAs != "" { 159 | internal.CompleteOperation(c.ExportAs, c.CMD.COutStream.String()) 160 | } 161 | 162 | // fmt.Printf("calling notify") 163 | c.Alerts.Notify(c.CMD.COutStream.String()) 164 | 165 | } 166 | 167 | } 168 | 169 | // Execute : Runs Command If data is Not Present in Cache 170 | func (c *CMDWrap) Execute() error { 171 | 172 | //don't run if prechecks failed 173 | 174 | if len(c.CauseofFailure) > 0 { 175 | er := fmt.Errorf("%v", strings.Join(c.CauseofFailure, "\n")) 176 | return er 177 | } 178 | 179 | var runerror error 180 | 181 | if !shared.DefaultSettings.Purge { 182 | err := c.cacheIn() 183 | if err != nil { 184 | // ioutils.Cout.PrintWarning("Catched Data Was Not Found %v\n", err.Error()) 185 | } else { 186 | return nil 187 | } 188 | } 189 | 190 | // ioutils.Cout.PrintWarning("running cmd %v with cmd %v\n", c.UID, c.Raw) 191 | // ioutils.Cout.PrintWarning("running %v from cmdwrap", c.UID) 192 | runerror = c.CMD.Run() 193 | 194 | if runerror == nil { 195 | c.Export() 196 | 197 | //always cache 198 | c.cacheOut() 199 | } 200 | 201 | return runerror 202 | } 203 | 204 | // cacheIn : Check Cache And Import Output If Exists 205 | func (c *CMDWrap) cacheIn() error { 206 | wdir := path.Join(shared.DefaultSettings.CacheDIR, shared.DefaultSettings.ProjectName) 207 | cpath := path.Join(wdir, c.CacheKey) 208 | 209 | // if _, err := os.StartProcess() 210 | _, err := os.Stat(cpath) 211 | 212 | if err == nil { 213 | bin, _ := ioutil.ReadFile(cpath) 214 | _, err = c.CMD.COutStream.Write(bin) 215 | internal.CompleteOperation(c.ExportAs, string(bin)) 216 | if err != nil { 217 | return err 218 | } 219 | } 220 | 221 | return err 222 | } 223 | 224 | // cacheOut : Cache Output stream to be used later 225 | func (c *CMDWrap) cacheOut() error { 226 | 227 | wdir := path.Join(shared.DefaultSettings.CacheDIR, shared.DefaultSettings.ProjectName) 228 | 229 | //check if cache dir exists and dir name 230 | _, err := os.Stat(wdir) 231 | 232 | if err != nil { 233 | 234 | //Create New DIrectory 235 | err := os.Mkdir(wdir, 0755) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | 241 | bin := c.CMD.COutStream.Bytes() 242 | //Save Out to File With HashName rw-r--r-- 243 | err = ioutil.WriteFile(path.Join(wdir, c.CacheKey), bin, 0644) 244 | // g.COutStream.Write(bin) 245 | 246 | return err 247 | 248 | } 249 | 250 | // genCacheKey 251 | func (c *CMDWrap) genCacheKey() { 252 | //copy command 253 | tarr := []string{} 254 | 255 | for _, v := range c.CMD.Cmdsplit { 256 | //if it is a tempory file skip it from hash 257 | if !strings.Contains(v, shared.DefaultSettings.CacheDIR) { 258 | tarr = append(tarr, v) 259 | } 260 | } 261 | 262 | //sort to avoid duplicates 263 | sort.Strings(tarr) 264 | 265 | //Lets use # as separator 266 | suffix := strings.Join(tarr, "#") 267 | 268 | data := []byte(suffix) 269 | 270 | bin := md5.Sum(data) 271 | 272 | c.CacheKey = c.CMD.Cmdsplit[0] + "-" + hex.EncodeToString(bin[:]) 273 | } 274 | 275 | // addReason : It wraps multiple errors if case of a failure 276 | func (c *CMDWrap) addReason(format string, a ...any) { 277 | c.IsInvalid = true 278 | 279 | if c.CauseofFailure == nil { 280 | c.CauseofFailure = []string{} 281 | } 282 | 283 | c.CauseofFailure = append(c.CauseofFailure, fmt.Sprintf(format+"\n", a...)) 284 | 285 | } 286 | 287 | // Disolve : Disolves Command Into Multiple Commands 288 | func (c *CMDWrap) Disolve() ([]CMDWrap, error) { 289 | cmdarr := []CMDWrap{} 290 | //get for statement 291 | getfrom := "" 292 | dynvar := "" 293 | // filtered := []string{} 294 | 295 | for _, v := range strings.Split(c.Raw, " ") { 296 | if strings.Contains(v, "#for:") { 297 | datx := strings.TrimLeft(v, "#for:") 298 | splitdat := strings.Split(datx, ":") 299 | if len(splitdat) == 2 { 300 | getfrom = splitdat[0] 301 | dynvar = splitdat[1] 302 | } else { 303 | return cmdarr, fmt.Errorf("malformed for loop check syntax") 304 | 305 | } 306 | } 307 | } 308 | 309 | value, er1 := internal.ProcessVariable(getfrom) 310 | if er1 != nil { 311 | return cmdarr, er1 312 | } 313 | value = strings.TrimSpace(value) 314 | if len(value) < 1 { 315 | return cmdarr, fmt.Errorf("Variable Has No data . Hence No need for execution of this command") 316 | } 317 | 318 | if dynvar == "" { 319 | return cmdarr, fmt.Errorf("Something went wrong this was not supposed to happen %v", c.Raw) 320 | 321 | } 322 | 323 | for _, v := range strings.Split(value, "\n") { 324 | req := strings.TrimSpace(v) 325 | if v != "" { 326 | 327 | newcmd := strings.ReplaceAll(c.Raw, "#for:"+getfrom+":"+dynvar, "") 328 | 329 | newcmd = strings.ReplaceAll(newcmd, dynvar, req) 330 | 331 | //replace for loop statement 332 | // tmparr := strings.Split(newcmd) 333 | 334 | wrap := NewCMDWrap(newcmd, c.Comment+" Loop") 335 | 336 | cmdarr = append(cmdarr, wrap) 337 | } 338 | } 339 | 340 | return cmdarr, nil 341 | } 342 | 343 | // NewCMDWrap 344 | func NewCMDWrap(newcmd string, comment string) CMDWrap { 345 | 346 | nf, rawcmd := NewNotification(newcmd, comment) 347 | xz := &SimpleCMD{} 348 | 349 | tcmd := internal.NewCommand(rawcmd, comment) 350 | tcmd.Resolve() 351 | 352 | wrap := CMDWrap{ 353 | CMD: xz, 354 | Raw: rawcmd, 355 | Comment: tcmd.Comment, 356 | Alerts: nf, 357 | UID: tcmd.UID, 358 | } 359 | 360 | return wrap 361 | } 362 | -------------------------------------------------------------------------------- /pkg/core/crux.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | 9 | "github.com/tarunKoyalwar/talosplus/pkg/db" 10 | "github.com/tarunKoyalwar/talosplus/pkg/internal" 11 | "github.com/tarunKoyalwar/talosplus/pkg/ioutils" 12 | "github.com/tarunKoyalwar/talosplus/pkg/stringutils" 13 | 14 | "github.com/tarunKoyalwar/talosplus/pkg/scheduler" 15 | "github.com/tarunKoyalwar/talosplus/pkg/shared" 16 | "github.com/tarunKoyalwar/talosplus/pkg/shell" 17 | "github.com/tarunKoyalwar/talosplus/pkg/workshop" 18 | ) 19 | 20 | // Engine : Template Processing Engine of Talosplus 21 | type Engine struct { 22 | CMDs []*shell.CMDWrap // Command Instances 23 | IndexedCMDs map[string]*shell.CMDWrap // Indexed Commands 24 | ExecPyramid [][]*scheduler.Node // Scheduled / Indexed Pyramid 25 | ShowOutput bool // ShowOutput of every command 26 | BlackList map[string]bool // Blacklist specific variable (Beta) 27 | } 28 | 29 | // fillindex : Index all commands and store them in map 30 | func (e *Engine) fillindex() { 31 | for _, v := range e.CMDs { 32 | if v.Alerts == nil { 33 | ioutils.Cout.PrintInfo("Alerts Instance of CMDWrap is nil") 34 | } 35 | e.IndexedCMDs[v.UID] = v 36 | } 37 | } 38 | 39 | // Compile : Compile template 40 | func (e *Engine) Compile(shellscript string) { 41 | 42 | cmdarr := internal.ParseScript(shellscript) 43 | 44 | for _, v := range cmdarr { 45 | // create cmdwraps from basic commands 46 | wrap := shell.NewCMDWrap(v.Raw, v.Comment) 47 | e.CMDs = append(e.CMDs, &wrap) 48 | 49 | } 50 | e.fillindex() 51 | } 52 | 53 | // Schedule : Schedule programs by analyzing dependencies 54 | func (e *Engine) Schedule() { 55 | 56 | t := scheduler.NewScheduler() 57 | 58 | // remove commands from scheduling tree whose value is available 59 | arr := e.removeAvailable() 60 | 61 | //these are needless commands that should not run at all 62 | needless := map[string]bool{} 63 | for _, v := range arr { 64 | addresses := shared.DefaultRegistry.VarAddress[v] 65 | for _, addr := range addresses { 66 | needless[addr] = true 67 | } 68 | } 69 | 70 | ioutils.Cout.Header("[+] Skipping Following commands\n") 71 | 72 | for k := range needless { 73 | val := e.IndexedCMDs[k].Comment 74 | if val == "" { 75 | val = e.IndexedCMDs[k].Raw 76 | } 77 | ioutils.Cout.PrintColor(ioutils.Azure, "%v : %v", k, val) 78 | } 79 | 80 | ioutils.Cout.Seperator(60) 81 | 82 | // autobalance dependencies of blacklisted nodes 83 | t.BlackListed = needless 84 | 85 | for _, v := range e.CMDs { 86 | t.AddNode(v.UID, v.Comment) 87 | } 88 | 89 | // Run Scheduler 90 | t.Run() 91 | 92 | e.ExecPyramid = t.ExecPyramid 93 | 94 | } 95 | 96 | // removeAvailable : [BETA]remove Commands from Schedule whose output is already available/assigned by user 97 | func (e *Engine) removeAvailable() []string { 98 | updatedvars := []string{} 99 | if db.DB == nil { 100 | return updatedvars 101 | } 102 | 103 | //get all runtime vars 104 | z, err := db.DB.GetAllImplicit() 105 | if err != nil { 106 | return updatedvars 107 | } 108 | 109 | /* 110 | for every runtime variable 111 | remove all its dependents 112 | TODO Add Blacklist 113 | 114 | */ 115 | for k, v := range z { 116 | if shared.DefaultRegistry.Dependents[k] != nil { 117 | 118 | // If BlackListed Will run commands again 119 | if e.BlackList[k] { 120 | continue 121 | } 122 | 123 | //unmark these runtime variables as dependents and fill their values 124 | delete(shared.DefaultRegistry.Dependents, k) 125 | 126 | //fill value of this in the shared variable store 127 | err := shared.SharedVars.Set(k, v, false) 128 | if err != nil { 129 | ioutils.Cout.PrintWarning("failed to add variable from db %v", err.Error()) 130 | } else { 131 | updatedvars = append(updatedvars, k) 132 | } 133 | } 134 | } 135 | 136 | return updatedvars 137 | 138 | } 139 | 140 | // Execute : Will Execute Template In orderly Fashion 141 | func (e *Engine) Execute() { 142 | 143 | count := 0 144 | 145 | for _, v := range e.ExecPyramid { 146 | ioutils.Cout.Header("[^_^] Executing Level %v Commands\n", count) 147 | count += 1 148 | 149 | queue := []shell.CMDWrap{} 150 | 151 | //check if it for loop and dissolve for each level 152 | for _, ftest := range v { 153 | 154 | uid := ftest.UID 155 | c := e.IndexedCMDs[uid] 156 | 157 | if c == nil { 158 | ioutils.Cout.PrintWarning("This was not supposed to happen") 159 | ioutils.Cout.PrintWarning("%v with %v Not Found", ftest.Comment, uid) 160 | } else { 161 | 162 | c.Process() 163 | 164 | if !c.IsForLoop { 165 | queue = append(queue, *c) 166 | 167 | } else { 168 | dissolved, er := c.Disolve() 169 | 170 | if er != nil { 171 | ioutils.Cout.Printf("[-] %v Will Not be Executed because :%v\n", c.Comment, ioutils.Cout.ErrColor(er).Bold()) 172 | } else { 173 | for _, tinstance := range dissolved { 174 | 175 | dx := tinstance 176 | 177 | dx.Process() 178 | 179 | e.IndexedCMDs[dx.UID] = &dx 180 | e.CMDs = append(e.CMDs, &dx) 181 | queue = append(queue, dx) 182 | } 183 | } 184 | 185 | } 186 | 187 | } 188 | } 189 | 190 | finalqueue := []*shell.CMDWrap{} 191 | 192 | for _, c := range queue { 193 | //All Checks Passed 194 | if !c.IsInvalid { 195 | 196 | ioutils.Cout.PrintInfo("(*) Scheduled... %v", strings.Join(c.CMD.Cmdsplit, " ")) 197 | finalqueue = append(finalqueue, e.IndexedCMDs[c.UID]) 198 | // fmt.Println(unsafe.Sizeof(c)) 199 | 200 | } else { 201 | ioutils.Cout.Printf("[-] %v Will Not be Executed because :\n%v", c.Comment, ioutils.Cout.GetColor(ioutils.Azure, strings.Join(c.CauseofFailure, "\n"))) 202 | } 203 | } 204 | 205 | workshop.ExecQueue(finalqueue, shared.DefaultSettings.Limit, e.ShowOutput) 206 | ioutils.Cout.Seperator(60) 207 | 208 | } 209 | //cleanup 210 | 211 | defer cleanup() 212 | 213 | } 214 | 215 | // Evaluate : Summarizes & Evaluate All Script Data 216 | func (e *Engine) Evaluate() { 217 | 218 | tmp := []string{} 219 | 220 | if ioutils.Cout.VeryVerbose { 221 | //Only in very verbose Mode 222 | 223 | ioutils.Cout.Header("\n[*] Parsed Settings\n") 224 | ioutils.Cout.Value("%-16v : %v", "Purge Cache", shared.DefaultSettings.Purge) 225 | ioutils.Cout.Value("%-16v : %v", "Concurrency", shared.DefaultSettings.Limit) 226 | ioutils.Cout.Value("%-16v : %v", "ProjectName", shared.DefaultSettings.ProjectName) 227 | ioutils.Cout.Value("%-16v : %v", "CacheDir", shared.DefaultSettings.CacheDIR) 228 | ioutils.Cout.Value("%-16v : %v", "Verbose", ioutils.Cout.Verbose) 229 | 230 | ioutils.Cout.Seperator(60) 231 | 232 | gvars := shared.SharedVars.GetGlobalVars() 233 | 234 | ioutils.Cout.Header("\n[*] Used Explicit declared Variables\n") 235 | 236 | for k, v := range gvars { 237 | tarr := strings.Split(v, "\n") 238 | if len(tarr) == 1 { 239 | ioutils.Cout.Value("%-16v : %v", k, tarr[0]) 240 | } else { 241 | ioutils.Cout.Value("%-16v : %v", k, tarr[0]) 242 | for _, zx := range tarr[1:] { 243 | ioutils.Cout.Value("%-16v : %v", "", zx) 244 | } 245 | } 246 | 247 | } 248 | 249 | ioutils.Cout.Seperator(60) 250 | 251 | ioutils.Cout.Header("\n[*] Generated UIDs For Commands\n") 252 | for k, v := range e.IndexedCMDs { 253 | identifier := v.Comment 254 | if identifier == "" { 255 | identifier = v.Raw 256 | } 257 | ioutils.Cout.Value("%v : %v", k, identifier) 258 | } 259 | 260 | ioutils.Cout.Seperator(60) 261 | 262 | ioutils.Cout.Header("[*] Dependencies Found\n") 263 | 264 | } 265 | 266 | // Evaluate required , available and extra variables 267 | for k, v := range shared.DefaultRegistry.Dependents { 268 | if len(v) != 0 { 269 | 270 | vaddress := shared.DefaultRegistry.VarAddress[k] 271 | providers := []string{} 272 | 273 | for _, addr := range vaddress { 274 | if e.IndexedCMDs[addr] != nil { 275 | providers = append(providers, e.IndexedCMDs[addr].Comment) 276 | } else { 277 | providers = append(providers, k) 278 | } 279 | } 280 | 281 | requiredby := []string{} 282 | 283 | for _, uid := range v { 284 | if e.IndexedCMDs[uid] != nil { 285 | inst := e.IndexedCMDs[uid] 286 | requiredby = append(requiredby, inst.Comment) 287 | } 288 | } 289 | 290 | if ioutils.Cout.VeryVerbose { 291 | // Print Only in Verbose Mode 292 | 293 | zx := fmt.Sprintf("[ %v ] Will be Executed After [%v]\n", strings.Join(requiredby, " , "), strings.Join(providers, " , ")) 294 | ioutils.Cout.Printf("%v", ioutils.Cout.GetColor(ioutils.LightGreen, "%v", zx)) 295 | } 296 | 297 | } else { 298 | tmp = append(tmp, k) 299 | } 300 | } 301 | 302 | if ioutils.Cout.VeryVerbose { 303 | ioutils.Cout.Seperator(60) 304 | } 305 | 306 | if len(tmp) > 0 { 307 | 308 | ioutils.Cout.Header("[*] Following Values Were Never Used :") 309 | ioutils.Cout.PrintColor(ioutils.Azure, strings.Join(tmp, "\n")) 310 | ioutils.Cout.Seperator(60) 311 | 312 | } 313 | 314 | if ioutils.Cout.VeryVerbose { 315 | ioutils.Cout.Header("[*] Implicit Declarations\n") 316 | } 317 | 318 | notdeclared := []string{} 319 | for k, v := range shared.DefaultRegistry.FoundVars { 320 | if v { 321 | 322 | if ioutils.Cout.VeryVerbose { 323 | ioutils.Cout.Value("[+] Found Implicit Declaration of %v", k) 324 | } 325 | 326 | } else { 327 | notdeclared = append(notdeclared, k) 328 | } 329 | } 330 | 331 | if ioutils.Cout.VeryVerbose { 332 | ioutils.Cout.Seperator(60) 333 | } 334 | 335 | if len(notdeclared) > 0 { 336 | ioutils.Cout.Header("\n[*] Following Variables Where Not Found") 337 | fatal := "" 338 | 339 | for k, v := range shared.DefaultRegistry.FoundVars { 340 | if !v { 341 | fatal += fmt.Sprintf("[-] Missing Declaration for %v\n", k) 342 | } 343 | } 344 | 345 | ioutils.Cout.PrintColor(ioutils.Red, fatal) 346 | 347 | os.Exit(1) 348 | } 349 | 350 | } 351 | 352 | // PrintAllCMDs : Pretty Prints All Commands 353 | func (e *Engine) PrintAllCMDs() { 354 | 355 | if !ioutils.Cout.VeryVerbose { 356 | return 357 | } 358 | 359 | ioutils.Cout.Header("[*] All Accepted Commands\n") 360 | 361 | for _, v := range e.IndexedCMDs { 362 | ioutils.Cout.PrintColor(ioutils.Azure, "\n[+] %v", v.Comment) 363 | ioutils.Cout.Printf("=> %v", ioutils.Cout.GetColor(ioutils.Green, v.Raw)) 364 | if v.Alerts != nil { 365 | if v.Alerts.NotifyEnabled { 366 | 367 | ioutils.Cout.PrintColor(ioutils.Grey, "[&] %vResult", v.Alerts.NotifyMsg) 368 | 369 | } 370 | } 371 | 372 | } 373 | 374 | ioutils.Cout.Seperator(60) 375 | 376 | } 377 | 378 | // NewEngine 379 | func NewEngine() *Engine { 380 | z := Engine{ 381 | CMDs: []*shell.CMDWrap{}, 382 | IndexedCMDs: map[string]*shell.CMDWrap{}, 383 | ExecPyramid: [][]*scheduler.Node{}, 384 | BlackList: map[string]bool{}, 385 | } 386 | 387 | return &z 388 | } 389 | 390 | // NewVolatileEngine : Engine preconfigured with defaults for volatile use 391 | func NewVolatileEngine() *Engine { 392 | ioutils.Cout.Verbose = true 393 | 394 | z := Engine{ 395 | CMDs: []*shell.CMDWrap{}, 396 | IndexedCMDs: map[string]*shell.CMDWrap{}, 397 | ExecPyramid: [][]*scheduler.Node{}, 398 | BlackList: map[string]bool{}, 399 | } 400 | 401 | z.ShowOutput = true 402 | 403 | shared.DefaultSettings.Limit = 8 404 | shared.DefaultSettings.Purge = true 405 | 406 | db.UseBBoltDB(os.TempDir(), "talos"+stringutils.RandomString(3)+".db", "TalosDefault") 407 | 408 | return &z 409 | } 410 | 411 | // cleanup : clean uneeded items from fs i.e Exports or runtime files 412 | func cleanup() { 413 | 414 | exportpath := path.Join(shared.DefaultSettings.CacheDIR, shared.DefaultSettings.ProjectExportName) 415 | 416 | _, err := os.Stat(exportpath) 417 | if err != nil { 418 | return 419 | } 420 | 421 | // exports are runtime files {file} created and are not persistent 422 | // and is not part of fs cache 423 | os.RemoveAll(exportpath) 424 | } 425 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 2 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 3 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 4 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 5 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 6 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/disgoorg/disgo v0.10.2 h1:LUJjll/fqdBGJglIt8OSzxvJhn6xByTXVGPQQdtHk98= 12 | github.com/disgoorg/disgo v0.10.2/go.mod h1:Cyip4bCYHD3rHgDhBPT9cLo81e9AMbDe8ocM50UNRM4= 13 | github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= 14 | github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= 15 | github.com/disgoorg/snowflake/v2 v2.0.0 h1:+xvyyDddXmXLHmiG8SZiQ3sdZdZPbUR22fSHoqwkrOA= 16 | github.com/disgoorg/snowflake/v2 v2.0.0/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs= 17 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 18 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 19 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 20 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 21 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 22 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 24 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 25 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 26 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 27 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 28 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 29 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 30 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 31 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 32 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 33 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 34 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 35 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 36 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 37 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 38 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 39 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 40 | github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y= 41 | github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= 42 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 43 | github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= 44 | github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= 45 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 46 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 h1:4o97N9ftX1J3iKlIRVMPVOVZs4qbCczJvoFF2WA40t4= 50 | github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963/go.mod h1:DaY7wmLPMleyHDCD/14YApPCDtrARY4J8Eny2ZGsG/g= 51 | github.com/projectdiscovery/goflags v0.1.1 h1:AEtT14D9OC10HWyZwDQaSLjuK8ZKoBrSYlsLItvMKZI= 52 | github.com/projectdiscovery/goflags v0.1.1/go.mod h1:/YBPA+1igSkQbwD7a91o0HUIwMDlsmQDRZL2oSYSyEQ= 53 | github.com/projectdiscovery/stringsutil v0.0.0-20220422150559-b54fb5dc6833/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= 54 | github.com/projectdiscovery/stringsutil v0.0.1 h1:a6TCMT+D1aUsoZxNiYf9O30wiDOoLOHDwj89HBjr5BQ= 55 | github.com/projectdiscovery/stringsutil v0.0.1/go.mod h1:TDi2LEqR3OML0BxGoMbbfAHSk5AdfHX762Oc302sgmM= 56 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 57 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 58 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 59 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= 60 | github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 61 | github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= 62 | github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= 63 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 64 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 65 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 66 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 68 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 69 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 70 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 71 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 73 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 74 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 75 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 76 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 77 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 78 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 79 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 80 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 81 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 82 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 83 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 84 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 85 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 86 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 87 | go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c= 88 | go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 89 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 90 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU= 91 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 92 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 93 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 94 | golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 95 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= 96 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 97 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 100 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= 109 | golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 111 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 112 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 113 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 114 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 115 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 116 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 118 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 119 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 120 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 121 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 122 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 123 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 125 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 126 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 127 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 128 | --------------------------------------------------------------------------------