Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/icons/settings_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions core/requests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
from urllib import request
import json

_ORIGINAL_SOCKET = None

def socks_monkey_patch(proxy_info: dict = None):
import socks
import socket

if proxy_info["username"] and proxy_info["password"]:
socks.set_default_proxy(
socks.SOCKS5 if proxy_info["type"] == "SOCKS5" else socks.SOCKS4,
proxy_info["host"],
proxy_info["port"],
username=proxy_info["username"],
password=proxy_info["password"]
)
else:
socks.set_default_proxy(
socks.SOCKS5 if proxy_info["type"] == "SOCKS5" else socks.SOCKS4,
proxy_info["host"],
proxy_info["port"],
)

_ORIGINAL_SOCKET = socket.socket # save our socket before patching monkey patching socks
socket.socket = socks.socksocket


def http_monkey_patch(proxy_info: dict = None):
if proxy_info and proxy_info["type"] == "HTTP":
proxy_str = f"{proxy_info['host']}:{proxy_info['port']}"
if proxy_info["username"] and proxy_info["password"]:
proxy_str = f"{proxy_info['username']}:{proxy_info['password']}@{proxy_str}"

proxy_handler = request.ProxyHandler({
'http': 'http://' + proxy_str,
'https': 'http://' + proxy_str
})

opener = request.build_opener(proxy_handler)
request.install_opener(opener)


def undo_monkey_patching():
# This undos the custom opener for urllib
request.install_opener(request.build_opener())

# This tries to undo the monkey patching we did using Pysocks
if _ORIGINAL_SOCKET:
import socket
socket.socket = _ORIGINAL_SOCKET


def http_request(url: str, method: str, auth_token: str = None, payload: dict = None, longpoll: int = -1) -> dict:
if payload:
payload = json.dumps(payload).encode()
Expand Down
2 changes: 1 addition & 1 deletion logic/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def authenticate_account(user_data: dict) -> dict:
if not 'challenge' in response:
raise ValueError("Server did not give authenticatation challenge! Are you sure this is a Coldwire server ?")
except Exception:
if 'proxy_info' in user_data:
if user_data["settings"]["proxy_info"] is not None:
raise ValueError("Could not connect to server! Are you sure your proxy settings are valid ?")
else:
raise ValueError("Could not connect to server! Are you sure the URL is valid ?")
Expand Down
2 changes: 1 addition & 1 deletion logic/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def generate_and_send_pads(user_data, user_data_lock, contact_id: str, ui_queue)

contact_kyber_public_key = user_data["contacts"][contact_id]["ephemeral_keys"]["contact_public_key"]
our_lt_private_key = user_data["contacts"][contact_id]["lt_sign_keys"]["our_keys"]["private_key"]


ciphertext_blob, pads = generate_kyber_shared_secrets(contact_kyber_public_key)

Expand Down
11 changes: 6 additions & 5 deletions logic/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def check_account_file() -> bool:

def load_account_data(password = None) -> dict:
user_data = None
if not password:
if password is None:
with open(ACCOUNT_FILE_PATH, "r", encoding="utf-8") as f:
user_data = json.load(f)
else:
Expand All @@ -35,7 +35,8 @@ def load_account_data(password = None) -> dict:

user_data["tmp"] = {
"ephemeral_key_send_lock": {},
"pfs_do_not_inform": {}
"pfs_do_not_inform": {},
"password": password
}


Expand Down Expand Up @@ -99,8 +100,8 @@ def save_account_data(user_data: dict, user_data_lock, password = None) -> None:
with user_data_lock:
user_data = copy.deepcopy(user_data)

if password == None and "password" in user_data:
password = user_data["password"]
if (password is None) and (user_data["tmp"]["password"] is not None):
password = user_data["tmp"]["password"]

del user_data["tmp"]

Expand Down Expand Up @@ -157,7 +158,7 @@ def save_account_data(user_data: dict, user_data_lock, password = None) -> None:



if not password:
if password is None:
with open(ACCOUNT_FILE_PATH, "w", encoding="utf-8") as f:
json.dump(user_data, f, indent=2)
else:
Expand Down
10 changes: 10 additions & 0 deletions logic/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
def build_initial_user_data() -> dict:
return {
"server_url": None,
"contacts": {},
"tmp": {},
"settings": {
"proxy_info": None,
"ignore_new_contacts_smp": False,
}
}
76 changes: 47 additions & 29 deletions ui/connect_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from ui.password_window import PasswordWindow
from logic.storage import save_account_data
from logic.authentication import authenticate_account
from core.requests import socks_monkey_patch, http_monkey_patch, undo_monkey_patching
from logic.user import build_initial_user_data
from core.crypto import generate_sign_keys
from urllib.parse import urlparse
import tkinter as tk
Expand All @@ -20,7 +22,6 @@ def __init__(self, master):
self.configure(bg="black")
self.resizable(False, False)

# Server input
self.label = tk.Label(self, text="Enter server URL:", fg="white", bg="black")
self.label.pack(pady=(20, 5))

Expand All @@ -31,7 +32,6 @@ def __init__(self, master):

enhanced_entry(self.server_url, placeholder="I.e. example.com ...")

# Use Proxy
self.use_proxy_var = tk.IntVar()
self.proxy_check = tk.Checkbutton(
self,
Expand All @@ -46,18 +46,14 @@ def __init__(self, master):
)
self.proxy_check.pack(pady=(10, 0), anchor="center")

# Proxy Fields Frame (hidden initially)
self.proxy_fields_frame = tk.Frame(self, bg="black")

# Proxy row (type + address)
self.proxy_row = tk.Frame(self.proxy_fields_frame, bg="black")

self.proxy_type_var = tk.StringVar(value="SOCKS5")
self.proxy_type_var = tk.StringVar(value="HTTP")
self.proxy_type_var.trace_add("write", self.update_auth_visibility)

self.proxy_type_menu = tk.OptionMenu(
self.proxy_row, self.proxy_type_var, "HTTP", "SOCKS4", "SOCKS5"
)
self.proxy_type_menu = tk.OptionMenu(self.proxy_row, self.proxy_type_var, "HTTP", "SOCKS4", "SOCKS5")
self.proxy_type_menu.config(bg="gray15", fg="white", highlightthickness=0, width=7)
self.proxy_type_menu.pack(side="left", padx=(0, 5))

Expand All @@ -69,7 +65,6 @@ def __init__(self, master):

self.proxy_row.pack(pady=5)

# Auth row (username + password)
self.auth_frame = tk.Frame(self.proxy_fields_frame, bg="black")

self.proxy_user_entry = tk.Entry(
Expand All @@ -86,16 +81,15 @@ def __init__(self, master):

self.proxy_fields_frame.pack_forget()

# Status + Connect
self.status_label = tk.Label(self, text="", fg="red", bg="black")
self.status_label.pack(pady=5)

self.connect_button = tk.Button(self, text="Connect", command=self.on_connect, bg="gray25", fg="white")
self.connect_button.pack(pady=10)

# Shrink to fit default
# Shrink to fit default, autosize.
self.update_idletasks()
self.geometry("") # Autosize
self.geometry("")

def toggle_proxy_fields(self):
if self.use_proxy_var.get():
Expand All @@ -105,11 +99,11 @@ def toggle_proxy_fields(self):
self.proxy_fields_frame.pack_forget()

self.update_idletasks()
self.geometry("") # Resize window
self.geometry("") # Resize again

def update_auth_visibility(self, *args):
proxy_type = self.proxy_type_var.get().lower()
if proxy_type in ["http", "socks5"]:
proxy_type = self.proxy_type_var.get()
if proxy_type in ["HTTP", "SOCKS5"]:
self.auth_frame.pack(pady=5)
else:
self.auth_frame.pack_forget()
Expand All @@ -119,11 +113,14 @@ def update_auth_visibility(self, *args):


def password_callback(self, password):
# We save the password (if any) in the user data for ease of access to simplify development
self.user_data = {"server_url": self.server_url_fixed, "password": password, "contacts": {}, "tmp": {}, "proxy_info": None}
# We save the password (if any) in the user data tmp dict for ease of access across codebase (i.e. saving)
self.user_data = build_initial_user_data()
self.user_data["server_url"] = self.server_url_fixed
self.user_data["tmp"]["password"] = password

proxy_info = self.get_proxy_info()
if proxy_info:
self.user_data["proxy_info": proxy_info]
self.user_data["settings"]["proxy_info"] = proxy_info

private_key, public_key = generate_sign_keys()

Expand All @@ -137,6 +134,22 @@ def password_callback(self, password):
self.connect_to_server()

def connect_to_server(self):
if self.user_data["settings"]["proxy_info"]:
if self.user_data["settings"]["proxy_info"]["type"] in ["SOCKS5", "SOCKS4"]:
try:
import socks
except ImportError:
logger.error("SOCKS proxy set and we could not find PySocks. WARNING before you install PySocks: PySocks is largely unmaintained. It's highly recommended you use proxychains instead")
self.status_label.config(text="You need to install PySocks to enable SOCKS proxy support!")
return

socks_monkey_patch(self.user_data["settings"]["proxy_info"])
else:
http_monkey_patch(self.user_data["settings"]["proxy_info"])
else:
undo_monkey_patching()


try:
self.user_data = authenticate_account(self.user_data)
except ValueError as e:
Expand All @@ -145,31 +158,36 @@ def connect_to_server(self):

save_account_data(self.user_data, self.master.user_data_lock)
self.destroy()
self.master.ready_to_authenticate_callback(self.user_data["password"])
self.master.ready_to_authenticate_callback(self.user_data["tmp"]["password"], already_authenticated = True)


def get_proxy_info(self):
proxy_info = None
if self.use_proxy_var.get():
proxy_type = self.proxy_type_var.get().lower()
proxy_addr = self.proxy_addr_entry.get().strip()
proxy_type = self.proxy_type_var.get()
proxy_addr = self.proxy_addr_entry.get().strip()
username = self.proxy_user_entry.get().strip()
password = self.proxy_pass_entry.get().strip()

if not proxy_addr or ':' not in proxy_addr:
self.status_label.config(text="Invalid proxy address.")
return
host, port = proxy_addr.split(':', 1)

try:
port = int(port)
except ValueError:
self.status_label.config(text="Invalid proxy address port!")
return

proxy_info = {
"type": proxy_type,
"host": host,
"port": int(port)
"port": port,
"username": username,
"password": password
}

username = self.proxy_user_entry.get().strip()
password = self.proxy_pass_entry.get().strip()
if username and password:
proxy_info["username"] = username
proxy_info["password"] = password

if proxy_info:
logger.info("Using proxy: %s", json.dumps(proxy_info, indent=2))
return proxy_info
Expand Down Expand Up @@ -207,4 +225,4 @@ def on_connect(self, event=None):
PasswordWindow(self, self.password_callback)
else:
self.status_label.config(text="")
self.password_callback(self.user_data["password"])
self.password_callback(self.user_data["tmp"]["password"])
Loading