#!/usr/bin/env python3
#
# InspIRCd -- Internet Relay Chat Daemon
#
#   Copyright (C) 2024 Sadie Powell <sadie@witchery.services>
#
# This file is part of InspIRCd.  InspIRCd 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, version 2.
#
# 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 <http://www.gnu.org/licenses/>.
#


import os
import re
import socket
import ssl
import sys
import textwrap

CC_BOLD  = "\x1B[1m"    if sys.stdout.isatty() else ""
CC_RESET = "\x1B[0m"    if sys.stdout.isatty() else ""
CC_GREEN = "\x1B[1;32m" if sys.stdout.isatty() else ""
CC_RED   = "\x1B[1;31m" if sys.stdout.isatty() else ""

if len(sys.argv) < 3:
    program = os.path.basename(__file__)
    print(f"Usage: {program} <host/ip> <port> [selfsigned]", file=sys.stderr)
    sys.exit(1)

hostip = sys.argv[1]
if re.match(r"[^A-Za-z0-9.:-]", hostip):
    print(f"Error: invalid hostname or IP address: {hostip}", file=sys.stderr)
    sys.exit(1)

port = sys.argv[2]
if not port.isnumeric() or int(port) < 1 or int(port) > 65535:
    print(f"Error: invalid TCP port: {port}", file=sys.stderr)
    sys.exit(1)

self_signed = len(sys.argv) > 3 and sys.argv[3] == "selfsigned"
try:
    print(f"Checking whether {CC_BOLD}{hostip}/{port}{CC_RESET} is reachable ... ", end="", flush=True)
    sock = socket.create_connection((hostip, int(port)), 30)
    print(f"{CC_GREEN}yes{CC_RESET}")
except socket.error as error:
    print(textwrap.dedent(f"""
        {CC_RED}no{CC_RESET}

        It seems like the server endpoint you specified is not reachable! Make sure that:

          * You have specified a <bind> tag in your config for this endpoint.
          * You have rehashed or restarted the server since adding the <bind> tag.
          * If you are using a firewall incoming connections on TCP port {port} are allowed.
          * The endpoint your server is listening on is not local or private.

        The error provided by the socket library was:

          {error}

        See https://docs.inspircd.org/4/configuration/#bind for more information.
    """).strip())
    sys.exit(1)

data = b""
try:
    print(f"Checking whether {CC_BOLD}{hostip}/{port}{CC_RESET} is using plaintext ... ", end="", flush=True)
    sock.settimeout(5)
    data = sock.recv(1, socket.MSG_PEEK)
except socket.timeout:
    pass # The server is probably using deferred sockets
except socket.error as error:
    print(textwrap.dedent(f"""
        {CC_RED}error{CC_RESET}

        It seems like the server dropped the connection before sending anything! Make sure that:

          * The endpoint you specified is actually your IRC server.
          * If you are using a firewall incoming data on TCP port {port} are allowed.
          * The IP address you are connecting from has not been banned from the server.

        The error provided by the socket library was:

          {error}

        See https://docs.inspircd.org/4/configuration/#bind for more information.
    """).strip())
    sys.exit(1)

if re.match(br"[A-Z:@]", data):
    print(textwrap.dedent(f"""
        {CC_RED}yes{CC_RESET}

        It appears that the server endpoint is using plaintext! Make sure that:

          * You have one or more of the following modules loaded:
            - ssl_gnutls
            - ssl_openssl

          * The value of <bind:sslprofile> is the same as an <sslprofile:name> field.

          * The value of <sslprofile:provider> for your used TLS profile is set to
            "gnutls" if using the ssl_gnutls module or "openssl" if using the
            ssl_openssl module.

          * If you have your TLS configuration in a file other than inspircd.conf then
            that file is included by inspircd.conf.

        See the following links for more information:

          https://docs.inspircd.org/4/modules/ssl_gnutls/#configuration
          https://docs.inspircd.org/4/modules/ssl_openssl/#configuration
    """).strip())
    sys.exit(0)

try:
    print(f"{CC_GREEN}no{CC_RESET}")
    print(f"Checking whether {CC_BOLD}{hostip}/{port}{CC_RESET} can have an TLS session negotiated ... ", end="", flush=True)

    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.check_hostname = not self_signed
    context.verify_mode = ssl.CERT_NONE if self_signed else ssl.CERT_REQUIRED
    context.load_default_certs()
    context.wrap_socket(sock, server_hostname=hostip)
except ssl.SSLError as error:
    print(textwrap.dedent(  f"""
        {CC_RED}no{CC_RESET}

        It appears that something is wrong with your server. Make sure that:

          * You are not using an old version of GnuTLS or OpenSSL which only supports
            deprecated algorithms like SSLv3.
          * If you are using a self-signed certificate (not recommended) that you passed
            the `selfsigned` argument to this script.

        The error provided by the TLS library was:

          {error}
    """).strip())
    sys.exit(0)

print(textwrap.dedent(f"""
    {CC_GREEN}yes{CC_RESET}

    It seems like TLS is working fine on your server. If you are having trouble
    connecting try using a different client or connecting from a different host.

    You may also find running some of the following commands to be helpful:

       gnutls-cli-debug --port {port} {hostip}
       openssl s_client -connect {hostip}:{port} -debug -security_debug

    If you need any help working out what is wrong then visit our support channel
    at ircs://irc.teranova.net/inspircd.
""").strip())
