瀏覽代碼

update deps

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 1 年之前
父節點
當前提交
be2e24e63d

+ 8 - 8
.github/workflows/development.yml

@@ -11,11 +11,11 @@ jobs:
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     strategy:
     strategy:
       matrix:
       matrix:
-        go: [1.19]
+        go: ['1.20']
         os: [ubuntu-latest, macos-latest]
         os: [ubuntu-latest, macos-latest]
         upload-coverage: [true]
         upload-coverage: [true]
         include:
         include:
-          - go: 1.19
+          - go: '1.20'
             os: windows-latest
             os: windows-latest
             upload-coverage: false
             upload-coverage: false
 
 
@@ -232,7 +232,7 @@ jobs:
       - name: Set up Go
       - name: Set up Go
         uses: actions/setup-go@v3
         uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
 
       - name: Build
       - name: Build
         run: |
         run: |
@@ -252,7 +252,7 @@ jobs:
       - name: Set up Go
       - name: Set up Go
         uses: actions/setup-go@v3
         uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
 
       - name: Build
       - name: Build
         run: |
         run: |
@@ -326,7 +326,7 @@ jobs:
       - name: Set up Go
       - name: Set up Go
         uses: actions/setup-go@v3
         uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
 
       - name: Build
       - name: Build
         run: |
         run: |
@@ -438,7 +438,7 @@ jobs:
           echo 'apt-get install -q -y curl gcc' >> build.sh
           echo 'apt-get install -q -y curl gcc' >> build.sh
           if [ ${{ matrix.go }} == 'latest' ]
           if [ ${{ matrix.go }} == 'latest' ]
           then
           then
-            echo 'GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)' >> build.sh
+            echo 'GO_VERSION=$(curl -L https://go.dev/VERSION?m=text | head -n 1)' >> build.sh
           else
           else
             echo 'GO_VERSION=${{ matrix.go }}' >> build.sh
             echo 'GO_VERSION=${{ matrix.go }}' >> build.sh
           fi
           fi
@@ -481,7 +481,7 @@ jobs:
             apt-get install -q -y curl gcc
             apt-get install -q -y curl gcc
             if [ ${{ matrix.go }} == 'latest' ]
             if [ ${{ matrix.go }} == 'latest' ]
             then
             then
