Skip to content

Commit

Permalink
Merge pull request #3 from SparkPost/master
Browse files Browse the repository at this point in the history
up
  • Loading branch information
kakysha authored Jun 7, 2017
2 parents b6fcdc3 + c041325 commit 9dd3f2e
Show file tree
Hide file tree
Showing 57 changed files with 6,050 additions and 1,083 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
**/.*.swp
.vscode

**/*.test
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
language: go
sudo: false
go:
- 1.5
- 1.7
before_install:
go get github.com/mattn/goveralls
script:
- $HOME/gopath/bin/goveralls -service=travis-ci -ignore 'cmd/*/*.go,examples/*/*.go,helpers/*/*.go'
15 changes: 14 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@
SparkPost Go API client
=======================

.. image:: https://travis-ci.org/SparkPost/gosparkpost.svg?branch=master
:target: https://travis-ci.org/SparkPost/gosparkpost
:alt: Build Status

.. image:: https://coveralls.io/repos/SparkPost/gosparkpost/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/SparkPost/gosparkpost?branch=master
:alt: Code Coverage

.. image:: http://slack.sparkpost.com/badge.svg
:target: http://slack.sparkpost.com
:alt: Slack Community


The official Go package for using the SparkPost API.

Expand Down Expand Up @@ -91,8 +100,12 @@ Documentation
-------------

* `SparkPost API Reference`_
* `Code samples`_
* `Command-line tool: sparks`_

.. _SparkPost API Reference: https://www.sparkpost.com/api
.. _SparkPost API Reference: https://developers.sparkpost.com/api
.. _Code samples: examples/README.md
.. _Command-line tool\: sparks: cmd/sparks/README.md

Contribute
----------
Expand Down
21 changes: 21 additions & 0 deletions cmd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Tools for SparkPost and/or Email

### [fblgen](./fblgen/)

Generate and optionally send an FBL report in response to an email sent through SparkPost.

### [mimedump](./mimedump/)

Extract the HTML part of a saved email message.

### [oobgen](./oobgen/)

Generate and optionally send an out-of-band (OOB) bounce from an email with full headers.

### [qp](./qp/)

Encode or decode quoted-printable data. Inspired by the `base64` command-line tool, supports the same long options.

### [sparks](./sparks/)

Send email through SparkPost from the command line.
24 changes: 24 additions & 0 deletions cmd/fblgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## fblgen

Testing your response to FBL reports doesn't have to involve waiting for an angry/lazy recipient to click "This is Spam".
Here's how to send an FBL report in response to a message sent via SparkPost, and saved (with full headers) to a local file:

$ ./fblgen --file ./test.eml --verbose
Got domain [sparkpostmail.com] from Return-Path
Got MX [smtp.sparkpostmail.com.] for [sparkpostmail.com]
Would send FBL from [[email protected]] to [[email protected]] via [smtp.sparkpostmail.com.:smtp]

Note that this command (once you've added the `--send` flag) will attempt to connect from your local machine to the MX listed above.
It's entirely possible that there will be something blocking that port, for example a firewall, or your residential ISP.
Here are [two](http://nc110.sourceforge.net/) [ways](https://nmap.org/ncat/) to check whether that's the case.
Whichever command you run should return in under a second.
If there's a successful connection, you're good to go.

$ nc -vz -w 3 smtp.sparkpostmail.com 25
$ </dev/null ncat -vw 3s --send-only smtp.sparkpostmail.com 25

If you get a timeout, there are a couple solutions. The easiest is to `ssh` somewhere that allows outbound connections on port 25. Searching for "free ssh" will give you quite a few options, if you don't happen to have that sort of access set up already. My nostalgic favorite is [SDF](http://sdf.lonestar.org/).

Another option is to route your connections over a VPN, which is more involved, and out of the scope of this document.

Happy testing!
2 changes: 1 addition & 1 deletion cmd/fblgen/arf.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

var ArfFormat string = `From: <%s>
Date: Thu, 8 Mar 2005 17:40:36 EDT
Date: Mon, 02 Jan 2006 15:04:05 MST
Subject: FW: Earn money
To: <%s>
MIME-Version: 1.0
Expand Down
102 changes: 24 additions & 78 deletions cmd/fblgen/fblgen.go
Original file line number Diff line number Diff line change
@@ -1,117 +1,63 @@
package main

import (
"encoding/base64"
"flag"
"fmt"
"log"
"net"
"net/mail"
"net/smtp"
"os"
"regexp"
"strconv"
"strings"
)

var filename = flag.String("file", "", "path to email with a text/html part")
var dumpArf = flag.Bool("arf", false, "dump out multipart/report message")
var send = flag.Bool("send", false, "send fbl report")
var fblAddress = flag.String("fblto", "", "where to deliver the fbl report")
var verboseOpt = flag.Bool("verbose", false, "print out lots of messages")

var cidPattern *regexp.Regexp = regexp.MustCompile(`"customer_id"\s*:\s*"(\d+)"`)
var toPattern *regexp.Regexp = regexp.MustCompile(`"r"\s*:\s*"([^"\s]+)"`)
"github.com/SparkPost/gosparkpost/helpers/loadmsg"
)

