├── img.png ├── version.go ├── .gitignore ├── go.mod ├── .github ├── workflows │ └── go.yml └── dependabot.yml ├── .goreleaser.yml ├── release.sh ├── LICENSE ├── go.sum ├── channels_test.go ├── main_integration_test.go ├── channels.go ├── args.go ├── main.go └── README.md /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BountyStrike/Emissary/HEAD/img.png -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // VERSION Emissary version 4 | const VERSION = "1.1.0" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.ini 3 | *.swp 4 | bin/ 5 | dist/ 6 | *.tmp 7 | .vscode/ 8 | *.exe 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/BountyStrike/emissary 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.0 // indirect 7 | gopkg.in/ini.v1 v1.67.0 8 | ) 9 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.15 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - main: . 3 | binary: emissary 4 | env: 5 | - CGO_ENABLED=0 6 | goos: 7 | - linux 8 | - darwin 9 | - windows 10 | goarch: 11 | - 386 12 | - amd64 13 | - arm 14 | - arm64 15 | 16 | changelog: 17 | sort: asc 18 | filters: 19 | exclude: 20 | - balls 21 | - docs 22 | - Merge pull request 23 | - Merge branch 24 | 25 | archives: 26 | - format: tar.gz 27 | format_overrides: 28 | - goos: windows 29 | format: zip 30 | files: 31 | - LICENSE 32 | - README.md 33 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # nothing to see here, just a utility i use to create new releases ^_^ 3 | 4 | CURRENT_VERSION=$(cat version.go | grep VERSION | cut -d '"' -f 2) 5 | 6 | echo -n "Current version is $CURRENT_VERSION, select new version: " 7 | read NEW_VERSION 8 | echo "Creating version $NEW_VERSION ..." 9 | 10 | echo "Updating version.go" 11 | sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" version.go 12 | 13 | git add version.go 14 | git commit -m "Releasing v$NEW_VERSION" 15 | git push 16 | 17 | git tag -a v$NEW_VERSION -m "Release v$NEW_VERSION" 18 | git push origin v$NEW_VERSION 19 | 20 | rm -rf dist 21 | 22 | echo "All done, just run goreleaser now ^_^" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 BountyStrike 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 11 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /channels_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func TestPreparePayload(t *testing.T) { 10 | 11 | message := "msg" 12 | textField := "content" 13 | additionalData := "{\"someKey\":\"someValue\",\"someOtherKey\":\"someOtherValue\"}" 14 | json, err := PreparePayload(message, textField, additionalData) 15 | if err != nil { 16 | t.Fatal("PreparePayload returned error: ", err) 17 | } 18 | strJSON := string(json) 19 | expectedValue := `{"content":"msg","someKey":"someValue","someOtherKey":"someOtherValue"}` 20 | if strJSON != expectedValue { 21 | t.Errorf("Expected %s, got %s", expectedValue, strJSON) 22 | } 23 | } 24 | 25 | func TestSendRequest(t *testing.T) { 26 | // 1. start web server 27 | 28 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 | 30 | })) 31 | defer ts.Close() 32 | 33 | res, err := http.Get(ts.URL) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | webhook := ts.URL 39 | json := []byte(`{"content":"msg","someKey":"someValue","someOtherKey":"someOtherValue"}`) 40 | resp, err := SendRequest(webhook, json) 41 | 42 | res.Body.Close() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if err != nil { 48 | t.Fatal("SendRequest returned error: ", err) 49 | } 50 | 51 | if resp.StatusCode != 200 { 52 | t.Errorf("Expected status code %d, got %d", 200, resp.StatusCode) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /main_integration_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "os" 7 | "os/exec" 8 | "testing" 9 | ) 10 | 11 | func TestMainApp(t *testing.T) { 12 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | 14 | })) 15 | defer ts.Close() 16 | 17 | res, err := http.Get(ts.URL) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | webhook := "webhook:=" + ts.URL + "§data:={'chat_id': 'xxxx'}" 23 | old := os.Args 24 | os.Args = []string{"main", "-in", webhook, "-m", "hacker"} 25 | main() 26 | // Reset so next tests will work 27 | os.Args = old 28 | 29 | res.Body.Close() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | if res.StatusCode != 200 { 35 | t.Errorf("Expected status code %d, got %d", 200, res.StatusCode) 36 | } 37 | } 38 | 39 | func TestMainWithoutWebhook(t *testing.T) { 40 | 41 | // This is a hack in order to test the application without 42 | // failing the test when e.g. os.Exit(1) is executed. 43 | // By doing this I have complete control over the exit status code. 44 | if os.Getenv("EXEC") != "" { 45 | 46 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 47 | 48 | })) 49 | defer ts.Close() 50 | 51 | res, err := http.Get(ts.URL) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | webhook := ts.URL + "§data:={'chat_id': 'xxxx'}" 57 | os.Args = []string{"main", "-in", webhook, "-m", "hacker"} 58 | main() 59 | 60 | err = res.Body.Close() 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | if res.Request.Method == "" { 66 | t.Errorf("Expected no request to server, got a %s request", res.Request.Method) 67 | } 68 | 69 | panic("Should never reach this") 70 | } 71 | 72 | // Run the test in a subprocess 73 | cmd := exec.Command(os.Args[0], "-test.run=TestMainWithoutWebhook") 74 | cmd.Env = append(os.Environ(), "EXEC=TRUE") 75 | err := cmd.Run() 76 | 77 | // Cast the error as *exec.ExitError and compare the result 78 | e, ok := err.(*exec.ExitError) 79 | 80 | if ok != true { 81 | t.Errorf("Was not true, is %t", ok) 82 | } 83 | 84 | expectedErrorString := "exit status 1" 85 | if e.Error() != expectedErrorString { 86 | t.Errorf("Was not expectedSTring, got: %s", e.Error()) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /channels.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/smtp" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // EmailConfig contains email data 13 | type EmailConfig struct { 14 | username string 15 | password string 16 | server string 17 | port string 18 | recipient string 19 | subject string 20 | message string 21 | } 22 | 23 | // PreparePayload Prepare a json payload to be sent to channel 24 | func PreparePayload(message string, msgField string, additionalData string) ([]byte, error) { 25 | 26 | jayson := map[string]interface{}{ 27 | msgField: message, 28 | } 29 | // Required for valid json 30 | additionalData = strings.ReplaceAll(additionalData, "'", "\"") 31 | if additionalData != "" { 32 | data := []byte(`` + additionalData + ``) 33 | var f interface{} 34 | if err := json.Unmarshal(data, &f); err != nil { 35 | return nil, err 36 | } 37 | m := f.(map[string]interface{}) 38 | for k, v := range m { 39 | jayson[k] = v 40 | } 41 | } 42 | js, err := json.Marshal(jayson) 43 | if err != nil { 44 | return []byte{}, err 45 | } 46 | return js, nil 47 | } 48 | 49 | // Email Send messages via email 50 | func Email(email EmailConfig) error { 51 | // Set up authentication information. 52 | auth := smtp.PlainAuth("", email.username, email.password, email.server) 53 | 54 | // Connect to the server, authenticate, set the sender and recipient, 55 | // and send the email all in one step. 56 | to := []string{email.recipient} 57 | msg := []byte("To: " + email.recipient + "\r\n" + 58 | "Subject: " + email.subject + "\r\n" + 59 | "\r\n" + 60 | email.message + "\r\n") 61 | err := smtp.SendMail(email.server+":"+email.port, auth, email.username, to, msg) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // SendRequest Send the request to the webhook 70 | func SendRequest(endpoint string, data []byte) (*http.Response, error) { 71 | 72 | tr := &http.Transport{ 73 | MaxIdleConns: 10, 74 | IdleConnTimeout: 30 * time.Second, 75 | DisableCompression: true, 76 | } 77 | 78 | var resp *http.Response 79 | var err error 80 | 81 | client := &http.Client{Transport: tr} 82 | 83 | resp, err = client.Post(endpoint, "application/json", bytes.NewBuffer(data)) 84 | 85 | if err != nil { 86 | return resp, err 87 | } 88 | 89 | defer resp.Body.Close() 90 | 91 | return resp, nil 92 | } 93 | -------------------------------------------------------------------------------- /args.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type channels []string 11 | 12 | func (c *channels) String() string { 13 | return "" 14 | } 15 | 16 | func (c *channels) Set(value string) error { 17 | *c = append(*c, value) 18 | return nil 19 | } 20 | 21 | type inlines struct { 22 | hooks []inline 23 | } 24 | 25 | type inline struct { 26 | webhook string 27 | textField string 28 | data string 29 | } 30 | 31 | func (i *inlines) String() string { 32 | return "{'lol':1}" 33 | } 34 | 35 | func (i *inlines) Set(value string) error { 36 | split := strings.Split(value, "§") 37 | mul := inlines{} 38 | final := inline{} 39 | for _, val := range split { 40 | s := strings.Split(val, ":=") 41 | 42 | if strings.ToLower(s[0]) == "webhook" { 43 | final.webhook = s[1] 44 | } 45 | 46 | if strings.ToLower(s[0]) == "textField" { 47 | final.textField = s[1] 48 | } 49 | 50 | if strings.ToLower(s[0]) == "data" { 51 | final.data = s[1] 52 | } 53 | 54 | } 55 | mul.hooks = append(mul.hooks, final) 56 | *i = mul 57 | 58 | return nil 59 | } 60 | 61 | type cliOptions struct { 62 | email bool 63 | version bool 64 | stdin bool 65 | message string 66 | channel channels 67 | inline inlines 68 | data string 69 | text string 70 | header string 71 | rows int 72 | } 73 | 74 | func processArgs() cliOptions { 75 | 76 | opts := cliOptions{} 77 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 78 | flag.Var(&opts.channel, "channel", "Specify a custom channel you have defined in ~/.config/emissary.ini") 79 | flag.Var(&opts.channel, "ch", "Specify a custom channel you have defined in ~/.config/emissary.ini") 80 | flag.Var(&opts.inline, "inline", "Specify channel directly in the command line") 81 | flag.Var(&opts.inline, "in", "Specify channel directly in the command line") 82 | flag.BoolVar(&opts.email, "email", false, "Send via smtp") 83 | flag.BoolVar(&opts.email, "e", false, "Send via smtp") 84 | flag.StringVar(&opts.header, "header", "Data from Emissary", "Specify custom header to send. Default 'Data from Emissary'") 85 | flag.StringVar(&opts.header, "h", "Data from Emissary", "Specify custom header to send. Default 'Data from Emissary'") 86 | flag.StringVar(&opts.text, "text", "", "Specify the field that contains the message. Default is 'text'") 87 | flag.StringVar(&opts.text, "txt", "", "Specify the field that contains the message. Default is 'text'") 88 | flag.StringVar(&opts.data, "data", "", "Specify json data that should be sent") 89 | flag.StringVar(&opts.data, "d", "", "Specify json data that should be sent") 90 | flag.BoolVar(&opts.version, "version", false, "Show version number") 91 | flag.BoolVar(&opts.version, "v", false, "Show version number") 92 | flag.BoolVar(&opts.stdin, "stdin", false, "Take input from stdin") 93 | flag.BoolVar(&opts.stdin, "si", false, "Take input from stdin") 94 | flag.StringVar(&opts.message, "message", "", "The message you want to send") 95 | flag.StringVar(&opts.message, "m", "", "The message you want to send") 96 | flag.IntVar(&opts.rows, "rows", 20, "Max rows/lines to send, 0 for unlimited. Default 20") 97 | flag.IntVar(&opts.rows, "r", 20, "Max rows/lines to send, 0 for unlimited. Default 20") 98 | flag.Parse() 99 | 100 | return opts 101 | 102 | } 103 | 104 | func init() { 105 | flag.Usage = func() { 106 | h := "\nSend data through chat channels. Made by @dubs3c.\n\n" 107 | 108 | h += "Usage:\n" 109 | h += " emissary [options] [message]\n\n" 110 | 111 | h += "Options:\n" 112 | h += " -ch, --channel Specify a custom channel you have defined emissary.ini\n" 113 | h += " -in, --inline Specify channel directly in the commandline\n" 114 | h += " -m, --message Message to send\n" 115 | h += " -h, --header Custom header\n" 116 | h += " -si, --stdin Get message from stdin\n" 117 | h += " -e, --email Send via Email\n" 118 | h += " -txt, --text Specify the field that contains the message. Default is 'message'\n" 119 | h += " -d, --data Specify additional data in json format that should be sent\n" 120 | h += " -r, --rows Max rows/lines to send, 0 for unlimited. Default 20\n" 121 | h += " -v, --version Show version\n" 122 | 123 | h += "\nExamples:\n" 124 | h += " emissary --channel Telegram --message \"Hello telegram\"\n" 125 | h += " cat domins.txt | emissary -ch Slack --stdin --header \"New subdomains from Google!\"\n" 126 | h += " emissary -ch Discord -ch Telegram -m \"Your message\" \n" 127 | h += " emissary -in \"webhook:=https://api.telegram.org/botxxxxx/sendMessage§data:={'chat_id': 'xxxx'}\" -in \"webhook:=https://hooks.slack.com/services/xxxxx\" -m \"Hack the planet!\" \n" 128 | 129 | fmt.Fprintf(os.Stderr, h) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "os/user" 12 | "path/filepath" 13 | "strings" 14 | 15 | "gopkg.in/ini.v1" 16 | ) 17 | 18 | func checkResponse(httpResponse *http.Response, err error) { 19 | if httpResponse != nil { 20 | if httpResponse.StatusCode >= 400 { 21 | body, respErr := ioutil.ReadAll(httpResponse.Body) 22 | if respErr != nil { 23 | log.Println("Error reading response body:", respErr) 24 | } 25 | log.Println("Response HTTP Status code: ", httpResponse.StatusCode) 26 | log.Println("Response HTTP Body: ", string(body)) 27 | } 28 | } 29 | 30 | if err != nil { 31 | log.Printf("Something went wrong sending your message: %s\n", err) 32 | } 33 | } 34 | 35 | func main() { 36 | opts := processArgs() 37 | 38 | if opts.version { 39 | fmt.Printf("Emissary version: %s\n", VERSION) 40 | os.Exit(0) 41 | } 42 | if len(os.Args) <= 1 { 43 | flag.Usage() 44 | os.Exit(0) 45 | } 46 | 47 | var cfg *ini.File 48 | 49 | if len(opts.channel) != 0 || opts.email { 50 | 51 | User, err := user.Current() 52 | 53 | if err != nil { 54 | log.Fatal("Something went wrong trying to figure out your home directory", err) 55 | } 56 | 57 | configPath := filepath.FromSlash(User.HomeDir + "/.config/emissary.ini") 58 | 59 | cfg, err = ini.Load(configPath) 60 | if err != nil { 61 | log.Fatal("Fail to read configuration file: ", err) 62 | } 63 | } 64 | 65 | if len(opts.message) > 0 && opts.stdin { 66 | fmt.Println("[-] Please take input from either STDIN or cli argument, not both.") 67 | os.Exit(1) 68 | } 69 | 70 | if opts.message == "" && !opts.stdin { 71 | fmt.Println("[-] You forgot to set message...") 72 | os.Exit(1) 73 | } 74 | 75 | if len(opts.inline.hooks) > 0 { 76 | 77 | for _, val := range opts.inline.hooks { 78 | if val.webhook == "" { 79 | fmt.Println("[-] Inline webhook does not contain webhook...") 80 | os.Exit(1) 81 | } 82 | 83 | if val.textField == "" { 84 | val.textField = "text" 85 | } 86 | 87 | json, err := PreparePayload(opts.message, val.textField, val.data) 88 | 89 | if err != nil { 90 | log.Printf("Could not prepare payload for webhook %s", val.webhook) 91 | continue 92 | } 93 | 94 | resp, err := SendRequest(val.webhook, json) 95 | 96 | checkResponse(resp, err) 97 | } 98 | 99 | } 100 | 101 | messages := []string{opts.header + "\n--------"} 102 | 103 | if opts.stdin { 104 | count := 0 105 | sc := bufio.NewScanner(os.Stdin) 106 | msg := "" 107 | for sc.Scan() { 108 | msg = sc.Text() 109 | if opts.rows == 0 { 110 | messages = append(messages, msg) 111 | } else { 112 | if count < opts.rows { 113 | messages = append(messages, msg) 114 | } else { 115 | break 116 | } 117 | } 118 | count++ 119 | } 120 | 121 | messages = append(messages, fmt.Sprintf("--------\nSent %d lines", count)) 122 | opts.message = strings.Join(messages[:], "\n") 123 | } 124 | 125 | if len(opts.channel) != 0 { 126 | for _, ch := range opts.channel { 127 | webhook := cfg.Section(ch).Key("webhook").String() 128 | textField := cfg.Section(ch).Key("textField").MustString("text") 129 | additionalData := cfg.Section(ch).Key("data").String() 130 | 131 | if webhook == "" { 132 | log.Printf("[-] Channel %s does not contain a webhook...", ch) 133 | continue 134 | } 135 | 136 | // MS Teams hack for properly showing rows 137 | if strings.HasPrefix(webhook, "https://outlook.office.com") { 138 | split := strings.Split(opts.message, "\n") 139 | newMessage := "" 140 | for _, v := range split { 141 | newMessage += v + "\n\n" 142 | } 143 | opts.message = newMessage 144 | } 145 | 146 | json, err := PreparePayload(opts.message, textField, additionalData) 147 | 148 | if err != nil { 149 | log.Printf("Could not prepare payload for channel %s", ch) 150 | continue 151 | } 152 | 153 | resp, err := SendRequest(webhook, json) 154 | 155 | checkResponse(resp, err) 156 | 157 | } 158 | } 159 | 160 | if opts.email { 161 | emailUsername := cfg.Section("Email").Key("username").String() 162 | emailPassword := cfg.Section("Email").Key("password").String() 163 | emailRecipient := cfg.Section("Email").Key("recipient").String() 164 | emailPort := cfg.Section("Email").Key("port").String() 165 | emailServer := cfg.Section("Email").Key("server").String() 166 | emailSubject := cfg.Section("Email").Key("subject").String() 167 | 168 | emailConfig := EmailConfig{username: emailUsername, password: emailPassword, 169 | recipient: emailRecipient, port: emailPort, server: emailServer, subject: emailSubject, 170 | message: opts.message} 171 | 172 | checkResponse(nil, Email(emailConfig)) 173 | 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
6 |
7 | Send notifications via different channels such as Slack, Telegram or Teams in your bug bounty flow.
8 |