219 lines
6.6 KiB
Python
219 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
## This Blog post is executable python code, it requires the gevent modules to run
|
|
# pip as gevent, Debian as python3-gevent.
|
|
|
|
import os
|
|
import sys
|
|
import gevent
|
|
import gevent.socket as socket
|
|
import signal
|
|
|
|
|
|
# A long time ago circa 2010ish when i was taking the network programing class in
|
|
# college there was a blog post that explained in great detail,
|
|
# how to "fork into the background" e.g make a daemon on a Unix
|
|
# system. It was called Demystifing Daemons. By some guy on Blogspot,
|
|
# I think they went by Tom. I can no longer find this post.
|
|
# I get back more and more AI slop every time I look.
|
|
# This trick is not something i see often anymore.
|
|
# Therefore let's recreate a classic.
|
|
|
|
|
|
# Sometimes programs need to run unattended, without tying up
|
|
# a terminal session. This is less of a problem now today
|
|
# then 30 years ago with our new fangled terminal multiplexers
|
|
# and the like. But i find that there's value in learning archane
|
|
# knowledge. Unix has had the concept of daemons since AT&T version 7
|
|
# I think, but I'm finding that a decreasing number of people are
|
|
# familiar with this once basic knowledge.
|
|
|
|
# To that end this post will show how to create a Disk and Execution Monitor
|
|
# a daemon, also called a service by the systemd kids.
|
|
|
|
# In order to have a service we must first have something to serve.
|
|
# Here's one of my favorite prank services. RickRoll over http
|
|
|
|
# you know it, you love it, It's a valid HTTP response
|
|
|
|
RICKROLL_LYRICS = """
|
|
HTTP/1.1 200 OK
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Last-Modified: Mon, 27 July 1987 00:00 GMT
|
|
Content-Length: 982
|
|
|
|
|
|
We're no strangers to love
|
|
You know the rules and so do I
|
|
A full commitment's what I'm thinkin' of
|
|
You wouldn't get this from any other guy
|
|
|
|
I just wanna tell you how I'm feeling
|
|
Gotta make you understand
|
|
|
|
Never gonna give you up, never gonna let you down
|
|
Never gonna run around and desert you
|
|
Never gonna make you cry, never gonna say goodbye
|
|
Never gonna tell a lie and hurt you
|
|
|
|
We've known each other for so long
|
|
Your heart's been aching, but you're too shy to say it
|
|
Inside, we both know what's been going on
|
|
We know the game and we're gonna play it
|
|
|
|
And if you ask me how I'm feeling
|
|
Don't tell me you're too blind to see
|
|
|
|
Never gonna give you up, never gonna let you down
|
|
Never gonna run around and desert you
|
|
Never gonna make you cry, never gonna say goodbye
|
|
Never gonna tell a lie and hurt you
|
|
|
|
Never gonna give you up, never gonna let you down
|
|
Never gonna run around and desert you
|
|
Never gonna make you cry, never gonna say goodbye
|
|
Never gonna tell a lie and hurt you
|
|
|
|
"""
|
|
|
|
OTHER_RESP = """
|
|
HTTP/1.1 403 Forbidden
|
|
Date: Fri, 01 Jan 1988 12:56:49 GMT
|
|
Content-Type: application/json
|
|
Content-Length: 110
|
|
|
|
{
|
|
"error": "TogetherForever",
|
|
"message": "Together forever and never to part Together forever we two ."
|
|
}
|
|
|
|
"""
|
|
client_procs = []
|
|
|
|
svr_proc = None
|
|
|
|
|
|
# more on this later
|
|
class NullDevice:
|
|
def write(self, s):
|
|
pass
|
|
|
|
|
|
# See ln 104 and following
|
|
def hup_handle(sig, fr):
|
|
sys.exit()
|
|
|
|
|
|
# just standard Unix Network Programing Stuff
|
|
# Only interesting bit is if using INET6 in python at least
|
|
# IPV4 comes for free so no need to use AF_UNSPEC.
|
|
# See Refs 1 and 2 for more info
|
|
|
|
# By the way i hate python 3's concurrency stuff so we'll be using
|
|
# gevent.
|
|
|
|
|
|
def server_handler():
|
|
serversock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
|
serversock.bind(("", 1337))
|
|
serversock.listen(10)
|
|
while True:
|
|
client, addr = serversock.accept()
|
|
print(addr)
|
|
client_procs.append(gevent.spawn(client_handler, client))
|
|
gevent.sleep(0.25)
|
|
|
|
serversock.close()
|
|
return
|
|
|
|
|
|
# This is a simple infinite rickroll http server
|
|
# it only responds to GET requests. and no matter what
|
|
# will give you a rickroll back.
|
|
#
|
|
# This is somewhat useful in elfing with ai scraper bots.
|
|
# There are better methods if you wanna try that
|
|
|
|
# this is done with a bit of defensive programing
|
|
# to ensure a client can't cause us not to release control
|
|
# of the greenlet.
|
|
|
|
# gevent is a cooperative concurrency model.
|
|
# it multiplexs tasks on to one os thread
|
|
# each task is done inside a "greenlet".abs
|
|
# Which is like a thread except that it is
|
|
# the programers responsibility to handle scheduling
|
|
# By putting the greenlet to sleep when that's possible
|
|
|
|
|
|
def client_handler(sock):
|
|
print("Client handler spawn")
|
|
junk_counter = 0
|
|
while True:
|
|
if junk_counter > 3:
|
|
sock.close()
|
|
return
|
|
data = sock.recv(4096)
|
|
dstring = data.decode("UTF-8")
|
|
if dstring.startswith("GET"):
|
|
break
|
|
else:
|
|
sock.send(OTHER_RESP.encode("utf-8"))
|
|
junk_counter += 1
|
|
gevent.sleep(0.25)
|
|
|
|
sock.send(RICKROLL_LYRICS.encode("utf-8"))
|
|
sock.close()
|
|
return
|
|
|
|
|
|
def daemon_main():
|
|
svr_proc = gevent.spawn(server_handler)
|
|
client_procs.append(svr_proc)
|
|
gevent.joinall(client_procs)
|
|
sys.exit(0)
|
|
|
|
|
|
pid = os.fork() # Hmmm this looks an awful lot like... C
|
|
# Yes it does per C fork(3) creates a nearly identical copy of the
|
|
# calling process as a child of the calling process
|
|
# returning it's pid to the caller. Asexual reproduction at it's finest
|
|
|
|
if pid:
|
|
os._exit(0)
|
|
# exit without running exit handlers, that might cause race condition
|
|
# in the child
|
|
|
|
|
|
# Per the fork manual the child begins execution at the point where fork
|
|
# is called, as if the child had called it. The only difference being
|
|
# is the child process gets a zero as return value, and so the else branch
|
|
# of this if is followed.
|
|
else:
|
|
# It turns out my CS prof lied about the purpose of these two calls
|
|
# the child process needs to be the process group leader, when parent
|
|
# exits or it gets reaped by the init system
|
|
os.setpgrp()
|
|
os.umask(0)
|
|
|
|
print(os.getpid()) # to aid in stopping the server
|
|
# We want to close our connection to the controlling terminal
|
|
# to avoid accedentially spamming the use. And causing interactive processes
|
|
# to be SIGSTOP'ed. I do this with a Null Device class.
|
|
# You could just as easily do some sort of logging thing.
|
|
sys.stdin.close()
|
|
sys.stdout = NullDevice()
|
|
sys.stderr = NullDevice()
|
|
|
|
# The last thing we do before handing things off to the daemon's main
|
|
# function is set up the daemon's signal table how we want it
|
|
# fork, may have initialized it with the default handlers
|
|
# depending on implementation
|
|
signal.signal(signal.SIGHUP, hup_handle)
|
|
signal.signal(signal.SIGTERM, hup_handle)
|
|
daemon_main()
|
|
|
|
|
|
# References
|
|
# 1. Beej's guide to Network Programing https://beej.us/guide/bgnet/
|
|
# 2. Foundations of Python Network Programming 2ed Rhodes and Goerzen
|