Skip to content

Commit

Permalink
Major refactor to enable usage as a library
Browse files Browse the repository at this point in the history
  • Loading branch information
Bradley Kemp committed Jul 25, 2020
1 parent 5ecefae commit 2c03eef
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 217 deletions.
140 changes: 60 additions & 80 deletions abwhose.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package main

import (
"fmt"
"net"
"net/url"
"os"
"os/exec"
"strings"
"text/tabwriter"

"golang.org/x/net/publicsuffix"
"github.com/bradleyjkemp/abwhose/matchers"
)

func main() {
Expand All @@ -23,101 +21,83 @@ func main() {
}
}

var tabWriter = tabwriter.NewWriter(os.Stdout, 12, 2, 1, ' ', tabwriter.TabIndent)

func run(abuseURL string) error {
var abusive *url.URL
var err error
if strings.Contains(abuseURL, "/") {
// This looks like a full URL instead of a plain domain
if !strings.HasPrefix(abuseURL, "http://") && !strings.HasPrefix(abuseURL, "https://") {
// Doesn't have a protocol so won't url.Parse properly
abuseURL = "http://" + abuseURL
}

abusive, err = url.Parse(abuseURL)
if err != nil {
return fmt.Errorf("couldn't parse URL: %w", err)
}
} else {
// This is a plain domain name so we construct a URL directly
abusive = &url.URL{
Scheme: "http",
Host: abuseURL,
}
}

if abusive.Hostname() == "" {
return fmt.Errorf("%s doesn't look like a valid URL (hostname is empty)", abuseURL)
func run(query string) error {
u, err := parseURL(query)
if err != nil {
return err
}

// First look up abuse details for the domain itself (this will be the registrar)
rootDomain, _ := publicsuffix.EffectiveTLDPlusOne(abusive.Hostname())

// First check if this is a shared host
var sharedHost bool
for _, matcher := range sharedHostMatchers {
if match, display := matcher(rootDomain); match {
if !sharedHost {
fmt.Println("Report abuse to shared hosting provider:")
}
display()
sharedHost = true
}
}
// If this is a shared host then skip the WHOIS lookup
// as that information isn't useful.
if sharedHost {
if ok, contact := matchers.IsSharedHostingProvider(u); ok {
fmt.Println("Report abuse to shared hosting provider:")
printContactDetails(u, contact)
// If this is a shared host then skip the rest of the lookups
// as that information isn't useful.
return nil
}

err = getAbuseReportDetails("Report abuse to domain registrar:", abusive, rootDomain)
contacts, err := matchers.Registrar(u)
if err != nil {
return fmt.Errorf("failed to get registrar abuse details: %w", err)
}

// Now look up the IP in order to find the hosting provider
ips, err := net.LookupIP(abusive.Hostname())
if err != nil {
return fmt.Errorf("failed to find hosting provider: %w", err)
return err
}
fmt.Println("Report abuse to domain registrar:")
printContactDetails(u, contacts...)

// Abuse details for the IP should be the hosting provider
err = getAbuseReportDetails("Report abuse to host:", abusive, ips[0].String())
contacts, err = matchers.HostingProvider(u)
if err != nil {
return fmt.Errorf("failed to get host abuse details: %w", err)
return err
}
fmt.Println("Report abuse to hosting provider:")
printContactDetails(u, contacts...)
return nil
}

func getAbuseReportDetails(header string, abusive *url.URL, query string) error {
rawWhois, err := exec.Command("whois", query).CombinedOutput()
func parseURL(input string) (*url.URL, error) {
if !strings.Contains(input, "/") {
// This is likely a plain domain name so we construct a URL directly
return &url.URL{
Scheme: "http",
Host: input,
}, nil
}

// This looks like a full URL instead of a plain domain
if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
// Doesn't have a protocol so won't url.Parse properly
input = "http://" + input
}

u, err := url.Parse(input)
if err != nil {
return err
return nil, fmt.Errorf("couldn't parse URL: %w", err)
}
if u.Hostname() == "" {
return nil, fmt.Errorf("%s doesn't look like a valid URL (hostname is empty)", input)
}

gotMatch := false
for _, matcher := range whoisMatchers {
if match, display := matcher(string(rawWhois)); match {
if !gotMatch {
fmt.Println(header)
gotMatch = true
return u, nil
}

var tabWriter = tabwriter.NewWriter(os.Stdout, 12, 2, 1, ' ', tabwriter.TabIndent)

func printContactDetails(u *url.URL, contacts ...matchers.ProviderContact) {
for _, contact := range contacts {
switch c := contact.(type) {
case matchers.AbuseEmail:
if emailTemplateConfigured() {
offerToSendEmail(u, c)
} else {
fmt.Fprintf(tabWriter, " Email:\t%s\n", c.Email)
}
display()

case matchers.OnlineForm:
fmt.Fprintf(tabWriter, " %s:\tFill out abuse form %s\n", contact.Name(), c.URL)

default:
panic(fmt.Sprintf("unknown contact type: %T", contact))
}
}
if gotMatch {
return nil
if len(contacts) == 0 {
fmt.Fprintf(tabWriter, " Couldn't find any contact details\n")
}

// None of the specific matchers hit so use a generic one
found, display := fallbackEmailMatcher(header, abusive, string(rawWhois))
if found {
display()
return nil
}

fmt.Println(header)
fmt.Println(" couldn't find any abuse contact details")
return nil
tabWriter.Flush()
}
59 changes: 59 additions & 0 deletions email_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"net/url"
"os"
"os/exec"
"strings"

"github.com/bradleyjkemp/abwhose/matchers"
)

func emailTemplateConfigured() bool {
_, configured := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE")
return configured
}

func offerToSendEmail(u *url.URL, contact matchers.AbuseEmail) {
emailTemplateFile, _ := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE")
emailTemplateContents, err := ioutil.ReadFile(emailTemplateFile)
if err != nil {
fmt.Printf("Failed reading email template: %v\n", err)
return
}
mailto := &bytes.Buffer{}
err = template.Must(template.New("email").Parse(string(emailTemplateContents))).Execute(mailto, map[string]interface{}{
"domain": strings.Replace(u.Hostname(), ".", "[.]", -1),
"url": strings.Replace(u.Hostname(), ".", "[.]", -1) + u.RawPath + u.RawQuery,
"recipient": contact.Email,
})
if err != nil {
fmt.Printf("Error templating email: %v\n", err)
return
}
fmt.Printf(" Send email to %s? [Y/n] ", contact.Email)
if userSaysYes() {
exec.Command("open", mailto.String()).Run()
}
}

func userSaysYes() bool {
var response string
_, err := fmt.Scanln(&response)
if err != nil && err.Error() != "unexpected newline" {
panic(err)
}
okayResponses := map[string]bool{
"": true,
"y": true,
"yes": true,
}
if okayResponses[strings.ToLower(response)] {
return true
}
return false
}
137 changes: 0 additions & 137 deletions matchers.go

This file was deleted.

Loading

0 comments on commit 2c03eef

Please sign in to comment.