Untitled

 avatar
unknown
python
a year ago
9.5 kB
4
Indexable
#!/usr/bin/python3

import sys,re,os
import struct,random,math,itertools
import html
import requests
from datetime import date
import time

from bottle import route, run, get, post, request, response, abort, auth_basic

# an ugly spell to get unbuffer stdout on python3
class Unbuffered(object):
   def __init__(self, stream):
       self.stream = stream
   def write(self, data):
       self.stream.write(data)
       self.stream.flush()
   def writelines(self, datas):
       self.stream.writelines(datas)
       self.stream.flush()
   def __getattr__(self, attr):
       return getattr(self.stream, attr)
sys.stdout = Unbuffered(sys.stdout)

if len(sys.argv)<2:
    print("BookHook web server")
    print("Syntax: %s <port> [group]" % sys.argv[0])
    sys.exit(1)

port = int(sys.argv[1])
group = None
if len(sys.argv)>=3:
    group = int(sys.argv[2])

logfile = "log.txt"

target_url_file = "target_url.dat"
if group:
    target_url_file = f"target_url{group}.dat"

def get_target_url():
    try:
        with open(target_url_file) as f:
            r = f.read().strip()
    except FileNotFoundError:
        set_target_url("")
        return ""
    return r

def set_target_url(url):
    with open(target_url_file,"w") as f:
        f.write(url.strip())

failure_mode = None
failure_password = "dogs"

general_user = "admin"
general_pass = "ECE$%*"


# Define a function to authenticate users
def check_auth(username, password):
    # Replace this with your own authentication logic
    if username == general_user and password == general_pass:
        return True
    return False

def iff(c,a,b): 
    if c: return a
    else: return b
    
def get_background():
    return f"./static/bg{iff(group,group,0)}.gif"
    
def get_title():
    r = "Hypothetical Books: BookHook Manager"
    if group:
        r += f" - GROUP {group}"
    return r
    
@get('/fail')
@post('/fail')
@auth_basic(check_auth)
def fail():
    global failure_mode
    r = f"<html><body style='background: url({get_background()});'>\n"
    r += f"<h1>{get_title()} - FAILURE SIMULATOR</h1>\n"
    if request.method == 'POST':
        new_failure_mode = request.forms.get('f')
        given_password = request.forms.get('p')
        if new_failure_mode=="None": new_failure_mode = None
        if given_password == failure_password:
            failure_mode = new_failure_mode
        else:
            r += "<div style='color:red; background-color: yellow;'>DON'T HACK MY WEBS!!!!!</div>\n"
    else:
        given_password = ''
    r += f"failure_mode = {failure_mode}<p>"

    r += f"""
<form action='/fail' method=post>
Password: <input type=password name=p value='{given_password}'><p>
<input type=submit name=f value="None">
<input type=submit name=f value="changedxml">
<input type=submit name=f value="wrongclosetag">
<input type=submit name=f value="nodate">
<input type=submit name=f value="badxml">
<input type=submit name=f value="blank">
<input type=submit name=f value="get">
<input type=submit name=f value="chargen">
<input type=submit name=f value="slow10sec">
<input type=submit name=f value="slow10min">
<p>
</form>
<a href="/">Front page</a>
"""
    r+= "</body></html>"
    return r

@get('/')
@get('/fancy')
@auth_basic(check_auth)
def root():
    is_fancy = 'fancy' in request.url

    r = """<html><body>

<style>
#map {
    width: 600px;
    height: 400px;
    border: 1px #000 solid;
}
body {
"""
    r += f"background-image: url({get_background()});"
    r += """
}
</style>

"""
    r += f"<h1>{get_title()}</h1>\n"
    r += """
Connects to our POS systems over RS-232 and turns sales into XML hooks! By Ronnie Hypothetical, copyright 2002
<p>"""

    r += f"""
    <H2>Update target URL</h2>
    <form method=post action=/settarget>
    Target URL: <input type=text style="width:80%;" name=target value="{get_target_url()}"><br>
    <input type=submit>
    </form>
"""

    r += """
    <H2>Simulate purchase</h2>
    This will send an XML post to the target URL describing the purchase below for the purposes of testing.
    <p><i>Meta-note: In our case, this will be the ONLY posts sent to your system, since we don't actually have cash registers selling books in real life.</i>
    <form method=post action=/simulate name=simulate>
    Date: <input type=text name=date> (Format must by YYYY-MM-DD, blank defaults to today)<br>
    <table><tr><th>ISBN<th>Qty<th>Unit price</tr>
    """
    for i in range(iff(is_fancy,15,3)):
        r += f"<tr><td><input type=text name=isbn{i}><td><input type=text name=qty{i}><td><input type=text name=price{i}>\n"
    r+= """
    </table>
<Script>
function definputs() {
    document.querySelector('input[name="date"]').value = "2002-09-17";
    document.querySelector('input[name="isbn0"]').value = "0345409469";
    document.querySelector('input[name="qty0"]').value = 1;
    document.querySelector('input[name="price0"]').value = 7.99;
    document.querySelector('input[name="isbn1"]').value = "978-0345376596";
    document.querySelector('input[name="qty1"]').value = 2;
    document.querySelector('input[name="price1"]').value = 9.99;
    
}
</script>
    [
    <a href="#" onclick="definputs();">Fill with dummy info</a> &middot; 
    <a href="#" onclick="document.forms['simulate'].reset();">Clear</a>
    ]<bR><br>
    <input type=submit value="Simulate purchase">
    </form>
"""

    r+= """
    <h2>Info for future people</h2>
    This posts the sale as XML to the target URL. The <a href="/static/schema.xml">schema is here</a>. XML is the future!
    """

    r += "\n\n<!-- <img src='static/IAMCOOL.jpg'>A picture of me! --></body></html>\n"
    return r