-              GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
+              GO_VERSION=$(curl -L https://go.dev/VERSION?m=text | head -n 1)
             else
             else
               GO_VERSION=${{ matrix.go }}
               GO_VERSION=${{ matrix.go }}
             fi
             fi
@@ -546,7 +546,7 @@ jobs:
       - name: Set up Go
       - name: Set up Go
         uses: actions/setup-go@v3
         uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.19'
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
       - name: Run golangci-lint
       - name: Run golangci-lint
         uses: golangci/golangci-lint-action@v3
         uses: golangci/golangci-lint-action@v3

+ 1 - 1
.github/workflows/docker.yml

@@ -5,7 +5,7 @@ on:
   #  - cron: '0 4 * * *' # everyday at 4:00 AM UTC
   #  - cron: '0 4 * * *' # everyday at 4:00 AM UTC
   push:
   push:
     branches:
     branches:
-      - 2.4.x
+      - main
     tags:
     tags:
       - v*
       - v*
   pull_request:
   pull_request:

+ 1 - 1
.github/workflows/release.yml

@@ -5,7 +5,7 @@ on:
     tags: 'v*'
     tags: 'v*'
 
 
 env:
 env:
-  GO_VERSION: 1.19.8
+  GO_VERSION: 1.20.12
 
 
 jobs:
 jobs:
   prepare-sources-with-deps:
   prepare-sources-with-deps:

+ 125 - 109
go.mod

@@ -3,166 +3,182 @@ module github.com/drakkan/sftpgo/v2
 go 1.19
 go 1.19
 
 
 require (
 require (
-	cloud.google.com/go/storage v1.30.1
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0
-	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0
+	cloud.google.com/go/storage v1.35.1
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1
+	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
 	github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
 	github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
-	github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736
-	github.com/aws/aws-sdk-go-v2 v1.17.8
-	github.com/aws/aws-sdk-go-v2/config v1.18.20
-	github.com/aws/aws-sdk-go-v2/credentials v1.13.19
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2
-	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.61
-	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.14.8
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.31.2
-	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.19.2
-	github.com/aws/aws-sdk-go-v2/service/sts v1.18.8
-	github.com/cockroachdb/cockroach-go/v2 v2.3.3
-	github.com/coreos/go-oidc/v3 v3.5.0
+	github.com/alexedwards/argon2id v1.0.0
+	github.com/aws/aws-sdk-go-v2 v1.24.0
+	github.com/aws/aws-sdk-go-v2/config v1.26.1
+	github.com/aws/aws-sdk-go-v2/credentials v1.16.12
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10
+	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7
+	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.5
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5
+	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.25.5
+	github.com/aws/aws-sdk-go-v2/service/sts v1.26.5
+	github.com/cockroachdb/cockroach-go/v2 v2.3.5
+	github.com/coreos/go-oidc/v3 v3.9.0
 	github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
 	github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
 	github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
-	github.com/fclairamb/ftpserverlib v0.21.0
+	github.com/fclairamb/ftpserverlib v0.22.0
 	github.com/fclairamb/go-log v0.4.1
 	github.com/fclairamb/go-log v0.4.1
-	github.com/go-acme/lego/v4 v4.10.2
-	github.com/go-chi/chi/v5 v5.0.8
-	github.com/go-chi/jwtauth/v5 v5.1.0
-	github.com/go-chi/render v1.0.2
-	github.com/go-sql-driver/mysql v1.7.0
+	github.com/go-acme/lego/v4 v4.14.2
+	github.com/go-chi/chi/v5 v5.0.10
+	github.com/go-chi/jwtauth/v5 v5.3.0
+	github.com/go-chi/render v1.0.3
+	github.com/go-sql-driver/mysql v1.7.1
 	github.com/golang/mock v1.6.0
 	github.com/golang/mock v1.6.0
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
-	github.com/google/uuid v1.3.0
+	github.com/google/uuid v1.5.0
 	github.com/grandcat/zeroconf v1.0.0
 	github.com/grandcat/zeroconf v1.0.0
-	github.com/hashicorp/go-hclog v1.5.0
-	github.com/hashicorp/go-plugin v1.4.10-0.20230321181155-4b35dc2fedaa
-	github.com/hashicorp/go-retryablehttp v0.7.2
-	github.com/jackc/pgx/v5 v5.3.2-0.20230324225134-e9d64ec29d90
+	github.com/hashicorp/go-hclog v1.6.2
+	github.com/hashicorp/go-plugin v1.6.0
+	github.com/hashicorp/go-retryablehttp v0.7.5
+	github.com/jackc/pgx/v5 v5.4.3
 	github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
 	github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
-	github.com/klauspost/compress v1.16.4
-	github.com/lestrrat-go/jwx/v2 v2.0.9
+	github.com/klauspost/compress v1.17.4
+	github.com/lestrrat-go/jwx/v2 v2.0.18
 	github.com/lithammer/shortuuid/v3 v3.0.7
 	github.com/lithammer/shortuuid/v3 v3.0.7
-	github.com/mattn/go-sqlite3 v1.14.16
-	github.com/mhale/smtpd v0.8.0
+	github.com/mattn/go-sqlite3 v1.14.18
+	github.com/mhale/smtpd v0.8.1
 	github.com/minio/sio v0.3.1
 	github.com/minio/sio v0.3.1
-	github.com/otiai10/copy v1.10.0
+	github.com/otiai10/copy v1.14.0
 	github.com/pires/go-proxyproto v0.7.0
 	github.com/pires/go-proxyproto v0.7.0
-	github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6
+	github.com/pkg/sftp v1.13.6
 	github.com/pquerna/otp v1.4.0
 	github.com/pquerna/otp v1.4.0
-	github.com/prometheus/client_golang v1.14.0
+	github.com/prometheus/client_golang v1.17.0
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/robfig/cron/v3 v3.0.1
-	github.com/rs/cors v1.8.3
-	github.com/rs/xid v1.4.0
-	github.com/rs/zerolog v1.29.0
-	github.com/sftpgo/sdk v0.1.2
-	github.com/shirou/gopsutil/v3 v3.23.3
-	github.com/spf13/afero v1.9.5
-	github.com/spf13/cobra v1.7.0
-	github.com/spf13/viper v1.15.0
-	github.com/stretchr/testify v1.8.2
-	github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2
-	github.com/subosito/gotenv v1.4.2
+	github.com/rs/cors v1.10.1
+	github.com/rs/xid v1.5.0
+	github.com/rs/zerolog v1.31.0
+	github.com/sftpgo/sdk v0.0.0-20231214102927-0493fcf3bc52
+	github.com/shirou/gopsutil/v3 v3.23.11
+	github.com/spf13/afero v1.11.0
+	github.com/spf13/cobra v1.8.0
+	github.com/spf13/viper v1.18.1
+	github.com/stretchr/testify v1.8.4
+	github.com/studio-b12/gowebdav v0.9.0
+	github.com/subosito/gotenv v1.6.0
 	github.com/unrolled/secure v1.13.0
 	github.com/unrolled/secure v1.13.0
 	github.com/wagslane/go-password-validator v0.3.0
 	github.com/wagslane/go-password-validator v0.3.0
-	github.com/xhit/go-simple-mail/v2 v2.13.0
+	github.com/xhit/go-simple-mail/v2 v2.16.0
 	github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
 	github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
-	go.etcd.io/bbolt v1.3.7
-	go.uber.org/automaxprocs v1.5.2
-	gocloud.dev v0.29.0
-	golang.org/x/crypto v0.7.0
-	golang.org/x/net v0.9.0
-	golang.org/x/oauth2 v0.7.0
-	golang.org/x/sys v0.7.0
-	golang.org/x/time v0.3.0
-	google.golang.org/api v0.116.0
+	go.etcd.io/bbolt v1.3.8
+	go.uber.org/automaxprocs v1.5.3
+	gocloud.dev v0.35.0
+	golang.org/x/crypto v0.16.0
+	golang.org/x/net v0.19.0
+	golang.org/x/oauth2 v0.15.0
+	golang.org/x/sys v0.15.0
+	golang.org/x/time v0.5.0
+	google.golang.org/api v0.154.0
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 )
 )
 
 
 require (
 require (
-	cloud.google.com/go v0.110.0 // indirect
-	cloud.google.com/go/compute v1.19.0 // indirect
+	cloud.google.com/go v0.111.0 // indirect
+	cloud.google.com/go/compute v1.23.3 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
-	cloud.google.com/go/iam v1.0.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
+	cloud.google.com/go/iam v1.1.5 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
-	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.26 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.24 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.27 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.12.7 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.7 // indirect
-	github.com/aws/smithy-go v1.13.5 // indirect
+	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
+	github.com/aws/smithy-go v1.19.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
-	github.com/cenkalti/backoff/v4 v4.2.0 // indirect
+	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
-	github.com/fatih/color v1.15.0 // indirect
-	github.com/fsnotify/fsnotify v1.6.0 // indirect
-	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
-	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
+	github.com/fatih/color v1.16.0 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/go-jose/go-jose/v3 v3.0.1 // indirect
+	github.com/go-logr/logr v1.3.0 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-ole/go-ole v1.3.0 // indirect
 	github.com/go-test/deep v1.1.0 // indirect
 	github.com/go-test/deep v1.1.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
-	github.com/google/go-cmp v0.5.9 // indirect
-	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
-	github.com/googleapis/gax-go/v2 v2.8.0 // indirect
+	github.com/google/s2a-go v0.1.7 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+	github.com/googleapis/gax-go/v2 v2.12.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
-	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+	github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
 	github.com/kr/fs v0.1.0 // indirect
 	github.com/kr/fs v0.1.0 // indirect
-	github.com/lestrrat-go/blackmagic v1.0.1 // indirect
+	github.com/lestrrat-go/blackmagic v1.0.2 // indirect
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/httprc v1.0.4 // indirect
 	github.com/lestrrat-go/httprc v1.0.4 // indirect
 	github.com/lestrrat-go/iter v1.0.2 // indirect
 	github.com/lestrrat-go/iter v1.0.2 // indirect
 	github.com/lestrrat-go/option v1.0.1 // indirect
 	github.com/lestrrat-go/option v1.0.1 // indirect
-	github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
+	github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.18 // indirect
-	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
-	github.com/miekg/dns v1.1.53 // indirect
-	github.com/minio/sha256-simd v1.0.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+	github.com/miekg/dns v1.1.57 // indirect
+	github.com/minio/sha256-simd v1.0.1 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/oklog/run v1.1.0 // indirect
 	github.com/oklog/run v1.1.0 // indirect
-	github.com/pelletier/go-toml/v2 v2.0.7 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
-	github.com/prometheus/client_model v0.3.0 // indirect
-	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.9.0 // indirect
+	github.com/prometheus/client_model v0.5.0 // indirect
+	github.com/prometheus/common v0.45.0 // indirect
+	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
-	github.com/shoenig/go-m1cpu v0.1.5 // indirect
-	github.com/spf13/cast v1.5.0 // indirect
-	github.com/spf13/jwalterweatherman v1.1.0 // indirect
+	github.com/sagikazarmark/locafero v0.4.0 // indirect
+	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/segmentio/asm v1.2.0 // indirect
+	github.com/shoenig/go-m1cpu v0.1.6 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/cast v1.6.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/tklauser/go-sysconf v0.3.11 // indirect
-	github.com/tklauser/numcpus v0.6.0 // indirect
+	github.com/tklauser/go-sysconf v0.3.13 // indirect
+	github.com/tklauser/numcpus v0.7.0 // indirect
 	github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
 	github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
-	github.com/yusufpapurcu/wmi v1.2.2 // indirect
+	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	golang.org/x/mod v0.10.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
-	golang.org/x/tools v0.8.0 // indirect
-	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
-	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
-	google.golang.org/grpc v1.54.0 // indirect
-	google.golang.org/protobuf v1.30.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
+	go.opentelemetry.io/otel v1.21.0 // indirect
+	go.opentelemetry.io/otel/metric v1.21.0 // indirect
+	go.opentelemetry.io/otel/trace v1.21.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
+	golang.org/x/mod v0.14.0 // indirect
+	golang.org/x/sync v0.5.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.16.1 // indirect
+	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
+	google.golang.org/appengine v1.6.8 // indirect
+	google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/grpc v1.60.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 )
@@ -170,5 +186,5 @@ require (
 replace (
 replace (
 	github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
 	github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
 	github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0
 	github.com/robfig/cron/v3 => github.com/drakkan/cron/v3 v3.0.0-20230222140221-217a1e4d96c0
-	golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20230106095953-5417b4dfde62
+	golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20231109074321-cb261f488895
 )
 )

File diff suppressed because it is too large
+ 276 - 2600
go.sum


+ 1 - 1
internal/dataprovider/sqlqueries.go

@@ -219,7 +219,7 @@ func getUsersInGroupsQuery(numArgs int) string {
 	} else {
 	} else {
 		sb.WriteString("('')")
 		sb.WriteString("('')")
 	}
 	}
-	return fmt.Sprintf(`SELECT username FROM %s WHERE id IN (SELECT user_id from %s WHERE group_id IN (SELECT id FROM %s WHERE name IN (%s)))`,
+	return fmt.Sprintf(`SELECT username FROM %s WHERE id IN (SELECT user_id from %s WHERE group_id IN (SELECT id FROM %s WHERE name IN %s))`,
 		sqlTableUsers, sqlTableUsersGroupsMapping, getSQLQuotedName(sqlTableGroups), sb.String())
 		sqlTableUsers, sqlTableUsersGroupsMapping, getSQLQuotedName(sqlTableGroups), sb.String())
 }
 }
 
 

