Skip to content

Commit

Permalink
Merge pull request #19 from sargunv/python3
Browse files Browse the repository at this point in the history
Migrate to Python3
  • Loading branch information
Kazade authored Dec 26, 2022
2 parents c2f376d + 2903128 commit 4937c80
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 203 deletions.
2 changes: 0 additions & 2 deletions .flake8

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ docs/_build/

# PyBuilder
target/

# Virtualenv
venv/
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# dreampi

A daemon that creates a bridge between a Dreamcast's Dial-up Modem, and the Internet via the Pi

```shell
sudo apt install ./arm/*.deb
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
49 changes: 30 additions & 19 deletions config_server.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
#!/usr/bin/env python3

from __future__ import absolute_import

import json
import threading
import cgi
import os
from typing import Dict, List
from http.server import BaseHTTPRequestHandler, HTTPServer

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from dcnow import CONFIGURATION_FILE, scan_mac_address
from dcnow import CONFIGURATION_FILE, hash_mac_address


class DreamPiConfigurationService(BaseHTTPRequestHandler):
def _get_post_data(self) -> Dict[str, List[str]]:
ctype, pdict = cgi.parse_header(self.headers["content-type"])

def _get_post_data(self):
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
if ctype == 'multipart/form-data':
if ctype == "multipart/form-data":
pdict = {k: v.encode() for k, v in pdict.items()}
postvars = cgi.parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
length = int(self.headers.getheader('content-length'))
postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
elif ctype == "application/x-www-form-urlencoded":
length = int(self.headers["content-length"])
postvars = cgi.parse_qs(
self.rfile.read(length).decode(), keep_blank_values=True
)
else:
postvars = {}

Expand All @@ -32,11 +40,11 @@ def do_GET(self):
with open(CONFIGURATION_FILE, "r") as f:
enabled_state = json.loads(f.read())["enabled"]

self.wfile.write(json.dumps({
"mac_address": scan_mac_address(),
"is_enabled": enabled_state
}))

self.wfile.write(
json.dumps(
{"mac_address": hash_mac_address(), "is_enabled": enabled_state}
).encode()
)

def do_POST(self):
enabled_state = True
Expand All @@ -47,30 +55,33 @@ def do_POST(self):
self.end_headers()

post_data = self._get_post_data()
if 'disable' in post_data:
if "disable" in post_data:
enabled_state = False
else:
enabled_state = True

with open(CONFIGURATION_FILE, "w") as f:
f.write(json.dumps({"enabled": enabled_state}))

self.wfile.write(json.dumps({
"mac_address": scan_mac_address(),
"is_enabled": enabled_state
}))
self.wfile.write(
json.dumps(
{"mac_address": hash_mac_address(), "is_enabled": enabled_state}
).encode()
)


server = None
thread = None


def start():
global server
global thread
server = HTTPServer(('0.0.0.0', 1998), DreamPiConfigurationService)
server = HTTPServer(("0.0.0.0", 1998), DreamPiConfigurationService)
thread = threading.Thread(target=server.serve_forever)
thread.start()


def stop():
global server
global thread
Expand Down
86 changes: 56 additions & 30 deletions dcnow.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
#!/usr/bin/env python
#!/usr/bin/env python3

from __future__ import absolute_import

import threading
import os
import json
import time
import logging
import urllib
import urllib2
import sh

import logging.handlers
import urllib.request
import urllib.parse
import sh # type: ignore - sh module is dynamic
from typing import List, Optional
from hashlib import sha256

from uuid import getnode as get_mac

logger = logging.getLogger('dcnow')
logger = logging.getLogger("dcnow")

API_ROOT = "https://dcnow-2016.appspot.com"
UPDATE_END_POINT = "/api/update/{mac_address}/"
Expand All @@ -23,46 +26,57 @@
CONFIGURATION_FILE = os.path.expanduser("~/.dreampi.json")


def scan_mac_address():
def hash_mac_address():
mac = get_mac()
return sha256(':'.join(("%012X" % mac)[i:i+2] for i in range(0, 12, 2))).hexdigest()
return sha256(
":".join(("%012X" % mac)[i : i + 2] for i in range(0, 12, 2)).encode()
).hexdigest()


class DreamcastNowThread(threading.Thread):
def __init__(self, service):
def __init__(self, service: "DreamcastNowService"):
self._service = service
self._running = True
super(DreamcastNowThread, self).__init__()

def run(self):
def post_update():
if not self._service._enabled:
if not self._service.enabled:
return

lines = [ x for x in sh.tail("/var/log/syslog", "-n", "10", _iter=True) ]
lines: List[str] = list(
sh.tail( # type: ignore - sh has dynamic members
"/var/log/syslog", "-n", "10", _iter=True
)
)
dns_query = None
for line in lines[::-1]:
line: str = line
if "CONNECT" in line and "dreampi" in line:
# Don't seek back past connection
break

if "query[A]" in line:
# We did a DNS lookup, what was it?
remainder = line[line.find("query[A]") + len("query[A]"):].strip()
remainder = line[line.find("query[A]") + len("query[A]") :].strip()
domain = remainder.split(" ", 1)[0].strip()
dns_query = sha256(domain).hexdigest()
dns_query = sha256(domain.encode()).hexdigest()
break

user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT), Dreamcast Now'
header = { 'User-Agent' : user_agent }
mac_address = self._service._mac_address
user_agent = "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT), Dreamcast Now"
header = {"User-Agent": user_agent}
mac_address = self._service.mac_address_hash
data = {}
if dns_query:
data["dns_query"] = dns_query

data = urllib.urlencode(data)
req = urllib2.Request(API_ROOT + UPDATE_END_POINT.format(mac_address=mac_address), data, header)
urllib2.urlopen(req) # Send POST update
data = urllib.parse.urlencode(data).encode()
req = urllib.request.Request(
API_ROOT + UPDATE_END_POINT.format(mac_address=mac_address),
data,
header,
)
urllib.request.urlopen(req) # Send POST update

while self._running:
try:
Expand All @@ -79,17 +93,20 @@ def stop(self):
class DreamcastNowService(object):
def __init__(self):
self._thread = None
self._mac_address = None
self._enabled = True
self._mac_address_hash: Optional[str] = None
self._enabled: bool = True
self.reload_settings()

logger.setLevel(logging.INFO)
handler = logging.handlers.SysLogHandler(address='/dev/log')
logger.addHandler(handler)
syslog_handler = logging.handlers.SysLogHandler(address="/dev/log")
syslog_handler.setFormatter(
logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
)
logger.addHandler(syslog_handler)

def update_mac_address(self, dreamcast_ip):
self._mac_address = scan_mac_address()
logger.info("MAC address: {}".format(self._mac_address))
def update_mac_address(self):
self._mac_address_hash = hash_mac_address()
logger.info("MAC address: {}".format(self._mac_address_hash))

def reload_settings(self):
settings_file = CONFIGURATION_FILE
Expand All @@ -99,14 +116,23 @@ def reload_settings(self):
content = json.loads(settings.read())
self._enabled = content["enabled"]

def go_online(self, dreamcast_ip):
def go_online(self):
if not self._enabled:
return

self.update_mac_address(dreamcast_ip)
self.update_mac_address()
self._thread = DreamcastNowThread(self)
self._thread.start()

def go_offline(self):
self._thread.stop()
self._thread = None
if self._thread is not None:
self._thread.stop()
self._thread = None

@property
def enabled(self):
return self._enabled

@property
def mac_address_hash(self):
return self._mac_address_hash
Loading

0 comments on commit 4937c80

Please sign in to comment.