@@ -447,12 +447,18 @@ public void run()
447447 while (!Thread .currentThread ().isInterrupted ())
448448 {
449449 Socket client = SERVER_SOCKET .accept ();
450+
450451 Thread h = new Thread (() -> handle (client ));
452+
451453 h .setDaemon (true );
454+
452455 h .start ();
453456 }
454457 }
455- catch (Exception e ) { ExceptionHandler .dispatch (e ); }
458+ catch (Exception e )
459+ {
460+ ExceptionHandler .dispatch (e );
461+ }
456462 }
457463
458464 /** Per-connection session state. */
@@ -486,28 +492,33 @@ private void handle(final Socket CLIENT)
486492 while ((line = in .readLine ()) != null )
487493 {
488494 line = line .trim ();
495+
489496 if (line .isEmpty ()) continue ;
497+
490498 if (line .equalsIgnoreCase ("quit" ) || line .equalsIgnoreCase ("exit" )) break ;
491499
492- CommonRails .printSystemComponent (this , this .hashCode (),
493- ". ModuleInstallationService [" + session .remoteIp + "] cmd: " + line + " ." );
500+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService [" + session .remoteIp + "] cmd: " + line + " ." );
494501
495502 String response = dispatch (line , CLIENT .getInputStream (), out , session );
503+
496504 if (response != null ) writeLine (out , response );
497505 }
498506 }
499- catch (Exception e ) { ExceptionHandler .dispatch (e ); }
507+ catch (Exception e )
508+ {
509+ ExceptionHandler .dispatch (e );
510+ }
500511 finally
501512 {
502513 if (session .adminToken != null ) admin .ModuleAdmin .logout (session .adminToken );
503514 try { CLIENT .close (); } catch (Exception ignored ) {}
504515 }
505516 }
506517
507- private String dispatch (final String CMD , final InputStream RAW ,
508- final BufferedWriter OUT , final Session SESSION )
518+ private String dispatch (final String CMD , final InputStream RAW , final BufferedWriter OUT , final Session SESSION )
509519 {
510520 String [] parts = CMD .split ("\\ s+" , 4 );
521+
511522 switch (parts [0 ].toLowerCase ())
512523 {
513524 case "identify" :
@@ -554,146 +565,172 @@ private String identify(final String NATIONAL_ID_STR, final Session SESSION)
554565 try
555566 {
556567 long id = Long .parseLong (NATIONAL_ID_STR );
568+
557569 national .NationalFinanceID r = db .N21Store .loadNationalFinanceID (id );
570+
558571 if (r == null ) return "[identify] National ID " + id + " not found." ;
572+
559573 SESSION .nationalId = id ;
560- db .N21Store .storeModuleAction (id , "" , "identify" , SESSION .remoteIp ,
561- "" , 0 , "" , "" , "identified" );
562- CommonRails .printSystemComponent (this , this .hashCode (),
563- ". ModuleInstallationService identified National ID " + id + " ." );
574+
575+ db .N21Store .storeModuleAction (id , "" , "identify" , SESSION .remoteIp , "" , 0 , "" , "" , "identified" );
576+
577+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService identified National ID " + id + " ." );
578+
564579 return "[identify] National ID " + id + " recognised. Welcome." ;
565580 }
566- catch (NumberFormatException e ) { return "[identify] Invalid National ID." ; }
581+ catch (NumberFormatException e )
582+ {
583+ return "[identify] Invalid National ID." ;
584+ }
567585 }
568586
569587 private String adminLogin (final String PASSWORD , final Session SESSION )
570588 {
571589 if (SESSION .nationalId < 0 ) return "[admin] Identify yourself first." ;
590+
572591 String token = admin .ModuleAdmin .login (PASSWORD , SESSION .nationalId );
592+
573593 if (token == null )
574594 {
575- db .N21Store .storeModuleAction (SESSION .nationalId , "" , "admin-login-fail" ,
576- SESSION . remoteIp , "" , 0 , "" , "" , "failed" );
595+ db .N21Store .storeModuleAction (SESSION .nationalId , "" , "admin-login-fail" , SESSION . remoteIp , "" , 0 , "" , "" , "failed" );
596+
577597 return "[admin] Authentication failed." ;
578598 }
599+
579600 SESSION .adminToken = token ;
580- db .N21Store .storeModuleAction (SESSION .nationalId , "" , "admin-login" ,
581- SESSION .remoteIp , "" , 0 , "" , token , "success" );
601+
602+ db .N21Store .storeModuleAction (SESSION .nationalId , "" , "admin-login" , SESSION .remoteIp , "" , 0 , "" , token , "success" );
603+
582604 return "[admin] Authenticated. You may now unload modules and grant signatories." ;
583605 }
584606
585- private String installModule (final String NAME , final String SIG_HEX ,
586- final String BYTE_COUNT_STR , final InputStream RAW ,
587- final BufferedWriter OUT , final Session SESSION )
607+ private String installModule (final String NAME , final String SIG_HEX , final String BYTE_COUNT_STR , final InputStream RAW , final BufferedWriter OUT , final Session SESSION )
588608 {
589609 try
590610 {
591611 int byteCount = Integer .parseInt (BYTE_COUNT_STR );
612+
592613 if (byteCount <= 0 || byteCount > 50 * 1024 * 1024 )
593614 return "[install] Invalid byte count: " + byteCount ;
594615
595616 writeLine (OUT , "[install] Ready to receive " + byteCount + " bytes for '" + NAME + "'." );
596617
597618 byte [] data = new byte [byteCount ];
619+
598620 new DataInputStream (RAW ).readFully (data );
599621
600622 // Security check 1: SHA-256
601623 String actualHex = sha256hex (data );
624+
602625 if (!actualHex .equalsIgnoreCase (SIG_HEX ))
603626 {
604627 String result = "sig-mismatch expected=" + SIG_HEX + " got=" + actualHex ;
605- db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" ,
606- SESSION .remoteIp , "" , byteCount , SIG_HEX , "" , result );
607- CommonRails .printSystemComponent (this , this .hashCode (),
608- ". ModuleInstallationService SECURITY FAIL sig mismatch [" + NAME + "] ." );
628+
629+ db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" , SESSION .remoteIp , "" , byteCount , SIG_HEX , "" , result );
630+
631+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService SECURITY FAIL sig mismatch [" + NAME + "] ." );
632+
609633 return "[install] REJECTED — signature mismatch." ;
610634 }
611635
612636 // Security check 2: file type
613637 String detectedType = detectType (data );
638+
614639 if (detectedType == null )
615640 {
616- db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" ,
617- SESSION . remoteIp , "unknown" , byteCount , SIG_HEX , "" , "bad-type" );
618- CommonRails .printSystemComponent (this , this .hashCode (),
619- ". ModuleInstallationService SECURITY FAIL unsupported type [" + NAME + "] ." );
641+ db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" , SESSION . remoteIp , "unknown" , byteCount , SIG_HEX , "" , "bad-type" );
642+
643+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService SECURITY FAIL unsupported type [" + NAME + "] ." );
644+
620645 return "[install] REJECTED — unsupported file type (must be .jar, .zip, or .java)." ;
621646 }
622647
623- CommonRails .printSystemComponent (this , this .hashCode (),
624- ". ModuleInstallationService security passed [" + NAME + "] type=" + detectedType + " ." );
648+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService security passed [" + NAME + "] type=" + detectedType + " ." );
625649
626650 // Heuristics check — score the module before writing to disk
627651 try
628652 {
629653 Path tmp = Files .createTempFile ("nwe-heuristic-" , "." + detectedType );
654+
630655 Files .write (tmp , data );
656+
631657 heuristics .ModuleHeuristics .Result hr = heuristics .ModuleHeuristics .evaluate (tmp );
658+
632659 Files .deleteIfExists (tmp );
633660
634- CommonRails .printSystemComponent (this , this .hashCode (),
635- ". ModuleInstallationService heuristics [" + NAME + "] score=" + hr . score + " suitable=" + hr . suitable + " ." );
661+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService heuristics [" + NAME + "] score=" + hr . score + " suitable=" + hr . suitable + " ." );
662+
636663 writeLine (OUT , "[heuristics] " + hr .summary ());
637664
638665 if (!hr .suitable )
639666 {
640- db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" ,
641- SESSION .remoteIp , detectedType , byteCount , SIG_HEX , "" , "heuristics-fail score=" + hr .score );
642- return "[install] REJECTED — heuristics score " + hr .score + "/100 is below threshold ("
643- + heuristics .ModuleHeuristics .PASS_THRESHOLD + "). See findings above." ;
667+ db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install-reject" , SESSION .remoteIp , detectedType , byteCount , SIG_HEX , "" , "heuristics-fail score=" + hr .score );
668+
669+ return "[install] REJECTED — heuristics score " + hr .score + "/100 is below threshold (" + heuristics .ModuleHeuristics .PASS_THRESHOLD + "). See findings above." ;
644670 }
645671 }
646672 catch (Exception hEx )
647673 {
648674 // Heuristics failure must not block install — log and continue
649- CommonRails .printSystemComponent (this , this .hashCode (),
650- ". ModuleInstallationService heuristics error [" + NAME + "]: " + hEx .getMessage () + " — proceeding ." );
675+ CommonRails .printSystemComponent (this , this .hashCode (), ". ModuleInstallationService heuristics error [" + NAME + "]: " + hEx .getMessage () + " — proceeding ." );
651676 }
652677
653678 String filename = NAME .replaceAll ("[^a-zA-Z0-9._-]" , "_" ) + "." + detectedType ;
679+
654680 Path dest = INSTALL_DIR .resolve (filename );
681+
655682 Files .write (dest , data );
656683
657684 URLClassLoader loader = null ;
685+
658686 if (detectedType .equals ("jar" ))
659687 {
660- loader = new URLClassLoader (new URL []{ dest .toUri ().toURL () },
661- Thread .currentThread ().getContextClassLoader ());
688+ loader = new URLClassLoader (new URL []{ dest .toUri ().toURL () }, Thread .currentThread ().getContextClassLoader ());
662689 }
663690 else if (detectedType .equals ("zip" ))
664691 {
665692 Path unzipDir = INSTALL_DIR .resolve (NAME );
693+
666694 Files .createDirectories (unzipDir );
695+
667696 unzip (data , unzipDir );
668- loader = new URLClassLoader ( new URL []{ unzipDir . toUri (). toURL () },
669- Thread .currentThread ().getContextClassLoader ());
697+
698+ loader = new URLClassLoader ( new URL []{ unzipDir . toUri (). toURL () }, Thread .currentThread ().getContextClassLoader ());
670699 }
671700 else
672701 {
673702 javax .tools .JavaCompiler compiler = javax .tools .ToolProvider .getSystemJavaCompiler ();
703+
674704 if (compiler == null ) return "[install] No system compiler available (JDK required)." ;
705+
675706 Path srcFile = INSTALL_DIR .resolve (NAME + ".java" );
707+
676708 Files .write (srcFile , data );
709+
677710 if (compiler .run (null , null , null , srcFile .toString ()) != 0 )
678711 return "[install] Compilation failed for " + NAME + ".java" ;
679- loader = new URLClassLoader ( new URL []{ INSTALL_DIR . toUri (). toURL () },
680- Thread .currentThread ().getContextClassLoader ());
712+
713+ loader = new URLClassLoader ( new URL []{ INSTALL_DIR . toUri (). toURL () }, Thread .currentThread ().getContextClassLoader ());
681714 }
682715
683716 ModuleRegistry .register (new InstalledModule (NAME , dest , loader ));
684717
685718 String result = "installed " + detectedType + " " + byteCount + "B" ;
686- db .N21Store .storeModuleAction (SESSION .nationalId , NAME , "install" ,
687- SESSION .remoteIp , detectedType , byteCount , SIG_HEX , "" , result );
688719
689- CommonRails . printSystemComponent ( this , this . hashCode (),
690- ". ModuleInstallationService installed [" + NAME + "] for National ID "
691- + SESSION .nationalId + " ." );
720+ db . N21Store . storeModuleAction ( SESSION . nationalId , NAME , "install" , SESSION . remoteIp , detectedType , byteCount , SIG_HEX , "" , result );
721+
722+ CommonRails . printSystemComponent ( this , this . hashCode (), ". ModuleInstallationService installed [" + NAME + "] for National ID " + SESSION .nationalId + " ." );
692723
693724 return "[install] Module '" + NAME + "' installed (" + detectedType + ", " + byteCount + " bytes)." ;
694725 }
695- catch (NumberFormatException e ) { return "[install] Invalid byte count." ; }
696- catch (Exception e ) { ExceptionHandler .dispatch (e ); return "[install] Error: " + e .getMessage (); }
726+ catch (NumberFormatException e )
727+ {
728+ return "[install] Invalid byte count." ;
729+ }
730+ catch (Exception e )
731+ {
732+ ExceptionHandler .dispatch (e ); return "[install] Error: " + e .getMessage ();
733+ }
697734 }
698735
699736 private String unloadModule (final String NAME , final Session SESSION )
0 commit comments