#!/usr/bin/env python3
"""
Check for ports open on a remote hosts.

Author  : Stan Lovisa
Mod     : 2025-04-17    Add --protocol option.
        : 2025-04-27    Added lists for hosts and ports
        : 2025-04-28    Added print_report.

Version : 3.0

Usage: portknock-V3.py [-p port] [-H host] [-P (TCP/UDP) TCP default]

"""
import socket
import argparse

#---------------------------------------------------------------------------
def proccessArguments():
    parse = argparse.ArgumentParser(description='Port checker')
    parse.add_argument('-p','--port',help='port1,port2,.... ',required=True)
    parse.add_argument('-H','--host',help='host1,host2,.....',required=True)
    parse.add_argument('-P','--protocol',help='[TCP|UDP]',required=False, default='TCP')

    args = vars(parse.parse_args())
    argsObj = parse.parse_args()

    return args, argsObj
#---------------------------------------------------------------------------
def print_report(protocol,port,state):
    print(f"{protocol} Port {port: <3} is {state}.")

#---------------------------------------------------------------------------
def test_udp_port(host, port):
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.settimeout(1)  # Set a timeout for the connection attempt
    protocol = "UDP"

    try:
        udp_socket.sendto(b'', (host, port))
        data, addr = udp_socket.recvfrom(1024)
        state = "open"
        print_report(protocol,port,state)
    except socket.timeout:
        state = "closed"
        print_report(protocol,port,state)
    finally:
        udp_socket.close()
#---------------------------------------------------------------------------
def test_tcp_port(host, port):
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_socket.settimeout(1)  # Set a timeout for the connection attempt
    protocol = "TCP"

    try:
        tcp_socket.connect((host, port))
        state = "open"
        print_report(protocol,port,state)
    except socket.error:
        state = "open"
        print_report(protocol,port,state)
    finally:
        tcp_socket.close()
#---------------------------------------------------------------------------
args, argsObj = proccessArguments()

try:
    ports = [int(port.strip()) for port in args['port'].split(',')]
except ValueError:
    print("Error: All ports must be valid integers.")
except KeyError:
    print("Error: 'port' key not found in args")

hosts = [host.strip() for host in args['host'].split(',')]
protocol = args['protocol']

for host in hosts:
    print(f"{f' {host} ':-^30}")
    for port in ports:
        if protocol == "TCP":
            test_tcp_port(host, port)
        else:
            test_udp_port(host, port)

