From 9d1045b96da2b21ade0636b5b7cb67b7b40473a5 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 9 Sep 2022 16:55:22 +0200 Subject: [PATCH 1/2] Revert "Remove unnecessary decode('ascii')" This reverts commit 411e56964e74a7617652567e41ec3166b2a0cecb. --- wol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wol.py b/wol.py index 835e4aa..b5cbfe6 100755 --- a/wol.py +++ b/wol.py @@ -96,7 +96,7 @@ def handle(self): logging.debug('Connected {}'.format(client)) try: while self.rfile: - hostname = self.rfile.readline().strip() + hostname = self.rfile.readline().decode('ascii').strip() if hostname: logging.info('Request WoL at "{}" from {}'.format(hostname, client)) success = wake(hostname) From 1de2daff3368e9ea2c139d6e7d40c1c12c566e15 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 29 Aug 2022 12:15:58 +0200 Subject: [PATCH 2/2] Add support for /etc/wake-on-lan/special-wakeup.d Server systems, like the big boxes, can't be woken up using traditional wake-on-lan magic packets. For those systems, we plan to use IPMI. The idea is to have an executable file, probably a simple bash script, in /etc/wake-on-lan/special-wakeup.d/ that triggers the IPMI wakeup for the particular host. --- wol.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/wol.py b/wol.py index b5cbfe6..4465059 100755 --- a/wol.py +++ b/wol.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 +from pathlib import Path from pyparsing import * from typing import Dict, Optional import socketserver import argparse import logging import psutil +import stat import socket +import subprocess import sys import os import re @@ -74,9 +77,16 @@ def sendMagicPacket(macAddress: str, iface: str) -> bool: logging.exception('Sending magic packet to {} (on {}) failed'.format(macAddress, iface)) return False +SPECIAL_WAKEUP_DIR = Path("/etc/wake-on-lan/special-wakeup.d") + def wake(hostname: str) -> bool: global hosts - if hostname in hosts: + special_wakeup_file = SPECIAL_WAKEUP_DIR / hostname + if special_wakeup_file.is_file() and os.access(special_wakeup_file, os.X_OK): + with subprocess.Popen(special_wakeup_file) as proc: + returncode = proc.wait() + return returncode == 0 + elif hostname in hosts: logging.info('Waking up {}...'.format(hostname)) host = hosts[hostname] if not 'iface' in host: @@ -89,6 +99,8 @@ def wake(hostname: str) -> bool: logging.warning('Unknown host "{}"'.format(hostname)) return False +LDH_RE = re.compile(r"[0-9A-Za-z\-]+") + class WakeRequestHandler(socketserver.StreamRequestHandler): def handle(self): self.connection.settimeout(6) @@ -97,12 +109,20 @@ def handle(self): try: while self.rfile: hostname = self.rfile.readline().decode('ascii').strip() - if hostname: + if not hostname: + break + + # Check if nostname matches the letter-digits-hyphen rule of DNS. + # This also prevents remote code execution in case hostname is + # e.g., "../../../bin/sh". + if LDH_RE.match(hostname): logging.info('Request WoL at "{}" from {}'.format(hostname, client)) success = wake(hostname) - self.wfile.write(b"success\n" if success else b"failed\n") else: - break + success = False + logging.warning(f"Invalid hostname: {hostname}") + self.wfile.write(b"success\n" if success else b"failed\n") + except socket.timeout: logging.debug('Timeout of {}'.format(client))