package client

import (
	
	
	
	
	
	

	
	
	
	
)

// sessions hold TGTs and are keyed on the realm name
type sessions struct {
	Entries map[string]*session
	mux     sync.RWMutex
}

// destroy erases all sessions
func ( *sessions) () {
	.mux.Lock()
	defer .mux.Unlock()
	for ,  := range .Entries {
		.destroy()
		delete(.Entries, )
	}
}

// update replaces a session with the one provided or adds it as a new one
func ( *sessions) ( *session) {
	.mux.Lock()
	defer .mux.Unlock()
	// if a session already exists for this, cancel its auto renew.
	if ,  := .Entries[.realm];  {
		if  !=  {
			// Session in the sessions cache is not the same as one provided.
			// Cancel the one in the cache and add this one.
			.mux.Lock()
			defer .mux.Unlock()
			if .cancel != nil {
				.cancel <- true
			}
			.Entries[.realm] = 
			return
		}
	}
	// No session for this realm was found so just add it
	.Entries[.realm] = 
}

// get returns the session for the realm specified
func ( *sessions) ( string) (*session, bool) {
	.mux.RLock()
	defer .mux.RUnlock()
	,  := .Entries[]
	return , 
}

// session holds the TGT details for a realm
type session struct {
	realm                string
	authTime             time.Time
	endTime              time.Time
	renewTill            time.Time
	tgt                  messages.Ticket
	sessionKey           types.EncryptionKey
	sessionKeyExpiration time.Time
	cancel               chan bool
	mux                  sync.RWMutex
}

// jsonSession is used to enable marshaling some information of a session in a JSON format
type jsonSession struct {
	Realm                string
	AuthTime             time.Time
	EndTime              time.Time
	RenewTill            time.Time
	SessionKeyExpiration time.Time
}

// AddSession adds a session for a realm with a TGT to the client's session cache.
// A goroutine is started to automatically renew the TGT before expiry.
func ( *Client) ( messages.Ticket,  messages.EncKDCRepPart) {
	if strings.ToLower(.SName.NameString[0]) != "krbtgt" {
		// Not a TGT
		return
	}
	 := .SName.NameString[len(.SName.NameString)-1]
	 := &session{
		realm:                ,
		authTime:             .AuthTime,
		endTime:              .EndTime,
		renewTill:            .RenewTill,
		tgt:                  ,
		sessionKey:           .Key,
		sessionKeyExpiration: .KeyExpiration,
	}
	.sessions.update()
	.enableAutoSessionRenewal()
	.Log("TGT session added for %s (EndTime: %v)", , .EndTime)
}

// update overwrites the session details with those from the TGT and decrypted encPart
func ( *session) ( messages.Ticket,  messages.EncKDCRepPart) {
	.mux.Lock()
	defer .mux.Unlock()
	.authTime = .AuthTime
	.endTime = .EndTime
	.renewTill = .RenewTill
	.tgt = 
	.sessionKey = .Key
	.sessionKeyExpiration = .KeyExpiration
}

// destroy will cancel any auto renewal of the session and set the expiration times to the current time
func ( *session) () {
	.mux.Lock()
	defer .mux.Unlock()
	if .cancel != nil {
		.cancel <- true
	}
	.endTime = time.Now().UTC()
	.renewTill = .endTime
	.sessionKeyExpiration = .endTime
}

// valid informs if the TGT is still within the valid time window
func ( *session) () bool {
	.mux.RLock()
	defer .mux.RUnlock()
	 := time.Now().UTC()
	if .Before(.endTime) && .authTime.Before() {
		return true
	}
	return false
}

// tgtDetails is a thread safe way to get the session's realm, TGT and session key values
func ( *session) () (string, messages.Ticket, types.EncryptionKey) {
	.mux.RLock()
	defer .mux.RUnlock()
	return .realm, .tgt, .sessionKey
}

