Skip to content

Commit 23d6e0d

Browse files
committed
ftpd: add tls version, cipher and KEX to login log
Fixes #2124 Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
1 parent 4482862 commit 23d6e0d

2 files changed

Lines changed: 69 additions & 33 deletions

File tree

internal/ftpd/internal_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,16 +699,26 @@ func TestDriverMethodsNotImplemented(t *testing.T) {
699699

700700
func TestExtraData(t *testing.T) {
701701
mockCC := mockFTPClientContext{}
702-
_, ok := mockCC.Extra().(bool)
702+
_, ok := mockCC.Extra().(*tlsState)
703703
require.False(t, ok)
704-
mockCC.SetExtra(false)
705-
val, ok := mockCC.Extra().(bool)
704+
mockCC.SetExtra(&tlsState{
705+
LoginWithMutualTLS: false,
706+
Version: tls.VersionName(tls.VersionTLS13),
707+
Cipher: tls.CipherSuiteName(tls.TLS_AES_128_GCM_SHA256),
708+
KEX: tls.X25519MLKEM768.String(),
709+
})
710+
state, ok := mockCC.Extra().(*tlsState)
706711
require.True(t, ok)
707-
require.False(t, val)
708-
mockCC.SetExtra(true)
709-
val, ok = mockCC.Extra().(bool)
712+
require.False(t, state.LoginWithMutualTLS)
713+
require.Equal(t, tls.VersionName(tls.VersionTLS13), state.Version)
714+
require.Equal(t, tls.CipherSuiteName(tls.TLS_AES_128_GCM_SHA256), state.Cipher)
715+
require.Equal(t, tls.X25519MLKEM768.String(), state.KEX)
716+
mockCC.SetExtra(&tlsState{
717+
LoginWithMutualTLS: true,
718+
})
719+
state, ok = mockCC.Extra().(*tlsState)
710720
require.True(t, ok)
711-
require.True(t, val)
721+
require.True(t, state.LoginWithMutualTLS)
712722
}
713723

714724
func TestResolvePathErrors(t *testing.T) {

internal/ftpd/server.go

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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
4049
type 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
179188
func (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
232242
func (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) {
411433
func 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

Comments
 (0)