@@ -36,6 +36,9 @@ import (
3636var trafficMap sync.Map // key: username, value: *TrafficStats
3737var activeConnections sync.Map // key: connection ID, value: connection info
3838
39+ // Session metadata storage: sessionID -> connection metadata for session management
40+ var sessionMetadata sync.Map // key: sessionID, value: sessionInfo (username, ip, etc.)
41+
3942// SetExecutableDir changes current working directory to exe dir and returns it.
4043func SetExecutableDir () (string , error ) {
4144 exePath , err := os .Executable ()
@@ -94,12 +97,14 @@ type TrafficStats struct {
9497
9598// User structure with role-based access control, domain/IP blocking, and logging
9699type User struct {
97- Username string `json:"username"`
98- Password string `json:"password"`
99- Role string `json:"role"` // "user" or "admin"
100- BlockedDomains []string `json:"blocked_domains"` // List of blocked domains/IPs with wildcard support
101- BlockedIPs []string `json:"blocked_ips"` // List of blocked IPs with wildcard support
102- Log string `json:"log"` // "yes" or "no" - enable/disable user access logging
100+ Username string `json:"username"`
101+ Password string `json:"password"`
102+ Role string `json:"role"` // "user" or "admin"
103+ BlockedDomains []string `json:"blocked_domains"` // List of blocked domains/IPs with wildcard support
104+ BlockedIPs []string `json:"blocked_ips"` // List of blocked IPs with wildcard support
105+ Log string `json:"log"` // "yes" or "no" - enable/disable user access logging
106+ MaxSessions int `json:"max_sessions"` // Maximum number of concurrent sessions (default: 2)
107+ SessionTTLSeconds int `json:"session_ttl_seconds"` // Session TTL in seconds (default: 300)
103108}
104109
105110var users map [string ]User
@@ -139,6 +144,13 @@ func loadUsers(path string) {
139144 // New format: array of User objects
140145 users = make (map [string ]User )
141146 for _ , user := range userList {
147+ // Set default values if not specified
148+ if user .MaxSessions <= 0 {
149+ user .MaxSessions = 2 // Default max sessions
150+ }
151+ if user .SessionTTLSeconds <= 0 {
152+ user .SessionTTLSeconds = 300 // Default TTL: 5 minutes (300 seconds)
153+ }
142154 users [user .Username ] = user
143155 }
144156 log .Printf ("✅ Loaded %d users with role-based access control" , len (users ))
@@ -155,9 +167,11 @@ func loadUsers(path string) {
155167 users = make (map [string ]User )
156168 for username , password := range oldUserPass {
157169 users [username ] = User {
158- Username : username ,
159- Password : password ,
160- Role : "user" , // Default role for backward compatibility
170+ Username : username ,
171+ Password : password ,
172+ Role : "user" , // Default role for backward compatibility
173+ MaxSessions : 2 , // Default max sessions
174+ SessionTTLSeconds : 300 , // Default TTL: 5 minutes
161175 }
162176 }
163177 log .Printf ("✅ Loaded %d users from legacy format (all set to 'user' role)" , len (users ))
@@ -371,8 +385,33 @@ func createSSHConfig() *ssh.ServerConfig {
371385
372386 if user , ok := users [c .User ()]; ok && user .Password == string (pass ) {
373387 delete (failedAttempts , ip ) // reset count
374- log .Printf ("✅ User %s (%s) authenticated from %s" , c .User (), user .Role , ip )
375- return nil , nil
388+
389+ // Get session manager
390+ sm := GetSessionManager ()
391+
392+ // Get client version (before handshake, we don't have full metadata yet)
393+ // We'll use IP and username for now, and update with client version in handleConnection
394+ clientVersion := "SSH-2.0-Unknown" // Will be updated in handleConnection
395+
396+ // Create session
397+ sessionID , err := sm .CreateSession (c .User (), ip , clientVersion )
398+ if err != nil {
399+ log .Printf ("⚠️ Failed to create session for %s: %v" , c .User (), err )
400+ return nil , fmt .Errorf ("session creation failed" )
401+ }
402+
403+ // Store sessionID in memory for later use in handleConnection
404+ sessionMetadata .Store (c .User ()+ "|" + ip , sessionID )
405+
406+ log .Printf ("✅ User %s (%s) authenticated from %s [Session: %s]" , c .User (), user .Role , ip , sessionID [:16 ]+ "..." )
407+
408+ // Return sessionID in permissions for later retrieval
409+ perms := & ssh.Permissions {
410+ Extensions : map [string ]string {
411+ "session_id" : sessionID ,
412+ },
413+ }
414+ return perms , nil
376415 }
377416
378417 logInvalidLogin (c .User (), string (pass ), ip , clientPort , serverPort )
@@ -482,7 +521,7 @@ func handleSession(channel ssh.Channel, requests <-chan *ssh.Request, username s
482521██████╔╝███████╗██║░░██║░░╚██╔╝░░███████╗██║░░██║
483522╚═════╝░╚══════╝╚═╝░░╚═╝░░░╚═╝░░░╚══════╝╚═╝░░╚═╝
484523
485- 🛡️ Welcome to Abdal 4iProto Server ver 5.10
524+ 🛡️ Welcome to Abdal 4iProto Server ver 6.2
486525🧠 Developed by: Ebrahim Shafiei (EbraSha)
487526✉️ Prof.Shafiei@Gmail.com
488527
@@ -515,14 +554,89 @@ func handleConnection(conn net.Conn, config *ssh.ServerConfig) {
515554 }
516555 defer sshConn .Close ()
517556
557+ // Get session manager
558+ sm := GetSessionManager ()
559+
560+ // Extract sessionID from permissions (set during PasswordCallback)
561+ username := sshConn .User ()
562+ userIP , _ , _ := net .SplitHostPort (sshConn .RemoteAddr ().String ())
563+ clientVersionBytes := sshConn .ClientVersion ()
564+ clientVersion := string (clientVersionBytes )
565+
566+ var sessionID string
567+ // Try to get sessionID from permissions first
568+ perms := sshConn .Permissions
569+ if perms != nil && perms .Extensions != nil {
570+ if sid , ok := perms .Extensions ["session_id" ]; ok {
571+ sessionID = sid
572+ }
573+ }
574+
575+ // If not found in permissions, try to get from memory (fallback)
576+ if sessionID == "" {
577+ if sid , ok := sessionMetadata .Load (username + "|" + userIP ); ok {
578+ sessionID = sid .(string )
579+ }
580+ }
581+
582+ // Validate session if sessionID exists
583+ if sessionID != "" {
584+ // Check if session is valid
585+ if ! sm .IsSessionValid (sessionID ) {
586+ log .Printf ("🔒 Invalid or expired session for user %s from %s, closing connection" , username , userIP )
587+ return // Connection will be closed by defer
588+ }
589+
590+ // Register connection with session manager
591+ sm .RegisterConnection (sessionID , sshConn )
592+
593+ // Update client version (was unknown during PasswordCallback)
594+ if err := sm .UpdateSessionClientVersion (sessionID , clientVersion ); err != nil {
595+ log .Printf ("⚠️ Failed to update session client version: %v" , err )
596+ }
597+
598+ // Update last seen
599+ if err := sm .UpdateSessionLastSeen (sessionID ); err != nil {
600+ log .Printf ("⚠️ Failed to update session last seen: %v" , err )
601+ }
602+
603+ // Unregister on connection close
604+ defer func () {
605+ sm .UnregisterConnection (sessionID )
606+ sm .CloseSession (sessionID )
607+ }()
608+
609+ log .Printf ("🔐 Session validated: %s for user %s from %s" , sessionID [:16 ]+ "..." , username , userIP )
610+ } else {
611+ log .Printf ("⚠️ No sessionID found for user %s from %s, connection may not be tracked" , username , userIP )
612+ }
613+
518614 // Track active connection
519615 connID := fmt .Sprintf ("%s-%d" , sshConn .RemoteAddr ().String (), time .Now ().UnixNano ())
520616 activeConnections .Store (connID , sshConn .RemoteAddr ().String ())
521617 defer activeConnections .Delete (connID )
522618
523- log .Printf ("New SSH connection from %s (%s)" , sshConn .RemoteAddr (), sshConn . ClientVersion () )
619+ log .Printf ("New SSH connection from %s (%s)" , sshConn .RemoteAddr (), clientVersion )
524620 go ssh .DiscardRequests (reqs )
525621
622+ // Start periodic session last seen update if session exists
623+ if sessionID != "" {
624+ go func () {
625+ ticker := time .NewTicker (30 * time .Second ) // Update every 30 seconds
626+ defer ticker .Stop ()
627+ for range ticker .C {
628+ if ! sm .IsSessionValid (sessionID ) {
629+ log .Printf ("🔒 Session expired for user %s, stopping updates" , username )
630+ return
631+ }
632+ if err := sm .UpdateSessionLastSeen (sessionID ); err != nil {
633+ log .Printf ("⚠️ Failed to update session last seen: %v" , err )
634+ return
635+ }
636+ }
637+ }()
638+ }
639+
526640 for newChannel := range chans {
527641
528642 // Accessing username and IP address
@@ -962,6 +1076,10 @@ func startServer() {
9621076
9631077 // Load existing traffic files to preserve traffic statistics across restarts
9641078 loadExistingTrafficFiles ()
1079+
1080+ // Initialize session manager and start cleanup routine
1081+ sm := GetSessionManager ()
1082+ sm .StartCleanupRoutine ()
9651083
9661084 for _ , port := range serverConfig .Ports {
9671085 go func (p int ) {
0 commit comments