Browse Source

update deps

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 1 year ago
parent
commit
be2e24e63d

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

@@ -11,11 +11,11 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        go: [1.19]
+        go: ['1.20']
         os: [ubuntu-latest, macos-latest]
         upload-coverage: [true]
         include:
-          - go: 1.19
+          - go: '1.20'
             os: windows-latest
             upload-coverage: false
 
@@ -232,7 +232,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
       - name: Build
         run: |
@@ -252,7 +252,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
       - name: Build
         run: |
@@ -326,7 +326,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19
+          go-version: '1.20'
 
       - name: Build
         run: |
@@ -438,7 +438,7 @@ jobs:
           echo 'apt-get install -q -y curl gcc' >> build.sh
           if [ ${{ matrix.go }} == 'latest' ]
           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
             echo 'GO_VERSION=${{ matrix.go }}' >> build.sh
           fi
@@ -481,7 +481,7 @@ jobs:
             apt-get install -q -y curl gcc
             if [ ${{ matrix.go }} == 'latest' ]
             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
               GO_VERSION=${{ matrix.go }}
             fi
@@ -546,7 +546,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19
+          go-version: '1.19'
       - uses: actions/checkout@v3
       - name: Run golangci-lint
         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
   push:
     branches:
-      - 2.4.x
+      - main
     tags:
       - v*
   pull_request:

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

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

+ 125 - 109
go.mod

@@ -3,166 +3,182 @@ module github.com/drakkan/sftpgo/v2
 go 1.19
 
 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/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/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/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/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/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/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/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/otiai10/copy v1.10.0
+	github.com/otiai10/copy v1.14.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/prometheus/client_golang v1.14.0
+	github.com/prometheus/client_golang v1.17.0
 	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/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
-	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
 )
 
 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/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/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/boombuler/barcode v1.0.1 // 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/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/goccy/go-json v0.10.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/hcl v1.0.0 // indirect
 	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.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/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/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/httprc v1.0.4 // indirect
 	github.com/lestrrat-go/iter v1.0.2 // 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/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/mapstructure v1.5.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/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/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/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/yusufpapurcu/wmi v1.2.2 // indirect
+	github.com/yusufpapurcu/wmi v1.2.3 // 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/yaml.v3 v3.0.1 // indirect
 )
@@ -170,5 +186,5 @@ require (
 replace (
 	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
-	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 {
 		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())
 }
 

+ 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) {
+	if u.Permissions == nil {
+		u.Permissions = make(map[string][]string)
+	}
 	for k, v := range group.UserSettings.Permissions {
 		if k == "/" {
 			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}
 	err = c.checkAndLoadHostKeys(configDir, serverConfig)
 	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, ecdsaKeyName)
 	assert.FileExists(t, ed25519KeyName)

+ 118 - 44
internal/sftpd/server.go

@@ -47,6 +47,8 @@ const (
 	defaultPrivateECDSAKeyName   = "id_ecdsa"
 	defaultPrivateEd25519KeyName = "id_ed25519"
 	sourceAddressCriticalOption  = "source-address"
+	kexDHGroupExchangeSHA1       = "diffie-hellman-group-exchange-sha1"
+	kexDHGroupExchangeSHA256     = "diffie-hellman-group-exchange-sha256"
 )
 
 var (
@@ -61,11 +63,8 @@ var (
 		ssh.KeyAlgoED25519,
 	}
 	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.KeyAlgoRSASHA512, ssh.KeyAlgoRSASHA256,
 		ssh.KeyAlgoED25519,
 	}
 	supportedKexAlgos = []string{
@@ -75,6 +74,11 @@ var (
 		"diffie-hellman-group18-sha512", "diffie-hellman-group14-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{
 		"aes128-gcm@openssh.com", "aes256-gcm@openssh.com",
 		"chacha20-poly1305@openssh.com",
@@ -83,11 +87,19 @@ var (
 		"3des-cbc",
 		"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{
 		"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
 		"hmac-sha2-512-etm@openssh.com", "hmac-sha2-512",
 		"hmac-sha1", "hmac-sha1-96",
 	}
+	preferredMACs = []string{
+		"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
+	}
 
 	revokedCertManager = revokedCertificates{
 		certs: map[string]bool{},
@@ -315,22 +327,21 @@ func (c *Configuration) Initialize(configDir string) error {
 		return common.ErrNoBinding
 	}
 
-	if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
-		serviceStatus.HostKeys = nil
+	if err := c.loadModuli(configDir); err != nil {
 		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
 	}
-
-	if err := c.loadModuli(configDir); err != nil {
+	if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
+		serviceStatus.HostKeys = nil
 		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
 	}
 	c.configureKeyboardInteractiveAuth(serverConfig)
@@ -419,35 +430,43 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
 			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)
-		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)
-		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)
-		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
 }
 
