diff --git a/README.md b/README.md
deleted file mode 100644
index af87183..0000000
--- a/README.md
+++ /dev/null
@@ -1,269 +0,0 @@
-
-
-
-
-[![platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux%20%7C%20OSX-success.svg)](https://pypi.org/project/h8mail/) [![PyPI version](https://badge.fury.io/py/h8mail.svg)](https://badge.fury.io/py/h8mail)
-[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/h8mail.svg)](https://pypi.org/project/h8mail/) [![Downloads](https://pepy.tech/badge/h8mail)](https://pepy.tech/project/h8mail) [![travis](https://img.shields.io/travis/khast3x/h8mail.svg)](https://travis-ci.org/khast3x/h8mail)
-[![Docker Pulls](https://img.shields.io/docker/pulls/kh4st3x00/h8mail.svg)](https://hub.docker.com/r/kh4st3x00/h8mail) [![MicroBadger Size (tag)](https://img.shields.io/microbadger/image-size/kh4st3x00/h8mail.svg?color=ok)](https://hub.docker.com/r/kh4st3x00/h8mail/builds)
-**h8mail** is an email OSINT and breach hunting tool using [different breach and reconnaissance services](#apis), or local breaches such as Troy Hunt's "Collection1" and the infamous "Breach Compilation" torrent.
-
-----
-
-
-
-
-
-
-
-
-----
-
-
-## :book: Table of Content
-
-- [Table of Content](#book-Table-of-Content)
-- [Features](#tangerine-Features)
- - [APIs](#APIs)
-- [Usage](#tangerine-Usage)
-- [Usage examples](#tangerine-Usage-examples)
-- [Thanks & Credits](#tangerine-Thanks--Credits)
-- [Related open source projects](#tangerine-Related-open-source-projects)
-
-
-----
-
-
-## :tangerine: Features
-
-* :mag_right: Email pattern matching (reg exp), useful for reading from other tool outputs
-* :earth_africa: Pass URLs to directly find and target emails in pages
-* :dizzy: Loosey patterns for local searchs ("john.smith", "evilcorp")
-* :package: Painless install. Available through `pip`, only requires `requests`
-* :white_check_mark: Bulk file-reading for targeting
-* :memo: Output to CSV file
-* :muscle: Compatible with the "Breach Compilation" torrent scripts
-* :house: Search cleartext and compressed .gz files locally using multiprocessing
- * :cyclone: Compatible with "Collection#1"
-* :fire: Get related emails
-* :dragon_face: Chase related emails by adding them to the ongoing search
-* :crown: Supports premium lookup services for advanced users
-* :factory: Custom query premium APIs. Supports username, hash, ip, domain and password and more
-* :books: Regroup breach results for all targets and methods
-* :eyes: Includes option to hide passwords for demonstrations
-* :rainbow: Delicious colors
-
----
-
-### :package: `pip3 install h8mail`
-
------
-
-
-#### APIs
-
-| Service | Functions | Status |
-|-|-|-|
-| [HaveIBeenPwned(v3)](https://haveibeenpwned.com/) | Number of email breaches | :white_check_mark: :key: |
-| [HaveIBeenPwned Pastes(v3)](https://haveibeenpwned.com/Pastes) | URLs of text files mentioning targets | :white_check_mark: :key: |
-| [Hunter.io](https://hunter.io/) - Public | Number of related emails | :white_check_mark: |
-| [Hunter.io](https://hunter.io/) - Service (free tier) | Cleartext related emails, Chasing | :white_check_mark: :key: |
-| [Snusbase](https://snusbase.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs - Fast :zap: | :white_check_mark: :key: |
-| [Leak-Lookup](https://leak-lookup.com/) - Public | Number of search-able breach results | :white_check_mark: (:key:) |
-| [Leak-Lookup](https://leak-lookup.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: :key: |
-| [Emailrep.io](https://emailrep.io/) - Service (free) | Last seen in breaches, social media profiles | :white_check_mark: :key: |
-| [Scylla.sh](https://scylla.sh/) - Service (free) | Cleartext passwords, hashs and salts, usernames, IPs, domain | :white_check_mark: |
-| [Dehashed.sh](https://dehashed.com/) - Service | Cleartext passwords, hashs and salts, usernames, IPs, domain | :construction: :key: |
-| :new: [IntelX.io](https://intelx.io/signup) - Service (free trial) | Cleartext passwords, hashs and salts, usernames, IPs, domain, Bitcoin Wallets, IBAN | :white_check_mark: :key: |
-
-*:key: - API key required*
-
-
-
-
------
-
-## :tangerine: Usage
-
-```bash
-usage: h8mail [-h] [-t USER_TARGETS [USER_TARGETS ...]]
- [-u USER_URLS [USER_URLS ...]] [-q USER_QUERY] [--loose]
- [-c CONFIG_FILE [CONFIG_FILE ...]] [-o OUTPUT_FILE]
- [-bc BC_PATH] [-sk] [-k CLI_APIKEYS [CLI_APIKEYS ...]]
- [-lb LOCAL_BREACH_SRC [LOCAL_BREACH_SRC ...]]
- [-gz LOCAL_GZIP_SRC [LOCAL_GZIP_SRC ...]] [-sf]
- [-ch [CHASE_LIMIT]] [--power-chase] [--hide] [--debug]
- [--gen-config]
-
-Email information and password lookup tool
-
-optional arguments:
- -h, --help show this help message and exit
- -t USER_TARGETS [USER_TARGETS ...], --targets USER_TARGETS [USER_TARGETS ...]
- Either string inputs or files. Supports email pattern
- matching from input or file, filepath globing and
- multiple arguments
- -u USER_URLS [USER_URLS ...], --url USER_URLS [USER_URLS ...]
- Either string inputs or files. Supports URL pattern
- matching from input or file, filepath globing and
- multiple arguments. Parse URLs page for emails.
- Requires http:// or https:// in URL.
- -q USER_QUERY, --custom-query USER_QUERY
- Perform a custom query. Supports username, password,
- ip, hash, domain. Performs an implicit "loose" search
- when searching locally
- --loose Allow loose search by disabling email pattern
- recognition. Use spaces as pattern seperators
- -c CONFIG_FILE [CONFIG_FILE ...], --config CONFIG_FILE [CONFIG_FILE ...]
- Configuration file for API keys. Accepts keys from
- Snusbase, WeLeakInfo, Leak-Lookup, HaveIBeenPwned,
- Emailrep, Dehashed and hunterio
- -o OUTPUT_FILE, --output OUTPUT_FILE
- File to write CSV output
- -bc BC_PATH, --breachcomp BC_PATH
- Path to the breachcompilation torrent folder. Uses the
- query.sh script included in the torrent
- -sk, --skip-defaults Skips HaveIBeenPwned and HunterIO check. Ideal for
- local scans
- -k CLI_APIKEYS [CLI_APIKEYS ...], --apikey CLI_APIKEYS [CLI_APIKEYS ...]
- Pass config options. Supported format: "K=V,K=V"
- -lb LOCAL_BREACH_SRC [LOCAL_BREACH_SRC ...], --local-breach LOCAL_BREACH_SRC [LOCAL_BREACH_SRC ...]
- Local cleartext breaches to scan for targets. Uses
- multiprocesses, one separate process per file, on
- separate worker pool by arguments. Supports file or
- folder as input, and filepath globing
- -gz LOCAL_GZIP_SRC [LOCAL_GZIP_SRC ...], --gzip LOCAL_GZIP_SRC [LOCAL_GZIP_SRC ...]
- Local tar.gz (gzip) compressed breaches to scans for
- targets. Uses multiprocesses, one separate process per
- file. Supports file or folder as input, and filepath
- globing. Looks for 'gz' in filename
- -sf, --single-file If breach contains big cleartext or tar.gz files, set
- this flag to view the progress bar. Disables
- concurrent file searching for stability
- -ch [CHASE_LIMIT], --chase [CHASE_LIMIT]
- Add related emails from hunter.io to ongoing target
- list. Define number of emails per target to chase.
- Requires hunter.io private API key if used without
- power-chase
- --power-chase Add related emails from ALL API services to ongoing
- target list. Use with --chase
- --hide Only shows the first 4 characters of found passwords
- to output. Ideal for demonstrations
- --debug Print request debug information
- --gen-config, -g Generates a configuration file template in the current
- working directory & exits. Will overwrite existing
- h8mail_config.ini file
-```
-
------
-
-## :tangerine: Usage examples
-
-###### Query for a single target
-
-```bash
-$ h8mail -t target@example.com
-```
-
-###### Query for list of targets, indicate config file for API keys, output to `pwned_targets.csv`
-```bash
-$ h8mail -t targets.txt -c config.ini -o pwned_targets.csv
-```
-
-###### Query a list of targets against local copy of the Breach Compilation, pass API keys for [Snusbase](https://snusbase.com/) from the command line
-```bash
-$ h8mail -t targets.txt -bc ../Downloads/BreachCompilation/ -k "snusbase_url=$snusbase_url,snusbase_token=$snusbase_token"
-```
-
-###### Query without making API calls against local copy of the Breach Compilation
-```bash
-$ h8mail -t targets.txt -bc ../Downloads/BreachCompilation/ -sk
-```
-
-###### Search every .gz file for targets found in targets.txt locally
-
-```bash
-$ h8mail -t targets.txt -gz /tmp/Collection1/ -sk
-```
-
-###### Check a cleartext dump for target. Add the next 10 related emails to targets to check. Read keys from CLI
-
-```bash
-$ h8mail -t admin@evilcorp.com -lb /tmp/4k_Combo.txt -ch 10 -k "hunterio=ABCDE123"
-```
-###### Query username. Read keys from CLI
-
-```bash
-$ h8mail -t JSmith89 -q username -k "dehashed_email=user@email.com" "dehashed_key=ABCDE123"
-```
-
-###### Query IP. Chase all related targets. Read keys from CLI
-
-
-```bash
-$ h8mail -t 42.202.0.42 -q ip -c h8mail_config_priv.ini -ch 2 --power-chase
-```
-
-###### Fetch URL content (CLI + file). Target all found emails
-
-
-```bash
-$ h8mail -u "https://pastebin.com/raw/kQ6WNKqY" "list_of_urls.txt"
-```
-
-
------
-
-## :tangerine: Thanks & Credits
-
-* [Snusbase](https://snusbase.com/) for being developer friendly
-* [kodykinzie](https://twitter.com/kodykinzie) for making a nice [introduction and walkthrough article](https://null-byte.wonderhowto.com/how-to/exploit-recycled-credentials-with-h8mail-break-into-user-accounts-0188600/) and [video](https://www.youtube.com/watch?v=z8G_vBBHtfA) on installing and using h8mail
-* [Leak-Lookup](https://leak-lookup.com/) for being developer friendly
-* [WeLeakInfo](https://weleakinfo.com/) for being developer friendly
-* h8mail's Pypi integration is strongly based on the work of audreyr's [CookieCutter PyPackage](https://github.com/audreyr/cookiecutter-pypackage)
-* Logo generated using Hatchful by Shopify
-* [Jake Creps](https://twitter.com/jakecreps) for his [h8mail v2 introduction](https://jakecreps.com/2019/06/21/h8mail/)
-* [Alejandro Caceres](https://twitter.com/_hyp3ri0n) for making scylla.sh available. Be sure to [support](https://www.buymeacoffee.com/Eiw47ImnT) him if you can
-* [IntelX](https://intelx.io) for being developer friendly
-
-:purple_heart: **h8mail can be found in:**
-* [BlackArch Linux](https://blackarch.org/recon.html)
-* [Tsurugi DFIR VM](https://tsurugi-linux.org/)
-* [Trace Labs OSINT VM](https://www.tracelabs.org/trace-labs-osint-vm/)
-
-
------
-
-## :tangerine: Related open source projects
-* [WhatBreach](https://github.com/Ekultek/WhatBreach) by Ekultek
-* [HashBuster](https://github.com/s0md3v/Hash-Buster) by s0md3v
-* [BaseQuery](https://github.com/g666gle/BaseQuery) by g666gle
-* [LeakLooker](https://github.com/woj-ciech/LeakLooker) by woj-ciech
-* [buster](https://github.com/sham00n/buster) by sham00n
-* [Scavenger](https://github.com/rndinfosecguy/Scavenger) by ndinfosecguy
-* [pwndb](https://github.com/davidtavarez/pwndb) by davidtavarez
-
-
------
-
-## :tangerine: Notes
-
-* Service providers that wish being integrated can send me an email at `k at khast3x dot club` (PGP friendly)
-* h8mail is maintained on my free time. Feedback and war stories are welcomed.
-* Licence is BSD 3 clause
-* My code is [signed](https://help.github.com/en/articles/signing-commits) with my [Keybase](https://keybase.io/ktx) PGP key. You can get it using:
-```bash
-# curl + gpg pro tip: import ktx's keys
-curl https://keybase.io/ktx/pgp_keys.asc | gpg --import
-
-# the Keybase app can push to gpg keychain, too
-keybase pgp pull ktx
-```
-___
-
-*If you wish to stay updated on this project:*
-
-
-
-
-
-
diff --git a/h8mail/requirements.txt b/h8mail/requirements.txt
deleted file mode 100644
index f229360..0000000
--- a/h8mail/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-requests
diff --git a/h8mail/utils/classes.py b/h8mail/utils/classes.py
deleted file mode 100644
index 85a51f7..0000000
--- a/h8mail/utils/classes.py
+++ /dev/null
@@ -1,824 +0,0 @@
-#!/usr/bin/env python
-from .intelx import intelx as i
-from time import sleep
-from .colors import colors as c
-import requests
-import json
-import socket
-import sys
-import platform
-from .version import __version__
-
-
-class local_breach_target:
- """
- Class is called when performing local file search.
- This class is meant to store found data, to be later appended to the existing target objects.
- local_to_targets() tranforms to target object
- Used by both cleartext and gzip search
- """
-
- def __init__(self, target_data, fp, ln, content):
- self.target = target_data
- self.filepath = fp
- self.line = ln
- self.content = content
-
- def dump(self):
- print(f"Email: {self.target}")
- print(f"Path: {self.filepath}")
- print(f"Line: {self.line}")
- print(f"Content: {self.content}")
- print()
-
-
-class target:
- """
- Main class used to create and follow breach data.
- Found data is stored in self.data. Each method increments self.pwned when data is found.
- """
-
- def __init__(self, target_data, debug=False):
- self.headers = {
- "User-Agent": "h8mail-v.{h8ver}-OSINT-and-Education-Tool (PythonVersion={pyver}; Platform={platfrm})".format(
- h8ver=__version__,
- pyver=sys.version.split(" ")[0],
- platfrm=platform.platform().split("-")[0],
- )
- }
- self.target = target_data
- self.pwned = 0
- self.data = [()]
- self.debug = debug
- if debug:
- print(
- c.fg.red,
- f"DEBUG: Created target object for {self.target}",
- c.reset,
- )
-
- def not_exists(self, pattern):
- for d in self.data:
- if len(d) >= 2:
- if d[1] == pattern:
- return False
- return True
-
- def make_request(
- self,
- url,
- meth="GET",
- timeout=20,
- redirs=True,
- data=None,
- params=None,
- verify=True,
- auth=None,
- ):
- try:
- response = requests.request(
- url=url,
- headers=self.headers,
- method=meth,
- timeout=timeout,
- allow_redirects=redirs,
- data=data,
- params=params,
- verify=verify,
- auth=auth,
- )
- # response = requests.request(url="http://127.0.0.1:8000", headers=self.headers, method=meth, timeout=timeout, allow_redirects=redirs, data=data, params=params)
- if self.debug:
- c.debug_news("DEBUG: Sent the following---------------------")
- print(self.headers)
- print(url, meth, data, params)
- c.debug_news("DEBUG: Received the following---------------------")
- c.debug_news(response.url)
- c.debug_news("DEBUG: RESPONSE HEADER---------------------")
- print(
- "\n".join(
- f"{k}: {v}" for k, v in response.headers.items()
- )
- )
- c.debug_news("DEBUG: RESPONSE BODY---------------------")
- print(response.content)
- # print(response)
- except Exception as ex:
- c.bad_news("Request could not be made for " + self.target)
- print(url)
- print(ex)
- print(response)
- return response
-
- # New HIBP API
- def get_hibp3(self, api_key):
- try:
- c.info_news("[" + self.target + "]>[hibp]")
- sleep(1.3)
- url = f"https://haveibeenpwned.com/api/v3/breachedaccount/{self.target}"
- self.headers.update({"hibp-api-key": api_key})
- response = self.make_request(url)
- if response.status_code not in [200, 404]:
- c.bad_news("Could not contact HIBP v3 for " + self.target)
- print(response.status_code)
- return
-
- if response.status_code == 200:
- data = response.json()
- for d in data: # Returned type is a dict of Name : Service
- for _, ser in d.items():
- self.data.append(("HIBP3", ser))
- self.pwned += 1
-
- c.good_news(
- "Found {num} breaches for {target} using HIBP v3".format(
- num=len(self.data) - 1, target=self.target
- )
- )
- self.get_hibp3_pastes()
- self.headers.popitem()
- elif response.status_code == 404:
- c.info_news(
- f"No breaches found for {self.target} using HIBP v3"
- )
-
- else:
- c.bad_news(
- f"HIBP v3: got API response code {response.status_code} for {self.target}"
- )
-
- except Exception as e:
- c.bad_news("haveibeenpwned v3: " + self.target)
- print(e)
-
- # New HIBP API
- def get_hibp3_pastes(self):
- try:
- c.info_news("[" + self.target + "]>[hibp-paste]")
- sleep(1.3)
- url = f"https://haveibeenpwned.com/api/v3/pasteaccount/{self.target}"
-
- response = self.make_request(url)
- if response.status_code not in [200, 404]:
- c.bad_news("Could not contact HIBP PASTE for " + self.target)
- print(response.status_code)
- print(response)
- return
-
- if response.status_code == 200:
-
- data = response.json()
- for d in data: # Returned type is a dict of Name : Service
- self.pwned += 1
- if "Pastebin" in d["Source"]:
- self.data.append(
- ("HIBP3_PASTE", "https://pastebin.com/" + d["Id"])
- )
- else:
- self.data.append(("HIBP3_PASTE", d["Id"]))
-
- c.good_news(
- "Found {num} pastes for {target} using HIBP v3 Pastes".format(
- num=len(data), target=self.target
- )
- )
-
- elif response.status_code == 404:
- c.info_news(
- f"No pastes found for {self.target} using HIBP v3 PASTE"
- )
- else:
- c.bad_news(
- f"HIBP v3 PASTE: got API response code {response.status_code} for {self.target}"
- )
-
- except Exception as ex:
- c.bad_news("HIBP v3 PASTE error: " + self.target)
- print(ex)
-
- def get_intelx(self, api_keys):
- try:
- intel_files = []
- intelx = i(key=api_keys["intelx_key"], ua="h8mail-v.{h8ver}-OSINT-and-Education-Tool (PythonVersion={pyver}; Platform={platfrm})".format(
- h8ver=__version__,
- pyver=sys.version.split(" ")[0],
- platfrm=platform.platform().split("-")[0],
- ))
- from .intelx_helpers import intelx_getsearch
- from .localsearch import local_search
- from os import remove, fspath
-
- maxfile = 10
- if api_keys["intelx_maxfile"]:
- maxfile = int(api_keys["intelx_maxfile"])
- search = intelx_getsearch(self.target, intelx, maxfile)
- if self.debug:
- import json
-
- print(json.dumps(search, indent=4))
-
- for record in search["records"]:
- filename = record["systemid"].strip() + ".txt"
- intel_files.append(filename)
- if record["media"] != 24:
- c.info_news(
- "Skipping {name}, not text ({type})".format(
- type=record["mediah"], name=record["name"]
- )
- )
- continue
- c.good_news(
- "["
- + self.target
- + "]>[intelx.io] Fetching "
- + record["name"]
- + " as file "
- + filename
- + " ("
- + "{:,.0f}".format(record["size"] / float(1 << 20))
- + " MB)"
- )
- intelx.FILE_READ(record["systemid"], 0, record["bucket"], filename)
- found_list = local_search([filename], [self.target])
- for f in found_list:
- self.pwned += 1
- self.data.append(
- (
- "INTELX.IO",
- "{name} | Line: {line} - {content}".format(
- name=record["name"].strip(),
- line=f.line,
- content=" ".join(f.content.split()),
- ),
- )
- )
- # print(contents) # Contains search data
- for file in intel_files:
- if self.debug:
- c.info_news(
- "["
- + self.target
- + f"]>[intelx.io] [DEBUG] Keeping {file}"
- )
- else:
- c.info_news(
- "["
- + self.target
- + f"]>[intelx.io] Removing {file}"
- )
- remove(file)
-
- except Exception as ex:
- c.bad_news("intelx.io error: " + self.target)
- print(ex)
-
- def get_emailrepio(self, api_key=""):
- try:
- sleep(0.5)
- if len(api_key) != 0:
- self.headers.update({"Key": api_key})
- c.info_news("[" + self.target + "]>[emailrep.io+key]")
- else:
- c.info_news("[" + self.target + "]>[emailrep.io]")
- url = f"https://emailrep.io/{self.target}"
- response = self.make_request(url)
- if response.status_code not in [200, 404, 429]:
- c.bad_news("Could not contact emailrep for " + self.target)
- print(response.status_code)
- print(response)
- return
-
- if response.status_code == 429:
- c.info_news(
- "[warning] Is your emailrep key working? Get a free API key here: https://bit.ly/3b1e7Pw"
- )
- elif response.status_code == 404:
- c.info_news(
- f"No data found for {self.target} using emailrep.io"
- )
- elif response.status_code == 200:
- data = response.json()
-
- self.data.append(
- (
- "EMAILREP_INFO",
- "Reputation: {rep} | Deliverable: {deli}".format(
- rep=data["reputation"].capitalize(),
- deli=data["details"]["deliverable"],
- ),
- )
- )
-
- if data["details"]["credentials_leaked"] is True:
- self.pwned += int(data["references"]) # or inc num references
- if data["references"] == 1:
- self.data.append(
- (
- "EMAILREP_LEAKS",
- f"{data['references']} leaked credential",
- )
- )
- else:
- self.data.append(
- (
- "EMAILREP_LEAKS",
- "{} leaked credentials".format(data["references"]),
- )
- )
- c.good_news(
- "Found {num} breaches for {target} using emailrep.io".format(
- num=data["references"], target=self.target
- )
- )
- if len(data["details"]["profiles"]) != 0:
- for profile in data["details"]["profiles"]:
- self.data.append(("EMAILREP_SOCIAL", profile.capitalize()))
- c.good_news(
- "Found {num} social profiles linked to {target} using emailrep.io".format(
- num=len(data["details"]["profiles"]), target=self.target
- )
- )
- if "never" in data["details"]["last_seen"]:
- return
- self.data.append(("EMAILREP_1ST_SN", data["details"]["first_seen"]))
- c.good_news(
- "{target} was first seen on the {data}".format(
- data=data["details"]["first_seen"], target=self.target
- )
- )
- self.data.append(("EMAILREP_LASTSN", data["details"]["last_seen"]))
- c.good_news(
- "{target} was last seen on the {data}".format(
- data=data["details"]["last_seen"], target=self.target
- )
- )
- else:
- c.bad_news(
- "emailrep.io: got API response code {code} for {target}".format(
- code=response.status_code, target=self.target
- )
- )
- if len(api_key) != 0:
- self.headers.popitem()
- except Exception as ex:
- c.bad_news("emailrep.io error: " + self.target)
- print(ex)
-
- def get_scylla(self, user_query="email"):
- try:
- c.info_news("[" + self.target + "]>[scylla.sh]")
- sleep(0.5)
- self.headers.update({"Accept": "application/json"})
- if user_query == "email":
- uri_scylla = 'Email: "' + self.target + '"'
- elif user_query == "password":
- uri_scylla = 'Password: "' + self.target + '"'
- elif user_query == "username":
- uri_scylla = 'User: "' + self.target + '"'
- elif user_query == "ip":
- uri_scylla = 'IP: "' + self.target + '"'
- elif user_query == "hash":
- uri_scylla = 'Hash: "' + self.target + '"'
- elif user_query == "domain":
- uri_scylla = 'Email: "*@' + self.target + '"'
- url = "https://scylla.sh/search?q={}".format(
- requests.utils.requote_uri(uri_scylla)
- )
-
- # https://github.com/khast3x/h8mail/issues/64
- response = self.make_request(
- url,
- verify=False,
- auth=requests.auth.HTTPBasicAuth("sammy", "BasicPassword!"),
- )
- self.headers.popitem()
-
- if response.status_code not in [200, 404]:
- c.bad_news("Could not contact scylla.sh for " + self.target)
- print(response.status_code)
- print(response)
- return
- data = response.json()
- total = 0
- for d in data:
- for field, k in d["_source"].items():
- if k is not None:
- total += 1
- c.good_news(
- "Found {num} entries for {target} using scylla.sh ".format(
- num=total, target=self.target
- )
- )
- for d in data:
- for field, k in d["_source"].items():
- if "User" in field and k is not None:
- self.data.append(("SCYLLA_USERNAME", k))
- self.pwned += 1
- elif (
- "Email" in field and k is not None and user_query != "email"
- ):
- self.data.append(("SCYLLA_EMAIL", k))
- self.pwned += 1
- elif "Password" in field and k is not None:
- self.data.append(("SCYLLA_PASSWORD", k))
- self.pwned += 1
- elif "PassHash" in field and k is not None:
- self.data.append(("SCYLLA_HASH", k))
- self.pwned += 1
- elif "PassSalt" in field and k is not None:
- self.data.append(("SCYLLA_HASHSALT", k))
- self.pwned += 1
- elif "IP" in field and k is not None:
- self.data.append(("SCYLLA_LASTIP", k))
- self.pwned += 1
- elif "Domain" in field and k is not None:
- self.data.append(("SCYLLA_SOURCE", k))
- self.pwned += 1
- except Exception as ex:
- c.bad_news("scylla.sh error: " + self.target)
- print(ex)
-
- def get_hunterio_public(self):
- try:
- c.info_news("[" + self.target + "]>[hunter.io public]")
- target_domain = self.target.split("@")[1]
- url = f"https://api.hunter.io/v2/email-count?domain={target_domain}"
- req = self.make_request(url)
- response = req.json()
- if response["data"]["total"] != 0:
- self.data.append(("HUNTER_PUB", response["data"]["total"]))
- c.good_news(
- "Found {num} related emails for {target} using hunter.io (public)".format(
- num=response["data"]["total"], target=self.target
- )
- )
- except Exception as ex:
- c.bad_news("hunter.io (public API) error: " + self.target)
- print(ex)
-
- def get_hunterio_private(self, api_key):
- try:
- c.info_news("[" + self.target + "]>[hunter.io private]")
- target_domain = self.target.split("@")[1]
- url = f"https://api.hunter.io/v2/domain-search?domain={target_domain}&api_key={api_key}"
- req = self.make_request(url)
- response = req.json()
- b_counter = 0
- for e in response["data"]["emails"]:
- self.data.append(("HUNTER_RELATED", e["value"]))
- b_counter += 1
- if self.pwned != 0:
- self.pwned += 1
- c.good_news(
- "Found {num} related emails for {target} using hunter.io (private)".format(
- num=b_counter, target=self.target
- )
- )
- except Exception as ex:
- c.bad_news(
- f"hunter.io (private API) error for {self.target}:"
- )
- print(ex)
-
- def get_snusbase(self, api_url, api_key, user_query):
- try:
- if user_query == "ip":
- user_query = "lastip"
- if user_query in ["domain"]:
- c.bad_news(
- f"Snusbase does not support {user_query} search (yet)"
- )
- return
- c.info_news("[" + self.target + "]>[snusbase]")
- url = api_url
- self.headers.update({"Authorization": api_key})
- payload = {"type": user_query, "term": self.target}
- req = self.make_request(url, meth="POST", data=payload)
- self.headers.popitem()
- response = req.json()
- c.good_news(
- "Found {num} entries for {target} using Snusbase".format(
- num=len(response["result"]), target=self.target
- )
- )
- for result in response["result"]:
- if result["email"] and self.not_exists(result["email"]):
- self.data.append(("SNUS_RELATED", result["email"].strip()))
- if result["username"]:
- self.data.append(("SNUS_USERNAME", result["username"]))
- self.pwned += 1
- if result["password"]:
- self.data.append(("SNUS_PASSWORD", result["password"]))
- self.pwned += 1
- if result["hash"]:
- if result["salt"]:
- self.data.append(
- (
- "SNUS_HASH_SALT",
- result["hash"].strip() + " : " + result["salt"].strip(),
- )
- )
- self.pwned += 1
- else:
- self.data.append(("SNUS_HASH", result["hash"]))
- self.pwned += 1
- if result["lastip"]:
- self.data.append(("SNUS_LASTIP", result["lastip"]))
- self.pwned += 1
- if result["name"]:
- self.data.append(("SNUS_NAME", result["name"]))
- self.pwned += 1
- if result["tablenr"] and self.not_exists(result["tablenr"]):
- self.data.append(("SNUS_SOURCE", result["tablenr"]))
-
- except Exception as ex:
- c.bad_news(f"Snusbase error with {self.target}")
- print(ex)
-
- def get_leaklookup_pub(self, api_key):
- try:
- c.info_news("[" + self.target + "]>[leaklookup public]")
- url = "https://leak-lookup.com/api/search"
- payload = {"key": api_key, "type": "email_address", "query": self.target}
- req = self.make_request(url, meth="POST", data=payload, timeout=20)
- response = req.json()
- if "false" in response["error"] and len(response["message"]) != 0:
- c.good_news(
- "Found {num} entries for {target} using LeakLookup (public)".format(
- num=len(response["message"]), target=self.target
- )
- )
- for result in response["message"]:
- self.pwned += 1
- self.data.append(("LEAKLOOKUP_PUB", result))
- if "false" in response["error"] and len(response["message"]) == 0:
- c.info_news(
- f"No breaches found for {self.target} using Leak-lookup (public)"
- )
-
- except Exception as ex:
- c.bad_news(
- f"Leak-lookup error with {self.target} (public)"
- )
- print(ex)
-
- def get_leaklookup_priv(self, api_key, user_query):
- try:
- if user_query == "ip":
- user_query = "ipadress"
- if user_query in ["hash"]:
- c.bad_news(
- f"Leaklookup does not support {user_query} search (yet)"
- )
- return
- c.info_news("[" + self.target + "]>[leaklookup private]")
- url = "https://leak-lookup.com/api/search"
- payload = {"key": api_key, "type": user_query, "query": self.target}
- req = self.make_request(url, meth="POST", data=payload, timeout=60)
- response = req.json()
- if "false" in response["error"] and len(response["message"]) != 0:
- b_counter = 0
- for db, data in response["message"].items():
- for d in data:
- if "username" in d.keys():
- self.pwned += 1
- self.data.append(("LKLP_USERNAME", d["username"]))
- if "email_address" in d.keys() and self.not_exists(
- d["email_address"]
- ):
- self.data.append(
- ("LKLP_RELATED", d["email_address"].strip())
- )
- if "password" in d.keys():
- self.pwned += 1
- self.data.append(("LKLP_PASSWORD", d["password"]))
- b_counter += 1
- if "hash" in d.keys():
- self.pwned += 1
- self.data.append(("LKLP_HASH", d["password"]))
- b_counter += 1
- if "ipaddress" in d.keys():
- self.pwned += 1
- self.data.append(("LKLP_LASTIP", d["ipaddress"]))
- for tag in [
- "address",
- "address1",
- "address2",
- "country",
- "zip",
- "zipcode",
- "postcode",
- "state",
- ]:
- if tag in d.keys():
- self.pwned += 1
- self.data.append(
- ("LKLP_GEO", d[tag] + " (type: " + tag + ")")
- )
- for tag in [
- "firstname",
- "middlename",
- "lastname",
- "mobile",
- "number",
- "userid",
- ]:
- if tag in d.keys():
- self.pwned += 1
- self.data.append(
- ("LKLP_ID", d[tag] + " (type: " + tag + ")")
- )
- if self.not_exists(db):
- self.data.append(("LKLP_SOURCE", db))
-
- c.good_news(
- "Found {num} entries for {target} using LeakLookup (private)".format(
- num=b_counter, target=self.target
- )
- )
-
- if "false" in response["error"] and len(response["message"]) == 0:
- c.info_news(
- f"No breaches found for {self.target} using Leak-lookup (private)"
- )
- except Exception as ex:
- c.bad_news(
- f"Leak-lookup error with {self.target} (private)"
- )
- print(ex)
-
- def get_weleakinfo_priv(self, api_key, user_query):
- try:
- c.info_news("[" + self.target + "]>[weleakinfo priv]")
- sleep(0.4)
- url = "https://api.weleakinfo.com/v3/search"
- self.headers.update({"Authorization": "Bearer " + api_key})
- self.headers.update({"Content-Type": "application/x-www-form-urlencoded"})
-
- payload = {"type": user_query, "query": self.target}
- req = self.make_request(url, meth="POST", data=payload, timeout=30)
- self.headers.popitem()
- self.headers.popitem()
- response = req.json()
- if req.status_code == 400:
- c.bad_news(
- f"Got WLI API response code {req.status_code}: Invalid search type provided"
- )
- return
- elif req.status_code != 200:
- c.bad_news(f"Got WLI API response code {req.status_code} (private)")
- return
- if req.status_code == 200:
- if response["Success"] is False:
- c.bad_news(
- f"WeLeakInfo (private) error response {response['Message']}"
- )
- return
- c.good_news(
- "Found {num} entries for {target} using WeLeakInfo (private)".format(
- num=response["Total"], target=self.target
- )
- )
- self.data.append(("WLI_TOTAL", response["Total"]))
- if response["Total"] == 0:
- return
- for result in response["Data"]:
- if "Username" in result:
- self.data.append(("WLI_USERNAME", result["Username"]))
- if "Email" in result and self.not_exists(result["Email"]):
- self.data.append(("WLI_RELATED", result["Email"].strip()))
- if "Password" in result:
- self.data.append(("WLI_PASSWORD", result["Password"]))
- self.pwned += 1
- if "Hash" in result:
- self.data.append(("WLI_HASH", result["Hash"]))
- self.pwned += 1
- if "Database" in result and self.not_exists(result["Database"]):
- self.data.append(("WLI_SOURCE", result["Database"]))
- except Exception as ex:
- c.bad_news(
- f"WeLeakInfo error with {self.target} (private)"
- )
- print(ex)
-
- def get_weleakinfo_pub(self, api_key):
- try:
- c.info_news("[" + self.target + "]>[weleakinfo public]")
- url = "https://api.weleakinfo.com/v3/public/email/{query}".format(
- query=self.target
- )
- self.headers.update({"Authorization": "Bearer " + api_key})
- req = self.make_request(url, timeout=30)
- self.headers.popitem()
- response = req.json()
- if req.status_code != 200:
- c.bad_news(f"Got WLI API response code {req.status_code} (public)")
- return
- else:
- c.good_news(
- "Found {num} entries for {target} using WeLeakInfo (public)".format(
- num=response["Total"], target=self.target
- )
- )
- if response["Success"] is False:
- c.bad_news(response["Message"])
- return
- self.data.append(("WLI_PUB_TOTAL", response["Total"]))
- if response["Total"] == 0:
- return
- for name, data in response["Data"].items():
- self.data.append(("WLI_PUB_SRC", name + " (" + str(data) + ")"))
- except Exception as ex:
- c.bad_news(
- f"WeLeakInfo error with {self.target} (public)"
- )
- print(ex)
-
- def get_dehashed(self, api_email, api_key, user_query):
- try:
- # New Dehashed API needs fixing, waiting for devs to respond
- c.bad_news("Dehashed is temporarily unavailable")
- c.bad_news("This should be fixed in the next updates\n")
- return
-
- if user_query == "hash":
- user_query == "hashed_password"
- if user_query == "ip":
- user_query == "ip_address"
-
- c.info_news("[" + self.target + "]>[dehashed]")
- url = "https://api.dehashed.com/search?query="
- if user_query == "domain":
- search_query = "email" + ":" + '"*@' + self.target + '"'
- else:
- search_query = user_query + ":" + '"' + self.target + '"'
- self.headers.update({"Accept": "application/json"})
- req = self.make_request(
- url + search_query, meth="GET", timeout=60, auth=(api_email, api_key)
- )
- if req.status_code == 200:
- response = req.json()
- if response["total"] is not None:
- c.good_news(
- "Found {num} entries for {target} using Dehashed.com".format(
- num=str(response["total"]), target=self.target
- )
- )
-
- for result in response["entries"]:
- if (
- "username" in result
- and result["username"] is not None
- and len(result["username"].strip()) > 0
- ):
- self.data.append(("DHASHD_USERNAME", result["username"]))
- if (
- "email" in result
- and self.not_exists(result["email"])
- and result["email"] is not None
- and len(result["email"].strip()) > 0
- ):
- self.data.append(("DHASHD_RELATED", result["email"].strip()))
- if (
- "password" in result
- and result["password"] is not None
- and len(result["password"].strip()) > 0
- ):
- self.data.append(("DHASHD_PASSWORD", result["password"]))
- self.pwned += 1
- if (
- "hashed_password" in result
- and result["hashed_password"] is not None
- and len(result["hashed_password"].strip()) > 0
- ):
- self.data.append(("DHASHD_HASH", result["hashed_password"]))
- self.pwned += 1
- for tag in ["name", "vin", "address", "phone"]:
- if (
- tag in result
- and result[tag] is not None
- and len(result[tag].strip()) > 0
- ):
- self.data.append(
- ("DHASHD_ID", result[tag] + " (type: " + tag + ")")
- )
- self.pwned += 1
-
- if "obtained_from" in result and self.not_exists(
- result["obtained_from"]
- ):
- self.data.append(("DHASHD_SOURCE", result["obtained_from"]))
-
- if response["balance"] is not None:
- self.data.append(
- (
- "DHASHD_CREDITS",
- str(response["balance"]) + " DEHASHED CREDITS REMAINING",
- )
- )
- else:
- c.bad_news("Dehashed error: status code " + req.status_code)
- self.headers.popitem()
- except Exception as ex:
- c.bad_news(f"Dehashed error with {self.target}")
- print(ex)
diff --git a/h8mail/utils/localzstdsearch.py b/h8mail/utils/localzstdsearch.py
new file mode 100644
index 0000000..c85e3bb
--- /dev/null
+++ b/h8mail/utils/localzstdsearch.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+
+from multiprocessing import Pool
+from .classes import local_breach_target
+from .colors import colors as c
+from .localsearch import raw_in_count, progress
+import io
+import os
+import sys
+import signal
+import zstandard as zstd
+
+
+def progress_zstd(count):
+ """
+ Prints count without rewriting to stdout
+ """
+ sys.stdout.write("Lines checked:%i\r" % (count))
+ sys.stdout.write("\033[K")
+
+
+def zstd_worker(filepath, target_list):
+ """
+ Searches for every email from target_list in every line of filepath.
+ Uses python zstandard bindings to decompress file line by line.
+ Archives with multiple files are read as long single files.
+ Attempts to decode line using cp437. If it fails, catch and use raw data.
+ """
+ try:
+ found_list = []
+ size = os.stat(filepath).st_size
+ with open(filepath, "rb") as breach_file:
+ dctx = zstd.ZstdDecompressor()
+ stream_reader = dctx.stream_reader(breach_file)
+ zstdfile = io.TextIOWrapper(stream_reader, encoding='utf-8', errors='replace')
+ c.info_news(
+ "Worker [{PID}] is searching for targets in {filepath} ({size:,.0f} MB)".format(
+ PID=os.getpid(), filepath=filepath, size=size / float(1 << 20)
+ )
+ )
+ for cnt, line in enumerate(zstdfile):
+ for t in target_list:
+ if t in str(line):
+ c.good_news(
+ f"Found occurrence [{filepath}] Line {cnt}: {line}"
+ )
+ found_list.append(
+ local_breach_target(t, filepath, cnt, str(line))
+ )
+ return found_list
+ except Exception as e:
+ c.bad_news("Something went wrong with zstd worker")
+ print(e)
+
+
+def local_zstd_search(files_to_parse, target_list):
+ """
+ Receives list of all files to check for target_list.
+ Starts a worker pool, one worker per file.
+ Return list of local_breach_targets objects to be tranformed in target objects.
+ """
+ original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ pool = Pool()
+ found_list = []
+ signal.signal(signal.SIGINT, original_sigint_handler)
+ # Launch
+ try:
+ async_results = [
+ pool.apply_async(zstd_worker, args=(f, target_list))
+ for i, f in enumerate(files_to_parse)
+ ]
+ for r in async_results:
+ if r.get() is not None:
+ found_list.extend(r.get(60))
+ except KeyboardInterrupt:
+ c.bad_news("Caught KeyboardInterrupt, terminating workers")
+ pool.terminate()
+ else:
+ c.info_news("Terminating worker pool")
+ pool.close()
+ pool.join()
+ return found_list
+
+
+def local_search_single_zstd(files_to_parse, target_list):
+ """
+ Single process searching of every target_list emails, in every files_to_parse list.
+ To be used when stability for big files is a priority
+ Return list of local_breach_target objects to be tranformed in target objects
+ """
+ found_list = []
+ for file_to_parse in files_to_parse:
+ with open(file_to_parse, "rb") as breach_file:
+ dctx = zstd.ZstdDecompressor()
+ stream_reader = dctx.stream_reader(breach_file)
+ zstdfile = io.TextIOWrapper(stream_reader, encoding= 'utf-8', errors='replace')
+ size = os.stat(file_to_parse).st_size
+ c.info_news(
+ f"Searching for targets in {file_to_parse} ({size} bytes)"
+ )
+ for cnt, line in enumerate(zstdfile):
+ progress_zstd(cnt)
+ for t in target_list:
+ if t in str(line):
+ c.good_news(
+ f"Found occurrence [{file_to_parse}] Line {cnt}: {line}"
+ )
+ found_list.append(
+ local_breach_target(t, file_to_parse, cnt, str(line))
+ )
+ return found_list
diff --git a/h8mail/utils/run.py b/h8mail/utils/run.py
deleted file mode 100644
index 32905f3..0000000
--- a/h8mail/utils/run.py
+++ /dev/null
@@ -1,361 +0,0 @@
-# -*- coding: utf-8 -*-
-# Most imports are after python2/3 check further down
-import configparser
-import argparse
-import os
-import re
-import time
-import sys
-
-from .breachcompilation import breachcomp_check
-from .classes import target
-from .colors import colors as c
-from .helpers import (
- fetch_emails,
- find_files,
- get_config_from_file,
- get_emails_from_file,
- print_banner,
- save_results_csv,
- check_latest_version,
- check_scylla_online,
-)
-from .localsearch import local_search, local_search_single, local_to_targets
-from .localgzipsearch import local_gzip_search, local_search_single_gzip
-from .summary import print_summary
-from .chase import chase
-from .print_results import print_results
-from .gen_config import gen_config_file
-from .url import target_urls
-
-
-def target_factory(targets, user_args):
- """
- Receives list of emails and user args. Fetchs API keys from config file using user_args path and cli keys.
- For each target, launch target.methods() associated to found config artifacts.
- Handles chase logic with counters from enumerate()
- """
- # Removing duplicates here to avoid dups from chasing
- targets = list(set(targets))
-
- finished = []
- if user_args.config_file is not None or user_args.cli_apikeys is not None:
- api_keys = get_config_from_file(user_args)
- else:
- api_keys = None
- init_targets_len = len(targets)
-
- query = "email"
- skip_default_queries = False
- if user_args.user_query is not None:
- query = user_args.user_query
- skip_default_queries = True # custom query skips default query automatically
-
- scylla_up = False
- if user_args.skip_defaults is False:
- scylla_up = check_scylla_online()
-
-
-
- for counter, t in enumerate(targets):
- c.info_news("Target factory started for {target}".format(target=t))
- if user_args.debug:
- current_target = target(t, debug=True)
- else:
- current_target = target(t)
- if not skip_default_queries:
- if not user_args.skip_defaults:
- current_target.get_hunterio_public()
- ## emailrep seems to insta-block h8mail user agent without a key
- # if api_keys is None or "emailrep" not in api_keys:
- # current_target.get_emailrepio()
- # elif (
- # api_keys is not None and "emailrep" in api_keys and query == "email"
- # ):
- # current_target.get_emailrepio(api_keys["emailrep"])
-
- if api_keys is not None:
- if "hibp" in api_keys and query == "email":
- current_target.get_hibp3(api_keys["hibp"])
- if "emailrep" in api_keys and query == "email":
- current_target.get_emailrepio(api_keys["emailrep"])
- if "hunterio" in api_keys and query == "email":
- current_target.get_hunterio_private(api_keys["hunterio"])
- if "intelx_key" in api_keys:
- current_target.get_intelx(api_keys)
- if "snusbase_token" in api_keys:
- if "snusbase_url" in api_keys:
- snusbase_url = api_keys["snusbase_url"]
- else:
- snusbase_url = "http://api.snusbase.com/v2/search"
- current_target.get_snusbase(
- snusbase_url, api_keys["snusbase_token"], query
- )
- if "leak-lookup_priv" in api_keys:
- current_target.get_leaklookup_priv(api_keys["leak-lookup_priv"], query)
- if "leak-lookup_pub" in api_keys and query == "email":
- current_target.get_leaklookup_pub(api_keys["leak-lookup_pub"])
- if "weleakinfo_pub" in api_keys and query == "email":
- current_target.get_weleakinfo_pub(api_keys["weleakinfo_pub"])
- if "weleakinfo_priv" in api_keys:
- current_target.get_weleakinfo_priv(api_keys["weleakinfo_priv"], query)
- if "dehashed_key" in api_keys:
- if "dehashed_email" in api_keys:
- current_target.get_dehashed(
- api_keys["dehashed_email"], api_keys["dehashed_key"], query
- )
- else:
- c.bad_news("Missing Dehashed email")
- if scylla_up:
- current_target.get_scylla(query)
-
- # Chasing
- if user_args.chase_limit and counter <= init_targets_len:
- user_args_force_email = user_args
- user_args_force_email.user_query = "email"
- user_args_force_email.chase_limit -= 1
- finished_chased = target_factory(
- chase(current_target, user_args), user_args_force_email
- )
- finished.extend((finished_chased))
- finished.append(current_target)
- return finished
-
-
-def h8mail(user_args):
- """
- Handles most user arg logic. Creates a list() of targets from user input.
- Starts the target object factory loop; starts local searches after factory if in user inputs
- Prints results, saves to csv if in user inputs
- """
-
- if user_args.user_targets and user_args.user_urls:
- c.bad_news("Cannot use --url with --target. Use one or the other.")
- exit(1)
-
- if not user_args.user_targets and not user_args.user_urls:
- c.bad_news("Missing Target or URL")
- exit(1)
-
- start_time = time.time()
-
- import warnings
-
- warnings.filterwarnings('ignore', message='Unverified HTTPS request')
-
- targets = []
- if user_args.user_urls:
- targets = target_urls(user_args)
- if len(targets) == 0:
- c.bad_news("No targets found in URLs. Quitting")
- exit(0)
-
- # If we found emails from URLs, `targets` array already has stuff
- if len(targets) != 0:
- if user_args.user_targets is None:
- user_args.user_targets = []
- user_args.user_targets.extend(targets)
-
- else: # Find targets in user input or file
- if user_args.user_targets is not None:
- for arg in user_args.user_targets:
- user_stdin_target = fetch_emails(arg, user_args)
- if os.path.isfile(arg):
- c.info_news("Reading from file " + arg)
- targets.extend(get_emails_from_file(arg, user_args))
- elif user_stdin_target:
- targets.extend(user_stdin_target)
- else:
- c.bad_news("No targets found in user input. Quitting")
- exit(0)
-
- c.info_news("Removing duplicates")
- targets = list(set(targets))
-
- c.good_news("Targets:")
- for t in targets:
- c.good_news(t)
-
- # Launch
- breached_targets = target_factory(targets, user_args)
-
- # These are not done inside the factory as the factory iterates over each target individually
- # The following functions perform line by line checks of all targets per line
-
- if user_args.bc_path:
- breached_targets = breachcomp_check(breached_targets, user_args.bc_path)
-
- local_found = None
- # Handle cleartext search
- if user_args.local_breach_src:
- for arg in user_args.local_breach_src:
- res = find_files(arg)
- if user_args.single_file:
- local_found = local_search_single(res, targets)
- else:
- local_found = local_search(res, targets)
- if local_found is not None:
- breached_targets = local_to_targets(
- breached_targets, local_found, user_args
- )
- # Handle gzip search
- if user_args.local_gzip_src:
- for arg in user_args.local_gzip_src:
- res = find_files(arg, "gz")
- if user_args.single_file:
- local_found = local_search_single_gzip(res, targets)
- else:
- local_found = local_gzip_search(res, targets)
- if local_found is not None:
- breached_targets = local_to_targets(
- breached_targets, local_found, user_args
- )
-
- print_results(breached_targets, user_args.hide)
-
- print_summary(start_time, breached_targets)
- if user_args.output_file:
- save_results_csv(user_args.output_file, breached_targets)
-
-
-def parse_args(args):
- """
- Seperate functions to make it easier to run tests
- Pass args as an array
- """
- parser = argparse.ArgumentParser(
- description="Email information and password lookup tool", prog="h8mail"
- )
-
- parser.add_argument(
- "-t",
- "--targets",
- dest="user_targets",
- help="Either string inputs or files. Supports email pattern matching from input or file, filepath globing and multiple arguments",
- nargs="+",
- )
- parser.add_argument(
- "-u",
- "--url",
- dest="user_urls",
- help="Either string inputs or files. Supports URL pattern matching from input or file, filepath globing and multiple arguments. Parse URLs page for emails. Requires http:// or https:// in URL.",
- nargs="+",
- )
- parser.add_argument(
- "-q",
- "--custom-query",
- dest="user_query",
- help='Perform a custom query. Supports username, password, ip, hash, domain. Performs an implicit "loose" search when searching locally',
- )
- parser.add_argument(
- "--loose",
- dest="loose",
- help="Allow loose search by disabling email pattern recognition. Use spaces as pattern seperators",
- action="store_true",
- default=False,
- )
- parser.add_argument(
- "-c",
- "--config",
- dest="config_file",
- help="Configuration file for API keys. Accepts keys from Snusbase, WeLeakInfo, Leak-Lookup, HaveIBeenPwned, Emailrep, Dehashed and hunterio",
- nargs="+",
- )
- parser.add_argument(
- "-o", "--output", dest="output_file", help="File to write CSV output"
- )
- parser.add_argument(
- "-bc",
- "--breachcomp",
- dest="bc_path",
- help="Path to the breachcompilation torrent folder. Uses the query.sh script included in the torrent",
- )
- parser.add_argument(
- "-sk",
- "--skip-defaults",
- dest="skip_defaults",
- help="Skips HaveIBeenPwned and HunterIO check. Ideal for local scans",
- action="store_true",
- default=False,
- )
- parser.add_argument(
- "-k",
- "--apikey",
- dest="cli_apikeys",
- help='Pass config options. Supported format: "K=V,K=V"',
- nargs="+",
- )
- parser.add_argument(
- "-lb",
- "--local-breach",
- dest="local_breach_src",
- help="Local cleartext breaches to scan for targets. Uses multiprocesses, one separate process per file, on separate worker pool by arguments. Supports file or folder as input, and filepath globing",
- nargs="+",
- )
- parser.add_argument(
- "-gz",
- "--gzip",
- dest="local_gzip_src",
- help="Local tar.gz (gzip) compressed breaches to scans for targets. Uses multiprocesses, one separate process per file. Supports file or folder as input, and filepath globing. Looks for 'gz' in filename",
- nargs="+",
- )
- parser.add_argument(
- "-sf",
- "--single-file",
- dest="single_file",
- help="If breach contains big cleartext or tar.gz files, set this flag to view the progress bar. Disables concurrent file searching for stability",
- action="store_true",
- default=False,
- ),
- parser.add_argument(
- "-ch",
- "--chase",
- dest="chase_limit",
- help="Add related emails from hunter.io to ongoing target list. Define number of emails per target to chase. Requires hunter.io private API key if used without power-chase",
- type=int,
- nargs="?",
- ),
- parser.add_argument(
- "--power-chase",
- dest="power_chase",
- help="Add related emails from ALL API services to ongoing target list. Use with --chase",
- action="store_true",
- default=False,
- ),
- parser.add_argument(
- "--hide",
- dest="hide",
- help="Only shows the first 4 characters of found passwords to output. Ideal for demonstrations",
- action="store_true",
- default=False,
- ),
- parser.add_argument(
- "--debug",
- dest="debug",
- help="Print request debug information",
- action="store_true",
- default=False,
- ),
- parser.add_argument(
- "--gen-config",
- "-g",
- dest="gen_config",
- help="Generates a configuration file template in the current working directory & exits. Will overwrite existing h8mail_config.ini file",
- action="store_true",
- default=False,
- ),
-
- return parser.parse_args(args)
-
-
-def main():
-
- print_banner("warn")
- print_banner("version")
- print_banner()
- check_latest_version()
- user_args = parse_args(sys.argv[1:])
- if user_args.gen_config:
- gen_config_file()
- exit(0)
- h8mail(user_args)
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 574f225..0000000
--- a/setup.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""The setup script."""
-from setuptools import setup, find_packages, Extension
-from distutils.util import convert_path
-import os
-
-here = os.path.abspath(os.path.dirname(__file__))
-exec(open(os.path.join(here, "h8mail/utils/version.py")).read())
-
-with open("PyPi.rst") as readme_file:
- readme = readme_file.read()
-
-# with open("HISTORY.rst") as history_file:
-# history = history_file.read()
-
-requirements = ["requests"]
-
-setup_requirements = ["requests"]
-
-test_requirements = ["requests"]
-
-setup(
- author="khast3x",
- author_email="k@khast3x.club",
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: BSD License",
- "Natural Language :: English",
- "Programming Language :: Python :: 3.6",
- "Programming Language :: Python :: 3.7",
- "Environment :: Console",
- ],
- description="Email OSINT and password breach hunting. Use h8mail to find passwords through different breach and reconnaissance services, or using your local data",
- install_requires=requirements,
- license="BSD license",
- # long_description_content_type="text/markdown",
- long_description=readme + "\n\n",
- # long_description=readme + "\n\n" + history,
- include_package_data=True,
- keywords="h8mail",
- name="h8mail",
- packages=find_packages(),
- entry_points={"console_scripts": ["h8mail = h8mail.__main__:main"]},
- setup_requires=setup_requirements,
- url="https://github.com/khast3x/h8mail",
- version=__version__,
- zip_safe=False,
-)