visidata: add IOC types with cached, throttled lookups
Centralize provider caching and rate-limit handling, then add Domain/URL/Hash IOC types and safer VT/IPInfo key resolution so lookups stay reliable on free-tier APIs.
This commit is contained in:
@@ -17,6 +17,18 @@ try:
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import plugins.ioc
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
# Optional local lookup settings (tokens, key preference, throttling).
|
||||
# Keep this as a separate module so secrets can stay out of versioned config.
|
||||
try:
|
||||
import lookup_config
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import json
|
||||
@@ -171,12 +183,23 @@ def vendor(mac):
|
||||
def _get_vt():
|
||||
try:
|
||||
from virus_total_apis import PublicApi as VirusTotalPublicApi
|
||||
import os.path
|
||||
with open(os.path.expanduser('~/.virustotal_api_key')) as af:
|
||||
API_KEY = af.readline().strip()
|
||||
vt = VirusTotalPublicApi(API_KEY)
|
||||
api_key = str(
|
||||
getattr(options, 'tke_vt_api_key', '')
|
||||
or os.getenv('VT_API_KEY')
|
||||
or os.getenv('VIRUSTOTAL_API_KEY')
|
||||
or ''
|
||||
)
|
||||
if not api_key:
|
||||
try:
|
||||
with open(os.path.expanduser('~/.virustotal_api_key')) as af:
|
||||
api_key = af.readline().strip()
|
||||
except Exception:
|
||||
api_key = ''
|
||||
if not api_key:
|
||||
return None
|
||||
vt = VirusTotalPublicApi(api_key)
|
||||
return vt
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@disk_cache_decorator()
|
||||
@@ -204,19 +227,20 @@ def dns_lookup(domain, record='A'):
|
||||
try:
|
||||
import dns
|
||||
import dns.resolver as rs
|
||||
except ModuleNotFoundError:
|
||||
return "module not available"
|
||||
try:
|
||||
# dnspython 2.x prefers resolve(); keep a fallback for older versions.
|
||||
try:
|
||||
result = rs.resolve(domain, record)
|
||||
except AttributeError:
|
||||
result = rs.query(domain, record)
|
||||
return ",".join([x.to_text() for x in result])
|
||||
except dns.resolver.NoAnswer as e:
|
||||
except dns.resolver.NoAnswer:
|
||||
return ""
|
||||
except dns.exception.DNSException as e:
|
||||
except dns.exception.DNSException:
|
||||
# return e.msg
|
||||
return ""
|
||||
except ModuleNotFoundError:
|
||||
return "module not available"
|
||||
|
||||
@disk_cache_decorator()
|
||||
def _asn(ip):
|
||||
@@ -250,14 +274,28 @@ def asn(ip, type="asn"):
|
||||
|
||||
@disk_cache_decorator()
|
||||
def _ipinfo(ip):
|
||||
token = str(getattr(options, 'tke_ipinfo_token', '') or os.getenv('IPINFO_TOKEN') or '')
|
||||
url = 'https://ipinfo.io/{}/json'.format(ip)
|
||||
if token:
|
||||
url = '{}?token={}'.format(url, token)
|
||||
try:
|
||||
import requests
|
||||
import json
|
||||
r = requests.get(url='http://ipinfo.io/{}/json'.format(ip))
|
||||
return r.json()
|
||||
except json.JSONDecodeError as e:
|
||||
return None
|
||||
from plugins.lookupcore import http_get_json
|
||||
|
||||
return http_get_json(url, provider='ipinfo')
|
||||
except ModuleNotFoundError:
|
||||
try:
|
||||
import requests
|
||||
import json
|
||||
|
||||
r = requests.get(url=url, timeout=10)
|
||||
if not r.ok:
|
||||
return None
|
||||
return r.json()
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@functools.lru_cache(maxsize=1000)
|
||||
@@ -284,7 +322,8 @@ def split_number2ip(number):
|
||||
|
||||
@functools.lru_cache(maxsize=1000)
|
||||
def mx_lookup(domain):
|
||||
domain = domain.lstrip("www.")
|
||||
if domain.startswith("www."):
|
||||
domain = domain[4:]
|
||||
try:
|
||||
mxs = dns_lookup(domain, 'MX').split(",")
|
||||
mxt = [x.split(" ")[1] for x in mxs if len(x.split(" ")) == 2]
|
||||
|
||||
Reference in New Issue
Block a user