├── data └── bans.txt ├── requirements.txt ├── certs ├── openssl.cnf ├── server.crt └── server.key ├── LICENSE ├── README.md ├── PUBLIC_SERVER_SETUP.md ├── client.py └── server.py /data/bans.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # External dependencies 2 | colorama 3 | -------------------------------------------------------------------------------- /certs/openssl.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = req_distinguished_name 3 | x509_extensions = v3_req 4 | prompt = no 5 | 6 | [req_distinguished_name] 7 | CN = 127.0.0.1 # Common Name (CN) 8 | 9 | [v3_req] 10 | keyUsage = critical, digitalSignature, keyEncipherment 11 | extendedKeyUsage = serverAuth 12 | subjectAltName = @alt_names 13 | 14 | [alt_names] 15 | IP.1 = 127.0.0.1 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Kunal Dharme 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDDzCCAfegAwIBAgIUOiZACFCxKcsGn4OPG5+clau5iYQwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMCAXDTI1MDMwODE1MjE1MVoYDzIxMjUw 4 | MjEyMTUyMTUxWjAUMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEB 5 | AQUAA4IBDwAwggEKAoIBAQDxebE2NMW6AO/jJioHufNUBPDR8LXFFLQpnYqwveYx 6 | NXGxj0fDNYbxGc7rCbbzuqWpaaX8dipoupA1kgE4DfHjYyoUMfjO2VEJ/p5lGLOt 7 | dbkxGqftUEQpsPaB+tbhPkaPqF3K7z+8MRayyzwf9jXEH99axZoPxsxiAVe6fLs1 8 | +AFmifpZA+r//fBe6CV7Oa6fhnziaT3pn5wltjeL5AAPoiMVEnkFGCviPjd10ggT 9 | uuGA3aVmfXmC+aLjHBjQ/0KiJffrpI42kTac6nWWDqRTtd8X4K+MdLExLlfRhOB/ 10 | 23hiYNMmIFmOQ7T+YmyjT1+NUiBwwYWQewbbisn0+Xv1AgMBAAGjVzBVMA4GA1Ud 11 | DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAAB 12 | MB0GA1UdDgQWBBR0GpmpI5pAqm/loz6X8ckooKDLfzANBgkqhkiG9w0BAQsFAAOC 13 | AQEA4rtuhKz0Ged0gBgywkRBAshpTTkmPP7kc7czBN5nv0rPej6vyGxp2t3DEgdQ 14 | rmLqS+YBGwaq3v4qvdZZlrIIyJSEMsCTxMvXJ5pzhDXo45Zp07jxxgt0fLAY8Sfg 15 | ng6nVU1R8tENEROfgF0gviMgieCdLAR4LbPiGyo3mW7sL9nnbYlYBAC/ZBtrEZEs 16 | 7v78/1QzOGn9ot78Zdqarz1rsZW0iNiQyt3xUXLLx3pOeu+6/VHCbPitewT9K1Kf 17 | 7Kd3jkkico3+2QCOD+Jmh1TPy4HXfq9Du1AFi17G8YkqtYy1rjO1isjSARVc9q4W 18 | RMC6aOWo7Q5rQt4cnWD4Rghksw== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDxebE2NMW6AO/j 3 | JioHufNUBPDR8LXFFLQpnYqwveYxNXGxj0fDNYbxGc7rCbbzuqWpaaX8dipoupA1 4 | kgE4DfHjYyoUMfjO2VEJ/p5lGLOtdbkxGqftUEQpsPaB+tbhPkaPqF3K7z+8MRay 5 | yzwf9jXEH99axZoPxsxiAVe6fLs1+AFmifpZA+r//fBe6CV7Oa6fhnziaT3pn5wl 6 | tjeL5AAPoiMVEnkFGCviPjd10ggTuuGA3aVmfXmC+aLjHBjQ/0KiJffrpI42kTac 7 | 6nWWDqRTtd8X4K+MdLExLlfRhOB/23hiYNMmIFmOQ7T+YmyjT1+NUiBwwYWQewbb 8 | isn0+Xv1AgMBAAECggEAAW8M/NcopVHq1c1PTk8rXYfArBOuBxUQ1Lb3H4ZrSVAq 9 | 591cS4LWfOCnLe6XeC3SBBxAOzEyqDpYINLXF75TJVcD4FncJcjj0W29WzjQ7CZ1 10 | PQZiKq7QOvOuaEFcoYl7fajC+pPvhG0aRLBQ+HsTGXXswa7bQombIMxiR/L6iL8M 11 | L/QwQnSidwW0YLZeXc3/ji/Z/PzU9rvjslGjYkMwhXvpYlNhA/GMlt5cjziVENpc 12 | L97s+t6g7S1dloK9jtN1Zc7QoLyQNUMEVHsQfR+U6zw2uyQ4s87ltu3lQzLPFYl8 13 | KED+6/0Io+2pgrDBlHbBKym7GA8SncT/yS91ADes6QKBgQD6yNhKi5VQBpIw2gS1 14 | OBVmhvBgyiz8Bb6OHGhxRUoR/MyjY134pLw3QJ8r95KPWmgIdBdgmWiPNTtlj+Nc 15 | QZ+izL8USxhnf4ELOeXoUR2GcIXHStQs6UXBzvhJlDKPjOn10/4x9WJWKPr06CFQ 16 | ChzY/dZCBgnr8cQVOMW1Gn4PTQKBgQD2f0k5Kh9FO+5tJnLQo9yaQjHSNyWn9LNk 17 | J/pobiBdvybBsSCPWIlARvf+RM5enb3nDgqYfDgBStszgB7LSwhipA+ZZKGk85DS 18 | 6t/II5Wh4rIblyJ/9gwdw4DjgTMb899Atpd/dauyrf6xDYoLxDt1eN28KzlqBzb2 19 | 4Or9KBIbSQKBgQDDncaY3ygos/d0g5sIgCaxQrBdz1Ynf5OT5jTRpeqQ/U/iYq/6 20 | MwVdU9rUwNhhWXOspGFXyFH24t7h6cq/O7D5nBuwCKOQl7RgvVjITf1p0HuJaKT4 21 | Iz12X6rsOdz2fERCrImrDm0WKAEY57iUZk76M4XdGjjDAmKg6xfMnE2YQQKBgQDr 22 | u5D4RUtAd+8tnW6CD6NCNvnUcv7lH8SLjRwR0PfND6ht5gogbGhd+0Dhf0XpAGwD 23 | Us9ZQrqAqej12JoaivhK9WEhrjZUPMaMnyhCC53nApiYgDHzNAgkUrqpV/7bxZ8F 24 | EVnTAGKib3m6MxuI4zKFxoAvmxq+RoYPFcxFLJQcWQKBgBz0aIizVzKvqdpc3LgA 25 | hyz0wNTO+57KS5DUPi0MOLvluwnba5E2bGM3+JHC9SQ02Mn9yhfJ7VACdb+9qeoC 26 | XgTNIPCznnHVkAmEL0864hmnBbc8p3836/uYLRvGdh2wC4aSAHD+iN1nVsvUPg3f 27 | e/gvcU2Go+RMxuoFRSL0atnA 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stealth-Chat 2 | # Author : Kunal Dharme 3 | 4 | Stealth-Chat is a secure, SSL/TLS-encrypted chat application that enables multiple users to communicate privately. The application consists of a server and multiple clients, with additional administrative controls such as banning and kicking users. 5 | 6 | ## Features 7 | - **Secure Communication**: Uses SSL/TLS encryption for secure message transmission. 8 | - **Admin Controls**: Admin can kick or ban users. 9 | - **User Authentication**: Admin authentication with a password. 10 | - **Ban System**: Banned users are prevented from reconnecting. 11 | 12 | ## Project Structure 13 | ``` 14 | Stealth-Chat/ 15 | │── certs/ # Contains SSL/TLS certificates and keys 16 | │ ├── server.crt # Server certificate 17 | │ ├── server.key # Server private key 18 | │── data/ # Contains banned users list 19 | │ ├── bans.txt # List of banned users 20 | │── client.py # Client-side script 21 | │── server.py # Server-side script 22 | │── requirements.txt # Required dependencies 23 | │── README.md # Documentation 24 | |── PUBLIC_SERVER_SETUP.md # Guide to deploy on a public server 25 | ``` 26 | 27 | ## Prerequisites 28 | Ensure you have Python installed (version 3.x or higher recommended). 29 | Install required dependencies using: 30 | ```sh 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | ## Running the Server 35 | Start the server using: 36 | ```sh 37 | python server.py 38 | ``` 39 | The server listens on `127.0.0.1:54321` by default. 40 | 41 | ## Running the Client 42 | Start the client using: 43 | ```sh 44 | python client.py 45 | ``` 46 | Upon connection, the client will prompt for a nickname. If the nickname is `admin`, a password will be required. The default admin password is `password`. 47 | 48 | ## Admin Commands 49 | Admin users can execute the following commands in the chat: 50 | - `/kick ` - Remove a user from the chat. 51 | - `/ban ` - Remove and ban a user from reconnecting. 52 | - `/exit` - Disconnect from the server. 53 | - `/` - Commands must start with a forward slash (`/`). 54 | 55 | ## Security Considerations 56 | - **SSL/TLS Encryption**: Ensures all communication is secure. 57 | - **Admin Authentication**: Prevents unauthorized access to admin functions. 58 | - **Ban List**: Prevents banned users from reconnecting. 59 | 60 | ## License 61 | This project is licensed under the MIT License. 62 | 63 | ## Author 64 | Developed by Kunal Dharme. 65 | 66 | -------------------------------------------------------------------------------- /PUBLIC_SERVER_SETUP.md: -------------------------------------------------------------------------------- 1 | # Deploying Stealth-Chat to a Public Server 2 | 3 | This guide explains how to modify your Stealth-Chat application to work over the public internet instead of localhost. 4 | 5 | ## 1. Configure the Server for Public Access 6 | By default, the server listens on `127.0.0.1` (localhost), which only allows local connections. To allow public connections: 7 | 8 | - Open `server.py` 9 | - Change the `host` variable from `127.0.0.1` to `0.0.0.0`: 10 | 11 | ```python 12 | host = '0.0.0.0' # Allow external connections 13 | port = 54321 14 | ``` 15 | 16 | This makes the server listen on all available network interfaces. 17 | 18 | ## 2. Set Up a Public IP or Domain 19 | If running on a cloud service (e.g., AWS, DigitalOcean, or a VPS): 20 | - Obtain the public IP address assigned to your server. 21 | - (Optional) Set up a domain name and configure DNS to point to your server's IP. 22 | 23 | ## 3. Configure Firewall and Port Forwarding 24 | Ensure port `54321` is open for incoming connections: 25 | 26 | - **Linux (UFW Firewall)**: 27 | ```sh 28 | sudo ufw allow 54321/tcp 29 | ``` 30 | - **Windows Firewall**: 31 | - Open `Windows Defender Firewall` 32 | - Add a new inbound rule to allow connections on port `54321`. 33 | - **Router Port Forwarding** (for home-hosted servers): 34 | - Log into your router settings. 35 | - Forward TCP port `54321` to your local server's private IP address. 36 | 37 | ## 4. Update the Client to Connect to the Public Server 38 | In `client.py`, change: 39 | ```python 40 | secure_client.connect(('127.0.0.1', 54321)) 41 | ``` 42 | To: 43 | ```python 44 | secure_client.connect(('your-public-ip-or-domain', 54321)) 45 | ``` 46 | Replace `your-public-ip-or-domain` with your server’s public IP or domain. 47 | 48 | ## 5. Use a Valid SSL Certificate 49 | The current setup uses self-signed certificates, which may cause security warnings. For public deployment, get a certificate from [Let's Encrypt](https://letsencrypt.org/): 50 | ```sh 51 | sudo apt install certbot 52 | sudo certbot certonly --standalone -d yourdomain.com 53 | ``` 54 | Update `server.py` to use the new certificate and key files. 55 | 56 | ## 6. Run the Server Permanently 57 | Use `tmux` or `screen` to keep the server running in the background: 58 | ```sh 59 | nohup python server.py & 60 | ``` 61 | Or create a systemd service for automatic startup. 62 | 63 | ## 7. Secure the Server 64 | - Use strong passwords. 65 | - Regularly update the system (`sudo apt update && sudo apt upgrade`). 66 | - Restrict access using firewall rules. 67 | 68 | Now your chat server is accessible over the internet! -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import ssl 3 | import threading 4 | import colorama 5 | import os 6 | from colorama import Fore, Back, Style 7 | 8 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 9 | CERTS_DIR = os.path.join(BASE_DIR, "certs") 10 | CERT_FILE = os.path.join(CERTS_DIR, "server.crt") 11 | 12 | # Initialize colorama for colored console output 13 | colorama.init(autoreset=True) 14 | 15 | SERVER_CERT = os.path.join(CERTS_DIR, "server.crt") #Server certificate to verify authenticity 16 | CLIENT_CONTEXT = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) 17 | CLIENT_CONTEXT.load_verify_locations(SERVER_CERT) 18 | 19 | # Get user's nickname 20 | nickname = input(Fore.BLUE + "Choose a nickname: ") 21 | password = None 22 | 23 | # If the user is 'admin', prompt for a password 24 | if nickname == 'admin': 25 | password = input("Enter password for admin: ") 26 | 27 | # Create a client socket and connect to the server 28 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 29 | secure_client = CLIENT_CONTEXT.wrap_socket(client, server_hostname='127.0.0.1') 30 | secure_client.connect(('127.0.0.1', 54321)) 31 | 32 | stop_thread = False # Flag to stop threads when needed 33 | 34 | def receive(): 35 | """Function to receive messages from the server.""" 36 | global stop_thread 37 | try: 38 | while True: 39 | if stop_thread: 40 | break 41 | try: 42 | message = secure_client.recv(1024).decode('ascii') 43 | 44 | # Handle server authentication requests 45 | if message == 'NICK': 46 | secure_client.send(nickname.encode('ascii')) 47 | next_message = secure_client.recv(1024).decode('ascii') 48 | 49 | if next_message == 'PASS' and password: 50 | secure_client.send(password.encode('ascii')) 51 | if secure_client.recv(1024).decode('ascii') == 'REFUSE': 52 | print(Fore.RED + "Connection was refused! Wrong password.") 53 | stop_thread = True 54 | 55 | elif next_message == 'BAN': 56 | print(Fore.RED + "Connection refused because you are banned!") 57 | secure_client.close() 58 | stop_thread = True 59 | exit() 60 | else: 61 | print(message) # Print received messages 62 | except Exception as e: 63 | print(Fore.RED + f"An error occurred!: {e}") 64 | stop_thread = True 65 | secure_client.close() 66 | break 67 | except KeyboardInterrupt: 68 | print(Fore.RED + "\nExiting chat...") 69 | stop_thread = True 70 | secure_client.close() 71 | exit() 72 | 73 | def write(): 74 | """Function to send messages to the server.""" 75 | global stop_thread 76 | try: 77 | while True: 78 | if stop_thread: 79 | break 80 | try: 81 | message = input("") # Get user input 82 | except EOFError: 83 | print(Fore.RED + "\nExiting chat due to EOFError...") 84 | secure_client.send("EXIT".encode('ascii')) 85 | stop_thread = True 86 | secure_client.close() 87 | break 88 | 89 | # Handle exit command 90 | if message == "/exit": 91 | print(Fore.YELLOW + "You have left the chat.") 92 | secure_client.send("EXIT".encode('ascii')) 93 | stop_thread = True 94 | secure_client.close() 95 | break #exit the write loop 96 | 97 | formatted_message = f'{Fore.GREEN}{nickname}{Style.RESET_ALL}: {Fore.CYAN}{message}{Style.RESET_ALL}' 98 | 99 | if message.startswith('/'): 100 | # Admin-only commands 101 | if nickname == 'admin': 102 | if message.startswith('/kick '): 103 | secure_client.send(f'KICK {message[6:]}'.encode('ascii')) 104 | elif message.startswith('/ban '): 105 | secure_client.send(f'BAN {message[5:]}'.encode('ascii')) 106 | else: 107 | print(Fore.YELLOW + "Unknown command!") 108 | else: 109 | print(Fore.YELLOW + "Commands can only be executed by the admin!") 110 | else: 111 | secure_client.send(formatted_message.encode('ascii')) 112 | except KeyboardInterrupt: 113 | print(Fore.RED + "\nExiting chat...") 114 | secure_client.send("EXIT".encode('ascii')) 115 | stop_thread = True 116 | secure_client.close() 117 | exit(0) 118 | 119 | # Start the receiving and writing threads 120 | receive_thread = threading.Thread(target=receive) 121 | receive_thread.start() 122 | 123 | write_thread = threading.Thread(target=write) 124 | write_thread.start() 125 | 126 | receive_thread.join() 127 | write_thread.join() -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import ssl 3 | import socket 4 | import colorama 5 | import logging 6 | import os 7 | from colorama import Fore, Back, Style 8 | 9 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Get script directory 10 | BANS_FILE = os.path.join(BASE_DIR, "data", "bans.txt") 11 | 12 | CERTS_DIR = os.path.join(BASE_DIR, "certs") 13 | CERT_FILE = os.path.join(CERTS_DIR, "server.crt") 14 | KEY_FILE = os.path.join(CERTS_DIR, "server.key") 15 | 16 | logging.basicConfig(level=logging.INFO) 17 | 18 | # Initialize colorama for colored terminal output 19 | colorama.init(autoreset=True) 20 | 21 | SERVER_CERT = os.path.join(CERTS_DIR, "server.crt") 22 | SERVER_KEY = os.path.join(CERTS_DIR, "server.key") #server private key 23 | SERVER_CONTEXT = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 24 | SERVER_CONTEXT.load_cert_chain(certfile=SERVER_CERT, keyfile=SERVER_KEY) 25 | 26 | # Define server host and port 27 | host = '127.0.0.1' # localhost 28 | port = 54321 29 | 30 | # Create and configure server socket 31 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | server.bind((host, port)) 33 | server.listen() 34 | 35 | # Lists to store connected clients and their nicknames 36 | clients = [] 37 | nicknames = [] 38 | 39 | def broadcast(message): 40 | """Send a message to all connected clients.""" 41 | for client in clients: 42 | try: 43 | client.send(message) 44 | except: 45 | client.close() 46 | clients.remove(client) 47 | 48 | def handle(client): 49 | """Handle messages from a specific client.""" 50 | while True: 51 | try: 52 | msg = client.recv(1024).decode('ascii') 53 | if msg == 'EXIT': # Client wants to exit 54 | if client in clients: 55 | index = clients.index(client) 56 | nickname = nicknames[index] 57 | clients.remove(client) 58 | nicknames.pop(index) 59 | broadcast((Fore.YELLOW + f"{nickname} left the chat!" + Style.RESET_ALL).encode('ascii')) 60 | print(Fore.YELLOW + f"{nickname} disconnected.") 61 | client.close() 62 | break # exit the loop 63 | 64 | elif msg.startswith('KICK '): 65 | if nicknames[clients.index(client)] == 'admin': 66 | name_to_kick = msg[5:] 67 | kick_user(name_to_kick) 68 | else: 69 | client.send((Fore.YELLOW + 'Command was refused!').encode('ascii')) 70 | elif msg.startswith('BAN '): 71 | if nicknames[clients.index(client)] == 'admin': 72 | name_to_ban = msg[4:] 73 | kick_user(name_to_ban) 74 | with open(BANS_FILE, 'a') as f: 75 | f.write(f'{name_to_ban}\n') 76 | print(Fore.RED + f'{name_to_ban} was banned!') 77 | else: 78 | client.send((Fore.YELLOW + 'Command was refused!').encode('ascii')) 79 | 80 | else: 81 | broadcast(msg.encode('ascii')) # Broadcast received message to all clients 82 | 83 | except: 84 | if client in clients: 85 | try: 86 | index = clients.index(client) 87 | clients.remove(client) 88 | client.close() 89 | nickname = nicknames.pop(index) 90 | broadcast((Fore.YELLOW + f"{nickname} left the chat!").encode('ascii')) 91 | except ValueError: 92 | pass #client was already removed 93 | 94 | def receive(): 95 | """Accept new client connections.""" 96 | try: 97 | while True: 98 | client, address = server.accept() 99 | secure_client = SERVER_CONTEXT.wrap_socket(client, server_side=True) 100 | 101 | print(Fore.GREEN + f"Connected with {str(address)}") 102 | 103 | secure_client.send('NICK'.encode('ascii')) 104 | nickname = secure_client.recv(1024).decode('ascii') 105 | 106 | try: 107 | with open(BANS_FILE, 'r') as f: 108 | bans = f.readlines() 109 | except FileNotFoundError: 110 | bans = [] 111 | 112 | if nickname in [ban.strip() for ban in bans]: 113 | secure_client.send('BAN'.encode('ascii')) 114 | secure_client.close() 115 | continue 116 | 117 | if nickname == 'admin': 118 | secure_client.send('PASS'.encode('ascii')) 119 | password = secure_client.recv(1024).decode('ascii') 120 | 121 | if password != 'password': 122 | secure_client.send('REFUSE'.encode('ascii')) 123 | secure_client.close() 124 | continue 125 | 126 | nicknames.append(nickname) 127 | clients.append(secure_client) 128 | 129 | print(Fore.BLUE + f"Nickname of the client is {nickname}!") 130 | broadcast((Fore.GREEN + f"{nickname} joined the chat!").encode('ascii')) 131 | secure_client.send((Back.YELLOW + Fore.BLACK + "Connected to the server!").encode('ascii')) 132 | 133 | thread = threading.Thread(target=handle, args=(secure_client,), daemon=True) 134 | thread.start() 135 | 136 | except KeyboardInterrupt: 137 | print(Fore.RED + "\nServer shutting down..") 138 | logging.info("server is shutting down...") 139 | 140 | for client in clients[:]: #Iterate over a copy to prevent modification issues 141 | try: 142 | client.send(Fore.RED + "SERVER_SHUTDOWN".encode('ascii')) 143 | except: 144 | pass #ignore errors if the client is already disconnected 145 | client.close() 146 | server.close() 147 | logging.info("Server successfully shut down.") 148 | exit(0) 149 | 150 | def kick_user(name): 151 | """Remove a user from the chat if they are kicked.""" 152 | if name in nicknames: 153 | name_index = nicknames.index(name) 154 | client_to_kick = clients[name_index] 155 | clients.remove(client_to_kick) 156 | nicknames.remove(name) 157 | 158 | try: 159 | client_to_kick.send((Fore.BLACK + Back.LIGHTRED_EX +'You were kicked by an admin!').encode('ascii')) 160 | client_to_kick.close() 161 | except: 162 | pass #ignore if already disconnected 163 | 164 | broadcast((Fore.RED + f'{name} was kicked by an admin!').encode('ascii')) 165 | else: 166 | print(Fore.YELLOW + f"Attemped to kick {name}, but they are not in the chat.") 167 | 168 | print(Fore.GREEN + "Server is listening...") 169 | receive() 170 | --------------------------------------------------------------------------------