├── go.mod ├── .github ├── dependabot.yml └── workflows │ └── releaser.yml ├── matchers ├── whois_client.go ├── types.go ├── email_fallback.go ├── provider_contacts.go ├── whois_matchers.go └── shared_hosting.go ├── go.sum ├── .goreleaser.yml ├── LICENSE ├── README.md ├── email_template.go └── abwhose.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bradleyjkemp/abwhose 2 | 3 | go 1.13 4 | 5 | require golang.org/x/net v0.0.0-20200202094626-16171245cfb2 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /matchers/whois_client.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "os/exec" 4 | 5 | var WHOISClient = func(query string) (rawResult []byte, err error) { 6 | return exec.Command("whois", query).CombinedOutput() 7 | } 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 3 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 4 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 6 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | 4 | builds: 5 | - goos: 6 | - linux 7 | - darwin 8 | - windows 9 | goarch: 10 | - amd64 11 | 12 | brews: 13 | - 14 | tap: 15 | owner: bradleyjkemp 16 | name: homebrew-formulae 17 | homepage: "https://github.com/bradleyjkemp/abwhose" 18 | description: "The simplest way to find how to report abusive domains." 19 | 20 | archives: 21 | - replacements: 22 | darwin: macOS 23 | linux: Linux 24 | windows: Windows 25 | format: zip 26 | 27 | snapshot: 28 | name_template: "{{ .Tag }}-next" 29 | -------------------------------------------------------------------------------- /.github/workflows/releaser.yml: -------------------------------------------------------------------------------- 1 | name: Release with goreleaser 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@v2 14 | - 15 | name: Unshallow 16 | run: git fetch --prune --unshallow 17 | - 18 | name: Set up Go 19 | uses: actions/setup-go@v1 20 | with: 21 | go-version: 1.13.x 22 | - 23 | name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v1 25 | with: 26 | version: latest 27 | args: release --rm-dist 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.RELEASER_GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /matchers/types.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type ProviderContact interface { 8 | Name() string // Returns a Name that uniquely identifies the recipient of an abuse report 9 | } 10 | 11 | type OnlineForm struct { 12 | ProviderName 13 | URL string 14 | } 15 | 16 | type AbuseEmail struct { 17 | ProviderName 18 | Email string 19 | } 20 | 21 | func (a AbuseEmail) Name() string { 22 | // Use a nice provider names if given 23 | if a.ProviderName != "" { 24 | return a.ProviderName.Name() 25 | } 26 | 27 | // Otherwise attempt to split out the domain from the email 28 | emailParts := strings.SplitN(a.Email, "@", 2) 29 | if len(emailParts) > 1 { 30 | return emailParts[1] 31 | } 32 | 33 | // Fall back to just the email itself 34 | return a.Email 35 | } 36 | 37 | type ProviderName string 38 | 39 | func (m ProviderName) Name() string { 40 | return string(m) 41 | } 42 | 43 | type Matcher struct { 44 | Contact ProviderContact 45 | Matches func(string) bool 46 | } 47 | -------------------------------------------------------------------------------- /matchers/email_fallback.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "regexp" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | var emailRegexes = []*regexp.Regexp{ 10 | regexp.MustCompile(`(?i)(abuse@[a-z0-9\-.]*)`), 11 | regexp.MustCompile(`(?m)^OrgAbuseEmail:\s+(.*)$`), 12 | regexp.MustCompile(`(?m)^Registrar Abuse Contact Email:\s+(.+)$`), 13 | } 14 | 15 | func getRawEmailContacts(rawWhois string) []ProviderContact { 16 | var emails = map[string]struct{}{} 17 | for _, matcher := range emailRegexes { 18 | for _, email := range matcher.FindAllStringSubmatch(rawWhois, -1) { 19 | emails[strings.TrimSpace(email[1])] = struct{}{} 20 | } 21 | } 22 | if len(emails) == 0 { 23 | return nil 24 | } 25 | 26 | sortedEmails := make([]ProviderContact, 0, len(emails)) 27 | for email := range emails { 28 | sortedEmails = append(sortedEmails, AbuseEmail{Email: email}) 29 | } 30 | sort.Slice(sortedEmails, func(i, j int) bool { 31 | return sortedEmails[i].(AbuseEmail).Email < sortedEmails[j].(AbuseEmail).Email 32 | }) 33 | return sortedEmails 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bradley Kemp 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. -------------------------------------------------------------------------------- /matchers/provider_contacts.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "sort" 8 | 9 | "golang.org/x/net/publicsuffix" 10 | ) 11 | 12 | // Gets the abuse contact details for the registrar of a domain name. 13 | func Registrar(u *url.URL) ([]ProviderContact, error) { 14 | // First look up abuse details for the domain itself 15 | rootDomain, err := publicsuffix.EffectiveTLDPlusOne(u.Hostname()) 16 | if err != nil { 17 | return nil, fmt.Errorf("failed to get root domain: %w", err) 18 | } 19 | 20 | return getContactsFromWHOIS(rootDomain) 21 | } 22 | 23 | // Gets the abuse contact details for the hosting provider of a domain name. 24 | func HostingProvider(u *url.URL) ([]ProviderContact, error) { 25 | ips, err := net.LookupIP(u.Hostname()) 26 | if err != nil { 27 | return nil, fmt.Errorf("failed to find hosting provider: %w", err) 28 | } 29 | 30 | contacts := map[string]ProviderContact{} 31 | for _, ip := range ips { 32 | ipContacts, err := getContactsFromWHOIS(ip.String()) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | for _, contact := range ipContacts { 38 | contacts[contact.Name()] = contact 39 | } 40 | } 41 | 42 | dedupedContacts := make([]ProviderContact, 0, len(contacts)) 43 | for _, contact := range contacts { 44 | dedupedContacts = append(dedupedContacts, contact) 45 | } 46 | sort.Slice(dedupedContacts, func(i, j int) bool { 47 | return dedupedContacts[i].Name() < dedupedContacts[j].Name() 48 | }) 49 | return dedupedContacts, nil 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **[UPDATE] Prefer to use a web UI? You can now use [phish.report](https://phish.report) for even easier phishing reporting** 2 | 3 | # abwhose [![GitHub release](https://img.shields.io/github/release/bradleyjkemp/abwhose.svg)](https://github.com/bradleyjkemp/abwhose/releases/latest) 4 | 5 | The simplest way to find the *correct* place to report a domain for abuse. 6 | 7 | ```bash 8 | $ abwhose phishing-mcphishface.com 9 | 10 | Report abuse to domain registrar: 11 | Email: abuse@theregistrar.com 12 | Report abuse to host: 13 | HostPhish: Submit this form - https://wehostphish.biz/dev/null.php 14 | ``` 15 | 16 | Never again send an abuse report via email only to get a response days later saying: 17 | > Sorry, we only take abuse reports through this online form: https://reallyslow.com/new-report 18 | 19 | Instead use `abwhose` and always send your abuse reports to the correct place the first time. 20 | 21 | ### Installation 22 | 23 | ```bash 24 | brew install bradleyjkemp/formulae/abwhose 25 | ``` 26 | 27 | ### Pre-filling abuse email reports 28 | 29 | `abwhose` can automatically open your email client pre-filled with a template of your choice. 30 | 31 | To use this feature just: 32 | 1. Create an email template file somewhere on your filesystem (see below for an example). 33 | 1. Set the environment variable `ABWHOSE_MAILTO_TEMPLATE` to the path to you template file. 34 | 35 | An example template you could use is: 36 | ``` 37 | mailto:{{.recipient}}?subject=Phishing site: {{.domain}}&body=To whom it may concern, 38 | 39 | Please take down this phishing site: {{.domain}} 40 | 41 | Thanks 42 | ``` 43 | -------------------------------------------------------------------------------- /email_template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "io/ioutil" 8 | "net/url" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | 13 | "github.com/bradleyjkemp/abwhose/matchers" 14 | ) 15 | 16 | func emailTemplateConfigured() bool { 17 | _, configured := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE") 18 | return configured 19 | } 20 | 21 | func offerToSendEmail(u *url.URL, contact matchers.AbuseEmail) { 22 | emailTemplateFile, _ := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE") 23 | emailTemplateContents, err := ioutil.ReadFile(emailTemplateFile) 24 | if err != nil { 25 | fmt.Printf("Failed reading email template: %v\n", err) 26 | return 27 | } 28 | mailto := &bytes.Buffer{} 29 | err = template.Must(template.New("email").Parse(string(emailTemplateContents))).Execute(mailto, map[string]interface{}{ 30 | "domain": strings.Replace(u.Hostname(), ".", "[.]", -1), 31 | "url": strings.Replace(u.Hostname(), ".", "[.]", -1) + u.RawPath + u.RawQuery, 32 | "recipient": contact.Email, 33 | }) 34 | if err != nil { 35 | fmt.Printf("Error templating email: %v\n", err) 36 | return 37 | } 38 | fmt.Printf(" Send email to %s? [Y/n] ", contact.Email) 39 | if userSaysYes() { 40 | exec.Command("open", mailto.String()).Run() 41 | } 42 | } 43 | 44 | func userSaysYes() bool { 45 | var response string 46 | _, err := fmt.Scanln(&response) 47 | if err != nil && err.Error() != "unexpected newline" { 48 | panic(err) 49 | } 50 | okayResponses := map[string]bool{ 51 | "": true, 52 | "y": true, 53 | "yes": true, 54 | } 55 | if okayResponses[strings.ToLower(response)] { 56 | return true 57 | } 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /matchers/whois_matchers.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Matches WHOIS data to the best way to report abuse to the registrar/hosting provider. 9 | // 10 | // Try to keep this sorted alphabetically by ProviderName 11 | var WHOIS = []Matcher{ 12 | {OnlineForm{"Cloudflare", "https://www.cloudflare.com/abuse/form"}, whoisContains("abuse@cloudflare.com")}, 13 | {OnlineForm{"Digital Ocean", "https://www.digitalocean.com/company/contact/#abuse"}, whoisContains("descr: Digital Ocean, Inc.")}, 14 | {OnlineForm{"Dynadot", "https://www.dynadot.com/report_abuse.html"}, whoisContains("abuse@dynadot.com")}, 15 | {OnlineForm{"GoDaddy", "https://supportcenter.godaddy.com/AbuseReport"}, whoisContains("abuse@godaddy.com")}, 16 | {AbuseEmail{"Hostinger", "abuse@hostinger.com"}, whoisContains("netname: HOSTING-SERVERS")}, 17 | {OnlineForm{"Namecheap", "https://support.namecheap.com/index.php?/Tickets/Submit"}, whoisContains("abuse@namecheap.com")}, 18 | {OnlineForm{"Namesilo", "https://www.namesilo.com/report_abuse.php or https://new.namesilo.com/phishing_report.php"}, whoisContains("abuse@namesilo.com")}, 19 | {AbuseEmail{"OrangeWebsite", "abuse-dept@orangewebsite.com"}, whoisContains("abuse@orangewebsite.com")}, 20 | {OnlineForm{"PublicDomainRegistry", "https://publicdomainregistry.com/process-for-handling-abuse/"}, whoisContains("abuse-contact@publicdomainregistry.com")}, 21 | {OnlineForm{"Tucows", "https://tucowsdomains.com/report-abuse/"}, whoisContains("abuse@tucows.com", "domainabuse@tucows.com")}, 22 | } 23 | 24 | func whoisContains(needles ...string) func(string) bool { 25 | return func(whois string) bool { 26 | for _, needle := range needles { 27 | if strings.Contains(whois, needle) { 28 | return true 29 | } 30 | } 31 | return false 32 | } 33 | } 34 | 35 | func getContactsFromWHOIS(query string) ([]ProviderContact, error) { 36 | rawWhois, err := WHOISClient(query) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to query whois: %s, %w", string(rawWhois), err) 39 | } 40 | 41 | var contacts []ProviderContact 42 | for _, m := range WHOIS { 43 | if m.Matches(string(rawWhois)) { 44 | contacts = append(contacts, m.Contact) 45 | } 46 | } 47 | 48 | // One of the WHOIS matched so return that info 49 | if len(contacts) > 0 { 50 | return contacts, nil 51 | } 52 | 53 | // Nothing matched so try and extract raw email addresses 54 | return getRawEmailContacts(string(rawWhois)), nil 55 | } 56 | -------------------------------------------------------------------------------- /abwhose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "strings" 8 | "text/tabwriter" 9 | 10 | "github.com/bradleyjkemp/abwhose/matchers" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 2 { 15 | fmt.Println("Usage: abwhose ") 16 | os.Exit(1) 17 | } 18 | if err := run(os.Args[1]); err != nil { 19 | fmt.Println(err) 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | func run(query string) error { 25 | u, err := parseURL(query) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | if ok, contact := matchers.IsSharedHostingProvider(u); ok { 31 | fmt.Println("Report abuse to shared hosting provider:") 32 | printContactDetails(u, contact) 33 | // If this is a shared host then skip the rest of the lookups 34 | // as that information isn't useful. 35 | return nil 36 | } 37 | 38 | contacts, err := matchers.Registrar(u) 39 | if err != nil { 40 | return err 41 | } 42 | fmt.Println("Report abuse to domain registrar:") 43 | printContactDetails(u, contacts...) 44 | 45 | contacts, err = matchers.HostingProvider(u) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Println("Report abuse to hosting provider:") 50 | printContactDetails(u, contacts...) 51 | return nil 52 | } 53 | 54 | func parseURL(input string) (*url.URL, error) { 55 | if !strings.Contains(input, "/") { 56 | // This is likely a plain domain name so we construct a URL directly 57 | return &url.URL{ 58 | Scheme: "http", 59 | Host: input, 60 | }, nil 61 | } 62 | 63 | // This looks like a full URL instead of a plain domain 64 | if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") { 65 | // Doesn't have a protocol so won't url.Parse properly 66 | input = "http://" + input 67 | } 68 | 69 | u, err := url.Parse(input) 70 | if err != nil { 71 | return nil, fmt.Errorf("couldn't parse URL: %w", err) 72 | } 73 | if u.Hostname() == "" { 74 | return nil, fmt.Errorf("%s doesn't look like a valid URL (hostname is empty)", input) 75 | } 76 | 77 | return u, nil 78 | } 79 | 80 | var tabWriter = tabwriter.NewWriter(os.Stdout, 12, 2, 1, ' ', tabwriter.TabIndent) 81 | 82 | func printContactDetails(u *url.URL, contacts ...matchers.ProviderContact) { 83 | for _, contact := range contacts { 84 | switch c := contact.(type) { 85 | case matchers.AbuseEmail: 86 | if emailTemplateConfigured() { 87 | offerToSendEmail(u, c) 88 | } else { 89 | fmt.Fprintf(tabWriter, " Email:\t%s\n", c.Email) 90 | } 91 | 92 | case matchers.OnlineForm: 93 | fmt.Fprintf(tabWriter, " %s:\tFill out abuse form %s\n", contact.Name(), c.URL) 94 | 95 | default: 96 | panic(fmt.Sprintf("unknown contact type: %T", contact)) 97 | } 98 | } 99 | if len(contacts) == 0 { 100 | fmt.Fprintf(tabWriter, " Couldn't find any contact details\n") 101 | } 102 | tabWriter.Flush() 103 | } 104 | -------------------------------------------------------------------------------- /matchers/shared_hosting.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | ) 7 | 8 | // Returns the contact details of the shared hosting provider if it exists. 9 | // If this matches, these contact details should be preferred over the 10 | // registrar and hosting provider. 11 | func IsSharedHostingProvider(u *url.URL) (bool, ProviderContact) { 12 | for _, m := range SharedHosts { 13 | if m.Matches(u.Host) { 14 | return true, m.Contact 15 | } 16 | } 17 | return false, nil 18 | } 19 | 20 | // Matches content served by shared hosting providers i.e. where the abusive content 21 | // is not served by the domain/server owner. 22 | // 23 | // Try to keep this sorted alphabetically by ProviderName 24 | var SharedHosts = []Matcher{ 25 | {OnlineForm{"000webhost", "https://www.000webhost.com/report-abuse"}, isSubDomainOf("000webhost.com", "000webhostapp.com")}, 26 | {AbuseEmail{"Adobe", "hellospark@adobe.com"}, isSubDomainOf("spark.adobe.com")}, 27 | {OnlineForm{"Bitly", "https://bitly.is/reporting-abuse"}, isSubDomainOf("bit.ly")}, 28 | {OnlineForm{"Blogger", "https://support.google.com/blogger/answer/76315"}, isSubDomainOf("blogger.com", "blogspot.com")}, 29 | {OnlineForm{"ChangeIP", "https://www.changeip.com/contact-us.php"}, isSubDomainOf("dynamic-dns.net", "longmusic.com", "wikaba.com", "zzux.com", "dumb1.com", "onedumb.com", "youdontcare.com", "yourtrap.com", "2waky.com", "sexidude.com", "mefound.com", "organiccrap.com", "toythieves.com", "justdied.com", "jungleheart.com", "mrbasic.com", "mrbonus.com", "x24hr.com", "dns04.com", "dns05.com", "zyns.com", "my03.com", "fartit.com", "itemdb.com", "instanthq.com", "xxuz.com", "jkub.com", "itsaol.com", "faqserv.com", "jetos.com", "qpoe.com", "qhigh.com", "vizvaz.com", "mrface.com", "isasecret.com", "mrslove.com", "otzo.com", "americanunfinished.com", "serveusers.com", "serveuser.com", "freetcp.com", "ddns.info", "ns01.info", "ns02.info", "myftp.info", "mydad.info", "mymom.info", "mypicture.info", "myz.info", "squirly.info", "toh.info", "xxxy.info", "freewww.info", "freeddns.com", "myddns.com", "dynamicdns.biz", "ns01.biz", "ns02.biz", "xxxy.biz", "sexxxy.biz", "freewww.biz", "www1.biz", "dhcp.biz", "edns.biz", "ftp1.biz", "mywww.biz", "gr8domain.biz", "gr8name.biz", "ftpserver.biz", "wwwhost.biz", "moneyhome.biz", "port25.biz", "esmtp.biz", "dsmtp.biz", "sixth.biz", "ninth.biz", "misecure.com", "got-game.org", "bigmoney.biz", "dns2.us", "dns1.us", "ns02.us", "ns01.us", "changeip.us", "changeip.biz", "almostmy.com", "ocry.com", "ourhobby.com", "dnsfailover.net", "ygto.com", "ddns.ms", "ddns.us", "gettrials.com", "25u.com", "4dq.com", "4pu.com", "3-a.net", "dsmtp.com", "dsmtp.com", "mynumber.org", "ns1.name", "ns2.name", "ns3.name", "rebatesrule.net", "ezua.com", "sendsmtp.com", "ssmailer.com", "trickip.net", "trickip.org", "dnsrd.com", "lflinkup.com", "lflinkup.net", "lflinkup.org", "lflink.com", "dns-dns.com", "b0tnet.com", "proxydns.com", "changeip.net", "mysecondarydns.com", "changeip.org", "dns-stuff.com", "dynssl.com", "mylftv.com", "mynetav.com", "mynetav.net", "mynetav.org", "dns-report.com", "homingbeacon.net", "ikwb.com", "acmetoy.com", "ddns.mobi", "dnset.com", "as19557.net", "toshibanetcam.com", "authorizeddns.net", "authorizeddns.org", "authorizeddns.us", "cleansite.biz", "cleansite.info", "cleansite.us", "https443.net", "https443.org", "mypop3.net", "mypop3.org", "ssl443.org", "iownyour.biz", "iownyour.org", "onmypc.biz", "onmypc.info", "onmypc.net", "onmypc.org", "onmypc.us", "dubya.info", "dubya.us", "dubya.biz", "dubya.net", "changeip.co", "wwwhost.us")}, 30 | {OnlineForm{"Cloudflare", "https://www.cloudflare.com/abuse/form"}, isSubDomainOf("workers.dev")}, 31 | {AbuseEmail{"Glitch", "support@glitch.com"}, isSubDomainOf("glitch.me", "glitch.com")}, 32 | {OnlineForm{"GoDaddy", "https://supportcenter.godaddy.com/AbuseReport"}, isSubDomainOf("godaddysites.com")}, 33 | {OnlineForm{"Google Cloud", "https://support.google.com/code/contact/cloud_platform_report"}, isSubDomainOf("appspot.com", "googleapis.com", "web.app")}, 34 | {OnlineForm{"Google Sites", "https://support.google.com/docs/answer/2463296?hl=en-GB#zippy=%2Cgoogle-sites"}, isSubDomainOf("sites.google.com")}, 35 | {AbuseEmail{"IBM", "abuse@softlayer.com"}, isSubDomainOf("appdomain.cloud")}, 36 | {OnlineForm{"Jimdo", "https://jimdo-legal.zendesk.com/hc/en-us/requests/new?ticket_form_id=239123"}, isSubDomainOf("jimdosite.com")}, 37 | {OnlineForm{"Microsoft", "https://msrc.microsoft.com/report/abuse"}, isSubDomainOf("blob.core.windows.net")}, 38 | {AbuseEmail{"Replit", "contact@repl.it"}, isSubDomainOf("repl.co")}, 39 | {AbuseEmail{"Netlify", "fraud@netlify.com"}, isSubDomainOf("netlify.app")}, 40 | {OnlineForm{"Notion", "https://www.notion.so/Report-inappropriate-content-9feb9f2f9d8c40b1b7d289b155907de0"}, isSubDomainOf("notion.so", "notion.com")}, 41 | {AbuseEmail{"Square", "spoof@squareup.com"}, isSubDomainOf("square.site")}, 42 | {OnlineForm{"Weebly", "https://www.weebly.com/uk/spam"}, isSubDomainOf("weebly.com")}, 43 | {OnlineForm{"Yola", "https://helpcenter.yola.com/hc/en-us/requests/new?ticket_form_id=360001504300"}, isSubDomainOf("yolasite.com")}, 44 | {OnlineForm{"Fleek", "https://form.typeform.com/to/BzFH0anw?typeform-source=fleek.co"}, isSubDomainOf("fleek.co")}, 45 | } 46 | 47 | func isSubDomainOf(domains ...string) func(string) bool { 48 | return func(abusiveDomain string) bool { 49 | for _, domain := range domains { 50 | if abusiveDomain == domain || strings.HasSuffix(abusiveDomain, "."+domain) { 51 | return true 52 | } 53 | } 54 | return false 55 | } 56 | } 57 | --------------------------------------------------------------------------------