Skip to content

Commit

Permalink
* initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipe Gonçalves committed Feb 25, 2014
1 parent 8b189b8 commit 88ce21b
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 1 deletion.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
bomber
======

Mass sender of email samples
Simple program to send huge amounts of predefined email samples to an SMTP server.
Just an excuse to try out Go, really...

```
Usage of bomber:
-c="all": Category of messages to send
-l=false: Only list available categories.
-n=100: Number of message to be sent
-s="localhost": SMTP server to send the message to
-samples="./samples/": Directory containing email message samples in JSON
-throttle=0: Throttle the message flow (msg/second).
-v=false: Prints details of execution.
```
184 changes: 184 additions & 0 deletions bomber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"github.com/fmgoncalves/bomber/encoding/mail/sample"
"fmt"
"io"
"io/ioutil"
"net/smtp"
"flag"
"time"
"math/rand"
"strings"
"runtime"
"os"
)

const (
default_host = "localhost"
default_dir = "samples"
default_port = 25
)

func die(err error) {
if err != nil {
panic(fmt.Sprintf("%v", err))
}
return
}

func send_default_header(key string, value string, sample_keys map[string] struct{}, wc io.WriteCloser){
if _, contains := sample_keys[strings.ToLower(key)] ; !contains {
wc.Write([]byte(key + ":" + value + "\r\n"))
}
}

func hasCategory(s sample.MailSample, category string) bool {
c := strings.ToLower(category)
for _, s_category := range s.Type {
if c == strings.ToLower(s_category) {
return true
}
}
return false
}

func send_sample(host string, s sample.MailSample) error {
var err error
// Connect to the remote SMTP server.
var port int
if s.Port > 0 {
port = s.Port
} else {
port = default_port
}
c, err := smtp.Dial(fmt.Sprintf("%s:%d",host, port))
if err != nil { return err }
//die(err)
defer c.Quit()
// MAIL_FROM, TO
c.Mail(s.From)
c.Rcpt(s.To)
// DATA
wc, err := c.Data()
if err != nil { return err }
//die(err)
defer wc.Close()
// Default headers
keyz := s.DefinedHeaders()
send_default_header("From", s.From, keyz, wc)
send_default_header("To", s.To, keyz, wc)
send_default_header("Date", time.Now().UTC().Format(time.RFC822), keyz, wc)
send_default_header("Message-Id", fmt.Sprintf("<%v@crap>",rand.Int()), keyz, wc)

for _, h := range s.Headers {
wc.Write([]byte(h + "\r\n"))
}
wc.Write([]byte("\r\n"))
wc.Write([]byte(s.Body))
return err
}