func main() {
var filename = flag.String("file", "", "path to raw email")
var dumpArf = flag.Bool("arf", false, "dump out multipart/report message")
var send = flag.Bool("send", false, "send fbl report")
var fblAddress = flag.String("fblto", "", "where to deliver the fbl report")
var verboseOpt = flag.Bool("verbose", false, "print out lots of messages")

flag.Parse()
var verbose bool
if verboseOpt != nil && *verboseOpt == true {
if *verboseOpt == true {
verbose = true
}

if filename == nil || strings.TrimSpace(*filename) == "" {
if *filename == "" {
log.Fatal("--file is required")
}

fh, err := os.Open(*filename)
msg := loadmsg.Message{Filename: *filename}
err := msg.Load()
if err != nil {
log.Fatal(err)
}

msg, err := mail.ReadMessage(fh)
if err != nil {
log.Fatal(err)
}

b64hdr := strings.Replace(msg.Header.Get("X-MSFBL"), " ", "", -1)
if verbose == true {
log.Printf("X-MSFBL: %s\n", b64hdr)
}

var dec []byte
b64 := base64.StdEncoding
if strings.Index(b64hdr, "|") >= 0 {
// Everything before the pipe is an encoded hmac
// TODO: verify contents using hmac
encs := strings.Split(b64hdr, "|")
dec, err = b64.DecodeString(encs[1])
if err != nil {
log.Fatal(err)
}
} else {
dec, err = b64.DecodeString(b64hdr)
if err != nil {
log.Fatal(err)
}
}

cidMatches := cidPattern.FindSubmatch(dec)
if cidMatches == nil || len(cidMatches) < 2 {
log.Fatalf("No key \"customer_id\" in X-MSFBL header:\n%s\n", string(dec))
}
cid, err := strconv.Atoi(string(cidMatches[1]))
if err != nil {
log.Fatal(err)
}

toMatches := toPattern.FindSubmatch(dec)
if toMatches == nil || len(toMatches) < 2 {
log.Fatalf("No key \"r\" (recipient) in X-MSFBL header:\n%s\n", string(dec))
}

if verbose == true {
log.Printf("Decoded FBL (cid=%d): %s\n", cid, string(dec))
}

returnPath := msg.Header.Get("Return-Path")
if fblAddress != nil && *fblAddress != "" {
returnPath = *fblAddress
}
fblAddr, err := mail.ParseAddress(returnPath)
if err != nil {
log.Fatal(err)
if *fblAddress != "" {
msg.SetReturnPath(*fblAddress)
}

atIdx := strings.Index(fblAddr.Address, "@") + 1
atIdx := strings.Index(msg.ReturnPath.Address, "@")
if atIdx < 0 {
log.Fatalf("Unsupported Return-Path header [%s]\n", returnPath)
log.Fatalf("Unsupported Return-Path header [%s]\n", msg.ReturnPath.Address)
}
fblDomain := fblAddr.Address[atIdx:]
fblDomain := msg.ReturnPath.Address[atIdx+1:]
fblTo := fmt.Sprintf("fbl@%s", fblDomain)
if verbose == true {
if fblAddress != nil && *fblAddress != "" {
if *fblAddress != "" {
log.Printf("Got domain [%s] from --fblto\n", fblDomain)
} else {
log.Printf("Got domain [%s] from Return-Path header\n", fblDomain)
log.Printf("Got domain [%s] from Return-Path\n", fblDomain)
}
}

// from/to are opposite here, since we're simulating a reply
fblFrom := string(toMatches[1])
arf := BuildArf(fblFrom, fblTo, b64hdr, cid)
fblFrom := string(msg.Recipient)
arf := BuildArf(fblFrom, fblTo, msg.MSFBL, msg.CustID)

if dumpArf != nil && *dumpArf == true {
if *dumpArf == true {
fmt.Fprintf(os.Stdout, "%s", arf)
}

Expand All @@ -127,7 +73,7 @@ func main() {
}
smtpHost := fmt.Sprintf("%s:smtp", mxs[0].Host)

if send != nil && *send == true {
if *send == true {
log.Printf("Sending FBL from [%s] to [%s] via [%s]...\n",
fblFrom, fblTo, smtpHost)
err = smtp.SendMail(smtpHost, nil, fblFrom, []string{fblTo}, []byte(arf))
Expand All @@ -137,7 +83,7 @@ func main() {
log.Printf("Sent.\n")
} else {
if verbose == true {
log.Printf("Would send FBL from [%s] to [%s] via [%s]...\n",
log.Printf("Would send FBL from [%s] to [%s] via [%s]\n",
fblFrom, fblTo, smtpHost)
}
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/mimedump/mimedump.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
mime "github.com/jhillyerd/go.enmime"
)

var filename = flag.String("file", "", "path to email with a text/html part")

func main() {
var filename = flag.String("file", "", "path to email with a text/html part")

flag.Parse()

if filename == nil || strings.TrimSpace(*filename) == "" {
Expand Down
24 changes: 24 additions & 0 deletions cmd/oobgen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## oobgen

Testing your response to out-of-band (OOB) bounces doesn't have to involve waiting for one to be sent to you.
Here's how to send an OOB bounce in response to a message sent via SparkPost, and saved (with full headers) to a local file:

$ ./oobgen --file ./test.eml --verbose
Got domain [sparkpostmail.com] from Return-Path
Got MX [smtp.sparkpostmail.com.] for [sparkpostmail.com]
Would send OOB from [[email protected]] to [[email protected]] via [smtp.sparkpostmail.com.:smtp]

Note that this command (once you've added the `--send` flag) will attempt to connect from your local machine to the MX listed above.
It's entirely possible that there will be something blocking that port, for example a firewall, or your residential ISP.
Here are [two](http://nc110.sourceforge.net/) [ways](https://nmap.org/ncat/) to check whether that's the case.
Whichever command you run should return in under a second.
If there's a successful connection, you're good to go.

$ nc -vz -w 3 smtp.sparkpostmail.com 25
$ </dev/null ncat -vw 3s --send-only smtp.sparkpostmail.com 25

If you get a timeout, there are a couple solutions. The easiest is to `ssh` somewhere that allows outbound connections on port 25. Searching for "free ssh" will give you quite a few options, if you don't happen to have that sort of access set up already. My nostalgic favorite is [SDF](http://sdf.lonestar.org/).

Another option is to route your connections over a VPN, which is more involved, and out of the scope of this document.

Happy testing!
69 changes: 69 additions & 0 deletions cmd/oobgen/oob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"fmt"
"strings"
"time"
)

// FIXME: allow swapping out the error message
var OobFormat string = `From: %s
Date: Mon, 02 Jan 2006 15:04:05 MST
Subject: Returned mail: see transcript for details
Auto-Submitted: auto-generated (failure)
To: %s
Content-Type: multipart/report; report-type=delivery-status;
boundary="%s"
This is a MIME-encapsulated message
--%s
The original message was received at Mon, 02 Jan 2006 15:04:05 -0700
from example.com.sink.sparkpostmail.com [52.41.116.105]
----- The following addresses had permanent fatal errors -----
<%s>
(reason: 550 5.0.0 <%s>... User unknown)
----- Transcript of session follows -----
... while talking to %s:
>>> DATA
<<< 550 5.0.0 <%s>... User unknown
550 5.1.1 <%s>... User unknown
<<< 503 5.0.0 Need RCPT (recipient)
--%s
Content-Type: message/delivery-status
Reporting-MTA: dns; %s
Received-From-MTA: DNS; %s
Arrival-Date: Mon, 02 Jan 2006 15:04:05 MST
Final-Recipient: RFC822; %s
Action: failed
Status: 5.0.0
Remote-MTA: DNS; %s
Diagnostic-Code: SMTP; 550 5.0.0 <%s>... User unknown
Last-Attempt-Date: Mon, 02 Jan 2006 15:04:05 MST
--%s
Content-Type: message/rfc822
%s
--%s--
`

func BuildOob(from, to, rawMsg string) string {
boundary := fmt.Sprintf("_----%d===_61/00-25439-267B0055", time.Now().Unix())
fromDomain := from[strings.Index(from, "@")+1:]
toDomain := to[strings.Index(to, "@")+1:]
msg := fmt.Sprintf(OobFormat,
from, to, boundary,
boundary, to, to, toDomain, to, to,
boundary, toDomain, fromDomain, to, toDomain, to,
boundary, rawMsg,
boundary)
return msg
}
Loading

0 comments on commit 9dd3f2e

Please sign in to comment.