diff --git a/README.md b/README.md index 4384434..9c6ef4f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,2 @@ -Entropy: - -Sequence entropy: 4.37 -This is close to the theoretical maximum entropy (log2(21) ≈ 4.39), suggesting the sequence is highly random. \ No newline at end of file +pip uninstall pycrypto +pip install pycryptodome \ No newline at end of file diff --git a/build.py b/build.py index b08bb1c..8753357 100644 --- a/build.py +++ b/build.py @@ -1,4 +1,6 @@ + + @task def format(): """ @@ -28,7 +30,7 @@ def build(): Build the program. Output is pgs. """ format() - system("gcc pgs.c -o pgs -lpython3.12 -I/usr/include/python3.14") + system("PY_SSIZE_T_CLEAN=1 gcc pgs.c -o pgs -lpython3.12 -I/usr/include/python3.14") @task def run(): diff --git a/pgs.c b/pgs.c index 5b9a222..5942b75 100644 --- a/pgs.c +++ b/pgs.c @@ -1,4 +1,6 @@ +#define PY_SSIZE_T_CLEAN 1 #include "py.h" +#include #include #include #include @@ -121,7 +123,7 @@ void close_connection(int epoll_fd, connection_t *conn) { int forward_data(int from_fd, int to_fd) { static char buffer[BUFFER_SIZE]; - // Feels great to do somehow. Better safe than sorry. + // Feels great to do somehow. Better safe than sorry. memset(buffer, 0, BUFFER_SIZE); ssize_t bytes_read = recv(from_fd, buffer, sizeof(buffer), 0); if (bytes_read > 0) { @@ -183,7 +185,7 @@ int main() { struct epoll_event events[MAX_EVENTS]; memset(events, 0, sizeof(events)); - printf("Intercepting load balancer listening on port %d\n", LISTEN_PORT); + printf("Pretty Good Server listening on port %d\n", LISTEN_PORT); connection_t connections[MAX_EVENTS][sizeof(connection_t)] = {0}; while (1) { int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); @@ -219,7 +221,6 @@ int main() { epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event); } else { - // Handle data forwarding for existing connections connection_t *conn = connections[events[i].data.fd]; if (events[i].events & (EPOLLHUP | EPOLLERR)) { printf("Connection closed: client_fd=%d, upstream_fd=%d\n", @@ -231,7 +232,7 @@ int main() { int upstream_fd = py_route(conn->client_fd, conn->upstream_fd); if (upstream_fd == -1) { - close(conn->client_fd); + close_connection(epoll_fd, conn); continue; } set_nonblocking(upstream_fd); @@ -246,8 +247,8 @@ int main() { epoll_ctl(epoll_fd, EPOLL_CTL_ADD, upstream_fd, &upstream_event); - printf("Connected: client_fd=%d, upstream_fd=%d\n", - conn->client_fd, conn->upstream_fd); + printf("Connected: client_fd=%d, upstream_fd=%d\n", conn->client_fd, + conn->upstream_fd); continue; } diff --git a/pgscript.py b/pgscript.py index 83b69f0..c9d41da 100644 --- a/pgscript.py +++ b/pgscript.py @@ -1,5 +1,19 @@ import socket import os +from datetime import datetime +import pathlib +import html +server_start = datetime.now() +import pgs +print(pgs.add(1,2)) + +def get_server_uptime(): + seconds = (datetime.now() - server_start).total_seconds() + if seconds > 60*60: # hour + return f"{int(seconds/60/60)} hours" + elif seconds > 60: # minute + return f"{int(seconds/60)} minutes" + return f"{seconds} seconds" # Hostname, only resolved at startup. hostname = socket.gethostname() @@ -69,20 +83,78 @@ def route(downstream,upstream): u = socket.fromfd(upstream, socket.AF_INET, socket.SOCK_STREAM) #u = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #print("FD:",u.fileno()) - peek = os.read(downstream, 4096) + peek = pgs.read(downstream, 4096).tobytes() - if peek.startswith(b"SSH"): + + if pgs.is_ssh(peek): print("Forwarding to ssh molodetz") u.connect(("molodetz.nl", 22)) - elif is_http(peek): - print("Forwarding to zhurnal") + elif pgs.is_http(peek): + if b'/random' in peek or b'random.' in peek: + + print("Forwarding to 127.0.0.1:3028.") peek = peek.replace(b'/random', b'/') peek = peek.replace(b'random.', b'') u.connect(("127.0.0.1", 3028)) elif b'molodetz.local' in peek: + print("Forwarding to 127.0.0.1:8082.") peek = peek.replace(b'molodetz.local', b'localhost') u.connect(("127.0.0.1", 8082)) + elif b'bench.local' in peek: + print("Responding with bench page.") + body = f"""\n\nBenchmark page.\n\n\n

