#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Simple CMS # # Copyright (C) 2011-2024 Michael Büsch # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys, os, fcntl, socket, time, pathlib, argparse def get_backsock(rundir): notify_socket = os.getenv("NOTIFY_SOCKET") if notify_socket: listen_fds = os.getenv("LISTEN_FDS") if not listen_fds: raise ValueError("env: LISTEN_FDS not set.") listen_fds = int(listen_fds, 10) if listen_fds <= 0 or listen_fds > 1: raise ValueError("env: Invalid LISTEN_FDS.") with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sdsock: sdsock.connect(notify_socket) fd = 3 for i in range(fd, fd + listen_fds): fcntl.fcntl(i, fcntl.F_SETFD, fcntl.FD_CLOEXEC) backsock = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM) sdsock.sendall(b"READY=1") return backsock else: backsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) backsock.bind(str(rundir / "cms-backd.sock")) backsock.listen() return backsock def run_backd(backsock, rundir): debug = False while True: try: conn, addr = backsock.accept() if debug: startStamp = time.monotonic() msg = recv_message(conn, MAGIC_BACK) except Exception as e: print(f"cms-backd: Receive failed: {e}", file=sys.stderr) time.sleep(0.1) continue status = 200 # Ok reply_body = b"" reply_mime = "" extra_headers = [] cms = CMS(domain="", rundir=rundir, debug=debug) try: if isinstance(msg, MsgGet): cms.domain = msg.host protocol = "https" if msg.https else "http" reply_body, reply_mime = cms.get(msg.path, msg.query, protocol) if "html" in reply_mime: extra_headers.append("Cache-Control: max-age=10") elif reply_mime.startswith("image/"): extra_headers.append("Cache-Control: max-age=3600") elif reply_mime.startswith("text/css"): extra_headers.append("Cache-Control: max-age=600") elif isinstance(msg, MsgPost): cms.domain = msg.host protocol = "https" if msg.https else "http" reply_body, reply_mime = cms.post(msg.path, msg.query, msg.body, msg.body_mime, protocol) extra_headers.append("Cache-Control: no-cache") else: raise RuntimeError("Received invalid message.") except CMSException as e: status = e.httpStatusCode reply_body, reply_mime, extra_headers = cms.getErrorPage(e, protocol) extra_headers.append("Cache-Control: no-store") if debug: delta = time.monotonic() - startStamp us = round(delta * 1e6) extra_headers.append(f"X-CMS-Py-Walltime: {us} us") reply = MsgReply( status=status, body=reply_body, mime=reply_mime, extra_headers=extra_headers, ) reply_data = reply.pack() try: conn.sendall(reply_data) except Exception as e: print(f"cms-backd: Failed to send reply: {e}", file=sys.stderr) continue if __name__ == "__main__": p = argparse.ArgumentParser() p.add_argument("--rundir", type=pathlib.Path, default=pathlib.Path("/run")) p.add_argument("--pythonpath", type=pathlib.Path, default=pathlib.Path("/opt/cms/lib/python3/site-packages")) p.add_argument("--no-cython", action="store_true") args = p.parse_args() sys.path.insert(0, str(args.pythonpath)) imported = False if not args.no_cython: try: from cms_cython.socket import * from cms_cython import CMS, CMSException imported = True except ImportError as e: print("cms-backd: Failed to import cms_cython:", e, file=sys.stderr) if not imported: from cms.socket import * from cms import CMS, CMSException backsock = get_backsock(args.rundir) run_backd(backsock, args.rundir) # vim: ts=4 sw=4 expandtab