ftpd: allow to require TLS on a per-user basis

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2022-07-26 18:51:39 +02:00
parent 81de7d271e
commit ec5da8b4a5
15 changed files with 182 additions and 21 deletions

View File

@@ -926,6 +926,61 @@ func TestLoginNonExistentUser(t *testing.T) {
assert.Error(t, err)
}
func TestFTPSecurity(t *testing.T) {
u := getTestUser()
u.Filters.FTPSecurity = 1
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
client, err := getFTPClient(user, true, nil)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
_, err = getFTPClient(user, false, nil)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "TLS is required")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestGroupFTPSecurity(t *testing.T) {
g := getTestGroup()
g.UserSettings.Filters.FTPSecurity = 1
group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
assert.NoError(t, err)
u := getTestUser()
u.Groups = []sdk.GroupMapping{
{
Name: group.Name,
Type: sdk.GroupTypePrimary,
},
}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
client, err := getFTPClient(user, true, nil)
if assert.NoError(t, err) {
err = checkBasicFTP(client)
assert.NoError(t, err)
err := client.Quit()
assert.NoError(t, err)
}
_, err = getFTPClient(user, false, nil)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "TLS is required")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveGroup(group, http.StatusOK)
assert.NoError(t, err)
}
func TestLoginExternalAuth(t *testing.T) {
if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows")
@@ -1055,6 +1110,19 @@ func TestPreLoginHook(t *testing.T) {
err := client.Quit()
assert.NoError(t, err)
}
user.Status = 0
user.Filters.FTPSecurity = 1
err = os.WriteFile(preLoginPath, getPreLoginScriptContent(user, false), os.ModePerm)
assert.NoError(t, err)
client, err = getFTPClient(u, true, nil)
if !assert.Error(t, err) {
err := client.Quit()
assert.NoError(t, err)
}
_, err = getFTPClient(user, false, nil)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "TLS is required")
}
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)

View File

@@ -325,6 +325,10 @@ func (cc mockFTPClientContext) HasTLSForTransfers() bool {
return false
}
func (cc mockFTPClientContext) SetTLSRequirement(requirement ftpserver.TLSRequirement) error {
return nil
}
func (cc mockFTPClientContext) GetLastCommand() string {
return ""
}

View File

@@ -221,6 +221,23 @@ func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string)
return connection, nil
}
// PreAuthUser implements the MainDriverExtensionUserVerifier interface
func (s *Server) PreAuthUser(cc ftpserver.ClientContext, username string) error {
if s.binding.TLSMode == 0 && s.tlsConfig != nil {
user, err := dataprovider.GetFTPPreAuthUser(username, util.GetIPFromRemoteAddress(cc.RemoteAddr().String()))
if err == nil {
if user.Filters.FTPSecurity == 1 {
return cc.SetTLSRequirement(ftpserver.MandatoryEncryption)
}
return nil
}
if _, ok := err.(*util.RecordNotFoundError); !ok {
return common.ErrInternalFailure
}
}
return nil
}
// WrapPassiveListener implements the MainDriverExtensionPassiveWrapper interface
func (s *Server) WrapPassiveListener(listener net.Listener) (net.Listener, error) {
if s.binding.HasProxy() {