@@ -876,8 +895,9 @@ func (c *Configuration) checkHostKeyAutoGeneration(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 {
 		m = strings.TrimSpace(m)
 		if !util.IsFileInputValid(m) {
@@ -892,12 +912,29 @@ func (c *Configuration) loadModuli(configDir string) error {
 		if err := ssh.ParseModuli(m); err != nil {
 			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
 }
 
+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.
 func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
 	if err := c.checkHostKeyAutoGeneration(configDir); err != nil {
@@ -932,22 +969,45 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
 		k := HostKey{
 			Path:        hostKey,
 			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)
-		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.
-		serverConfig.AddHostKey(private)
+		serverConfig.AddHostKey(mas)
 		for _, cert := range hostCertificates {
-			signer, err := ssh.NewCertSigner(cert, private)
+			signer, err := ssh.NewCertSigner(cert.Certificate, mas)
 			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)
-				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
 	for idx := range serviceStatus.HostKeys {
 		h := &serviceStatus.HostKeys[idx]
@@ -957,8 +1017,8 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
 	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 {
 		certPath = strings.TrimSpace(certPath)
 		if !util.IsFileInputValid(certPath) {
@@ -984,7 +1044,10 @@ func (c *Configuration) loadHostCertificates(configDir string) ([]*ssh.Certifica
 		if cert.CertType != ssh.HostCert {
 			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
 }
@@ -1218,3 +1281,14 @@ func (r *revokedCertificates) isRevoked(fp string) bool {
 func Reload() error {
 	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 (
 	"strings"
 	"time"
+
+	"golang.org/x/crypto/ssh"
 )
 
 const (
@@ -34,6 +36,18 @@ var (
 	sshHashCommands    = []string{"md5sum", "sha1sum", "sha256sum", "sha384sum", "sha512sum"}
 	systemCommands     = []string{"git-receive-pack", "git-upload-pack", "git-upload-archive", "rsync"}
 	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 {
@@ -44,10 +58,21 @@ type sshSubsystemExecMsg struct {
 	Command string
 }
 
+type hostCertificate struct {
+	Certificate *ssh.Certificate
+	Path        string
+}
+
 // HostKey defines the details for a used host key
 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

+ 3 - 0
internal/sftpd/sftpd_test.go

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

+ 1 - 1
internal/version/version.go

@@ -17,7 +17,7 @@ package version
 
 import "strings"
 
-const version = "2.4.5"
+const version = "2.4.6-dev"
 
 var (
 	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.
 		// So we check some common content types to detect if this is a "directory".
 		isDir := util.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType))
-		if obj.ContentLength == 0 && !isDir {
+		if util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir {
 			_, err = fs.headObject(name + "/")
 			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))
 	}
 	if !fs.IsNotExist(err) {
@@ -195,7 +195,7 @@ func (fs *S3Fs) getStatForDir(name string) (os.FileInfo, error) {
 	if err != nil {
 		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))
 }
 
@@ -492,6 +492,7 @@ func (fs *S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 		}
 		for _, fileObject := range page.Contents {
 			objectModTime := util.GetTimeFromPointer(fileObject.LastModified)
+			objectSize := util.GetIntFromPointer(fileObject.Size)
 			name, isDir := fs.resolve(fileObject.Key, prefix)
 			if name == "" || name == "/" {
 				continue
@@ -505,7 +506,7 @@ func (fs *S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
 			if t, ok := modTimes[name]; ok {
 				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))
 		}
 	}
@@ -647,11 +648,12 @@ func (fs *S3Fs) GetDirSize(dirname string) (int, int64, error) {
 		}
 		for _, fileObject := range page.Contents {
 			isDir := strings.HasSuffix(util.GetStringFromPointer(fileObject.Key), "/")
-			if isDir && fileObject.Size == 0 {
+			objectSize := util.GetIntFromPointer(fileObject.Size)
+			if isDir && objectSize == 0 {
 				continue
 			}
 			numFiles++
-			size += fileObject.Size
+			size += objectSize
 			if numFiles%1000 == 0 {
 				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
 			}
 			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 {
 				return err
 			}
@@ -794,10 +797,11 @@ func (fs *S3Fs) mkdirInternal(name string) error {
 
 func (fs *S3Fs) hasContents(name string) (bool, error) {
 	prefix := fs.getPrefix(name)
+	maxKeys := int32(2)
 	paginator := s3.NewListObjectsV2Paginator(fs.svc, &s3.ListObjectsV2Input{
 		Bucket:  aws.String(fs.config.Bucket),
 		Prefix:  aws.String(prefix),
-		MaxKeys: 2,
+		MaxKeys: &maxKeys,
 	})
 
 	if paginator.HasMorePages() {
@@ -891,7 +895,7 @@ func (fs *S3Fs) doMultipartCopy(source, target, contentType string, fileSize int
 				Bucket:          aws.String(fs.config.Bucket),
 				CopySource:      aws.String(source),
 				Key:             aws.String(target),
-				PartNumber:      partNum,
+				PartNumber:      &partNum,
 				UploadId:        aws.String(uploadID),
 				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()
 			completedParts = append(completedParts, types.CompletedPart{
 				ETag:       partResp.CopyPartResult.ETag,
-				PartNumber: partNum,
+				PartNumber: &partNum,
 			})
 			partMutex.Unlock()
 		}(partNumber, start, end)
@@ -933,7 +937,13 @@ func (fs *S3Fs) doMultipartCopy(source, target, contentType string, fileSize int
 		return copyError
 	}
 	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))

+ 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.
     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.
-  version: 2.4.5
+  version: 2.4.6
   contact:
     name: API support
     url: 'https://github.com/drakkan/sftpgo'
@@ -5532,6 +5532,10 @@ components:
           type: string
         fingerprint:
           type: string
+        algorithms:
+          type: array
+          items:
+            type: string
     SSHBinding:
       type: object
       properties:

+ 2 - 0
templates/webadmin/status.html

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

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