diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml
index 58682430..697ed854 100644
--- a/.github/workflows/development.yml
+++ b/.github/workflows/development.yml
@@ -236,7 +236,7 @@ jobs:
- name: Build
run: |
- go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
+ go build -trimpath -tags nopgxregisterdefaulttypes,nogcs,nos3,noportable,nobolt,nomysql,nopgsql,nosqlite,nometrics,noazblob,unixcrypt -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
./sftpgo -v
cp -r openapi static templates internal/bundle/
go build -trimpath -tags nopgxregisterdefaulttypes,bundle -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -o sftpgo
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index c9ef5653..ec1a0ed0 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -72,6 +72,9 @@ jobs:
elif [[ $DOCKER_PKG == debian-plugins ]]; then
VERSION="${VERSION}-plugins"
VERSION_SLIM="${VERSION}-slim"
+ FEATURES="${FEATURES},unixcrypt"
+ elif [[ $DOCKER_PKG == debian ]]; then
+ FEATURES="${FEATURES},unixcrypt"
fi
DOCKER_IMAGES=("drakkan/sftpgo" "ghcr.io/drakkan/sftpgo")
TAGS="${DOCKER_IMAGES[0]}:${VERSION}"
diff --git a/docs/build-from-source.md b/docs/build-from-source.md
index b8287676..052dd477 100644
--- a/docs/build-from-source.md
+++ b/docs/build-from-source.md
@@ -14,6 +14,7 @@ The following build tags are available:
- `noportable`, disable portable mode, default enabled
- `nometrics`, disable Prometheus metrics, default enabled
- `bundle`, embed static files and templates. Before building with this tag enabled you have to copy `openapi`, `static` and `templates` dirs to `internal/bundle` directory. Default disabled
+- `unixcrypt`, enable linking to `libcrypt`, default disabled, requires `CGO`
If no build tag is specified the build will include the default features.
diff --git a/go.mod b/go.mod
index 190933af..5352381b 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
+ github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
github.com/aws/aws-sdk-go-v2 v1.17.7
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
@@ -35,7 +36,7 @@ require (
github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-plugin v1.4.10-0.20230306173702-d78f3fc2891d
github.com/hashicorp/go-retryablehttp v0.7.2
- github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90
+ github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
github.com/klauspost/compress v1.16.3
github.com/lestrrat-go/jwx/v2 v2.0.9
diff --git a/go.sum b/go.sum
index 16099809..3c62d13b 100644
--- a/go.sum
+++ b/go.sum
@@ -534,6 +534,8 @@ github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
+github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 h1:I9YN9WMo3SUh7p/4wKeNvD/IQla3U3SUa61U7ul+xM4=
+github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964/go.mod h1:eFiR01PwTcpbzXtdMces7zxg6utvFM5puiWHpWB8D/k=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -1389,8 +1391,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
-github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90 h1:gBugq4KF3zkdaM4oQHSfhUFAfkVQOQpbD20wNggWPP4=
-github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo=
+github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf h1:BLqUs5GEGMJ+h9s63INbbwXUXe95wvhvh1esISGgau0=
+github.com/jackc/pgx/v5 v5.3.2-0.20230325152211-ca022267dbbf/go.mod h1:sU+RaYl9qnhD3Ce+mwnFii6YEPx70mCYghBzKvqq4qo=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go
index d0e69d03..e310518d 100644
--- a/internal/dataprovider/dataprovider.go
+++ b/internal/dataprovider/dataprovider.go
@@ -99,6 +99,7 @@ const (
md5cryptApr1PwdPrefix = "$apr1$"
sha256cryptPwdPrefix = "$5$"
sha512cryptPwdPrefix = "$6$"
+ yescryptPwdPrefix = "$y$"
md5LDAPPwdPrefix = "{MD5}"
trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
operationAdd = "add"
@@ -165,10 +166,11 @@ var (
internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix,
- sha256cryptPwdPrefix, sha512cryptPwdPrefix}
- pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
- pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
- unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix}
+ sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix}
+ pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
+ pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
+ unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix,
+ yescryptPwdPrefix}
sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
logSender = "dataprovider"
sqlTableUsers string
@@ -3081,13 +3083,13 @@ func isPasswordOK(user *User, password string) (bool, error) {
return match, err
}
updatePwd = config.PasswordHashing.Algo != HashingAlgoArgon2ID
- } else if util.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
- match, err = comparePbkdf2PasswordAndHash(password, user.Password)
+ } else if util.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) {
+ match, err = compareUnixPasswordAndHash(user, password)
if err != nil {
return match, err
}
- } else if util.IsStringPrefixInSlice(user.Password, unixPwdPrefixes) {
- match, err = compareUnixPasswordAndHash(user, password)
+ } else if util.IsStringPrefixInSlice(user.Password, pbkdfPwdPrefixes) {
+ match, err = comparePbkdf2PasswordAndHash(password, user.Password)
if err != nil {
return match, err
}
@@ -3258,6 +3260,9 @@ func checkUserAndPubKey(user *User, pubKey []byte, isSSHCert bool) (User, string
}
func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
+ if strings.HasPrefix(user.Password, yescryptPwdPrefix) {
+ return compareYescryptPassword(user.Password, password)
+ }
var crypter crypt.Crypter
if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
crypter = sha512_crypt.New()
diff --git a/internal/dataprovider/unixcrypt.go b/internal/dataprovider/unixcrypt.go
new file mode 100644
index 00000000..aff3cbc4
--- /dev/null
+++ b/internal/dataprovider/unixcrypt.go
@@ -0,0 +1,39 @@
+// Copyright (C) 2019-2023 Nicola Murino
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+//go:build unixcrypt
+// +build unixcrypt
+
+package dataprovider
+
+import (
+ "strings"
+
+ "github.com/amoghe/go-crypt"
+
+ "github.com/drakkan/sftpgo/v2/internal/version"
+)
+
+func init() {
+ version.AddFeature("+unixcrypt")
+}
+
+func compareYescryptPassword(hashedPwd, plainPwd string) (bool, error) {
+ lastIdx := strings.LastIndex(hashedPwd, "$")
+ pwd, err := crypt.Crypt(plainPwd, hashedPwd[:lastIdx+1])
+ if err != nil {
+ return false, err
+ }
+ return pwd == hashedPwd, nil
+}
diff --git a/internal/dataprovider/unixcrypt_disabled.go b/internal/dataprovider/unixcrypt_disabled.go
new file mode 100644
index 00000000..71e63ad9
--- /dev/null
+++ b/internal/dataprovider/unixcrypt_disabled.go
@@ -0,0 +1,32 @@
+// Copyright (C) 2019-2023 Nicola Murino
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+//go:build !unixcrypt
+// +build !unixcrypt
+
+package dataprovider
+
+import (
+ "errors"
+
+ "github.com/drakkan/sftpgo/v2/internal/version"
+)
+
+func init() {
+ version.AddFeature("-unixcrypt")
+}
+
+func compareYescryptPassword(_, _ string) (bool, error) {
+ return false, errors.New("yescrypt hash format is not supported or disabled")
+}
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 198cd57d..dd3ea8e5 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -7175,7 +7175,7 @@ components:
type: array
items:
type: string
- description: 'Features for the current build. Available features are `portable`, `bolt`, `mysql`, `sqlite`, `pgsql`, `s3`, `gcs`, `azblob`, `metrics`. If a feature is available it has a `+` prefix, otherwise a `-` prefix'
+ description: 'Features for the current build. Available features are `portable`, `bolt`, `mysql`, `sqlite`, `pgsql`, `s3`, `gcs`, `azblob`, `metrics`, `unixcrypt`. If a feature is available it has a `+` prefix, otherwise a `-` prefix'
Token:
type: object
properties: