diff --git a/checkmyip.py b/checkmyip.py index 610bfeb..c7d7a1c 100644 --- a/checkmyip.py +++ b/checkmyip.py @@ -1,10 +1,11 @@ -#!/usr/bin/python +#!/usr/local/bin/python3.9 -##### CheckMyIP Server ##### -##### Written by John W Kerns ##### -##### http://blog.packetsar.com ##### -##### https://github.com/packetsar/checkmyip ##### +##### BSD Cafe CheckMyIP Server ##### +##### Originally written by John W Kerns ##### +##### http://blog.packetsar.com ##### +##### Forked to BSD Cafe by Stefano Marinelli ##### +##### https://brew.bsd.cafe/BSDCafe/checkmyip ##### ##### Inform version here ##### @@ -37,18 +38,18 @@ j2send = """{ "port": "{{ port }}", "protocol": "{{ proto }}", "version": "%s", -"website": "https://github.com/packetsar/checkmyip", -"sponsor": "Sponsored by ConvergeOne, https://www.convergeone.com/" +"website": "https://brew.bsd.cafe/BSDCafe/checkmyip", +"sponsor": "Served by BSD Cafe, https://bsd.cafe/" }""" % version ##### Handles all prnting to console and logging to the logfile ##### class log_management: def __init__(self): - self.logpath = "/etc/checkmyip/" # Log file directory path - self.logfile = "/etc/checkmyip/%scheckmyip.log" % \ + self.logpath = "/var/log/checkmyip/" # Log file directory path + self.logfile = "/var/log/checkmyip/%scheckmyip.log" % \ time.strftime("%Y-%m-%d_") # Log file full path - self.paramikolog = "/etc/checkmyip/%sssh.log" % \ + self.paramikolog = "/var/log/checkmyip/%sssh.log" % \ time.strftime("%Y-%m-%d_") # SSH log file path self.thread = threading.Thread(target=self._change_logfiles) self.thread.daemon = True @@ -89,9 +90,9 @@ class log_management: def _change_logfiles(self, thread=True): while True: time.sleep(10) - self.logfile = "/etc/checkmyip/%scheckmyip.log" % \ + self.logfile = "/var/log/checkmyip/%scheckmyip.log" % \ time.strftime("%Y-%m-%d_") # Log file full path - self.paramikolog = "/etc/checkmyip/%sssh.log" % \ + self.paramikolog = "/var/log/checkmyip/%sssh.log" % \ time.strftime("%Y-%m-%d_") # SSH log file path paramiko.util.log_to_file(self.paramikolog) @@ -148,14 +149,14 @@ def j2format(j2tmp, valdict): ##### Cleans IP addresses coming from socket library ##### def cleanip(addr): - ip = addr[0] - port = addr[1] - family = "ipv6" - if len(ip) > 6: # If this IP is not a super short v6 address - if ip[:7] == "::ffff:": # If this is a prefixed IPv4 address - ip = ip.replace("::ffff:", "") # Return the cleaned IP - family = "ipv4" - return (ip, port, family) # Return the uncleaned IP if not matched + ip, port = addr[:2] + family = "ipv6" # Default to IPv6 + if ip.startswith("::ffff:"): # Check if this is a prefixed IPv4 address + ip = ip.replace("::ffff:", "") # Clean the IP + family = "ipv4" + elif ":" not in ip: # Simple check to recognize IPv4 + family = "ipv4" + return ip, port, family # Return cleaned IP and family ##### TCP listener methods. Gets used once for each listening port ##### @@ -164,16 +165,18 @@ def listener(port, talker): listen_port = port buffer_size = 1024 while True: - sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) # v6 family + # Use AF_INET6 but also handle IPv4 connections + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) # Allow IPv4-mapped IPv6 addresses sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((listen_ip, listen_port)) sock.listen(buffer_size) - client, addr = sock.accept() - ip, port, family = cleanip(addr) # Get all cleaned IP info - valdict = {"ip": ip, "port": port, "family": family} # Put in dict - thread = threading.Thread(target=talker, args=(client, valdict)) - thread.start() # Start talker thread to listen to port - + while True: + client, addr = sock.accept() + ip, port, family = cleanip(addr) # Get cleaned IP info including family + valdict = {"ip": ip, "port": port, "family": family, "proto": ""} + thread = threading.Thread(target=talker, args=(client, valdict)) + thread.start() # Start talker thread to listen to port ##### Telnet responder method. Is run in own thread for each telnet query ##### def telnet_talker(client, valdict, proto="telnet"): @@ -209,35 +212,47 @@ def ssh_talker(client, valdict, proto="ssh"): ##### HTTP responder method. Gets run in own thread for each HTTP query ##### ##### Automatically detects if client is a browser or a telnet client ##### +##### HTTP responder method. Gets run in own thread for each HTTP query ##### def http_talker(client, valdict, proto="http"): - time.sleep(.1) # Sleep to allow the client to send some data - client.setblocking(0) # Set the socket recv as non-blocking - browser = False # Is the client using a browser? - try: # client.recv() will raise an error if the buffer is empty - data = client.recv(2048) # Recieve data from the buffer (if any) - print(data) # Print to stdout - browser = True # Set client browser to True - except: # If buffer was empty, then like a telnet client on TCP80 - browser = False # Set client browser to False - if not browser: # If the client is not a browser - telnet_talker(client, valdict, "http-telnet") # Hand to telnet_talker - else: # If client is a browser - # Proceed with standard HTTP response (with headers) - valdict.update({"proto": proto}) - log(j2format(j2log, valdict)) - response_body_raw = j2format(j2send, valdict)+"\n" - response_headers_raw = """HTTP/1.1 200 OK + time.sleep(.1) # Sleep to allow the client to send some data + client.setblocking(0) # Set the socket recv as non-blocking + browser = False # Is the client using a browser? + headers = {} + try: + data = client.recv(2048) # Receive data from the buffer (if any) + request_lines = data.decode().split('\r\n') + for line in request_lines: + if ": " in line: + key, value = line.split(": ", 1) + headers[key] = value + # Extract real IP and adjust family based on headers if present + if 'X-Real-IP' in headers or 'X-Forwarded-For' in headers: + real_ip = headers.get('X-Real-IP', headers.get('X-Forwarded-For').split(',')[0]) + ip, port, family = cleanip((real_ip, valdict['port'])) + valdict.update({"ip": ip, "family": family}) + browser = True # Set client browser to True + except socket.error: # If buffer was empty, then like a telnet client on TCP80 + browser = False # Set client browser to False + if not browser: # If the client is not a browser + telnet_talker(client, valdict, "http-telnet") # Hand to telnet_talker + else: # If client is a browser + # Proceed with standard HTTP response (with headers) + valdict.update({"proto": proto}) + log(j2format(j2log, valdict)) + response_body_raw = j2format(j2send, valdict)+"\n" + response_headers_raw = """HTTP/1.1 200 OK Content-Length: %s Content-Type: application/json; encoding=utf8 Connection: close""" % str(len(response_body_raw)) # Response with headers - client.send(f'{response_headers_raw}\n\n{response_body_raw}'.encode()) - client.close() + client.send(f'{response_headers_raw}\n\n{response_body_raw}'.encode()) + client.close() + ##### Server startup method. Starts a listener thread for each TCP port ##### def start(): - talkers = {22: ssh_talker, 23: telnet_talker, - 80: http_talker} # Three listeners on different ports + talkers = {2222: ssh_talker, 2223: telnet_talker, + 2280: http_talker} # Three listeners on different ports for talker in talkers: # Launch a thread for each listener thread = threading.Thread(target=listener,