+ 3 - 0
internal/dataprovider/user.go

@@ -1784,6 +1784,9 @@ func (u *User) mergeVirtualFolders(group Group, groupType int, replacer *strings
 }
 }
 
 
 func (u *User) mergePermissions(group Group, groupType int, replacer *strings.Replacer) {
 func (u *User) mergePermissions(group Group, groupType int, replacer *strings.Replacer) {
+	if u.Permissions == nil {
+		u.Permissions = make(map[string][]string)
+	}
 	for k, v := range group.UserSettings.Permissions {
 	for k, v := range group.UserSettings.Permissions {
 		if k == "/" {
 		if k == "/" {
 			if groupType == sdk.GroupTypePrimary {
 			if groupType == sdk.GroupTypePrimary {

+ 7 - 0
internal/sftpd/internal_test.go

@@ -1975,6 +1975,13 @@ func TestLoadHostKeys(t *testing.T) {
 	c.HostKeys = []string{nonDefaultKeyName, rsaKeyName, ecdsaKeyName, ed25519KeyName}
 	c.HostKeys = []string{nonDefaultKeyName, rsaKeyName, ecdsaKeyName, ed25519KeyName}
 	err = c.checkAndLoadHostKeys(configDir, serverConfig)
 	err = c.checkAndLoadHostKeys(configDir, serverConfig)
 	assert.Error(t, err)
 	assert.Error(t, err)
+	c.HostKeyAlgorithms = []string{ssh.KeyAlgoRSASHA256}
+	c.HostKeys = []string{ecdsaKeyName}
+	err = c.checkAndLoadHostKeys(configDir, serverConfig)
+	assert.Error(t, err)
+	c.HostKeyAlgorithms = preferredHostKeyAlgos
+	err = c.checkAndLoadHostKeys(configDir, serverConfig)
+	assert.NoError(t, err)
 	assert.FileExists(t, rsaKeyName)
 	assert.FileExists(t, rsaKeyName)
 	assert.FileExists(t, ecdsaKeyName)
 	assert.FileExists(t, ecdsaKeyName)
 	assert.FileExists(t, ed25519KeyName)
 	assert.FileExists(t, ed25519KeyName)

+ 118 - 44
internal/sftpd/server.go

@@ -47,6 +47,8 @@ const (
 	defaultPrivateECDSAKeyName   = "id_ecdsa"
 	defaultPrivateECDSAKeyName   = "id_ecdsa"
 	defaultPrivateEd25519KeyName = "id_ed25519"
 	defaultPrivateEd25519KeyName = "id_ed25519"
 	sourceAddressCriticalOption  = "source-address"
 	sourceAddressCriticalOption  = "source-address"
+	kexDHGroupExchangeSHA1       = "diffie-hellman-group-exchange-sha1"
+	kexDHGroupExchangeSHA256     = "diffie-hellman-group-exchange-sha256"
 )
 )
 
 
 var (
 var (
@@ -61,11 +63,8 @@ var (
 		ssh.KeyAlgoED25519,
 		ssh.KeyAlgoED25519,
 	}
 	}
 	preferredHostKeyAlgos = []string{
 	preferredHostKeyAlgos = []string{
-		ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSASHA256v01,
-		ssh.CertAlgoECDSA256v01,
-		ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01, ssh.CertAlgoED25519v01,
+		ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512,
 		ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
 		ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
-		ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSASHA256,
 		ssh.KeyAlgoED25519,
 		ssh.KeyAlgoED25519,
 	}
 	}
 	supportedKexAlgos = []string{
 	supportedKexAlgos = []string{
@@ -75,6 +74,11 @@ var (
 		"diffie-hellman-group18-sha512", "diffie-hellman-group14-sha1",
 		"diffie-hellman-group18-sha512", "diffie-hellman-group14-sha1",
 		"diffie-hellman-group1-sha1",
 		"diffie-hellman-group1-sha1",
 	}
 	}
+	preferredKexAlgos = []string{
+		"curve25519-sha256", "curve25519-sha256@libssh.org",
+		"ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
+		"diffie-hellman-group14-sha256",
+	}
 	supportedCiphers = []string{
 	supportedCiphers = []string{
 		"aes128-gcm@openssh.com", "aes256-gcm@openssh.com",
 		"aes128-gcm@openssh.com", "aes256-gcm@openssh.com",
 		"chacha20-poly1305@openssh.com",
 		"chacha20-poly1305@openssh.com",
@@ -83,11 +87,19 @@ var (
 		"3des-cbc",
 		"3des-cbc",
 		"arcfour", "arcfour128", "arcfour256",
 		"arcfour", "arcfour128", "arcfour256",
 	}
 	}
+	preferredCiphers = []string{
+		"aes128-gcm@openssh.com", "aes256-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"aes128-ctr", "aes192-ctr", "aes256-ctr",
+	}
 	supportedMACs = []string{
 	supportedMACs = []string{
 		"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
 		"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
 		"hmac-sha2-512-etm@openssh.com", "hmac-sha2-512",
 		"hmac-sha2-512-etm@openssh.com", "hmac-sha2-512",
 		"hmac-sha1", "hmac-sha1-96",
 		"hmac-sha1", "hmac-sha1-96",
 	}
 	}
+	preferredMACs = []string{
+		"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
+	}
 
 
 	revokedCertManager = revokedCertificates{
 	revokedCertManager = revokedCertificates{
 		certs: map[string]bool{},
 		certs: map[string]bool{},
@@ -315,22 +327,21 @@ func (c *Configuration) Initialize(configDir string) error {
 		return common.ErrNoBinding
 		return common.ErrNoBinding
 	}
 	}
 
 
-	if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
-		serviceStatus.HostKeys = nil
+	if err := c.loadModuli(configDir); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if err := c.initializeCertChecker(configDir); err != nil {
+	sftp.SetSFTPExtensions(sftpExtensions...) //nolint:errcheck // we configure valid SFTP Extensions so we cannot get an error
+
+	if err := c.configureSecurityOptions(serverConfig); err != nil {
 		return err
 		return err
 	}
 	}
-
-	if err := c.loadModuli(configDir); err != nil {
+	if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
+		serviceStatus.HostKeys = nil
 		return err
 		return err
 	}
 	}
 
 
-	sftp.SetSFTPExtensions(sftpExtensions...) //nolint:errcheck // we configure valid SFTP Extensions so we cannot get an error
-
-	if err := c.configureSecurityOptions(serverConfig); err != nil {
+	if err := c.initializeCertChecker(configDir); err != nil {
 		return err
 		return err
 	}
 	}
 	c.configureKeyboardInteractiveAuth(serverConfig)
 	c.configureKeyboardInteractiveAuth(serverConfig)
@@ -419,35 +430,43 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
 			return fmt.Errorf("unsupported host key algorithm %#v", hostKeyAlgo)
 			return fmt.Errorf("unsupported host key algorithm %#v", hostKeyAlgo)
 		}
 		}
 	}
 	}
-	serverConfig.HostKeyAlgorithms = c.HostKeyAlgorithms
 
 
-	if len(c.KexAlgorithms) > 0 {
+	if len(c.KexAlgorithms) == 0 {
+		c.KexAlgorithms = preferredKexAlgos
+	} else {
 		c.KexAlgorithms = util.RemoveDuplicates(c.KexAlgorithms, true)
 		c.KexAlgorithms = util.RemoveDuplicates(c.KexAlgorithms, true)
-		for _, kex := range c.KexAlgorithms {
-			if !util.Contains(supportedKexAlgos, kex) {
-				return fmt.Errorf("unsupported key-exchange algorithm %#v", kex)
-			}
+	}
+	for _, kex := range c.KexAlgorithms {
+		if !util.Contains(supportedKexAlgos, kex) {
+			return fmt.Errorf("unsupported key-exchange algorithm %q", kex)
 		}
 		}
-		serverConfig.KeyExchanges = c.KexAlgorithms
 	}
 	}
-	if len(c.Ciphers) > 0 {
+	serverConfig.KeyExchanges = c.KexAlgorithms
+
+	if len(c.Ciphers) == 0 {
+		c.Ciphers = preferredCiphers
+	} else {
 		c.Ciphers = util.RemoveDuplicates(c.Ciphers, true)
 		c.Ciphers = util.RemoveDuplicates(c.Ciphers, true)
-		for _, cipher := range c.Ciphers {
-			if !util.Contains(supportedCiphers, cipher) {
-				return fmt.Errorf("unsupported cipher %#v", cipher)
-			}
+	}
+	for _, cipher := range c.Ciphers {
+		if !util.Contains(supportedCiphers, cipher) {
+			return fmt.Errorf("unsupported cipher %#v", cipher)
 		}
 		}
-		serverConfig.Ciphers = c.Ciphers
 	}
 	}
-	if len(c.MACs) > 0 {
+	serverConfig.Ciphers = c.Ciphers
+
+	if len(c.MACs) == 0 {
+		c.MACs = preferredMACs
+	} else {
 		c.MACs = util.RemoveDuplicates(c.MACs, true)
 		c.MACs = util.RemoveDuplicates(c.MACs, true)
-		for _, mac := range c.MACs {
-			if !util.Contains(supportedMACs, mac) {
-				return fmt.Errorf("unsupported MAC algorithm %#v", mac)
-			}
+	}
+	for _, mac := range c.MACs {
+		if !util.Contains(supportedMACs, mac) {
+			return fmt.Errorf("unsupported MAC algorithm %#v", mac)
 		}
 		}
-		serverConfig.MACs = c.MACs
 	}
 	}
+	serverConfig.MACs = c.MACs
+
 	return nil
 	return nil
 }
 }
 
 
@@ -876,8 +895,9 @@ func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
 }
 }
 
 
 func (c *Configuration) loadModuli(configDir string) error {
 func (c *Configuration) loadModuli(configDir string) error {
-	supportedKexAlgos = util.Remove(supportedKexAlgos, "diffie-hellman-group-exchange-sha1")
-	supportedKexAlgos = util.Remove(supportedKexAlgos, "diffie-hellman-group-exchange-sha256")
+	supportedKexAlgos = util.Remove(supportedKexAlgos, kexDHGroupExchangeSHA1)
+	supportedKexAlgos = util.Remove(supportedKexAlgos, kexDHGroupExchangeSHA256)
+	preferredKexAlgos = util.Remove(preferredKexAlgos, kexDHGroupExchangeSHA256)
 	for _, m := range c.Moduli {
 	for _, m := range c.Moduli {
 		m = strings.TrimSpace(m)
 		m = strings.TrimSpace(m)
 		if !util.IsFileInputValid(m) {
 		if !util.IsFileInputValid(m) {
@@ -892,12 +912,29 @@ func (c *Configuration) loadModuli(configDir string) error {
 		if err := ssh.ParseModuli(m); err != nil {
 		if err := ssh.ParseModuli(m); err != nil {
 			return err
 			return err
 		}
 		}
-		supportedKexAlgos = append(supportedKexAlgos, "diffie-hellman-group-exchange-sha1",
-			"diffie-hellman-group-exchange-sha256")
+		if !util.Contains(supportedKexAlgos, kexDHGroupExchangeSHA1) {
+			supportedKexAlgos = append(supportedKexAlgos, kexDHGroupExchangeSHA1)
+		}
+		if !util.Contains(supportedKexAlgos, kexDHGroupExchangeSHA256) {
+			supportedKexAlgos = append(supportedKexAlgos, kexDHGroupExchangeSHA256)
+		}
+		if !util.Contains(preferredKexAlgos, kexDHGroupExchangeSHA256) {
+			preferredKexAlgos = append(preferredKexAlgos, kexDHGroupExchangeSHA256)
+		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+func (c *Configuration) getHostKeyAlgorithms(keyFormat string) []string {
+	var algos []string
+	for _, algo := range algorithmsForKeyFormat(keyFormat) {
+		if util.Contains(c.HostKeyAlgorithms, algo) {
+			algos = append(algos, algo)
+		}
+	}
+	return algos
+}
+
 // If no host keys are defined we try to use or generate the default ones.
 // If no host keys are defined we try to use or generate the default ones.
 func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
 func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
 	if err := c.checkHostKeyAutoGeneration(configDir); err != nil {
 	if err := c.checkHostKeyAutoGeneration(configDir); err != nil {
@@ -932,22 +969,45 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
 		k := HostKey{
 		k := HostKey{
 			Path:        hostKey,
 			Path:        hostKey,
 			Fingerprint: ssh.FingerprintSHA256(private.PublicKey()),
 			Fingerprint: ssh.FingerprintSHA256(private.PublicKey()),
+			Algorithms:  c.getHostKeyAlgorithms(private.PublicKey().Type()),
+		}
+		mas, err := ssh.NewSignerWithAlgorithms(private.(ssh.AlgorithmSigner), k.Algorithms)
+		if err != nil {
+			logger.Warn(logSender, "", "could not create signer for key %q with algorithms %+v: %v", k.Path, k.Algorithms, err)
+			logger.WarnToConsole("could not create signer for key %q with algorithms %+v: %v", k.Path, k.Algorithms, err)
+			continue
 		}
 		}
 		serviceStatus.HostKeys = append(serviceStatus.HostKeys, k)
 		serviceStatus.HostKeys = append(serviceStatus.HostKeys, k)
-		logger.Info(logSender, "", "Host key %#v loaded, type %#v, fingerprint %#v", hostKey,
-			private.PublicKey().Type(), k.Fingerprint)
+		logger.Info(logSender, "", "Host key %q loaded, type %q, fingerprint %q, algorithms %+v", hostKey,
+			private.PublicKey().Type(), k.Fingerprint, k.Algorithms)
 
 
 		// Add private key to the server configuration.
 		// Add private key to the server configuration.
-		serverConfig.AddHostKey(private)
+		serverConfig.AddHostKey(mas)
 		for _, cert := range hostCertificates {
 		for _, cert := range hostCertificates {
-			signer, err := ssh.NewCertSigner(cert, private)
+			signer, err := ssh.NewCertSigner(cert.Certificate, mas)
 			if err == nil {
 			if err == nil {
+				var algos []string
+				for _, algo := range algorithmsForKeyFormat(signer.PublicKey().Type()) {
+					if underlyingAlgo, ok := certKeyAlgoNames[algo]; ok {
+						if util.Contains(mas.Algorithms(), underlyingAlgo) {
+							algos = append(algos, algo)
+						}
+					}
+				}
+				serviceStatus.HostKeys = append(serviceStatus.HostKeys, HostKey{
+					Path:        cert.Path,
+					Fingerprint: ssh.FingerprintSHA256(signer.PublicKey()),
+					Algorithms:  algos,
+				})
 				serverConfig.AddHostKey(signer)
 				serverConfig.AddHostKey(signer)
-				logger.Info(logSender, "", "Host certificate loaded for host key %#v, fingerprint %#v",
-					hostKey, ssh.FingerprintSHA256(signer.PublicKey()))
+				logger.Info(logSender, "", "Host certificate loaded for host key %q, fingerprint %q, algorithms %+v",
+					hostKey, ssh.FingerprintSHA256(signer.PublicKey()), algos)
 			}
 			}
 		}
 		}
 	}
 	}
+	if len(serviceStatus.HostKeys) == 0 {
+		return errors.New("ssh: server has no host keys")
+	}
 	var fp []string
 	var fp []string
 	for idx := range serviceStatus.HostKeys {
 	for idx := range serviceStatus.HostKeys {
 		h := &serviceStatus.HostKeys[idx]
 		h := &serviceStatus.HostKeys[idx]
@@ -957,8 +1017,8 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
 	return nil
 	return nil
 }
 }
 
 
-func (c *Configuration) loadHostCertificates(configDir string) ([]*ssh.Certificate, error) {
-	var certs []*ssh.Certificate
+func (c *Configuration) loadHostCertificates(configDir string) ([]hostCertificate, error) {
+	var certs []hostCertificate
 	for _, certPath := range c.HostCertificates {
 	for _, certPath := range c.HostCertificates {
 		certPath = strings.TrimSpace(certPath)
 		certPath = strings.TrimSpace(certPath)
 		if !util.IsFileInputValid(certPath) {
 		if !util.IsFileInputValid(certPath) {
@@ -984,7 +1044,10 @@ func (c *Configuration) loadHostCertificates(configDir string) ([]*ssh.Certifica
 		if cert.CertType != ssh.HostCert {
 		if cert.CertType != ssh.HostCert {
 			return nil, fmt.Errorf("the file %#v is not an host certificate", certPath)
 			return nil, fmt.Errorf("the file %#v is not an host certificate", certPath)
 		}
 		}
-		certs = append(certs, cert)
+		certs = append(certs, hostCertificate{
+			Path:        certPath,
+			Certificate: cert,
+		})
 	}
 	}
 	return certs, nil
 	return certs, nil
 }
 }
@@ -1218,3 +1281,14 @@ func (r *revokedCertificates) isRevoked(fp string) bool {
 func Reload() error {
 func Reload() error {
 	return revokedCertManager.load()
 	return revokedCertManager.load()
 }
 }
+
+func algorithmsForKeyFormat(keyFormat string) []string {
+	switch keyFormat {
+	case ssh.KeyAlgoRSA:
+		return []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512, ssh.KeyAlgoRSA}
+	case ssh.CertAlgoRSAv01:
+		return []string{ssh.CertAlgoRSASHA256v01, ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSAv01}
+	default:
+		return []string{keyFormat}
+	}
+}

+ 27 - 2
internal/sftpd/sftpd.go

@@ -20,6 +20,8 @@ package sftpd
 import (
 import (
 	"strings"
 	"strings"
 	"time"
 	"time"
+
+	"golang.org/x/crypto/ssh"
 )
 )
 
 
 const (
 const (
@@ -34,6 +36,18 @@ var (
 	sshHashCommands    = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"}
 	sshHashCommands    = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"}
 	systemCommands     = []string{"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
 	systemCommands     = []string{"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
 	serviceStatus      ServiceStatus
 	serviceStatus      ServiceStatus
+	certKeyAlgoNames   = map[string]string{
+		ssh.CertAlgoRSAv01:        ssh.KeyAlgoRSA,
+		ssh.CertAlgoRSASHA256v01:  ssh.KeyAlgoRSASHA256,
+		ssh.CertAlgoRSASHA512v01:  ssh.KeyAlgoRSASHA512,
+		ssh.CertAlgoDSAv01:        ssh.KeyAlgoDSA,
+		ssh.CertAlgoECDSA256v01:   ssh.KeyAlgoECDSA256,
+		ssh.CertAlgoECDSA384v01:   ssh.KeyAlgoECDSA384,
+		ssh.CertAlgoECDSA521v01:   ssh.KeyAlgoECDSA521,
+		ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256,
+		ssh.CertAlgoED25519v01:    ssh.KeyAlgoED25519,
+		ssh.CertAlgoSKED25519v01:  ssh.KeyAlgoSKED25519,
+	}
 )
 )
 
 
 type sshSubsystemExitStatus struct {
 type sshSubsystemExitStatus struct {
@@ -44,10 +58,21 @@ type sshSubsystemExecMsg struct {
 	Command string
 	Command string
 }
 }
 
 
+type hostCertificate struct {
+	Certificate *ssh.Certificate
+	Path        string
+}
+
 // HostKey defines the details for a used host key
 // HostKey defines the details for a used host key
 type HostKey struct {
 type HostKey struct {
-	Path        string `json:"path"`
-	Fingerprint string `json:"fingerprint"`
+	Path        string   `json:"path"`
+	Fingerprint string   `json:"fingerprint"`
+	Algorithms  []string `json:"algorithms"`
+}
+
+// GetAlgosAsString returns the host key algorithms as comma separated string
+func (h *HostKey) GetAlgosAsString() string {
+	return strings.Join(h.Algorithms, ", ")
 }
 }
 
 
 // ServiceStatus defines the service status
 // ServiceStatus defines the service status

+ 3 - 0
internal/sftpd/sftpd_test.go

@@ -429,11 +429,13 @@ func TestInitialization(t *testing.T) {
 	if assert.Error(t, err) {
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "unsupported MAC algorithm")
 		assert.Contains(t, err.Error(), "unsupported MAC algorithm")
 	}
 	}
+	sftpdConf.MACs = nil
 	sftpdConf.KexAlgorithms = []string{"not a KEX"}
 	sftpdConf.KexAlgorithms = []string{"not a KEX"}
 	err = sftpdConf.Initialize(configDir)
 	err = sftpdConf.Initialize(configDir)
 	if assert.Error(t, err) {
 	if assert.Error(t, err) {
 		assert.Contains(t, err.Error(), "unsupported key-exchange algorithm")
 		assert.Contains(t, err.Error(), "unsupported key-exchange algorithm")
 	}
 	}
+	sftpdConf.KexAlgorithms = nil
 	sftpdConf.HostKeyAlgorithms = []string{"not a host key algo"}
 	sftpdConf.HostKeyAlgorithms = []string{"not a host key algo"}
 	err = sftpdConf.Initialize(configDir)
 	err = sftpdConf.Initialize(configDir)
 	if assert.Error(t, err) {
 	if assert.Error(t, err) {
@@ -562,6 +564,7 @@ func TestBasicSFTPHandling(t *testing.T) {
 	assert.NotEmpty(t, sshCommands)
 	assert.NotEmpty(t, sshCommands)
 	sshAuths := status.GetSupportedAuthsAsString()
 	sshAuths := status.GetSupportedAuthsAsString()
 	assert.NotEmpty(t, sshAuths)
 	assert.NotEmpty(t, sshAuths)
+	assert.NotEmpty(t, status.HostKeys[0].GetAlgosAsString())
 }
 }
 
 
 func TestBasicSFTPFsHandling(t *testing.T) {
 func TestBasicSFTPFsHandling(t *testing.T) {

+ 1 - 1
internal/util/util.go

@@ -313,7 +313,7 @@ func GetIntFromPointer(val *int64) int64 {
 // GetTimeFromPointer returns the time value or now
 // GetTimeFromPointer returns the time value or now
 func GetTimeFromPointer(val *time.Time) time.Time {
 func GetTimeFromPointer(val *time.Time) time.Time {
 	if val == nil {
 	if val == nil {
-		return time.Now()
+		return time.Unix(0, 0)
 	}
 	}
 	return *val
 	return *val
 }
 }

+ 1 - 1
internal/version/version.go

@@ -17,7 +17,7 @@ package version
 
 
 import "strings"
 import "strings"
 
 
-const version = "2.4.5"
+const version = "2.4.6-dev"
 
 
 var (
 var (
 	commit = ""
 	commit = ""

+ 21 - 11
internal/vfs/s3fs.go

@@ -165,11 +165,11 @@ func (fs *S3Fs) Stat(name string) (os.FileInfo, error) {
 		// Some S3 providers (like SeaweedFS) remove the trailing '/' from object keys.
 		// Some S3 providers (like SeaweedFS) remove the trailing '/' from object keys.
 		// So we check some common content types to detect if this is a "directory".
 		// So we check some common content types to detect if this is a "directory".
 		isDir := util.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType))
 		isDir := util.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType))
-		if obj.ContentLength == 0 && !isDir {
+		if util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir {
 			_, err = fs.headObject(name + "/")
 			_, err = fs.headObject(name + "/")
 			isDir = err == nil
 			isDir = err == nil
 		}
 		}
-		return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, isDir, obj.ContentLength,
+		return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, isDir, util.GetIntFromPointer(obj.ContentLength),
 			util.GetTimeFromPointer(obj.LastModified), false))
 			util.GetTimeFromPointer(obj.LastModified), false))
 	}
 	}
 	if !fs.IsNotExist(err) {
 	if !fs.IsNotExist(err) {
@@ -195,7 +195,7 @@ func (fs *S3Fs) getStatForDir(name string) (os.FileInfo, error) {
 	if err != nil {
 	if err != nil {
 		return result, err
 		return result, err
 	}
 	}
-	return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, true, obj.ContentLength,
+	return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, true, util.GetIntFromPointer(obj.ContentLength),
 		util.GetTimeFromPointer(obj.LastModified), false))
 		util.GetTimeFromPointer(obj.LastModified), false))
 }
 }
 
 
@@ -492,6 +492,7 @@ func (fs *S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 		}
 		}
 		for _, fileObject := range page.Contents {
 		for _, fileObject := range page.Contents {
 			objectModTime := util.GetTimeFromPointer(fileObject.LastModified)
 			objectModTime := util.GetTimeFromPointer(fileObject.LastModified)
+			objectSize := util.GetIntFromPointer(fileObject.Size)
 			name, isDir := fs.resolve(fileObject.Key, prefix)
 			name, isDir := fs.resolve(fileObject.Key, prefix)
 			if name == "" || name == "/" {
 			if name == "" || name == "/" {
 				continue
 				continue
@@ -505,7 +506,7 @@ func (fs *S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 			if t, ok := modTimes[name]; ok {
 			if t, ok := modTimes[name]; ok {
 				objectModTime = util.GetTimeFromMsecSinceEpoch(t)
 				objectModTime = util.GetTimeFromMsecSinceEpoch(t)
 			}
 			}
-			result = append(result, NewFileInfo(name, (isDir && fileObject.Size == 0), fileObject.Size,
+			result = append(result, NewFileInfo(name, (isDir && objectSize == 0), objectSize,
 				objectModTime, false))
 				objectModTime, false))
 		}
 		}
 	}
 	}
@@ -647,11 +648,12 @@ func (fs *S3Fs) GetDirSize(dirname string) (int, int64, error) {
 		}
 		}
 		for _, fileObject := range page.Contents {
 		for _, fileObject := range page.Contents {
 			isDir := strings.HasSuffix(util.GetStringFromPointer(fileObject.Key), "/")
 			isDir := strings.HasSuffix(util.GetStringFromPointer(fileObject.Key), "/")
-			if isDir && fileObject.Size == 0 {
+			objectSize := util.GetIntFromPointer(fileObject.Size)
+			if isDir && objectSize == 0 {
 				continue
 				continue
 			}
 			}
 			numFiles++
 			numFiles++
-			size += fileObject.Size
+			size += objectSize
 			if numFiles%1000 == 0 {
 			if numFiles%1000 == 0 {
 				fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
 				fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
 			}
 			}
@@ -716,7 +718,8 @@ func (fs *S3Fs) Walk(root string, walkFn filepath.WalkFunc) error {
 				continue
 				continue
 			}
 			}
 			err := walkFn(util.GetStringFromPointer(fileObject.Key),
 			err := walkFn(util.GetStringFromPointer(fileObject.Key),
-				NewFileInfo(name, isDir, fileObject.Size, util.GetTimeFromPointer(fileObject.LastModified), false), nil)
+				NewFileInfo(name, isDir, util.GetIntFromPointer(fileObject.Size),
+					util.GetTimeFromPointer(fileObject.LastModified), false), nil)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
@@ -794,10 +797,11 @@ func (fs *S3Fs) mkdirInternal(name string) error {
 
 
 func (fs *S3Fs) hasContents(name string) (bool, error) {
 func (fs *S3Fs) hasContents(name string) (bool, error) {
 	prefix := fs.getPrefix(name)
 	prefix := fs.getPrefix(name)
+	maxKeys := int32(2)
 	paginator := s3.NewListObjectsV2Paginator(fs.svc, &s3.ListObjectsV2Input{
 	paginator := s3.NewListObjectsV2Paginator(fs.svc, &s3.ListObjectsV2Input{
 		Bucket:  aws.String(fs.config.Bucket),
 		Bucket:  aws.String(fs.config.Bucket),
 		Prefix:  aws.String(prefix),
 		Prefix:  aws.String(prefix),
-		MaxKeys: 2,
+		MaxKeys: &maxKeys,
 	})
 	})
 
 
 	if paginator.HasMorePages() {
 	if paginator.HasMorePages() {
@@ -891,7 +895,7 @@ func (fs *S3Fs) doMultipartCopy(source, target, contentType string, fileSize int
 				Bucket:          aws.String(fs.config.Bucket),
 				Bucket:          aws.String(fs.config.Bucket),
 				CopySource:      aws.String(source),
 				CopySource:      aws.String(source),
 				Key:             aws.String(target),
 				Key:             aws.String(target),
-				PartNumber:      partNum,
+				PartNumber:      &partNum,
 				UploadId:        aws.String(uploadID),
 				UploadId:        aws.String(uploadID),
 				CopySourceRange: aws.String(fmt.Sprintf("bytes=%d-%d", partStart, partEnd-1)),
 				CopySourceRange: aws.String(fmt.Sprintf("bytes=%d-%d", partStart, partEnd-1)),
 			})
 			})
@@ -920,7 +924,7 @@ func (fs *S3Fs) doMultipartCopy(source, target, contentType string, fileSize int
 			partMutex.Lock()
 			partMutex.Lock()
 			completedParts = append(completedParts, types.CompletedPart{
 			completedParts = append(completedParts, types.CompletedPart{
 				ETag:       partResp.CopyPartResult.ETag,
 				ETag:       partResp.CopyPartResult.ETag,
-				PartNumber: partNum,
+				PartNumber: &partNum,
 			})
 			})
 			partMutex.Unlock()
 			partMutex.Unlock()
 		}(partNumber, start, end)
 		}(partNumber, start, end)
@@ -933,7 +937,13 @@ func (fs *S3Fs) doMultipartCopy(source, target, contentType string, fileSize int
 		return copyError
 		return copyError
 	}
 	}
 	sort.Slice(completedParts, func(i, j int) bool {
 	sort.Slice(completedParts, func(i, j int) bool {
-		return completedParts[i].PartNumber < completedParts[j].PartNumber
+		getPartNumber := func(number *int32) int32 {
+			if number == nil {
+				return 0
+			}
+			return *number
+		}
+		return getPartNumber(completedParts[i].PartNumber) < getPartNumber(completedParts[j].PartNumber)
 	})
 	})
 
 
 	completeCtx, completeCancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
 	completeCtx, completeCancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))

+ 5 - 1
openapi/openapi.yaml

@@ -27,7 +27,7 @@ info:
     SFTPGo supports groups to simplify the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
     SFTPGo supports groups to simplify the administration of multiple accounts by letting you assign settings once to a group, instead of multiple times to each individual user.
     The SFTPGo WebClient allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Authy, Google Authenticator and other compatible apps.
     The SFTPGo WebClient allows end users to change their credentials, browse and manage their files in the browser and setup two-factor authentication which works with Authy, Google Authenticator and other compatible apps.
     From the WebClient each authorized user can also create HTTP/S links to externally share files and folders securely, by setting limits to the number of downloads/uploads, protecting the share with a password, limiting access by source IP address, setting an automatic expiration date.
     From the WebClient each authorized user can also create HTTP/S links to externally share files and folders securely, by setting limits to the number of downloads/uploads, protecting the share with a password, limiting access by source IP address, setting an automatic expiration date.
-  version: 2.4.5
+  version: 2.4.6
   contact:
   contact:
     name: API support
     name: API support
     url: 'https://github.com/drakkan/sftpgo'
     url: 'https://github.com/drakkan/sftpgo'
@@ -5532,6 +5532,10 @@ components:
           type: string
           type: string
         fingerprint:
         fingerprint:
           type: string
           type: string
+        algorithms:
+          type: array
+          items:
+            type: string
     SSHBinding:
     SSHBinding:
       type: object
       type: object
       properties:
       properties:

+ 2 - 0
templates/webadmin/status.html

@@ -46,6 +46,8 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>.
                     <br>
                     <br>
                     Fingerprint: "{{.Fingerprint}}"
                     Fingerprint: "{{.Fingerprint}}"
                     <br>
                     <br>
+                    Algorithms: "{{.GetAlgosAsString}}"
+                    <br>
                     {{end}}
                     {{end}}
                     {{end}}
                     {{end}}
                 </p>
                 </p>

Some files were not shown because too many files changed in this diff