33import os
44import time
55import hmac
6- import hashlib
76import asyncio
87import argparse
98import traceback
3635)
3736from .console import Console , GameboyColor
3837
39- from .remote_terminal import RemoteTerminal
38+ from .remote_terminal import RemoteTerminal , user_directory_name
4039from .ssh_app_session import process_to_terminal
4140
4241
@@ -105,6 +104,7 @@ async def ssh_process_handler(process: SSHServerProcess[str]) -> int:
105104 console_cls : type [Console ] = process .get_extra_info ("console_cls" )
106105 namespace : argparse .Namespace = process .get_extra_info ("namespace" )
107106 command_parser : CommandParser = process .get_extra_info ("command_parser" )
107+ users_directory : Path = process .get_extra_info ("users_directory" )
108108 executor : ThreadPoolExecutor = process .get_extra_info ("executor" )
109109 display = process .channel .get_x11_display ()
110110 command = process .channel .get_command ()
@@ -126,15 +126,15 @@ async def ssh_process_handler(process: SSHServerProcess[str]) -> int:
126126 )
127127
128128 # Manage save directory — hash username to prevent path traversal
129- if " save_directory" in namespace . __dict__ :
130- if getattr ( namespace , "input_file" , False ):
131- setattr (namespace , "save_directory " , None )
132- else :
133- safe_name = hashlib . sha256 ( username . encode ( "utf-8" )). hexdigest ()[: 16 ]
134- save_directory = Path ( "ssh_save" ) / safe_name
135- save_directory . mkdir ( parents = True , exist_ok = True )
136- ( save_directory / "username" ). write_text ( username )
137- setattr (namespace , "save_directory" , save_directory )
129+ namespace . save_directory = (
130+ None
131+ if getattr (namespace , "input_file " , None )
132+ else users_directory / user_directory_name ( username )
133+ )
134+
135+ if namespace . save_directory is not None :
136+ namespace . save_directory . mkdir ( parents = True , exist_ok = True )
137+ (namespace . save_directory / "username" ). write_text ( username )
138138
139139 # Pop console arguments and extract configuration
140140 console_callback = console_cls .pop_console_arguments (namespace )
@@ -292,11 +292,13 @@ def __init__(
292292 console_cls : type [Console ],
293293 namespace : argparse .Namespace ,
294294 command_parser : CommandParser ,
295+ users_directory : Path ,
295296 executor : ThreadPoolExecutor ,
296297 ):
297298 self ._gambaterm_console_cls = console_cls
298299 self ._gambaterm_namespace = namespace
299300 self ._gambaterm_command_parser = command_parser
301+ self ._gambaterm_users_directory = users_directory
300302 self ._gambaterm_executor = executor
301303 self ._gambaterm_authentication = authentication
302304
@@ -305,6 +307,7 @@ def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
305307 conn .set_extra_info (executor = self ._gambaterm_executor )
306308 conn .set_extra_info (namespace = self ._gambaterm_namespace )
307309 conn .set_extra_info (command_parser = self ._gambaterm_command_parser )
310+ conn .set_extra_info (users_directory = self ._gambaterm_users_directory )
308311
309312 def begin_auth (self , username : str ) -> bool :
310313 return not isinstance (self ._gambaterm_authentication , NoAuthentication )
@@ -334,6 +337,7 @@ async def run_ssh_server(
334337 console_cls : type [Console ],
335338 namespace : argparse .Namespace ,
336339 command_parser : CommandParser ,
340+ users_directory : Path ,
337341 executor : ThreadPoolExecutor ,
338342) -> AsyncIterator [SSHAcceptor ]:
339343 # Gambaterm configuration
@@ -389,7 +393,12 @@ async def run_ssh_server(
389393
390394 server = await asyncssh .create_server (
391395 lambda : SSHServer (
392- authentication , console_cls , namespace , command_parser , executor
396+ authentication ,
397+ console_cls ,
398+ namespace ,
399+ command_parser ,
400+ users_directory ,
401+ executor ,
393402 ),
394403 bind ,
395404 port ,
@@ -465,13 +474,20 @@ def main(
465474 action = "store_true" ,
466475 help = "Disable authentication altogether (no password nor public key required)" ,
467476 )
477+ parser .add_argument (
478+ "--users-directory" ,
479+ type = Path ,
480+ default = Path ("users_save" ),
481+ help = "Directory containing one save directory per user (default is ./users_save)" ,
482+ )
468483
469484 # Parse arguments
470485 namespace = parser .parse_args (parser_args )
471486 bind : str = namespace .__dict__ .pop ("bind" )
472487 port : int = namespace .__dict__ .pop ("port" )
473488 password : str = namespace .__dict__ .pop ("password" )
474489 no_auth : bool = namespace .__dict__ .pop ("no_auth" )
490+ users_directory : Path = namespace .__dict__ .pop ("users_directory" )
475491
476492 # Determine authentication method
477493 if no_auth and password is None :
@@ -512,6 +528,7 @@ async def async_main() -> None:
512528 console_cls ,
513529 namespace ,
514530 command_parser ,
531+ users_directory ,
515532 executor ,
516533 ):
517534 await asyncio .Future ()
0 commit comments