func main() {
var (
SAMPLES_DIRECTORY string
HOST string
CATEGORY string
N_MESSAGES int
THROTTLING float64
LIST_ONLY bool
VERBOSE bool
)

flag.StringVar(&HOST, "s", default_host, "SMTP server to send the message to")
//flag.StringVar(&SAMPLES_DIRECTORY, "samples", default_dir, "Directory containing email message samples in JSON")
samples_dir := os.Getenv("BOMBER_SAMPLES")
if len(samples_dir) == 0 {
samples_dir = "samples"
}
flag.StringVar(&SAMPLES_DIRECTORY, "samples", samples_dir, "Directory containing email message samples in JSON")
flag.StringVar(&CATEGORY, "c", "all", "Category of messages to send")
flag.IntVar(&N_MESSAGES, "n", 100, "Number of message to be sent")
flag.Float64Var(&THROTTLING, "throttle", 0, "Throttle the message flow (msg/second).")
flag.BoolVar(&LIST_ONLY, "l", false, "Only list available categories.")
flag.BoolVar(&VERBOSE, "v", false, "Prints details of execution.")
flag.Parse()

l, err := ioutil.ReadDir(SAMPLES_DIRECTORY)
die(err)
sample_list := make([]sample.MailSample,len(l))
categories := make(map[string]bool)
idx := 0
for _, val := range l {
if ! val.IsDir() && strings.HasSuffix(val.Name(),".json") {
s, err := sample.Unmarshal(SAMPLES_DIRECTORY+"/"+val.Name())
if err != nil {
if VERBOSE {
fmt.Printf("Failed to load %s sample\n",val.Name())
}
}
if VERBOSE {
fmt.Printf("Loaded %s sample\n",val.Name())
}
if LIST_ONLY {
for _, s_category := range s.Type {
asdfg := strings.ToLower(s_category)
categories[asdfg] = true
}
}
if CATEGORY == "all" || hasCategory(s,CATEGORY) {
sample_list[idx] = s
idx++
}
}
}
if LIST_ONLY {
fmt.Printf("Categories found in samples:\n")
for cat, _ := range categories {
fmt.Printf("\t%v\n",cat)
}
return
}

if ( idx > 0 ){
sample_list = sample_list[0:idx]
} else {
panic("No samples to use.")
}

pn := runtime.NumCPU()
//runtime.GOMAXPROCS(pn)
if THROTTLING > 0 {
pn = int(THROTTLING)
}
if VERBOSE {
fmt.Printf("Sending %v messages in batches of %v\n", N_MESSAGES, pn);
}

c := make(chan int, pn)
for i:= 0; i < pn; i++ {
c <- 1
}
for i:= 0; i < N_MESSAGES; i++ {
<-c
go func (j int) {
if VERBOSE {
fmt.Printf("Sending message #%v\n",j+1)
}
err := send_sample(HOST, sample_list[rand.Int() % len(sample_list)])
if err != nil {
fmt.Printf("Failed to send message #%v: %v\n", j+1, err)
}
if THROTTLING > 0 {
// TODO throttling should consider the running time of send_sample
time.Sleep(time.Duration( (float64(pn) /THROTTLING) * 1000.0 * float64(time.Millisecond)))
}
c <- 1
}(i)
}
for i:= 0; i < pn; i++ {
<- c
}

return
}
85 changes: 85 additions & 0 deletions encoding/mail/sample/sample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package sample

import (
"encoding/json"
"io/ioutil"
"path"
"strings"
)

type MailSample struct {
Type []string
Port int
To string
From string
Headers []string
HeadersFile string
Body string
BodyFile string
}

func (s MailSample) DefinedHeaders() map[string]struct{} {
keys_map := make(map[string] struct{})
for _, val := range s.Headers {
key := strings.SplitN(val,":",2)[0]
keys_map[strings.ToLower(key)] = struct{}{};
}
return keys_map;
}



func (s *MailSample) buildHeaders(file_path string) error {
var err error
if len(s.Headers) == 0 && len(s.HeadersFile) != 0 {
f, err := ioutil.ReadFile(path.Dir(file_path)+"/"+s.HeadersFile)
if err != nil {
return err
}
headers_aux := strings.Split(string(f), "\n")
headers := make([]string, len(headers_aux))
idx := -1
for _, val := range headers_aux {
if ! strings.HasPrefix(val," ") && ! strings.HasPrefix(val,"\t") {
idx++
} else {
headers[idx] += "\r\n"
}
headers[idx] += strings.TrimRight(val,"\r")
}
headers = headers[0:idx+1]
s.Headers = headers
}
return err
}

func (s *MailSample) buildBody(file_path string) error {
var err error
if len(s.Body) == 0 && len(s.BodyFile) != 0 {
f, err := ioutil.ReadFile(path.Dir(file_path)+"/"+s.BodyFile)
if err != nil {
return err
}
s.Body = string(f)
}
return err
}

func Unmarshal(file_path string) (MailSample, error){
var s MailSample
f, err := ioutil.ReadFile(file_path)
if err != nil {
return s, err
}
err = json.Unmarshal(f, &s)
if err != nil {
return s, err
}
if err := s.buildHeaders(file_path); err != nil {
return s, err
}
if err := s.buildBody(file_path) ; err != nil {
return s, err
}
return s, nil
}
1 change: 1 addition & 0 deletions example_samples/eicar.body
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
7 changes: 7 additions & 0 deletions example_samples/eicar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"From":"[email protected]",
"To":"[email protected]",
"Type": ["virus"],
"Headers": ["Subject: Eicar virus message"],
"BodyFile":"eicar.body"
}
7 changes: 7 additions & 0 deletions example_samples/gtube.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"From":"[email protected]",
"To":"[email protected]",
"Type": ["spam","gtube"],
"Headers": ["Subject: Generic Test for Unsolicited Bulk Email"],
"Body":"XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"
}

0 comments on commit 88ce21b

Please sign in to comment.