// timeDetails is a thread safe way to get the session's validity time values
func ( *session) () (string, time.Time, time.Time, time.Time, time.Time) {
	.mux.RLock()
	defer .mux.RUnlock()
	return .realm, .authTime, .endTime, .renewTill, .sessionKeyExpiration
}

// JSON return information about the held sessions in a JSON format.
func ( *sessions) () (string, error) {
	.mux.RLock()
	defer .mux.RUnlock()
	var  []jsonSession
	 := make([]string, 0, len(.Entries))
	for  := range .Entries {
		 = append(, )
	}
	sort.Strings()
	for ,  := range  {
		, , , ,  := .Entries[].timeDetails()
		 := jsonSession{
			Realm:                ,
			AuthTime:             ,
			EndTime:              ,
			RenewTill:            ,
			SessionKeyExpiration: ,
		}
		 = append(, )
	}
	,  := json.MarshalIndent(, "", "  ")
	if  != nil {
		return "", 
	}
	return string(), nil
}

// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
func ( *Client) ( *session) {
	var  *time.Timer
	.mux.Lock()
	.cancel = make(chan bool, 1)
	.mux.Unlock()
	go func( *session) {
		for {
			.mux.RLock()
			 := (.endTime.Sub(time.Now().UTC()) * 5) / 6
			.mux.RUnlock()
			if  < 0 {
				return
			}
			 = time.NewTimer()
			select {
			case <-.C:
				,  := .refreshSession()
				if  != nil {
					.Log("error refreshing session: %v", )
				}
				if ! &&  == nil {
					// end this goroutine as there will have been a new login and new auto renewal goroutine created.
					return
				}
			case <-.cancel:
				// cancel has been called. Stop the timer and exit.
				.Stop()
				return
			}
		}
	}()
}

// renewTGT renews the client's TGT session.
func ( *Client) ( *session) error {
	, ,  := .tgtDetails()
	 := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", },
	}
	, ,  := .TGSREQGenerateAndExchange(, .Credentials.Domain(), , , true)
	if  != nil {
		return krberror.Errorf(, krberror.KRBMsgError, "error renewing TGT for %s", )
	}
	.update(.Ticket, .DecryptedEncPart)
	.sessions.update()
	.Log("TGT session renewed for %s (EndTime: %v)", , .DecryptedEncPart.EndTime)
	return nil
}

// refreshSession updates either through renewal or creating a new login.
// The boolean indicates if the update was a renewal.
func ( *Client) ( *session) (bool, error) {
	.mux.RLock()
	 := .realm
	 := .renewTill
	.mux.RUnlock()
	.Log("refreshing TGT session for %s", )
	if time.Now().UTC().Before() {
		 := .renewTGT()
		return true, 
	}
	 := .realmLogin()
	return false, 
}

// ensureValidSession makes sure there is a valid session for the realm
func ( *Client) ( string) error {
	,  := .sessions.get()
	if  {
		.mux.RLock()
		 := .endTime.Sub(.authTime) / 6
		if .endTime.Sub(time.Now().UTC()) >  {
			.mux.RUnlock()
			return nil
		}
		.mux.RUnlock()
		,  := .refreshSession()
		return 
	}
	return .realmLogin()
}

// sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm
func ( *Client) ( string) ( messages.Ticket,  types.EncryptionKey,  error) {
	 = .ensureValidSession()
	if  != nil {
		return
	}
	,  := .sessions.get()
	if ! {
		 = fmt.Errorf("could not find TGT session for %s", )
		return
	}
	_, ,  = .tgtDetails()
	return
}

// sessionTimes provides the timing information with regards to a session for the realm specified.
func ( *Client) ( string) (, , ,  time.Time,  error) {
	,  := .sessions.get()
	if ! {
		 = fmt.Errorf("could not find TGT session for %s", )
		return
	}
	_, , , ,  = .timeDetails()
	return
}

// spnRealm resolves the realm name of a service principal name
func ( *Client) ( types.PrincipalName) string {
	return .Config.ResolveRealm(.NameString[len(.NameString)-1])
}