So here’s the most basic whois server in Python (connects to a PostgreSQL DB for the cached data):
Also, a funny holiday reminder that the NSA is spying on all of us!
limiter.py
#!/usr/bin/python
import ast
import getpass
import hashlib
import os
import random
import re
import socket
import string
import sys
import time
def rstr(size):
outp = ""
for x in range(0, size):
outp += random.choice(string.digits + string.ascii_uppercase + string.ascii_lowercase + "~!@#$%^&*-_=+?")
return outp
fobj = open(sys.argv[1], "r")
conf = ast.literal_eval(fobj.read().strip())
fobj.close()
servport = conf["limiter"]["port"]
password = conf["limiter"]["pass"]
numtimes = conf["limiter"]["conn"]
timeouts = conf["limiter"]["time"]
prepad = rstr(8); pospad = rstr(8); cntrlist = []
addrlist = {}
sockobjc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockobjc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockobjc.bind(("", servport))
sockobjc.listen(1)
while (1):
(sockconn, sockaddr) = sockobjc.accept()
print("conn open:", sockaddr)
try:
sockconn.settimeout(1)
datalist = sockconn.recv(1024).strip().split(" ")
try:
sendflag = 0
scounter = datalist[0].strip()
inetaddr = datalist[1].strip()
authhash = datalist.pop()
sharedsec = (prepad + " " + password + " " + pospad)
temphash = hashlib.sha256(sharedsec + " " + " ".join(datalist) + " " + sharedsec).hexdigest()
if (temphash != authhash):
sockconn.send("0" + " " + prepad + " " + pospad)
sendflag = 1
else:
if (len(cntrlist) >= 65536):
prepad = rstr(8); pospad = rstr(8); cntrlist = []
if (scounter in cntrlist):
sockconn.send("1" + " " + scounter)
sendflag = 1
else:
cntrlist.append(scounter)
if (not inetaddr in addrlist.keys()):
addrlist[inetaddr] = [0, 0]
prestime = time.time()
if ((prestime - addrlist[inetaddr][0]) >= timeouts):
addrlist[inetaddr][0] = prestime
addrlist[inetaddr][1] = 0
print("conn info:", sockaddr, scounter, datalist, addrlist[inetaddr])
if (addrlist[inetaddr][1] < numtimes):
addrlist[inetaddr][1] += 1
sockconn.send("2" + " " + "ok")
sendflag = 1
if (sendflag != 1):
sockconn.send("2" + " " + "no")
except:
pass
except:
pass
try:
print("conn closed:", sockaddr)
sockconn.shutdown(socket.SHUT_RDWR)
sockconn.close()
except:
pass
whoiss.py
#!/usr/bin/python
import ast
import getpass
import hashlib
import os
import psycopg2
import random
import re
import socket
import string
import sys
import time
def sstr(inpt):
outp = ""
yesl = (string.digits + string.ascii_uppercase + string.ascii_lowercase + "-_.")
for chrl in inpt:
if (chrl in yesl):
outp += chrl
return outp
def rnum(size):
random.seed(time.time())
outp = ""
for x in range(0, size):
outp += random.choice(string.digits)
return outp
def lims(connaddr):
global limhost, limport
global limpass, limpre, limpos
x = 0
limresp = ["-1"]
try:
while ((x < 8) and (limresp[0] != "2")):
limnumb = rnum(8)
limobjc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
limobjc.connect((limhost, limport))
limdata = (limnumb + " " + connaddr)
limsec = (limpre + " " + limpass + " " + limpos)
limhash = hashlib.sha256(limsec + " " + limdata + " " + limsec).hexdigest()
limobjc.sendall(limdata + " " + limhash)
limresp = limobjc.recv(1024).strip().split(" ")
limobjc.close()
if (len(limresp) < 1):
limresp = ["-1"]
if (limresp[0] == "0"):
limpre = limresp[1]
limpos = limresp[2]
elif (limresp[0] == "1"):
pass
print("recv limit:", limdata, limresp)
x += 1
except:
pass
return limresp
'''{
"db":{"host":"127.0.0.1", "user":"whois", "pass":"Siohw1!", "name":"dbname", "table":"whois_cache", "col":"text"},
"limiter":{"host":"127.0.0.1", "port":3434, "pass":"Limiter1!", "conn":1, "time":10},
"whois":{"port":4343},
}'''
fobj = open(sys.argv[1], "r")
conf = ast.literal_eval(fobj.read().strip())
fobj.close()
servport = conf["whois"]["port"]
childlist = []
limhost = conf["limiter"]["host"]
limport = conf["limiter"]["port"]
limpass = conf["limiter"]["pass"]
limpre = ""; limpos = ""
sqlshost = conf["db"]["host"]
sqlsuser = conf["db"]["user"]
sqlspass = conf["db"]["pass"]
sqlsdb = conf["db"]["name"]
sqlstbl = conf["db"]["table"]
sqlscol = conf["db"]["col"]
sockobjc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockobjc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockobjc.bind(("", servport))
sockobjc.listen(1)
while (1):
for x in range(0, len(childlist)):
try:
os.waitpid(childlist[x], os.WNOHANG)
except:
childlist[x] = -1
while (-1 in childlist):
childlist.remove(-1)
lims("127.0.0.1")
(sockconn, sockaddr) = sockobjc.accept()
print("conn open:", sockaddr)
try:
childpid = os.fork()
except:
childpid = -1
if (childpid == 0):
resplim = lims(sockaddr[0])
try:
sqldom = sockconn.recv(1024).strip()
sqldom = sstr(sqldom.upper())
connresp = ""
print("conn data:", sockaddr, sqldom)
if ((resplim[0] == "2") and (resplim[1] == "ok")):
try:
conn = psycopg2.connect("host='%s' dbname='%s' user='%s' password='%s'" % (sqlshost, sqlsdb, sqlsuser, sqlspass))
curs = conn.cursor()
curs.execute("SELECT %s FROM %s WHERE name = '%s';" % (sqlscol, sqlstbl, sqldom))
rows = curs.fetchall()
conn.close()
leng = 0
for rowo in rows:
connresp = ("%s\n\n\n" % (rowo[0].strip()))
leng += 1
break
if (leng < 1):
connresp = ("NOT FOUND\n")
except:
connresp = ("SERVER ERROR (1)\n")
else:
connresp = ("LIMIT REACHED\n")
sockconn.send(connresp)
except:
pass
try:
print("conn closed:", sockaddr)
sockconn.shutdown(socket.SHUT_RDWR)
sockconn.close()
except:
pass
sys.exit(0)
else:
try:
sockconn.close()
except:
pass
childlist.append(childpid)
Limiter Notes:
- The shared secret is padded with 2 random strings:
- shared-secret = (pre-pad + common-password + post-pad)
- These 2 strings are re-generated after 2^16 unique nonces are used
- The message data is prefixed with a unique nonce string:
- message-data = (uniq-nonce + message-body)
- The limiter server uses sha256 hashing to authenticate data:
- auth-message = sha256(shared-secret + message-data + shared-secret)
- Can switch to a basic HMAC call if greater security is needed
- The limiter server is a single process which communicates with any connecting clients
- Can switch to a forked-child setup if faster connections are needed
Whois Notes:
- The whois server uses forked-child processes to communicate with multiple clients at the same time
- Each client process will make its own connection to the database server