Bench

\n

{counter}

\n\n\n""".encode() + + s = socket.fromfd(downstream, socket.AF_INET, socket.SOCK_STREAM) + s.sendall( + b'HTTP/1.1 200 Pretty Good Server.\r\n' + + b'Content-Length: ' + str(len(body)).encode() + b'\r\n' + + b'Content-Type: text/html\r\n\r\n' + + body + ) + u = None + s.shutdown(socket.SHUT_RDWR) + else: + # 404 + if env == "prod": + pgs.write(downstream, + b'HTTP/1.1 403 Authorization Required.\r\n\r\n') + else: + pgscript_source = html.escape(pathlib.Path(__file__).read_text()) + content = f"""
+Server: Pretty Good Server
+Environment: {env}
+Total connections: {counter}
+Local hostname: {hostname}
+Downstream FD: {downstream}
+Upstream FD: {upstream}
+Current time server: {datetime.now()}
+Server started on: {server_start}
+Server uptime: {get_server_uptime()}
+
+

Source code of pgscript.py

+Location: {pathlib.Path(__file__).resolve()} +
+{pgscript_source}
+
+ """ + body = f"""\n\nDebug page.\n\n\n

Pretty Good Server

\n

Debugging information

\n

{content}

\n\n\n""" + headers = ["HTTP/1.1 200 Pretty Good Server.", + "Content-Length: {len(body)}", + "Content-Type: text/html", + "" + ] + headers = "\r\n".join(headers) + response = f"{headers}{body}" + + pgs.write(downstream,response) + + # Unset socket so the server will close it. + # Do not disconnect in python! + # Instead of a 404, we also could've displayed a custom page. + # Maybe some server statistics? + u = None + elif is_https(peek) and env == "prod": print("Forwarding to dev.to") u.connect(("devrant.com", 443)) @@ -91,10 +163,11 @@ def route(downstream,upstream): else: # Error. print("Could not find upstream for header content.") - print(b"Closing connection. Your current environment: {env}") + print(f"Closing connection. Your current environment: {env}") # Don't have to close socket, pgs will do that himself. # Socket handling is done at one place to avoid race conditions. - + u = None + if not u: return -1 diff --git a/py.h b/py.h index 4a39b9f..d092c8d 100644 --- a/py.h +++ b/py.h @@ -1,3 +1,5 @@ +#define PY_SSIZE_T_CLEAN 1 +#include "pgs_api.h" #include #include @@ -5,8 +7,37 @@ PyObject *_pModule = NULL; bool python_initialized = false; PyObject *py_construct() { + + PyStatus status; + PyConfig config; if (!python_initialized) { - Py_Initialize(); + if (PyImport_AppendInittab("pgs", PyInit_mymodule) == -1) { + fprintf(stderr, "Failed to add mymodule to the interpreter's table\n"); + return NULL; + } + PyConfig_InitPythonConfig(&config); + + /* optional but recommended */ + status = PyConfig_SetBytesString(&config, &config.program_name, "pgs"); + if (PyStatus_Exception(status)) { + goto exception; + } + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + goto exception; + } + PyConfig_Clear(&config); + // Py_Initialize(); + if (!Py_IsInitialized()) { + fprintf(stderr, "Python initialization failed!\n"); + return NULL; + } + PyGILState_STATE gstate = PyGILState_Ensure(); + + PyRun_SimpleString("import pgs"); + + // Py_InitModule("pgscript", NULL); python_initialized = true; PyObject *sysPath = PySys_GetObject("path"); PyList_Append(sysPath, PyUnicode_FromString(".")); @@ -15,9 +46,14 @@ PyObject *py_construct() { _pModule = PyImport_Import(pName); Py_DECREF(pName); + PyGILState_Release(gstate); python_initialized = true; } return _pModule; +exception: + PyConfig_Clear(&config); + Py_ExitStatusException(status); + return NULL; } void py_destruct() { @@ -36,7 +72,12 @@ int py_route(int downstream, int upstream) { if (PyCallable_Check(pFunc)) { PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(downstream), PyLong_FromLong(upstream)); + + PyGILState_STATE gstate = PyGILState_Ensure(); + PyObject *pValue = PyObject_CallObject(pFunc, pArgs); + PyGILState_Release(gstate); + Py_DECREF(pArgs); if (pValue != NULL) { upstream_fd = PyLong_AsLong(pValue);