@@ -36,6 +36,15 @@ import (
3636 "github.com/drakkan/sftpgo/v2/internal/version"
3737)
3838
39+ // tlsState tracks TLS connection state for a client
40+ type tlsState struct {
41+ // LoginWithMutualTLS indicates whether the user logged in using TLS certificate authentication
42+ LoginWithMutualTLS bool
43+ Version string
44+ Cipher string
45+ KEX string
46+ }
47+
3948// Server implements the ftpserverlib MainDriver interface
4049type Server struct {
4150 ID int
@@ -178,7 +187,8 @@ func (s *Server) ClientDisconnected(cc ftpserver.ClientContext) {
178187// AuthUser authenticates the user and selects an handling driver
179188func (s * Server ) AuthUser (cc ftpserver.ClientContext , username , password string ) (ftpserver.ClientDriver , error ) {
180189 loginMethod := dataprovider .LoginMethodPassword
181- if verified , ok := cc .Extra ().(bool ); ok && verified {
190+ tlsState , ok := cc .Extra ().(* tlsState )
191+ if ok && tlsState != nil && tlsState .LoginWithMutualTLS {
182192 loginMethod = dataprovider .LoginMethodTLSCertificateAndPwd
183193 }
184194 ipAddr := util .GetIPFromRemoteAddress (cc .RemoteAddr ().String ())
@@ -230,40 +240,52 @@ func (s *Server) WrapPassiveListener(listener net.Listener) (net.Listener, error
230240
231241// VerifyConnection checks whether a user should be authenticated using a client certificate without prompting for a password
232242func (s * Server ) VerifyConnection (cc ftpserver.ClientContext , user string , tlsConn * tls.Conn ) (ftpserver.ClientDriver , error ) {
243+ if tlsConn == nil {
244+ return nil , nil
245+ }
246+ state := tlsConn .ConnectionState ()
247+ cc .SetExtra (& tlsState {
248+ LoginWithMutualTLS : false ,
249+ Cipher : tls .CipherSuiteName (state .CipherSuite ),
250+ Version : tls .VersionName (state .Version ),
251+ KEX : state .CurveID .String (),
252+ })
233253 if ! s .binding .isMutualTLSEnabled () {
234254 return nil , nil
235255 }
236- cc .SetExtra (false )
237- if tlsConn != nil {
238- state := tlsConn .ConnectionState ()
239- if len (state .PeerCertificates ) > 0 {
240- ipAddr := util .GetIPFromRemoteAddress (cc .RemoteAddr ().String ())
241- dbUser , err := dataprovider .CheckUserBeforeTLSAuth (user , ipAddr , common .ProtocolFTP , state .PeerCertificates [0 ])
256+
257+ if len (state .PeerCertificates ) > 0 {
258+ ipAddr := util .GetIPFromRemoteAddress (cc .RemoteAddr ().String ())
259+ dbUser , err := dataprovider .CheckUserBeforeTLSAuth (user , ipAddr , common .ProtocolFTP , state .PeerCertificates [0 ])
260+ if err != nil {
261+ dbUser .Username = user
262+ updateLoginMetrics (& dbUser , ipAddr , dataprovider .LoginMethodTLSCertificate , err , nil )
263+ return nil , dataprovider .ErrInvalidCredentials
264+ }
265+ if dbUser .IsTLSVerificationEnabled () {
266+ dbUser , err = dataprovider .CheckUserAndTLSCert (user , ipAddr , common .ProtocolFTP , state .PeerCertificates [0 ])
242267 if err != nil {
243- dbUser .Username = user
244- updateLoginMetrics (& dbUser , ipAddr , dataprovider .LoginMethodTLSCertificate , err , nil )
245- return nil , dataprovider .ErrInvalidCredentials
268+ return nil , err
246269 }
247- if dbUser .IsTLSVerificationEnabled () {
248- dbUser , err = dataprovider .CheckUserAndTLSCert (user , ipAddr , common .ProtocolFTP , state .PeerCertificates [0 ])
249- if err != nil {
250- return nil , err
251- }
252270
253- cc .SetExtra (true )
271+ cc .SetExtra (& tlsState {
272+ LoginWithMutualTLS : true ,
273+ Cipher : tls .CipherSuiteName (state .CipherSuite ),
274+ Version : tls .VersionName (state .Version ),
275+ KEX : state .CurveID .String (),
276+ })
254277
255- if dbUser .IsLoginMethodAllowed (dataprovider .LoginMethodTLSCertificate , common .ProtocolFTP ) {
256- connection , err := s .validateUser (dbUser , cc , dataprovider .LoginMethodTLSCertificate )
278+ if dbUser .IsLoginMethodAllowed (dataprovider .LoginMethodTLSCertificate , common .ProtocolFTP ) {
279+ connection , err := s .validateUser (dbUser , cc , dataprovider .LoginMethodTLSCertificate )
257280
258- defer updateLoginMetrics (& dbUser , ipAddr , dataprovider .LoginMethodTLSCertificate , err , connection )
281+ defer updateLoginMetrics (& dbUser , ipAddr , dataprovider .LoginMethodTLSCertificate , err , connection )
259282
260- if err != nil {
261- return nil , err
262- }
263- setStartDirectory (dbUser .Filters .StartDirectory , cc )
264- dataprovider .UpdateLastLogin (& dbUser )
265- return connection , nil
283+ if err != nil {
284+ return nil , err
266285 }
286+ setStartDirectory (dbUser .Filters .StartDirectory , cc )
287+ dataprovider .UpdateLastLogin (& dbUser )
288+ return connection , nil
267289 }
268290 }
269291 }
@@ -411,8 +433,12 @@ func setStartDirectory(startDirectory string, cc ftpserver.ClientContext) {
411433func updateLoginMetrics (user * dataprovider.User , ip , loginMethod string , err error , c * Connection ) {
412434 metric .AddLoginAttempt (loginMethod )
413435 if err == nil {
436+ info := ""
437+ if tlsState , ok := c .clientContext .Extra ().(* tlsState ); ok && tlsState != nil {
438+ info = fmt .Sprintf ("%s - %s - %s" , tlsState .Version , tlsState .Cipher , tlsState .KEX )
439+ }
414440 logger .LoginLog (user .Username , ip , loginMethod , common .ProtocolFTP , c .ID , c .GetClientVersion (),
415- c .clientContext .HasTLSForControl (), "" )
441+ c .clientContext .HasTLSForControl (), info )
416442 plugin .Handler .NotifyLogEvent (notifier .LogEventTypeLoginOK , common .ProtocolFTP , user .Username , ip , "" , nil )
417443 common .DelayLogin (nil )
418444 } else if err != common .ErrInternalFailure {
0 commit comments