Skip to content

Commit

Permalink
Add dnsme script (#84)
Browse files Browse the repository at this point in the history
* Add dnsme script

We pin the version of whois, because of this issue
mboot-github/python-whois#278

* Sort mx records

* Install whois dependency, and error gracefully if not installed

* Sort all lookups
  • Loading branch information
tjaartvdwalt committed Apr 12, 2023
1 parent c139ce4 commit 4cb4b00
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 8 deletions.
4 changes: 2 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ function install_dependencies {
if [[ $(uname -s) =~ "Linux" ]]; then
if which apt-get 2>/dev/null; then
if ! which autossh; then
sudo apt-get -y update && sudo apt-get install -y ssh autossh python3-pip
sudo apt-get -y update && sudo apt-get install -y ssh autossh python3-pip whois
fi
else
echo "We could not automatically install the dependencies on your system. Please install ssh and autossh manually."
fi
elif [[ $(uname -s) =~ "Darwin" ]]; then
if which brew 2>/dev/null; then
if ! which autossh; then
brew install autossh
brew install autossh whois
fi
else
echo "Could not find Homebrew (https://brew.sh/). Install Homebrew, or, manually install autossh."
Expand Down
167 changes: 167 additions & 0 deletions oo_bin/dnsme/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import re
import shutil
from datetime import datetime

import dns.resolver
import dns.reversename
import whois
from colorama import Style

from oo_bin.errors import DependencyNotMetError, DomainNotExistError


class Dnsme:
def __init__(self, domain):
self.domain = domain

if not shutil.which("whois"):
raise DependencyNotMetError("whois is not installed, or is not in the path")

if not self.__whois__:
raise DomainNotExistError(f"The domain {self.domain} does not exist")

self.a = None
self.mx = None
self.ns = None
self.txt = None

@property
def __whois__(self):
return whois.query(self.domain)

@property
def __a_lookup__(self):
try:
if not self.a:
self.a = sorted(dns.resolver.resolve(self.domain, "A"))
return self.a
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return []

@property
def __mx_lookup__(self):
try:
if not self.mx:
self.mx = sorted(
dns.resolver.resolve(self.domain, "MX"),
key=lambda d: (d.preference, d.exchange),
)
return self.mx
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return []

@property
def __ns_lookup__(self):
try:
if not self.ns:
self.ns = sorted(dns.resolver.resolve(self.domain, "NS"))
return self.ns
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return []

@property
def __reverse_lookup__(self):
reverse = []
for a_record in self.__a_lookup__:
try:
a = dns.reversename.from_address(f"{a_record}")
for ptr in dns.resolver.query(a, "PTR"):
reverse.append(ptr)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
pass

return reverse

@property
def __spf_lookup__(self):
spf_entries = []
for txt_entry in self.__txt_lookup__:
if re.search(r"spf", f"{txt_entry}"):
spf_entries.append(txt_entry)

return spf_entries

@property
def __dmarc_lookup__(self):
try:
return sorted(dns.resolver.resolve(f"_dmarc.{self.domain}", "TXT"))
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
return []

@property
def __dkim_lookup__(self):
dkims = []
try:
selector1 = dns.resolver.resolve(
f"selector1._domainkey.{self.domain}", "CNAME"
)
for dkim_entry in selector1:
dkims.append(dkim_entry)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
pass

try:
selector2 = dns.resolver.resolve(
f"selector2._domainkey.{self.domain}", "CNAME"
)
for dkim_entry in selector2:
dkims.append(dkim_entry)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
pass

return dkims

@property
def __txt_lookup__(self):
if not self.txt:
self.txt = sorted(dns.resolver.resolve(self.domain, "TXT"))
return self.txt

def __str__(self):
s = f"{Style.BRIGHT}Registrar Info\n"
s += f"{Style.RESET_ALL}Registrar: {self.__whois__.registrar}\n"
s += f"{self.domain} expires in {(self.__whois__.expiration_date - datetime.now()).days} \
days on {self.__whois__.expiration_date.astimezone()}\n"

s += "\n"

s += f"{Style.BRIGHT}A Records\n"
for a_entry in self.__a_lookup__:
s += f"{Style.RESET_ALL}{a_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}MX Records\n"
for mx_entry in self.__mx_lookup__:
s += f"{Style.RESET_ALL}{mx_entry.preference: <2} {mx_entry.exchange}\n"
s += "\n"

s += f"{Style.BRIGHT}NS Records\n"
for ns_entry in self.__ns_lookup__:
s += f"{Style.RESET_ALL}{ns_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}Reverse DNS\n"
for reverse_entry in self.__reverse_lookup__:
s += f"{Style.RESET_ALL}{reverse_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}SPF Records\n"
for spf_entry in self.__spf_lookup__:
s += f"{Style.RESET_ALL}{spf_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}DMARC Record\n"
for dmarc_entry in self.__dmarc_lookup__:
s += f"{Style.RESET_ALL}{dmarc_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}DKIM Records (Office 365 only)\n"
for dkim_entry in self.__dkim_lookup__:
s += f"{Style.RESET_ALL}{dkim_entry}\n"
s += "\n"

s += f"{Style.BRIGHT}TXT Records\n"
for txt_entry in self.__txt_lookup__:
s += f"{Style.RESET_ALL}{txt_entry}\n"

return s
4 changes: 4 additions & 0 deletions oo_bin/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ class ConfigNotFoundError(OOBinError):

class SystemNotSupportedError(OOBinError):
pass


class DomainNotExistError(OOBinError):
pass
14 changes: 11 additions & 3 deletions oo_bin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import colorama

from oo_bin import __version__
from oo_bin.dnsme import Dnsme
from oo_bin.errors import OOBinError
from oo_bin.tunnels import Rdp, Socks5, Vnc
from oo_bin.utils import auto_update
Expand All @@ -26,7 +27,14 @@ def cli():
pass


@cli.group(cls=SkipArg, invoke_without_command=True)
@cli.command(help="DNS Information")
@click.argument("domain")
def dnsme(domain):
dnsme = Dnsme(domain)
print(dnsme)


@cli.group(cls=SkipArg, invoke_without_command=True, help="Manage tunnels")
@click.pass_context
@click.option("-S", "--stop", is_flag=True, help="Stop all running socks5 tunnels")
@click.option("-s", "--status", is_flag=True, help="Socks5 tunnels status")
Expand All @@ -52,7 +60,7 @@ def tunnels(ctx, status, stop, update, profile):
)


@tunnels.command()
@tunnels.command(help="Manage rdp tunnels")
@click.option("-S", "--stop", is_flag=True, help="Stop all running rdp tunnels")
@click.option("-s", "--status", is_flag=True, help="Rdp tunnels status")
@click.option(
Expand All @@ -70,7 +78,7 @@ def rdp(status, stop, update, profile):
return rdp.run({"status": status, "stop": stop, "update": update})


@tunnels.command()
@tunnels.command(help="Manage vnc tunnels")
@click.option("-S", "--stop", is_flag=True, help="Stop running vnc tunnels")
@click.option("-s", "--status", is_flag=True, help="Vnc tunnels status")
@click.option(
Expand Down
4 changes: 2 additions & 2 deletions oo_bin/tunnels/socks5.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ def shell_complete(ctx, param, incomplete):
for e in [
{"name": "status", "help": "Tunnel status"},
{"name": "stop", "help": "Stop tunnel"},
{"name": "rdp", "help": "sub-command"},
{"name": "vnc", "help": "sub-command"},
{"name": "rdp", "help": "Manage rdp tunnels"},
{"name": "vnc", "help": "Manage vnc tunnels"},
]
if e["name"].startswith(incomplete)
]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
requires-python = ">=3.7"
dynamic = ["version"]

dependencies = ["click", "colorama", "progress", "pyxdg", "requests", "tomli >= 1.1.0 ; python_version < '3.11'"]
dependencies = ["click", "colorama", "dnspython", "progress", "pyxdg", "requests", "tomli >= 1.1.0 ; python_version < '3.11'", "whois == 0.9.24"]

[project.urls]
Home = "https://github.com/outsideopen/oo-bin"
Expand Down

0 comments on commit 4cb4b00

Please sign in to comment.