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 )
@@ -286,11 +286,13 @@ def __init__(
286286 console_cls : type [Console ],
287287 namespace : argparse .Namespace ,
288288 command_parser : CommandParser ,
289+ users_directory : Path ,
289290 executor : ThreadPoolExecutor ,
290291 ):
291292 self ._gambaterm_console_cls = console_cls
292293 self ._gambaterm_namespace = namespace
293294 self ._gambaterm_command_parser = command_parser
295+ self ._gambaterm_users_directory = users_directory
294296 self ._gambaterm_executor = executor
295297 self ._gambaterm_authentication = authentication
296298
@@ -299,6 +301,7 @@ def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
299301 conn .set_extra_info (executor = self ._gambaterm_executor )
300302 conn .set_extra_info (namespace = self ._gambaterm_namespace )
301303 conn .set_extra_info (command_parser = self ._gambaterm_command_parser )
304+ conn .set_extra_info (users_directory = self ._gambaterm_users_directory )
302305
303306 def begin_auth (self , username : str ) -> bool :
304307 return not isinstance (self ._gambaterm_authentication , NoAuthentication )
@@ -328,6 +331,7 @@ async def run_ssh_server(
328331 console_cls : type [Console ],
329332 namespace : argparse .Namespace ,
330333 command_parser : CommandParser ,
334+ users_directory : Path ,
331335 executor : ThreadPoolExecutor ,
332336) -> AsyncIterator [SSHAcceptor ]:
333337 # Gambaterm configuration
@@ -383,7 +387,12 @@ async def run_ssh_server(
383387
384388 server = await asyncssh .create_server (
385389 lambda : SSHServer (
386- authentication , console_cls , namespace , command_parser , executor
390+ authentication ,
391+ console_cls ,
392+ namespace ,
393+ command_parser ,
394+ users_directory ,
395+ executor ,
387396 ),
388397 bind ,
389398 port ,
@@ -459,13 +468,20 @@ def main(
459468 action = "store_true" ,
460469 help = "Disable authentication altogether (no password nor public key required)" ,
461470 )
471+ parser .add_argument (
472+ "--users-directory" ,
473+ type = Path ,
474+ default = Path ("users_save" ),
475+ help = "Directory containing one save directory per user (default is ./users_save)" ,
476+ )
462477
463478 # Parse arguments
464479 namespace = parser .parse_args (parser_args )
465480 bind : str = namespace .__dict__ .pop ("bind" )
466481 port : int = namespace .__dict__ .pop ("port" )
467482 password : str = namespace .__dict__ .pop ("password" )
468483 no_auth : bool = namespace .__dict__ .pop ("no_auth" )
484+ users_directory : Path = namespace .__dict__ .pop ("users_directory" )
469485
470486 # Determine authentication method
471487 if no_auth and password is None :
@@ -506,6 +522,7 @@ async def async_main() -> None:
506522 console_cls ,
507523 namespace ,
508524 command_parser ,
525+ users_directory ,
509526 executor ,
510527 ):
511528 await asyncio .Future ()
0 commit comments