· 5 years ago · May 29, 2020, 05:06 PM
1#!/usr/bin/env python3
2# Script created by Professor Possum.
3# Used for executing the doomsday device serice or disabling it.
4# also used to configure the authed/config.yaml for the doomsday device.
5# If authed/config.yaml is modified at all, the sepperate background doomsdaydevice python service
6# restarts and reloads then uses yaml.load(open("/home/doomsdaydevice/authed/config.yaml","r").read(), Loader=yaml.Loader)
7# Additionally, a reporting system is used to notify Professor Possum of any issues.
8# Professor possum views all messages in his browser at /admin nearly every minute cause he's that paranoid.
9# Disabling the doomsday device in the web interface only temporarily shuts it down.
10# To completely destroy the doomsday device, you must run /usr/bin/selfdestruct
11# WHich you DONT WANT TO DO EVER!
12from flask import Flask, render_template, make_response, request, redirect, url_for, send_from_directory, Response, jsonify
13import os
14import random
15import threading
16import time
17import re
18import linecache
19import sys
20import jwt
21import base64
22import sqlite4
23import ast
24from base64 import b64decode, b64encode
25import urllib.parse
26import datetime
27import json
28
29app = Flask(__name__, static_folder='static', static_url_path='')
30app.debug = True
31tcpport = 8080
32SECRET_KEY = "foobar"
33LOGINHTML = open('authed/login.html','r').read()
34HOMEHTML = open('authed/home.html','r').read()
35ADMINHTML = open('authed/admin.html','r').read()
36
37def query(cmd, values=False, commit=False, multiple=False, db_name="doomsdaydevice_users.db"):
38 try:
39 y=None
40 conn = sqlite3.connect(db_name)
41 c = conn.cursor()
42 if values:
43 c.execute(cmd, values)
44 else:
45 if multiple:
46 c.executescript(cmd)
47 else:
48 c.execute(cmd)
49 if not commit:
50 y = (c.fetchall())
51 conn.commit()
52 conn.close()
53 return y
54 except Exception as e:
55 return "ERROR IN YOUR SQLITE SYNTAX. {}. {}".format(cmd,str(e))
56 try:
57 conn.close()
58 except:
59 pass
60
61def PrintException(gimme_it=False):
62 exc_type, exc_obj, tb = sys.exc_info()
63 f = tb.tb_frame
64 lineno = tb.tb_lineno
65 filename = f.f_code.co_filename
66 linecache.checkcache(filename)
67 line = linecache.getline(filename, lineno, f.f_globals)
68 msg = 'EXCEPTION IN ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj)
69 if gimme_it:
70 return msg
71 print(msg)
72
73def decode_cookie(request):
74 try:
75 return jwt.decode(request.cookies['session'], SECRET_KEY, algorithms=['HS256'])
76 except:
77 return False
78
79def encode_cookie(session):
80 return jwt.encode(session, SECRET_KEY, algorithm='HS256')
81
82def json_cookie_response(dictionary, session):
83 res = make_response( jsonify(dictionary) )
84 res.set_cookie('session', encode_cookie(session))
85 return res
86
87@app.route('/')
88def index():
89 page = LOGINHTML
90 if 'session' in request.cookies:
91 session = decode_cookie(request)
92 if session:
93 page = HOMEHTML
94 resp = Response(page)
95 resp.headers['Cache-Control'] = "no-cache, no-store, must-revalidate"
96 resp.headers['Pragma'] = "no-cache"
97 resp.headers['Expires'] = "0"
98 return resp
99
100@app.route('/admin')
101def admin():
102 if 'session' in request.cookies:
103 session = decode_cookie(request)
104 if session and session['isadmin']:
105 return ADMINHTML
106 return '401 - Unauthorized', 401
107
108@app.route('/api/<path:path>', methods=['POST','GET'])
109def api(path):
110 try:
111 #split the raw unecoded sections of uri into a list of decoded strings
112 path = [urllib.parse.unquote(x) for x in list(filter(None, request.environ['RAW_URI'].split('/')))[1:]]
113 if bool(path):
114 # Check Login Information
115 if path[0].lower() == 'login' and 'username' in request.form and 'password' in request.form:
116 statement = "SELECT * FROM DEVICEUSERS WHERE username = '{}' AND password = '{}'".format(request.form["username"], request.form["password"])
117 result = query(statement)
118 if bool(result):
119 if 'ERROR' in result:
120 return jsonify({"request":False,"data":result})
121 else:
122 sid = str(random.random()).split('.')[1]+str(random.random()).split('.')[1]
123 return json_cookie_response({"request":True,"data":"Login Success!"}, {'username':result[0][0],"sid":sid,"isadmin":ast.literal_eval(result[0][2])})
124 return jsonify({"request":False,"data":"Incorrect username or password combination!"})
125 elif path[0].lower() == 'logout':
126 # Set cookie to expire to logout user
127 response = make_response(redirect('/'))
128 expires = datetime.datetime.utcnow() - datetime.timedelta(days=30)
129 response.set_cookie(key="session", value='false', expires=expires.strftime("%a, %d %b %Y %H:%M:%S GMT"))
130 return response
131 elif 'session' in request.cookies:
132 session = decode_cookie(request)
133 if session:
134 if path[0].lower() == 'file' and path[1].lower() in ['read','write'] and len(path) == 3 and path[2]:
135 if path[1].lower() == 'read':
136 if os.path.isfile(path[2]):
137 try:
138 # Send file contents if file exists
139 return jsonify({"request":True,"data":open(path[2],'r').read()})
140 except Exception as e:
141 return jsonify({"request":False,"data":str(e)})
142 elif os.path.isfile(path[2]) and request.method == "POST" and 'text' in request.form:
143 # Only admins should need to update files for security purposes!
144 if not session["isadmin"]:
145 return jsonify({"request":False,"data":"Only Admins May Update Files!"})
146 try:
147 tf = open(path[2],'w')
148 tf.write(request.form['text'])
149 tf.close()
150 return jsonify({"request":True,"data":'File {} updated!'.format(path[2])})
151 except Exception as e:
152 return jsonify({"request":False,"data":str(e)})
153 elif path[0].lower() == 'disable':
154 return jsonify({"request":True,"data":"5dd45b58b5468b566d67ffdd0292287e.png"})
155 elif path[0].lower() == 'message':
156 # Regular users can send a message which we store temporarily to a json file
157 if len(path) == 2 and path[1].lower() == 'send' and request.method == "POST" and 'message' in request.form and 'email' in request.form and 'name' in request.form:
158 fn = str(random.random()).split('.')[1]+str(random.random()).split('.')[1]
159 tf = open('messages/{}.json'.format(fn), 'w')
160 # Messages really shouldnt be that long so we limit name+email to 30 chars and message to 3000 chars.
161 json.dump({'name':request.form["name"][:30],'email':request.form["email"][:30],'message':request.form["message"][:3000]}, tf)
162 tf.close()
163 return jsonify({"request":True,"data":'Message has been sent. Please allow up to a minute for Prof P to view it.'})
164 # Only admins can list the messages from the /admin panel
165 elif len(path) == 2 and path[1].lower() == 'list' and session["isadmin"]:
166 return jsonify({"request":True,"data": [x.split('.')[0] for x in os.listdir('messages/') if x.endswith('.json')] })
167 # Only admins can view the messages from the /admin panel
168 elif len(path) == 3 and path[1].lower() == 'view' and path[2].isdigit() and session["isadmin"]:
169 fn = 'messages/{}.json'.format(path[2])
170 tmp = json.loads(open(fn, 'r').read())
171 # Delete file after retrieving its contents.
172 os.remove( fn )
173 return jsonify({"request":True,"data":tmp})
174 return jsonify({"request":False,"data":"Unknown Request"})
175 return '401 - Unauthorized', 401
176 return '404 - Unknown API path', 404
177 except:
178 PrintException()
179 return '500 - Server Error', 500
180
181@app.route('/<path:path>')
182def send_static(path):
183 return send_from_directory('static', path)
184
185if __name__ == '__main__':
186 app.run("0.0.0.0", tcpport, threaded=True)