@post('/settarget')
@auth_basic(check_auth)
def req_set_target():
    new_target = request.forms.get('target')
    set_target_url(new_target)
    
    r = '<html><body>ok. redirecting...<meta http-equiv="refresh" content="2; url=/"></body></html>'
    
    return r

# wraps a string in a file-like object that can have its reads delayed by a given number of seconds
class SlowStringFile(object):
    def __init__(self, content, delay):
        self.content = content
        self.delay = delay
        
    def read(self, size=-1):
        time.sleep(self.delay)
        if size==-1:
            result = self.content
            self.content = ''
        else:
            result = self.content[:size]
            self.content = self.content[size:]
        return result
        
    def __iter__(self):
        return self

    def __next__(self):
        data = self.read()
        if data:
            return data
        else:
            raise StopIteration

# a file-like object that generates random binary content
class CharGen(object):
    def __init__(self):
        pass
        
    def read(self, size=1024*1024):
        return os.urandom(size)
        
    def __iter__(self):
        return self

    def __next__(self):
        data = self.read()
        if data:
            return data
        else:
            raise StopIteration


@post('/simulate') 
@auth_basic(check_auth)
def req_simulate():
    i = 0
    sale_date = request.forms.get("date","").strip()
    if sale_date=="":
        sale_date = date.today().strftime("%Y-%m-%d")
    if failure_mode == 'nodate':
        xml = "<sale>\n"
    else:
        xml = f'<sale date="{sale_date}">\n'
    if failure_mode == 'badxml':
        xml += "/>\n"
    while request.forms.get(f"isbn{i}","").strip() != "":
        xml += "<item>"
        if failure_mode == 'changedxml': # just rearranged and spaced, still valid
            xml += "  <price>" + request.forms.get(f"price{i}","").strip() + "</price>\n"
            xml += "\t<qty>" + request.forms.get(f"qty{i}","").strip() + "</qty>\n\n"
            xml += "    <isbn>" + request.forms.get(f"isbn{i}","").strip() + "</isbn>"
        else:
            xml += "<isbn>" + request.forms.get(f"isbn{i}","").strip() + "</isbn>"
            xml += "<qty>" + request.forms.get(f"qty{i}","").strip() + "</qty>"
            xml += "<price>" + request.forms.get(f"price{i}","").strip() + "</price>"
        xml += "</item>\n"
        i += 1
    if failure_mode == 'wrongclosetag':
        xml += "</sales>\n"
    else:
        xml += "</sale>\n"
        
    if failure_mode=="blank":
        xml = ""
    
    url = get_target_url()
    
    r = "<html><body><style>pre { margin-left: 20px; background-color:#ddd; padding: 5px;}</style>\n"
    
    r += f"I will post the following to <u>{url}</u>: <pre>{html.escape(xml)}</pre>\n"
    
    if failure_mode=="slow10sec":
        xml = SlowStringFile(xml.encode('utf-8'),10)
    if failure_mode=="slow10min":
        xml = SlowStringFile(xml.encode('utf-8'),10*60)
    if failure_mode=='chargen':
        xml = CharGen()
    
    r += "<hr><p>Issuing request..."
    if failure_mode=="get":
        response = requests.get(url, data=xml, headers={'Content-Type': 'application/xml'})
    else:
        response = requests.post(url, data=xml, headers={'Content-Type': 'application/xml'})
    
    r += f"Got status code <u>{response.status_code}</u> and this response body:<pre>{html.escape(response.text)}</pre>\n"
    
    r += "<hr><a href=/>Go back</a>\n"
    
    r += "</body></html>"
    
    return r
        
from bottle import static_file
@route('/static/<filename>')
@auth_basic(check_auth)
def server_static(filename):
    return static_file(filename, root='./static/')

run(host='0.0.0.0', port=port, debug=True, server='paste') # install paste with: sudo apt install python3-paste
Leave a Comment