Compare commits
55 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f4df46790b | ||
|
909928e641 | ||
|
be2e24e63d | ||
|
8e86782d85 | ||
|
fc520076b3 | ||
|
2ccb6c2672 | ||
|
fcd97ee20d | ||
|
5b878b95e7 | ||
|
5eedaa4b5d | ||
|
b084564c3f | ||
|
b925504795 | ||
|
9c9c9fa3a5 | ||
|
87820d980b | ||
|
16d908e76b | ||
|
ca3f28d2f5 | ||
|
bd118e61bc | ||
|
88372b6da7 | ||
|
56f45dc36d | ||
|
7d19d3f10b | ||
|
a5d6441fb6 | ||
|
5605496c5f | ||
|
64c28bf6aa | ||
|
b9e75ff661 | ||
|
362396811e | ||
|
916e53cf9a | ||
|
5f35b8b704 | ||
|
08e29d4ee0 | ||
|
ff4c1b239e | ||
|
49c8affcef | ||
|
14139af165 | ||
|
af4f54bf11 | ||
|
9ba468698b | ||
|
0f74c077ac | ||
|
dd98c06397 | ||
|
b989cdabe5 | ||
|
9e7e89d69e | ||
|
f64056b820 | ||
|
7e6d944cb5 | ||
|
a66d207291 | ||
|
0a8edcd811 | ||
|
0fa08ddbaa | ||
|
3d4c35522a | ||
|
f400e67daa | ||
|
4e10275fd1 | ||
|
7bd71474ef | ||
|
0ac2120532 | ||
|
9e5287cfb4 | ||
|
450ab6b252 | ||
|
51d900558a | ||
|
a71690ff2a | ||
|
f390eab1de | ||
|
571e088fdd | ||
|
6714085d58 | ||
|
0389605d65 | ||
|
b8ef94ece7 |
136 changed files with 3949 additions and 4664 deletions
32
.github/workflows/development.yml
vendored
32
.github/workflows/development.yml
vendored
|
@ -2,7 +2,7 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [2.4.x]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
@ -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
|
||||
|
||||
|
@ -69,11 +69,11 @@ jobs:
|
|||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $LATEST_TAG-dev-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
|
@ -222,8 +222,8 @@ jobs:
|
|||
name: sftpgo-${{ matrix.os }}-go-${{ matrix.go }}
|
||||
path: output
|
||||
|
||||
test-bundle:
|
||||
name: Build in bundle mode
|
||||
test-build-flags:
|
||||
name: Test build flags
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -232,10 +232,12 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: '1.20'
|
||||
|
||||
- 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
|
||||
./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
|
||||
./sftpgo -v
|
||||
|
@ -250,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: |
|
||||
|
@ -296,7 +298,7 @@ jobs:
|
|||
MYSQL_USER: sftpgo
|
||||
MYSQL_PASSWORD: sftpgo
|
||||
options: >-
|
||||
--health-cmd "mysqladmin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
|
||||
--health-cmd "mariadb-admin status -h 127.0.0.1 -P 3306 -u root -p$MYSQL_ROOT_PASSWORD"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 6
|
||||
|
@ -324,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: |
|
||||
|
@ -436,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
|
||||
|
@ -479,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
|
||||
|
@ -544,9 +546,9 @@ 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
|
||||
with:
|
||||
version: latest
|
||||
version: v1.51.2
|
||||
|
|
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
|
@ -42,6 +42,7 @@ jobs:
|
|||
DOCKERFILE=Dockerfile
|
||||
MINOR=""
|
||||
MAJOR=""
|
||||
FEATURES="nopgxregisterdefaulttypes"
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
VERSION=nightly
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
|
@ -67,6 +68,7 @@ jobs:
|
|||
VERSION="${VERSION}-distroless"
|
||||
VERSION_SLIM="${VERSION}-slim"
|
||||
DOCKERFILE=Dockerfile.distroless
|
||||
FEATURES="${FEATURES},nosqlite"
|
||||
elif [[ $DOCKER_PKG == debian-plugins ]]; then
|
||||
VERSION="${VERSION}-plugins"
|
||||
VERSION_SLIM="${VERSION}-slim"
|
||||
|
@ -128,6 +130,7 @@ jobs:
|
|||
echo "plugins=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "dockerfile=${DOCKERFILE}" >> $GITHUB_OUTPUT
|
||||
echo "features=${FEATURES}" >> $GITHUB_OUTPUT
|
||||
echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
|
||||
echo "sha=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
|
@ -169,7 +172,7 @@ jobs:
|
|||
COMMIT_SHA=${{ steps.info.outputs.sha }}
|
||||
INSTALL_OPTIONAL_PACKAGES=${{ steps.info.outputs.full }}
|
||||
DOWNLOAD_PLUGINS=${{ steps.info.outputs.plugins }}
|
||||
FEATURES=nopgxregisterdefaulttypes
|
||||
FEATURES=${{ steps.info.outputs.features }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=SFTPGo
|
||||
org.opencontainers.image.description=Fully featured and highly configurable SFTP server with optional HTTP, FTP/S and WebDAV support
|
||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
tags: 'v*'
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.19.2
|
||||
GO_VERSION: 1.20.12
|
||||
|
||||
jobs:
|
||||
prepare-sources-with-deps:
|
||||
|
@ -92,11 +92,11 @@ jobs:
|
|||
$Env:GOOS='windows'
|
||||
$Env:GOARCH='arm64'
|
||||
go-winres simply --arch arm64 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\arm64\sftpgo.exe
|
||||
mkdir x86
|
||||
$Env:GOARCH='386'
|
||||
go-winres simply --arch 386 --product-version $Env:SFTPGO_VERSION-$GIT_COMMIT --file-version $FILE_VERSION --file-description "SFTPGo server" --product-name SFTPGo --copyright "AGPL-3.0" --original-filename sftpgo.exe --icon .\windows-installer\icon.ico
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
go build -trimpath -tags nopgxregisterdefaulttypes,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/internal/version.date=$DATE_TIME" -o .\x86\sftpgo.exe
|
||||
Remove-Item Env:\CGO_ENABLED
|
||||
Remove-Item Env:\GOOS
|
||||
Remove-Item Env:\GOARCH
|
||||
|
|
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
|
@ -0,0 +1 @@
|
|||
* @drakkan
|
|
@ -28,6 +28,8 @@ ARG DOWNLOAD_PLUGINS=false
|
|||
|
||||
RUN if [ "${DOWNLOAD_PLUGINS}" = "true" ]; then apt-get update && apt-get install --no-install-recommends -y curl && ./docker/scripts/download-plugins.sh; fi
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y openssh-server && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
|
||||
# Set to "true" to install jq and the optional git and rsync dependencies
|
||||
|
@ -45,6 +47,7 @@ RUN groupadd --system -g 1000 sftpgo && \
|
|||
--comment "SFTPGo user" --uid 1000 sftpgo
|
||||
|
||||
COPY --from=builder /workspace/sftpgo.json /etc/sftpgo/sftpgo.json
|
||||
COPY --from=builder /etc/ssh/moduli /etc/sftpgo/moduli
|
||||
COPY --from=builder /workspace/templates /usr/share/sftpgo/templates
|
||||
COPY --from=builder /workspace/static /usr/share/sftpgo/static
|
||||
COPY --from=builder /workspace/openapi /usr/share/sftpgo/openapi
|
||||
|
|
|
@ -25,6 +25,7 @@ RUN set -xe && \
|
|||
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --abbrev=8 --dirty)} && \
|
||||
go build $(if [ -n "${FEATURES}" ]; then echo "-tags ${FEATURES}"; fi) -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/internal/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/internal/version.date=`date -u +%FT%TZ`" -v -o sftpgo
|
||||
|
||||
RUN apk add --update --no-cache openssh-client-common
|
||||
|
||||
FROM alpine:3.16
|
||||
|
||||
|
@ -35,16 +36,13 @@ RUN apk add --update --no-cache ca-certificates tzdata mailcap
|
|||
|
||||
RUN if [ "${INSTALL_OPTIONAL_PACKAGES}" = "true" ]; then apk add --update --no-cache jq git rsync; fi
|
||||
|
||||
# set up nsswitch.conf for Go's "netgo" implementation
|
||||
# https://github.com/gliderlabs/docker-alpine/issues/367#issuecomment-424546457
|
||||
RUN test ! -e /etc/nsswitch.conf && echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||
|
||||
RUN mkdir -p /etc/sftpgo /var/lib/sftpgo /usr/share/sftpgo /srv/sftpgo/data /srv/sftpgo/backups
|
||||
|
||||
RUN addgroup -g 1000 -S sftpgo && \
|
||||
adduser -u 1000 -h /var/lib/sftpgo -s /sbin/nologin -G sftpgo -S -D -H -g "SFTPGo user" sftpgo
|
||||
|
||||
COPY --from=builder /workspace/sftpgo.json /etc/sftpgo/sftpgo.json
|
||||
COPY --from=builder /etc/ssh/moduli /etc/sftpgo/moduli
|
||||
COPY --from=builder /workspace/templates /usr/share/sftpgo/templates
|
||||
COPY --from=builder /workspace/static /usr/share/sftpgo/static
|
||||
COPY --from=builder /workspace/openapi /usr/share/sftpgo/openapi
|
||||
|
|
|
@ -15,7 +15,7 @@ ARG COMMIT_SHA
|
|||
# This ARG allows to disable some optional features and it might be useful if you build the image yourself.
|
||||
# For this variant we disable SQLite support since it requires CGO and so a C runtime which is not installed
|
||||
# in distroless/static-* images
|
||||
ARG FEATURES=nosqlite
|
||||
ARG FEATURES
|
||||
|
||||
COPY . .
|
||||
|
||||
|
@ -28,7 +28,7 @@ RUN sed -i 's|"users_base_dir": "",|"users_base_dir": "/srv/sftpgo/data",|' sftp
|
|||
sed -i 's|"backups"|"/srv/sftpgo/backups"|' sftpgo.json && \
|
||||
sed -i 's|"sqlite"|"bolt"|' sftpgo.json
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y media-types && rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y media-types openssh-server && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir /etc/sftpgo /var/lib/sftpgo /srv/sftpgo
|
||||
|
||||
|
@ -38,6 +38,7 @@ COPY --from=builder --chown=1000:1000 /etc/sftpgo /etc/sftpgo
|
|||
COPY --from=builder --chown=1000:1000 /srv/sftpgo /srv/sftpgo
|
||||
COPY --from=builder --chown=1000:1000 /var/lib/sftpgo /var/lib/sftpgo
|
||||
COPY --from=builder --chown=1000:1000 /workspace/sftpgo.json /etc/sftpgo/sftpgo.json
|
||||
COPY --from=builder --chown=1000:1000 /etc/ssh/moduli /etc/sftpgo/moduli
|
||||
COPY --from=builder /workspace/templates /usr/share/sftpgo/templates
|
||||
COPY --from=builder /workspace/static /usr/share/sftpgo/static
|
||||
COPY --from=builder /workspace/openapi /usr/share/sftpgo/openapi
|
||||
|
|
|
@ -134,7 +134,7 @@ APT and YUM repositories are [available](./docs/repo.md).
|
|||
SFTPGo is also available on some marketplaces:
|
||||
|
||||
- [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335)
|
||||
- [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/prasselsrl1645470739547.sftpgo_linux)
|
||||
- [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/eliamarzia1667381463185.sftpgo_linux)
|
||||
- [Elest.io](https://elest.io/open-source/sftpgo)
|
||||
|
||||
Purchasing from there will help keep SFTPGo a long-term sustainable project.
|
||||
|
|
|
@ -125,7 +125,7 @@ SFTPGo 基于 Linux 开发和创建。在每一次提交之后,代码会自动
|
|||
|
||||
</details>
|
||||
|
||||
SFTPGo 在 [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335) 和 [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/prasselsrl1645470739547.sftpgo_linux) 同样可用,在此付费可以帮助 SFTPGo 成为一个可持续发展的长期项目。
|
||||
SFTPGo 在 [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335) 和 [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/eliamarzia1667381463185.sftpgo_linux) 同样可用,在此付费可以帮助 SFTPGo 成为一个可持续发展的长期项目。
|
||||
|
||||
<details><summary>Windows 包</summary>
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
|
|||
|
||||
## Supported tags and respective Dockerfile links
|
||||
|
||||
- [v2.4.0, v2.4, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile)
|
||||
- [v2.4.0-plugins, v2.4-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile)
|
||||
- [v2.4.0-alpine, v2.4-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile.alpine)
|
||||
- [v2.4.0-slim, v2.4-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile)
|
||||
- [v2.4.0-alpine-slim, v2.4-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile.alpine)
|
||||
- [v2.4.0-distroless-slim, v2.4-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.4.0/Dockerfile.distroless)
|
||||
- [v2.4.5, v2.4, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile)
|
||||
- [v2.4.5-plugins, v2.4-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile)
|
||||
- [v2.4.5-alpine, v2.4-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile.alpine)
|
||||
- [v2.4.5-slim, v2.4-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile)
|
||||
- [v2.4.5-alpine-slim, v2.4-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile.alpine)
|
||||
- [v2.4.5-distroless-slim, v2.4-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.4.5/Dockerfile.distroless)
|
||||
- [edge](../Dockerfile)
|
||||
- [edge-plugins](../Dockerfile)
|
||||
- [edge-alpine](../Dockerfile.alpine)
|
||||
|
@ -98,7 +98,7 @@ docker logs some-sftpgo
|
|||
Important note: There are several ways to store data used by applications that run in Docker containers. We encourage users of the SFTPGo images to familiarize themselves with the options available, including:
|
||||
|
||||
- Let Docker manage the storage for SFTPGo data by [writing them to disk on the host system using its own internal volume management](https://docs.docker.com/engine/tutorials/dockervolumes/#adding-a-data-volume). This is the default and is easy and fairly transparent to the user. The downside is that the files may be hard to locate for tools and applications that run directly on the host system, i.e. outside containers.
|
||||
- Create a data directory on the host system (outside the container) and [mount this to a directory visible from inside the container]((https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume)). This places the SFTPGo files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly. The SFTPGo image runs using `1000` as UID/GID by default.
|
||||
- Create a data directory on the host system (outside the container) and [mount this to a directory visible from inside the container](https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume). This places the SFTPGo files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly. The SFTPGo image runs using `1000` as UID/GID by default.
|
||||
|
||||
The Docker documentation is a good starting point for understanding the different storage options and variations, and there are multiple blogs and forum postings that discuss and give advice in this area. We will simply show the basic procedure here for the latter option above:
|
||||
|
||||
|
|
|
@ -17,9 +17,23 @@ esac
|
|||
|
||||
echo "download plugins for arch ${SUFFIX}"
|
||||
|
||||
for PLUGIN in geoipfilter kms pubsub eventstore eventsearch metadata
|
||||
for PLUGIN in metadata
|
||||
do
|
||||
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
|
||||
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/latest/download/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.3/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
|
||||
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.3/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
chmod 755 "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
done
|
||||
|
||||
for PLUGIN in geoipfilter
|
||||
do
|
||||
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.1/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
|
||||
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.1/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
chmod 755 "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
done
|
||||
|
||||
for PLUGIN in pubsub eventstore eventsearch kms
|
||||
do
|
||||
echo "download plugin from https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.4/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}"
|
||||
curl -L "https://github.com/sftpgo/sftpgo-plugin-${PLUGIN}/releases/download/v1.0.4/sftpgo-plugin-${PLUGIN}-linux-${SUFFIX}" --output "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
chmod 755 "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
|
||||
done
|
|
@ -2,14 +2,17 @@
|
|||
|
||||
The built-in `defender` allows you to configure an auto-blocking policy for SFTPGo and thus helps to prevent DoS (Denial of Service) and brute force password guessing.
|
||||
|
||||
If enabled it will protect SFTP, HTTP, FTP and WebDAV services and it will automatically block hosts (IP addresses) that continually fail to log in or attempt to connect.
|
||||
If enabled it will protect SFTP, HTTP (WebClient and user API), FTP and WebDAV services and it will automatically block hosts (IP addresses) that continually fail to log in or attempt to connect.
|
||||
|
||||
You can configure a score for the following events:
|
||||
|
||||
- `score_valid`, defines the score for valid login attempts, eg. user accounts that exist. Default `1`.
|
||||
- `score_invalid`, defines the score for invalid login attempts, eg. non-existent user accounts or client disconnected for inactivity without authentication attempts. Default `2`.
|
||||
- `score_invalid`, defines the score for invalid login attempts, eg. non-existent user accounts. Default `2`.
|
||||
- `score_no_auth`, defines the score for clients disconnected without any authentication attempt. Default `2`.
|
||||
- `score_limit_exceeded`, defines the score for hosts that exceeded the configured rate limits or the configured max connections per host. Default `3`.
|
||||
|
||||
You can set the score to `0` to not penalize some events.
|
||||
|
||||
And then you can configure:
|
||||
|
||||
- `observation_time`, defines the time window, in minutes, for tracking client errors.
|
||||
|
|
|
@ -28,10 +28,13 @@ The following placeholders are supported:
|
|||
- `{{StatusString}}`. Status as string. Possible values "OK", "KO".
|
||||
- `{{ErrorString}}`. Error details. Replaced with an empty string if no errors occur.
|
||||
- `{{VirtualPath}}`. Path seen by SFTPGo users, for example `/adir/afile.txt`.
|
||||
- `{{VirtualDirPath}}`. Parent directory for VirtualPath, for example if VirtualPath is "/adir/afile.txt", VirtualDirPath is "/adir".
|
||||
- `{{FsPath}}`. Full filesystem path, for example `/user/homedir/adir/afile.txt` or `C:/data/user/homedir/adir/afile.txt` on Windows.
|
||||
- `{{ObjectName}}`. File/directory name, for example `afile.txt` or provider object name.
|
||||
- `{{ObjectType}}`. Object type for provider events: `user`, `group`, `admin`, etc.
|
||||
- `{{VirtualTargetPath}}`. Virtual target path for renames.
|
||||
- `{{VirtualTargetDirPath}}`. Parent directory for VirtualTargetPath.
|
||||
- `{{TargetName}}`. Target object name for renames.
|
||||
- `{{FsTargetPath}}`. Full filesystem target path for renames.
|
||||
- `{{FileSize}}`. File size.
|
||||
- `{{Protocol}}`. Used protocol, for example `SFTP`, `FTP`.
|
||||
|
|
|
@ -65,7 +65,7 @@ The configuration file contains the following sections:
|
|||
- `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
|
||||
- `setstat_mode`, integer. 0 means "normal mode": requests for changing permissions, owner/group and access/modification times are executed. 1 means "ignore mode": requests for changing permissions, owner/group and access/modification times are silently ignored. 2 means "ignore mode if not supported": requests for changing permissions and owner/group are silently ignored for cloud filesystems and executed for local/SFTP filesystem. Requests for changing modification times are always executed for local/SFTP filesystems and are executed for cloud based filesystems if the target is a file and there is a metadata plugin available. A metadata plugin can be found [here](https://github.com/sftpgo/sftpgo-plugin-metadata).
|
||||
- `temp_path`, string. Defines the path for temporary files such as those used for atomic uploads or file pipes. If you set this option you must make sure that the defined path exists, is accessible for writing by the user running SFTPGo, and is on the same filesystem as the users home directories otherwise the renaming for atomic uploads will become a copy and therefore may take a long time. The temporary files are not namespaced. The default is generally fine. Leave empty for the default.
|
||||
- `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGINX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported. If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too. For example, for HAProxy, add `send-proxy` or `send-proxy-v2` to each server configuration line. The following modes are supported:
|
||||
- `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGINX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported. If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too. For example, for HAProxy, add `send-proxy` or `send-proxy-v2` to each server configuration line. The PROXY protocol is supported for SSH/SFTP and FTP/S. The following modes are supported:
|
||||
- 0, disabled
|
||||
- 1, enabled. If the upstream IP is not allowed to send a proxy header the header be ignored. Using this mode does not mean that we can accept connections with and without the proxy header. We always try to read the proxy header and we ignore it if the upstream IP is not allowed to send a proxy header
|
||||
- 2, required. If the upstream IP is not allowed to send a proxy header the connection will be rejected
|
||||
|
@ -83,15 +83,16 @@ The configuration file contains the following sections:
|
|||
- `defender`, struct containing the defender configuration. See [Defender](./defender.md) for more details.
|
||||
- `enabled`, boolean. Default `false`.
|
||||
- `driver`, string. Supported drivers are `memory` and `provider`. The `provider` driver will use the configured data provider to store defender events and it is supported for `MySQL`, `PostgreSQL` and `CockroachDB` data providers. Using the `provider` driver you can share the defender events among multiple SFTPGO instances. For a single instance the `memory` driver will be much faster. Default: `memory`.
|
||||
- `ban_time`, integer. Ban time in minutes.
|
||||
- `ban_time_increment`, integer. Ban time increment, as a percentage, if a banned host tries to connect again.
|
||||
- `threshold`, integer. Threshold value for banning a client.
|
||||
- `score_invalid`, integer. Score for invalid login attempts, eg. non-existent user accounts or client disconnected for inactivity without authentication attempts.
|
||||
- `score_valid`, integer. Score for valid login attempts, eg. user accounts that exist.
|
||||
- `score_limit_exceeded`, integer. Score for hosts that exceeded the configured rate limits or the maximum, per-host, allowed connections.
|
||||
- `observation_time`, integer. Defines the time window, in minutes, for tracking client errors. A host is banned if it has exceeded the defined threshold during the last observation time minutes.
|
||||
- `entries_soft_limit`, integer. Ignored for `provider` driver. Default: 100.
|
||||
- `entries_hard_limit`, integer. The number of banned IPs and host scores kept in memory will vary between the soft and hard limit for `memory` driver. If you use the `provider` driver, this setting will limit the number of entries to return when you ask for the entire host list from the defender. Default: 150.
|
||||
- `ban_time`, integer. Ban time in minutes. Default: `30`.
|
||||
- `ban_time_increment`, integer. Ban time increment, as a percentage, if a banned host tries to connect again. Default: `50`.
|
||||
- `threshold`, integer. Threshold value for banning a client. Default: `15`.
|
||||
- `score_invalid`, integer. Score for invalid login attempts, eg. non-existent user accounts. Default: `2`.
|
||||
- `score_valid`, integer. Score for valid login attempts, eg. user accounts that exist. Default: `1`.
|
||||
- `score_limit_exceeded`, integer. Score for hosts that exceeded the configured rate limits or the maximum, per-host, allowed connections. Default: `3`.
|
||||
- `score_no_auth`, defines the score for clients disconnected without any authentication attempt. Default: `2`.
|
||||
- `observation_time`, integer. Defines the time window, in minutes, for tracking client errors. A host is banned if it has exceeded the defined threshold during the last observation time minutes. Default: `30`.
|
||||
- `entries_soft_limit`, integer. Ignored for `provider` driver. Default: `100`.
|
||||
- `entries_hard_limit`, integer. The number of banned IPs and host scores kept in memory will vary between the soft and hard limit for `memory` driver. If you use the `provider` driver, this setting will limit the number of entries to return when you ask for the entire host list from the defender. Default: `150`.
|
||||
- `safelist_file`, string. Path to a file containing a list of ip addresses and/or networks to never ban.
|
||||
- `blocklist_file`, string. Path to a file containing a list of ip addresses and/or networks to always ban. The lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. An host that is already banned will not be automatically unbanned if you put it inside the safe list, you have to unban it using the REST API.
|
||||
- `safelist`, list of IP addresses and/or IP ranges and/or networks to never ban. Invalid entries will be silently ignored. For large lists prefer `safelist_file`. `safelist` and `safelist_file` will be merged so that you can set both.
|
||||
|
@ -129,7 +130,8 @@ The configuration file contains the following sections:
|
|||
- `host_keys`, list of strings. It contains the daemon's private host keys. Each host key can be defined as a path relative to the configuration directory or an absolute one. If empty, the daemon will search or try to generate `id_rsa`, `id_ecdsa` and `id_ed25519` keys inside the configuration directory. If you configure absolute paths to files named `id_rsa`, `id_ecdsa` and/or `id_ed25519` then SFTPGo will try to generate these keys using the default settings.
|
||||
- `host_certificates`, list of strings. Public host certificates. Each certificate can be defined as a path relative to the configuration directory or an absolute one. Certificate's public key must match a private host key otherwise it will be silently ignored. Default: empty.
|
||||
- `host_key_algorithms`, list of strings. Public key algorithms that the server will accept for host key authentication. The supported values are: `rsa-sha2-512-cert-v01@openssh.com`, `rsa-sha2-256-cert-v01@openssh.com`, `ssh-rsa-cert-v01@openssh.com`, `ssh-dss-cert-v01@openssh.com`, `ecdsa-sha2-nistp256-cert-v01@openssh.com`, `ecdsa-sha2-nistp384-cert-v01@openssh.com`, `ecdsa-sha2-nistp521-cert-v01@openssh.com`, `ssh-ed25519-cert-v01@openssh.com`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-rsa`, `ssh-dss`, `ssh-ed25519`. Default values: `rsa-sha2-512-cert-v01@openssh.com`, `rsa-sha2-256-cert-v01@openssh.com`, `ecdsa-sha2-nistp256-cert-v01@openssh.com`, `ecdsa-sha2-nistp384-cert-v01@openssh.com`, `ecdsa-sha2-nistp521-cert-v01@openssh.com`, `ssh-ed25519-cert-v01@openssh.com`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `rsa-sha2-512`, `rsa-sha2-256`, `ssh-ed25519`.
|
||||
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values are: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`, `diffie-hellman-group16-sha512`, `diffie-hellman-group18-sha512`, `diffie-hellman-group14-sha1`, `diffie-hellman-group1-sha1`. Default values: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`. SHA512 based KEXs are disabled by default because they are slow.
|
||||
- `moduli`, list of strings. Diffie-Hellman moduli files. Each moduli file can be defined as a path relative to the configuration directory or an absolute one. If set, `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1` KEX algorithms will be available, `diffie-hellman-group-exchange-sha256` will be enabled by default if you don't explicitly set KEXs. Default: empty.
|
||||
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values are: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`, `diffie-hellman-group16-sha512`, `diffie-hellman-group18-sha512`, `diffie-hellman-group14-sha1`, `diffie-hellman-group1-sha1`. Default values: `curve25519-sha256`, `curve25519-sha256@libssh.org`, `ecdh-sha2-nistp256`, `ecdh-sha2-nistp384`, `ecdh-sha2-nistp521`, `diffie-hellman-group14-sha256`. SHA512 based KEXs are disabled by default because they are slow. If you set one or more moduli files, `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1` will be available.
|
||||
- `ciphers`, list of strings. Allowed ciphers in preference order. Leave empty to use default values. The supported values are: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`, `aes128-cbc`, `aes192-cbc`, `aes256-cbc`, `3des-cbc`, `arcfour256`, `arcfour128`, `arcfour`. Default values: `aes128-gcm@openssh.com`, `aes256-gcm@openssh.com`, `chacha20-poly1305@openssh.com`, `aes128-ctr`, `aes192-ctr`, `aes256-ctr`. Please note that the ciphers disabled by default are insecure, you should expect that an active attacker can recover plaintext if you enable them.
|
||||
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values are: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`, `hmac-sha2-512-etm@openssh.com`, `hmac-sha2-512`, `hmac-sha1`, `hmac-sha1-96`. Default values: `hmac-sha2-256-etm@openssh.com`, `hmac-sha2-256`.
|
||||
- `trusted_user_ca_keys`, list of public keys paths of certificate authorities that are trusted to sign user certificates for authentication. The paths can be absolute or relative to the configuration directory.
|
||||
|
|
|
@ -24,8 +24,7 @@ If you use WebDAV behind a reverse proxy ensure to preserve the `Host` header or
|
|||
Know issues:
|
||||
|
||||
- removing a directory tree on Cloud Storage backends could generate a `not found` error when removing the last (virtual) directory. This happens if the client cycles the directories tree itself and removes files and directories one by one instead of issuing a single remove command
|
||||
- the used [WebDAV library](https://pkg.go.dev/golang.org/x/net/webdav?tab=doc) asks to open a file to execute a `stat` and sometimes reads some bytes to find the content type. Stat calls are executed before and after a download too, so to be able to properly list a directory you need to grant both `list` and `download` permissions and to be able to upload files you need to gran both `list` and `upload` permissions
|
||||
- the used [WebDAV library](https://pkg.go.dev/golang.org/x/net/webdav?tab=doc) not always returns a proper error code/message, most of the times it simply returns `Method not Allowed`. I'll try to improve the library error codes in the future
|
||||
- to be able to properly list a directory you need to grant both `list` and `download` permissions and to be able to upload files you need to gran both `list` and `upload` permissions
|
||||
- if a file or a directory cannot be accessed, for example due to OS permissions issues or because a mapped path for a virtual folder is a missing, it will be omitted from the directory listing. If there is a different error then the whole directory listing will fail. This behavior is different from SFTP/FTP where you will be able to see the problematic file/directory in the directory listing, you will only get an error if you try to access it
|
||||
- if you use the native Windows client please check its usage and pay particular attention to the [registry settings](https://docs.microsoft.com/en-us/iis/publish/using-webdav/using-the-webdav-redirector#webdav-redirector-registry-settings). The default file size limit is 50MB and if you don't configure SFTPGo to use HTTPS you have to set `BasicAuthLevel` to `2`
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Data Backup
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](../../docs/eventmanager.md) to schedule backups.
|
||||
|
||||
The `backup` example script shows how to use the SFTPGo REST API to backup your data.
|
||||
|
||||
The script is written in Python and has the following requirements:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# File retention policies
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](../../docs/eventmanager.md) to schedule data retention checks.
|
||||
|
||||
The `checkretention` example script shows how to use the SFTPGo REST API to manage data retention.
|
||||
|
||||
:warning: Deleting files is an irreversible action, please make sure you fully understand what you are doing before using this feature, you may have users with overlapping home directories or virtual folders shared between multiple users, it is relatively easy to inadvertently delete files you need.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Update user quota
|
||||
|
||||
:warning: Since v2.4.0 you can use the [EventManager](../../docs/eventmanager.md) to schedule quota scans.
|
||||
|
||||
The `scanuserquota` example script shows how to use the SFTPGo REST API to update the users' quota.
|
||||
|
||||
The stored quota may be incorrect for several reasons, such as an unexpected shutdown while uploading files, temporary provider failures, files copied outside of SFTPGo, and so on.
|
||||
|
|
3
fail2ban/README.md
Normal file
3
fail2ban/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Fail2ban
|
||||
|
||||
:warning: We recommend using the [built-in defender](../docs/defender.md) instead of Fail2ban.
|
276
go.mod
276
go.mod
|
@ -1,176 +1,190 @@
|
|||
module github.com/drakkan/sftpgo/v2
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.27.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.4
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.5.1
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aws/aws-sdk-go-v2 v1.17.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.23
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.37
|
||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.21
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.4
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.17.1
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.16
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
cloud.google.com/go/storage v1.36.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
|
||||
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.20.1-0.20221012093027-95be4ae0c9a6
|
||||
github.com/fclairamb/ftpserverlib v0.22.0
|
||||
github.com/fclairamb/go-log v0.4.1
|
||||
github.com/go-acme/lego/v4 v4.9.0
|
||||
github.com/go-chi/chi/v5 v5.0.8-0.20221018120124-e5529d9db4d3
|
||||
github.com/go-chi/jwtauth/v5 v5.0.2
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/go-sql-driver/mysql v1.6.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.3.1
|
||||
github.com/hashicorp/go-plugin v1.4.5
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/jackc/pgx/v5 v5.0.4
|
||||
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.15.12
|
||||
github.com/lestrrat-go/jwx v1.2.25
|
||||
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/minio/sio v0.3.0
|
||||
github.com/otiai10/copy v1.7.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/pkg/sftp v1.13.6-0.20221020054726-e4133ab7e9bd
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/mattn/go-sqlite3 v1.14.19
|
||||
github.com/mhale/smtpd v0.8.1
|
||||
github.com/minio/sio v0.3.1
|
||||
github.com/otiai10/copy v1.14.0
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/sftpgo/sdk v0.1.2
|
||||
github.com/shirou/gopsutil/v3 v3.22.9
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.13.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423
|
||||
github.com/subosito/gotenv v1.4.1
|
||||
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.12.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.6
|
||||
go.uber.org/automaxprocs v1.5.1
|
||||
gocloud.dev v0.27.0
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/net v0.1.0
|
||||
golang.org/x/oauth2 v0.1.0
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/time v0.1.0
|
||||
google.golang.org/api v0.101.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.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.17.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.105.0 // indirect
|
||||
cloud.google.com/go/compute v1.12.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.1.1 // indirect
|
||||
cloud.google.com/go/iam v0.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.1 // 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.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.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
|
||||
github.com/aws/smithy-go v1.13.4 // 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.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.4.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.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-test/deep v1.0.8 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // 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.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.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // 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.0.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-20200714003250-2b9c44734f2b // 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.1.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // 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.0 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // 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.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/miekg/dns v1.1.50 // 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 v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.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.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/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.10 // indirect
|
||||
github.com/tklauser/numcpus v0.5.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
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.2.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-20221025140454-527a21cfbd71 // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.opencensus.io v0.24.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-20231214170342-aacd6d4b4611 // 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/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20221020054403-a265c1cba3cb
|
||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20221026175805-eaebd725b308
|
||||
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-20231218163640-d096410108fe
|
||||
)
|
||||
|
|
|
@ -669,7 +669,7 @@ func stopScheduler() {
|
|||
func startScheduler() error {
|
||||
stopScheduler()
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
rand.Seed(time.Now().UnixNano()) //nolint:staticcheck
|
||||
randSecs := rand.Intn(59)
|
||||
|
||||
scheduler = cron.New()
|
||||
|
|
|
@ -65,6 +65,12 @@ Please take a look at the usage below to customize the options.`,
|
|||
logger.ErrorToConsole("Unable to initialize KMS: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
mfaConfig := config.GetMFAConfig()
|
||||
err = mfaConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("Unable to initialize MFA: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
providerConf := config.GetProviderConf()
|
||||
// ignore actions
|
||||
providerConf.Actions.Hook = ""
|
||||
|
|
|
@ -154,8 +154,9 @@ var (
|
|||
ProtocolHTTP, ProtocolHTTPShare, ProtocolOIDC}
|
||||
disconnHookProtocols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP}
|
||||
// the map key is the protocol, for each protocol we can have multiple rate limiters
|
||||
rateLimiters map[string][]*rateLimiter
|
||||
isShuttingDown atomic.Bool
|
||||
rateLimiters map[string][]*rateLimiter
|
||||
isShuttingDown atomic.Bool
|
||||
ftpLoginCommands = []string{"PASS", "USER"}
|
||||
)
|
||||
|
||||
// Initialize sets the common configuration
|
||||
|
@ -926,7 +927,7 @@ func (conns *ActiveConnections) Remove(connectionID string) {
|
|||
metric.UpdateActiveConnectionsSize(lastIdx)
|
||||
logger.Debug(conn.GetProtocol(), conn.GetID(), "connection removed, local address %#v, remote address %#v close fs error: %v, num open connections: %v",
|
||||
conn.GetLocalAddress(), conn.GetRemoteAddress(), err, lastIdx)
|
||||
if conn.GetProtocol() == ProtocolFTP && conn.GetUsername() == "" {
|
||||
if conn.GetProtocol() == ProtocolFTP && conn.GetUsername() == "" && !util.Contains(ftpLoginCommands, conn.GetCommand()) {
|
||||
ip := util.GetIPFromRemoteAddress(conn.GetRemoteAddress())
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, conn.GetProtocol(),
|
||||
dataprovider.ErrNoAuthTryed.Error())
|
||||
|
|
|
@ -324,6 +324,7 @@ func TestDefenderIntegration(t *testing.T) {
|
|||
Threshold: 0,
|
||||
ScoreInvalid: 2,
|
||||
ScoreValid: 1,
|
||||
ScoreNoAuth: 2,
|
||||
ObservationTime: 15,
|
||||
EntriesSoftLimit: 100,
|
||||
EntriesHardLimit: 150,
|
||||
|
|
|
@ -309,7 +309,7 @@ func (c *BaseConnection) ListDir(virtualPath string) ([]os.FileInfo, error) {
|
|||
|
||||
// CheckParentDirs tries to create the specified directory and any missing parent dirs
|
||||
func (c *BaseConnection) CheckParentDirs(virtualPath string) error {
|
||||
fs, err := c.User.GetFilesystemForPath(virtualPath, "")
|
||||
fs, err := c.User.GetFilesystemForPath(virtualPath, c.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ func (c *BaseConnection) CheckParentDirs(virtualPath string) error {
|
|||
}
|
||||
dirs := util.GetDirsForVirtualPath(virtualPath)
|
||||
for idx := len(dirs) - 1; idx >= 0; idx-- {
|
||||
fs, err = c.User.GetFilesystemForPath(dirs[idx], "")
|
||||
fs, err = c.User.GetFilesystemForPath(dirs[idx], c.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -336,6 +336,17 @@ func (c *BaseConnection) CheckParentDirs(virtualPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetCreateChecks returns the checks for creating new files
|
||||
func (c *BaseConnection) GetCreateChecks(virtualPath string, isNewFile bool) int {
|
||||
if !isNewFile {
|
||||
return 0
|
||||
}
|
||||
if !c.User.HasPerm(dataprovider.PermCreateDirs, path.Dir(virtualPath)) {
|
||||
return vfs.CheckParentDir
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// CreateDir creates a new directory at the specified fsPath
|
||||
func (c *BaseConnection) CreateDir(virtualPath string, checkFilePatterns bool) error {
|
||||
if !c.User.HasPerm(dataprovider.PermCreateDirs, path.Dir(virtualPath)) {
|
||||
|
@ -743,7 +754,7 @@ func (c *BaseConnection) doStatInternal(virtualPath string, mode int, checkFileP
|
|||
if _, ok := vfolders[virtualPath]; ok {
|
||||
return vfs.NewFileInfo(virtualPath, true, 0, time.Unix(0, 0), false), nil
|
||||
}
|
||||
if checkFilePatterns {
|
||||
if checkFilePatterns && virtualPath != "/" {
|
||||
ok, policy := c.User.IsFileAllowed(virtualPath)
|
||||
if !ok && policy == sdk.DenyPolicyHide {
|
||||
return nil, c.GetNotExistError()
|
||||
|
@ -1048,7 +1059,15 @@ func (c *BaseConnection) hasSpaceForRename(fs vfs.Fs, virtualSourcePath, virtual
|
|||
// rename between user root dir and a virtual folder included in user quota
|
||||
return true
|
||||
}
|
||||
if errDst != nil && sourceFolder.IsIncludedInUserQuota() {
|
||||
// rename between a virtual folder included in user quota and the user root dir
|
||||
return true
|
||||
}
|
||||
quotaResult, _ := c.HasSpace(true, false, virtualTargetPath)
|
||||
if quotaResult.HasSpace && quotaResult.QuotaSize == 0 && quotaResult.QuotaFiles == 0 {
|
||||
// no quota restrictions
|
||||
return true
|
||||
}
|
||||
return c.hasSpaceForCrossRename(fs, quotaResult, initialSize, fsSourcePath)
|
||||
}
|
||||
|
||||
|
@ -1506,9 +1525,11 @@ func (c *BaseConnection) GetGenericError(err error) error {
|
|||
return sftp.ErrSSHFxFailure
|
||||
default:
|
||||
if err == ErrPermissionDenied || err == ErrNotExist || err == ErrOpUnsupported ||
|
||||
err == ErrQuotaExceeded || err == vfs.ErrStorageSizeUnavailable || err == ErrShuttingDown {
|
||||
err == ErrQuotaExceeded || err == ErrReadQuotaExceeded || err == vfs.ErrStorageSizeUnavailable ||
|
||||
err == ErrShuttingDown {
|
||||
return err
|
||||
}
|
||||
c.Log(logger.LevelError, "generic error: %+v", err)
|
||||
return ErrGenericFailure
|
||||
}
|
||||
}
|
||||
|
@ -1536,7 +1557,7 @@ func (c *BaseConnection) GetFsAndResolvedPath(virtualPath string) (vfs.Fs, strin
|
|||
// will not be listed
|
||||
return nil, "", c.GetPermissionDeniedError()
|
||||
}
|
||||
return nil, "", err
|
||||
return nil, "", c.GetGenericError(err)
|
||||
}
|
||||
|
||||
if isShuttingDown.Load() {
|
||||
|
|
|
@ -78,14 +78,16 @@ type DefenderConfig struct {
|
|||
BanTimeIncrement int `json:"ban_time_increment" mapstructure:"ban_time_increment"`
|
||||
// Threshold value for banning a client
|
||||
Threshold int `json:"threshold" mapstructure:"threshold"`
|
||||
// Score for invalid login attempts, eg. non-existent user accounts or
|
||||
// client disconnected for inactivity without authentication attempts
|
||||
// Score for invalid login attempts, eg. non-existent user accounts
|
||||
ScoreInvalid int `json:"score_invalid" mapstructure:"score_invalid"`
|
||||
// Score for valid login attempts, eg. user accounts that exist
|
||||
ScoreValid int `json:"score_valid" mapstructure:"score_valid"`
|
||||
// Score for limit exceeded events, generated from the rate limiters or for max connections
|
||||
// per-host exceeded
|
||||
ScoreLimitExceeded int `json:"score_limit_exceeded" mapstructure:"score_limit_exceeded"`
|
||||
// ScoreNoAuth defines the score for clients disconnected without authentication
|
||||
// attempts
|
||||
ScoreNoAuth int `json:"score_no_auth" mapstructure:"score_no_auth"`
|
||||
// Defines the time window, in minutes, for tracking client errors.
|
||||
// A host is banned if it has exceeded the defined threshold during
|
||||
// the last observation time minutes
|
||||
|
@ -157,8 +159,10 @@ func (d *baseDefender) getScore(event HostEvent) int {
|
|||
score = d.config.ScoreValid
|
||||
case HostEventLimitExceeded:
|
||||
score = d.config.ScoreLimitExceeded
|
||||
case HostEventUserNotFound, HostEventNoLoginTried:
|
||||
case HostEventUserNotFound:
|
||||
score = d.config.ScoreInvalid
|
||||
case HostEventNoLoginTried:
|
||||
score = d.config.ScoreNoAuth
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
@ -198,11 +202,33 @@ type hostScore struct {
|
|||
Events []hostEvent
|
||||
}
|
||||
|
||||
func (c *DefenderConfig) checkScores() error {
|
||||
if c.ScoreInvalid < 0 {
|
||||
c.ScoreInvalid = 0
|
||||
}
|
||||
if c.ScoreValid < 0 {
|
||||
c.ScoreValid = 0
|
||||
}
|
||||
if c.ScoreLimitExceeded < 0 {
|
||||
c.ScoreLimitExceeded = 0
|
||||
}
|
||||
if c.ScoreNoAuth < 0 {
|
||||
c.ScoreNoAuth = 0
|
||||
}
|
||||
if c.ScoreInvalid == 0 && c.ScoreValid == 0 && c.ScoreLimitExceeded == 0 && c.ScoreNoAuth == 0 {
|
||||
return fmt.Errorf("invalid defender configuration: all scores are disabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate returns an error if the configuration is invalid
|
||||
func (c *DefenderConfig) validate() error {
|
||||
if !c.Enabled {
|
||||
return nil
|
||||
}
|
||||
if err := c.checkScores(); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.ScoreInvalid >= c.Threshold {
|
||||
return fmt.Errorf("score_invalid %v cannot be greater than threshold %v", c.ScoreInvalid, c.Threshold)
|
||||
}
|
||||
|
@ -212,6 +238,9 @@ func (c *DefenderConfig) validate() error {
|
|||
if c.ScoreLimitExceeded >= c.Threshold {
|
||||
return fmt.Errorf("score_limit_exceeded %v cannot be greater than threshold %v", c.ScoreLimitExceeded, c.Threshold)
|
||||
}
|
||||
if c.ScoreNoAuth >= c.Threshold {
|
||||
return fmt.Errorf("score_no_auth %d cannot be greater than threshold %d", c.ScoreNoAuth, c.Threshold)
|
||||
}
|
||||
if c.BanTime <= 0 {
|
||||
return fmt.Errorf("invalid ban_time %v", c.BanTime)
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ func TestBasicDefender(t *testing.T) {
|
|||
Threshold: 5,
|
||||
ScoreInvalid: 2,
|
||||
ScoreValid: 1,
|
||||
ScoreNoAuth: 2,
|
||||
ScoreLimitExceeded: 3,
|
||||
ObservationTime: 15,
|
||||
EntriesSoftLimit: 1,
|
||||
|
@ -140,7 +141,7 @@ func TestBasicDefender(t *testing.T) {
|
|||
assert.True(t, hosts[0].BanTime.IsZero())
|
||||
assert.Empty(t, hosts[0].GetBanTime())
|
||||
}
|
||||
defender.AddEvent(testIP, HostEventNoLoginTried)
|
||||
defender.AddEvent(testIP, HostEventUserNotFound)
|
||||
defender.AddEvent(testIP, HostEventNoLoginTried)
|
||||
assert.Equal(t, 0, defender.countHosts())
|
||||
assert.Equal(t, 1, defender.countBanned())
|
||||
|
@ -511,6 +512,11 @@ func TestDefenderConfig(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
|
||||
c.ScoreValid = 1
|
||||
c.ScoreNoAuth = 10
|
||||
err = c.validate()
|
||||
require.Error(t, err)
|
||||
|
||||
c.ScoreNoAuth = 2
|
||||
c.BanTime = 0
|
||||
err = c.validate()
|
||||
require.Error(t, err)
|
||||
|
@ -540,6 +546,20 @@ func TestDefenderConfig(t *testing.T) {
|
|||
c.EntriesHardLimit = 20
|
||||
err = c.validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
c = DefenderConfig{
|
||||
Enabled: true,
|
||||
ScoreInvalid: -1,
|
||||
ScoreLimitExceeded: -1,
|
||||
ScoreNoAuth: -1,
|
||||
ScoreValid: -1,
|
||||
}
|
||||
err = c.validate()
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, 0, c.ScoreInvalid)
|
||||
assert.Equal(t, 0, c.ScoreValid)
|
||||
assert.Equal(t, 0, c.ScoreLimitExceeded)
|
||||
assert.Equal(t, 0, c.ScoreNoAuth)
|
||||
}
|
||||
|
||||
func BenchmarkDefenderBannedSearch(b *testing.B) {
|
||||
|
|
|
@ -39,6 +39,7 @@ func TestBasicDbDefender(t *testing.T) {
|
|||
Threshold: 5,
|
||||
ScoreInvalid: 2,
|
||||
ScoreValid: 1,
|
||||
ScoreNoAuth: 2,
|
||||
ScoreLimitExceeded: 3,
|
||||
ObservationTime: 15,
|
||||
EntriesSoftLimit: 1,
|
||||
|
@ -161,9 +162,9 @@ func TestBasicDbDefender(t *testing.T) {
|
|||
testIP2 := "123.45.67.91"
|
||||
testIP3 := "123.45.67.92"
|
||||
for i := 0; i < 3; i++ {
|
||||
defender.AddEvent(testIP, HostEventNoLoginTried)
|
||||
defender.AddEvent(testIP, HostEventUserNotFound)
|
||||
defender.AddEvent(testIP1, HostEventNoLoginTried)
|
||||
defender.AddEvent(testIP2, HostEventNoLoginTried)
|
||||
defender.AddEvent(testIP2, HostEventUserNotFound)
|
||||
}
|
||||
hosts, err = defender.GetHosts()
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -476,6 +477,21 @@ func (p *EventParams) AddError(err error) {
|
|||
p.errors = append(p.errors, err.Error())
|
||||
}
|
||||
|
||||
func (p *EventParams) setBackupParams(backupPath string) {
|
||||
if p.sender != "" {
|
||||
return
|
||||
}
|
||||
p.sender = dataprovider.ActionExecutorSystem
|
||||
p.FsPath = backupPath
|
||||
p.ObjectName = filepath.Base(backupPath)
|
||||
p.VirtualPath = "/" + p.ObjectName
|
||||
p.Timestamp = time.Now().UnixNano()
|
||||
info, err := os.Stat(backupPath)
|
||||
if err == nil {
|
||||
p.FileSize = info.Size()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *EventParams) getStatusString() string {
|
||||
switch p.Status {
|
||||
case 1:
|
||||
|
@ -503,6 +519,18 @@ func (p *EventParams) getUsers() ([]dataprovider.User, error) {
|
|||
}
|
||||
|
||||
func (p *EventParams) getUserFromSender() (dataprovider.User, error) {
|
||||
if p.sender == dataprovider.ActionExecutorSystem {
|
||||
return dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
Status: 1,
|
||||
Username: p.sender,
|
||||
HomeDir: dataprovider.GetBackupsPath(),
|
||||
Permissions: map[string][]string{
|
||||
"/": {dataprovider.PermAny},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
user, err := dataprovider.UserExists(p.sender)
|
||||
if err != nil {
|
||||
eventManagerLog(logger.LevelError, "unable to get user %q: %+v", p.sender, err)
|
||||
|
@ -585,6 +613,13 @@ func (p *EventParams) getStringReplacements(addObjectData bool) []string {
|
|||
"{{Timestamp}}", fmt.Sprintf("%d", p.Timestamp),
|
||||
"{{StatusString}}", p.getStatusString(),
|
||||
}
|
||||
if p.VirtualPath != "" {
|
||||
replacements = append(replacements, "{{VirtualDirPath}}", path.Dir(p.VirtualPath))
|
||||
}
|
||||
if p.VirtualTargetPath != "" {
|
||||
replacements = append(replacements, "{{VirtualTargetDirPath}}", path.Dir(p.VirtualTargetPath))
|
||||
replacements = append(replacements, "{{TargetName}}", path.Base(p.VirtualTargetPath))
|
||||
}
|
||||
if len(p.errors) > 0 {
|
||||
replacements = append(replacements, "{{ErrorString}}", strings.Join(p.errors, ", "))
|
||||
} else {
|
||||
|
@ -679,7 +714,7 @@ func getFileWriter(conn *BaseConnection, virtualPath string) (io.WriteCloser, in
|
|||
if err != nil && !fs.IsNotExist(err) {
|
||||
return nil, numFiles, truncatedSize, nil, conn.GetFsError(fs, err)
|
||||
}
|
||||
f, w, cancelFn, err := fs.Create(fsPath, 0)
|
||||
f, w, cancelFn, err := fs.Create(fsPath, 0, conn.GetCreateChecks(virtualPath, numFiles == 1))
|
||||
if err != nil {
|
||||
return nil, numFiles, truncatedSize, nil, conn.GetFsError(fs, err)
|
||||
}
|
||||
|
@ -1204,10 +1239,11 @@ func getUserForEventAction(user dataprovider.User) (dataprovider.User, error) {
|
|||
}
|
||||
|
||||
func replacePathsPlaceholders(paths []string, replacer *strings.Replacer) []string {
|
||||
for idx := range paths {
|
||||
paths[idx] = util.CleanPath(replaceWithReplacer(paths[idx], replacer))
|
||||
results := make([]string, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
results = append(results, util.CleanPath(replaceWithReplacer(p, replacer)))
|
||||
}
|
||||
return util.RemoveDuplicates(paths, false)
|
||||
return util.RemoveDuplicates(results, false)
|
||||
}
|
||||
|
||||
func executeDeleteFileFsAction(conn *BaseConnection, item string, info os.FileInfo) error {
|
||||
|
@ -1903,7 +1939,11 @@ func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams,
|
|||
case dataprovider.ActionTypeEmail:
|
||||
err = executeEmailRuleAction(action.Options.EmailConfig, params)
|
||||
case dataprovider.ActionTypeBackup:
|
||||
err = dataprovider.ExecuteBackup()
|
||||
var backupPath string
|
||||
backupPath, err = dataprovider.ExecuteBackup()
|
||||
if err == nil {
|
||||
params.setBackupParams(backupPath)
|
||||
}
|
||||
case dataprovider.ActionTypeUserQuotaReset:
|
||||
err = executeUsersQuotaResetRuleAction(conditions, params)
|
||||
case dataprovider.ActionTypeFolderQuotaReset:
|
||||
|
|
|
@ -951,6 +951,74 @@ func TestHiddenPatternFilter(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHiddenRoot(t *testing.T) {
|
||||
// only the "/ftp" directory is allowed and visibile in the "/" path
|
||||
// within /ftp any file/directory is allowed and visibile
|
||||
u := getTestUser()
|
||||
u.Filters.FilePatterns = []sdk.PatternsFilter{
|
||||
{
|
||||
Path: "/",
|
||||
AllowedPatterns: []string{"ftp"},
|
||||
DenyPolicy: sdk.DenyPolicyHide,
|
||||
},
|
||||
{
|
||||
Path: "/ftp",
|
||||
AllowedPatterns: []string{"*"},
|
||||
},
|
||||
}
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
err = os.MkdirAll(filepath.Join(user.HomeDir, fmt.Sprintf("ftp%d", i)), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(user.HomeDir, testFileName), []byte(""), 0666)
|
||||
assert.NoError(t, err)
|
||||
err = os.WriteFile(filepath.Join(user.HomeDir, "ftp.txt"), []byte(""), 0666)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
err = client.Mkdir("ftp")
|
||||
assert.NoError(t, err)
|
||||
entries, err := client.ReadDir("/")
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, entries, 1) {
|
||||
assert.Equal(t, "ftp", entries[0].Name())
|
||||
}
|
||||
_, err = client.Stat(".")
|
||||
assert.NoError(t, err)
|
||||
for _, name := range []string{testFileName, "ftp.txt"} {
|
||||
_, err = client.Stat(name)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = client.Stat(fmt.Sprintf("ftp%d", i))
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
}
|
||||
err = writeSFTPFile(testFileName, 4096, client)
|
||||
assert.ErrorIs(t, err, os.ErrPermission)
|
||||
err = writeSFTPFile("ftp123", 4096, client)
|
||||
assert.ErrorIs(t, err, os.ErrPermission)
|
||||
err = client.Rename(testFileName, testFileName+"_rename")
|
||||
assert.ErrorIs(t, err, os.ErrPermission)
|
||||
err = writeSFTPFile(path.Join("/ftp", testFileName), 4096, client)
|
||||
assert.NoError(t, err)
|
||||
err = client.Mkdir("/ftp/dir")
|
||||
assert.NoError(t, err)
|
||||
err = client.Rename(path.Join("/ftp", testFileName), path.Join("/ftp/dir", testFileName))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFileNotAllowedErrors(t *testing.T) {
|
||||
deniedDir := "/denied"
|
||||
u := getTestUser()
|
||||
|
@ -3353,6 +3421,10 @@ func TestEventRule(t *testing.T) {
|
|||
}
|
||||
a2 := dataprovider.BaseEventAction{
|
||||
Name: "action2",
|
||||
Type: dataprovider.ActionTypeBackup,
|
||||
}
|
||||
a3 := dataprovider.BaseEventAction{
|
||||
Name: "action3",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
|
@ -3362,8 +3434,8 @@ func TestEventRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
a3 := dataprovider.BaseEventAction{
|
||||
Name: "action3",
|
||||
a4 := dataprovider.BaseEventAction{
|
||||
Name: "action4",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
|
@ -3379,6 +3451,9 @@ func TestEventRule(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
action4, _, err := httpdtest.AddEventAction(a4, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "test rule1",
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
|
@ -3417,6 +3492,12 @@ func TestEventRule(t *testing.T) {
|
|||
Name: action3.Name,
|
||||
},
|
||||
Order: 3,
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action4.Name,
|
||||
},
|
||||
Order: 4,
|
||||
Options: dataprovider.EventActionOptions{
|
||||
IsFailureAction: true,
|
||||
},
|
||||
|
@ -3442,13 +3523,13 @@ func TestEventRule(t *testing.T) {
|
|||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action2.Name,
|
||||
Name: action3.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action3.Name,
|
||||
Name: action4.Name,
|
||||
},
|
||||
Order: 2,
|
||||
Options: dataprovider.EventActionOptions{
|
||||
|
@ -3469,7 +3550,7 @@ func TestEventRule(t *testing.T) {
|
|||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action2.Name,
|
||||
Name: action3.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
|
@ -3645,6 +3726,8 @@ func TestEventRule(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action4, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
lastReceivedEmail.reset()
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -3853,7 +3936,7 @@ func TestEventRuleFsActions(t *testing.T) {
|
|||
Type: dataprovider.FilesystemActionRename,
|
||||
Renames: []dataprovider.KeyValue{
|
||||
{
|
||||
Key: "/{{VirtualPath}}",
|
||||
Key: "/{{VirtualDirPath}}/{{ObjectName}}",
|
||||
Value: "/{{ObjectName}}_renamed",
|
||||
},
|
||||
},
|
||||
|
@ -4237,6 +4320,89 @@ func TestEventFsActionsGroupFilters(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBackupAsAttachment(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeBackup,
|
||||
}
|
||||
a2 := dataprovider.BaseEventAction{
|
||||
Name: "a2",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test@example.com"},
|
||||
Subject: `"{{Event}} {{StatusString}}"`,
|
||||
Body: "Domain: {{Name}}",
|
||||
Attachments: []string{"/{{VirtualPath}}"},
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "test rule certificate",
|
||||
Trigger: dataprovider.EventTriggerCertificate,
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action2.Name,
|
||||
},
|
||||
Order: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
renewalEvent := "Certificate renewal"
|
||||
|
||||
common.HandleCertificateEvent(common.EventParams{
|
||||
Name: "example.com",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
Status: 1,
|
||||
Event: renewalEvent,
|
||||
})
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
||||
assert.Contains(t, email.Data, `Domain: example.com`)
|
||||
assert.Contains(t, email.Data, "Content-Type: application/json")
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventActionHTTPMultipart(t *testing.T) {
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "action1",
|
||||
|
@ -5196,6 +5362,86 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleRenameEvent(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notify@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "action1",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test@example.com"},
|
||||
Subject: `"{{Event}}" from "{{Name}}"`,
|
||||
Body: `Fs path {{FsPath}}, Target path "{{VirtualTargetDirPath}}/{{TargetName}}", size: {{FileSize}}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
r1 := dataprovider.EventRule{
|
||||
Name: "test rename rule",
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"rename"},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
testFileSize := int64(32768)
|
||||
lastReceivedEmail.reset()
|
||||
err = writeSFTPFileNoCheck(testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
err = client.Mkdir("subdir")
|
||||
assert.NoError(t, err)
|
||||
err = client.Rename(testFileName, path.Join("/subdir", testFileName))
|
||||
assert.NoError(t, err)
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
|
||||
assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleCertificate(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
|
@ -6141,14 +6387,10 @@ func TestSFTPLoopError(t *testing.T) {
|
|||
|
||||
conn = common.NewBaseConnection("", common.ProtocolSFTP, "", "", user1)
|
||||
_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "SFTP loop")
|
||||
}
|
||||
assert.Error(t, err)
|
||||
conn = common.NewBaseConnection("", common.ProtocolFTP, "", "", user1)
|
||||
_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "SFTP loop")
|
||||
}
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -121,7 +121,7 @@ func (m *CertManager) IsRevoked(crt *x509.Certificate, caCrt *x509.Certificate)
|
|||
}
|
||||
|
||||
for _, crl := range m.crls {
|
||||
if !crl.HasExpired(time.Now()) && caCrt.CheckCRLSignature(crl) == nil {
|
||||
if !crl.HasExpired(time.Now()) && caCrt.CheckCRLSignature(crl) == nil { //nolint:staticcheck
|
||||
for _, rc := range crl.TBSCertList.RevokedCertificates {
|
||||
if rc.SerialNumber.Cmp(crt.SerialNumber) == 0 {
|
||||
return true
|
||||
|
@ -153,7 +153,7 @@ func (m *CertManager) LoadCRLs() error {
|
|||
logger.Warn(m.logSender, "unable to read revocation list %#v", revocationList)
|
||||
return err
|
||||
}
|
||||
crl, err := x509.ParseCRL(crlBytes)
|
||||
crl, err := x509.ParseCRL(crlBytes) //nolint:staticcheck
|
||||
if err != nil {
|
||||
logger.Warn(m.logSender, "unable to parse revocation list %#v", revocationList)
|
||||
return err
|
||||
|
|
|
@ -26,258 +26,258 @@ import (
|
|||
|
||||
const (
|
||||
serverCert = `-----BEGIN CERTIFICATE-----
|
||||
MIIEIDCCAgigAwIBAgIRAPOR9zTkX35vSdeyGpF8Rn8wDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMjU1WhcNMjIwNzAyMjEz
|
||||
MDUxWjARMQ8wDQYDVQQDEwZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQCte0PJhCTNqTiqdwk/s4JanKIMKUVWr2u94a+JYy5gJ9xYXrQ49SeN
|
||||
m+fwhTAOqctP5zNVkFqxlBytJZg3pqCKqRoOOl1qVgL3F3o7JdhZGi67aw8QMLPx
|
||||
tLPpYWnnrlUQoXRJdTlqkDqO8lOZl9HO5oZeidPZ7r5BVD6ZiujAC6Zg0jIc+EPt
|
||||
qhaUJ1CStoAeRf1rNWKmDsLv5hEaDWoaHF9sNVzDQg6atZ3ici00qQj+uvEZo8mL
|
||||
k6egg3rqsTv9ml2qlrRgFumt99J60hTt3tuQaAruHY80O9nGy3SCXC11daa7gszH
|
||||
ElCRvhUVoOxRtB54YBEtJ0gEpFnTO9J1AgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFAgDXwPV
|
||||
nhztNz+H20iNWgoIx8adMB8GA1UdIwQYMBaAFO1yCNAGr/zQTJIi8lw3w5OiuBvM
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQCR5kgIb4vAtrtsXD24n6RtU1yIXHPLNmDStVrH
|
||||
uaMYNnHlLhRlQFCjHhjWvZ89FQC7FeNOITc3FpibJySyw7JfnsyEOGxEbcAS4uLB
|
||||
2pdAiJPqdQtxIVcyi5vu53m1T5tm0sy8sBrGxU466aDQ8VGqjcjfTwNIyoFMd3p/
|
||||
ezFRvg2BudwU9hqApgfHfLi4WCuI3hLO2tbmgDinyH0HI0YYNNweGpiBYbTLF4Tx
|
||||
H6vHgD9USMZeu4+HX0IIsBiHQD7TTIe5ceREkPcNPd5qTpIvT3zKQ/KwwT90/zjP
|
||||
aWmz6pLxBfjRu7MY/bDfxfRUqsrLYJCVBoaDVRWR9rhiPIFkC5JzoWD/4hdj2iis
|
||||
N0+OOaJ77L+/ArFprE+7Fu3cSdYlfiNjV8R5kE29cAxKLI92CjAiTKrEuxKcQPKO
|
||||
+taWNKIYYjEDZwVnzlkTIl007X0RBuzu9gh4w5NwJdt8ZOJAp0JV0Cq+UvG+FC/v
|
||||
lYk82E6j1HKhf4CXmrjsrD1Fyu41mpVFOpa2ATiFGvms913MkXuyO8g99IllmDw1
|
||||
D7/PN4Qe9N6Zm7yoKZM0IUw2v+SUMIdOAZ7dptO9ZjtYOfiAIYN3jM8R4JYgPiuD
|
||||
DGSM9LJBJxCxI/DiO1y1Z3n9TcdDQYut8Gqdi/aYXw2YeqyHXosX5Od3vcK/O5zC
|
||||
pOJTYQ==
|
||||
MIIEIjCCAgqgAwIBAgIQfxHX0pnvRtkmtfLklgrcNzANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIyMDdaFw0zMzAxMDMxMDMw
|
||||
NDVaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAKbMWjMhyjMnDsq/19J9D44Y13uPSMN26NFOCfjVgV23zcqvI8W1
|
||||
csosYj89gSmIRxpcL2FtX7NjIT4vaqXob/en1lYy8hstacOs2cy2LcVZHfxu/hv3
|
||||
6hEKLY28tOD41L1CYZesBt3yV8vGcYIOnnAdIiG52SChnduTafBVE9Pq5P7qJ1gZ
|
||||
d4uBYxe8/Za0metKDvMN6FTK+THq56eD830iRwFOdSw3Z4NS/nQNeVW263E4CC4u
|
||||
BVxgwIHu6giqEfIoV6oVTY64y8X2YlwqvbVN/OtWNIJBLu+mN2EhR2ygpZdAyc82
|
||||
1yrk/X2/Dd3OiKSrrvXL1fOuNGlLNGD+3vUCAwEAAaNxMG8wDgYDVR0PAQH/BAQD
|
||||
AgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUabrE
|
||||
6ATHRqEf/CDQiNWI+0e/nhIwHwYDVR0jBBgwFoAUKPyWZxHuWgH3MA/996i3V4gd
|
||||
aYgwDQYJKoZIhvcNAQELBQADggIBAHFtnPXxCCeeGw4RiIai3bavGtyK5qooZUia
|
||||
hN8abJp9VJKYthLwF75c0wn8W0ZMTY8z9xgmFK9afWHCBNyK+0KCpd/LdDUfwvIn
|
||||
3RwR4HRFjNG+n1UZBA4l1W6X6kCq9/x7YaKLrek9aBHfxwMnoMrOeMUybm6D+B5E
|
||||
lSkAyJRq5VHVatM7UGmdux2MXK5IMpzlIBzz1pXddnzF3f9nfS54xt6ilWst9bMi
|
||||
6mBxisJmqc51L/Fyb2SoCJoO/6kv+3V5HnRNBcZuVE8G5/Uc+WRnyy9dh996W83b
|
||||
jNvSJ9UpspqMtKx7DKU4fC/3xYDjRimZvZ3akfIdkf3j5GVWMtVbx+QVSZ8aKBSM
|
||||
Zx35p8aF0zppTjp2JvBpiQlGIXKfPkmmH4bLpU7Z7qLXFFnp+fs3CjcIng19gGgi
|
||||
XQldgHVsl8FtIebxgW6wc5jb2y/fXjgx9c0SKEeeA3Pp6fExH8PdQdyHHmkHKQzO
|
||||
ozon1tZhQbcjkNz8kXFp3x3X/0i4TsR6vsUigSFHXT7DgusBK8eAiRVOLSpbfIyp
|
||||
7Ul/9DjhtYxcZjNI/xNJcECPGazNDdKh4TdLh35pnQHOsRXDWB873rr5xkJIUXbU
|
||||
ubo+q0VpmF7OtfPO9PrPilWAUhVDRx7CCTW3YUsWrYJkr8d6F/n6y7QPKMtB9Y2P
|
||||
jRJ4LDqX
|
||||
-----END CERTIFICATE-----`
|
||||
serverKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEArXtDyYQkzak4qncJP7OCWpyiDClFVq9rveGviWMuYCfcWF60
|
||||
OPUnjZvn8IUwDqnLT+czVZBasZQcrSWYN6agiqkaDjpdalYC9xd6OyXYWRouu2sP
|
||||
EDCz8bSz6WFp565VEKF0SXU5apA6jvJTmZfRzuaGXonT2e6+QVQ+mYrowAumYNIy
|
||||
HPhD7aoWlCdQkraAHkX9azVipg7C7+YRGg1qGhxfbDVcw0IOmrWd4nItNKkI/rrx
|
||||
GaPJi5OnoIN66rE7/Zpdqpa0YBbprffSetIU7d7bkGgK7h2PNDvZxst0glwtdXWm
|
||||
u4LMxxJQkb4VFaDsUbQeeGARLSdIBKRZ0zvSdQIDAQABAoIBAF4sI8goq7HYwqIG
|
||||
rEagM4rsrCrd3H4KC/qvoJJ7/JjGCp8OCddBfY8pquat5kCPe4aMgxlXm2P6evaj
|
||||
CdZr5Ypf8Xz3we4PctyfKgMhsCfuRqAGpc6sIYJ8DY4LC2pxAExe2LlnoRtv39np
|
||||
QeiGuaYPDbIUL6SGLVFZYgIHngFhbDYfL83q3Cb/PnivUGFvUVQCfRBUKO2d8KYq
|
||||
TrVB5BWD2GrHor24ApQmci1OOqfbkIevkK6bk8HUfSZiZGI9LUQiPHMxi5k2x43J
|
||||
nIwhZnW2N28dorKnWHg2vh7viGvinVRZ3MEyX150oCw/L6SYM4fqR6t2ZSBgNQHT
|
||||
ZNoDtwECgYEA4lXMgtYqKuSlZ3TKfxAj03tJ/gbRdKcUCEGXEbdpY70tTu6KESZS
|
||||
etid4Ut/sWEoPTJsgYiGbgJl571t1O8oR1UZYgh9hBGHLV6UEIt9n2PbExhE2vL3
|
||||
SB7+LfO+tMvM4qKUBN+uy4GpU0NiyEEecw4x4S7MRSyHFRIDR7B6RV0CgYEAxDgS
|
||||
mDaNUfSdfB5mXekLUJAwqeKRdL9RjXYaHbnoZ5kIwQ73tFikRwyTsLQwMhjE1l3z
|
||||
MItTzIAyTf/BlK3dsp6bHTaT7hXIjHBsuKATN5qAuUpzTrg9+QaCawVSlQgNeF3a
|
||||
iyfD4dVp66Bzn3gO757TWqmroBZ2e1owbAQvF/kCgYAKT/Jze6KMNcK7hfy78VZQ
|
||||
imuCoXjlob8t6R8i9YJdwv7Pe9rakS5s3nXDEBePU2fr8eIzvK6zUHSoLF9WtlbV
|
||||
eTEg4FYnsEzCam7AmjptCrWulwp8F1ng9ViLa3Gi9y4snU+1MSPbrdqzKnzTtvPW
|
||||
Ni1bnzA7bp3w/dMcbxQDGQKBgB50hY5SiUS7LuZg4YqZ7UOn3aXAoMr6FvJZ7lvG
|
||||
yyepPQ6aACBh0b2lWhcHIKPl7EdJdcGHHo6TJzusAqPNCKf8rh6upe9COkpx+K3/
|
||||
SnxK4sffol4JgrTwKbXqsZKoGU8hYhZPKbwXn8UOtmN+AvN2N1/PDfBfDCzBJtrd
|
||||
G2IhAoGBAN19976xAMDjKb2+wd/mQYA2fR7E8lodxdX3LDnblYmndTKY67nVo94M
|
||||
FHPKZSN590HkFJ+wmChnOrqjtosY+N25CKMS7939EUIDrq+B+bYTWM/gcwdLXNUk
|
||||
Rygw/078Z3ZDJamXmyez5WpeLFrrbmI8sLnBBmSjQvMb6vCEtQ2Z
|
||||
MIIEowIBAAKCAQEApsxaMyHKMycOyr/X0n0PjhjXe49Iw3bo0U4J+NWBXbfNyq8j
|
||||
xbVyyixiPz2BKYhHGlwvYW1fs2MhPi9qpehv96fWVjLyGy1pw6zZzLYtxVkd/G7+
|
||||
G/fqEQotjby04PjUvUJhl6wG3fJXy8Zxgg6ecB0iIbnZIKGd25Np8FUT0+rk/uon
|
||||
WBl3i4FjF7z9lrSZ60oO8w3oVMr5Mernp4PzfSJHAU51LDdng1L+dA15VbbrcTgI
|
||||
Li4FXGDAge7qCKoR8ihXqhVNjrjLxfZiXCq9tU3861Y0gkEu76Y3YSFHbKCll0DJ
|
||||
zzbXKuT9fb8N3c6IpKuu9cvV8640aUs0YP7e9QIDAQABAoIBADbD9gG/4HH3KwYr
|
||||
AyPbaBYR1f59xzhWfI7sfp2zDGzHAsy/wJETyILVG9UDzrriQeZHyk7E6J0vuSR/
|
||||
0RZ0QP8hnmBjDdcajBVxVXm/fzvCzPOrRcfNGI9LtjVJdmI/kSoq93wjQYXyIh2I
|
||||
JJC9WAwbpK9KJB5wsjH8LtZ4OLBlcdeB8jcvO6FzGij6HwyxqyPctxetlvpcmc/w
|
||||
zNJhps6t+TJ8PpNtEmTpOOmx85V6HMb3QJexwmUYygRaOoiQKBKZSNaOnGoC8w1d
|
||||
WahyyXJk4B3OUllqG1TLUgabFGqq2PeJSP8RvYFH8DUj+fdxD78qDHAygrL8ELLZ
|
||||
2O3Wi0ECgYEAyREnS/kylyIcAsyKczsKEDMIDUF9rGvm2B+QG7cLKHTu24oiNg5B
|
||||
Ik5nkaYmSSrC3O2/s4v47mYzMtWbLxlogiNK6ljLPpdU5/JaeHncZC+18seBoePQ
|
||||
9nOW3AvY2A6ihzy8sKRMfl3FUx/1rcXLdNwkMQo0FWR7nqVPUme9QkkCgYEA1F5n
|
||||
lhfDptiHekagKMTf9SGw4B2UiG6SLjMWhcG2AEFeXpZlsk7Qubnuzk0krjYp+JAI
|
||||
brlzMOkmBXBQywKLe3SG0s0McbRGWVFbEA1SA+WZV5rwJe5PO7W6ndCF2+slyZ5T
|
||||
dPwOY1RybV6R07EvjtfnE8Wtdyko4X22sTkyd00CgYA5MYnuEHqVhvxUx33yfS7F
|
||||
oN5/dsuayi6l94R0fcLMxUZUaJyGp9NbQNYxFgP5+BHp6i8HkZ9DoQqbQSudYCrc
|
||||
KdHbi1p0+XMLb2LQtkk8rl2hK6LyO+1qzUJyYWRTQQZ2VY6O6I1hvKaumH636XWQ
|
||||
TjZ1RKPAGg8X94nytNOfEQKBgQC/+TL0iDjyGyykyTFAiW/WXQVSIwtBJYr5Pm9u
|
||||
rESFCJJxOM1nmT2vlrecQDoXTZk1O6aTyQqrPSeEpRoz2fISwKyb5IYKRyeM2DFU
|
||||
WmY4ZZXvjnzmHP39APNYc8Z9nZzEHF5fEvdCrXTfDy0Ny08tdlhKFFkRreBprkW3
|
||||
APhwxQKBgDBdionnjdB9jdGbYHrsPaweMGdQNXkrTTCFfBA47F+qZswfon12yu4A
|
||||
+cBKCnQe2dQHl8AV3IeUKpmNghu4iICOASQEO9dS6OWZI5vBxZMePBm6+bjTOuf6
|
||||
ozecw3yR55tKpPImt87rhrWlwp35uWuhOr9GHYBdFSwgrEkVMw++
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caCRT = `-----BEGIN CERTIFICATE-----
|
||||
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
|
||||
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
|
||||
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
|
||||
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
|
||||
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
|
||||
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
|
||||
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
|
||||
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
|
||||
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
|
||||
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
|
||||
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
|
||||
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
|
||||
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
|
||||
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
|
||||
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
|
||||
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
|
||||
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
|
||||
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
|
||||
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
|
||||
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
|
||||
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
|
||||
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
|
||||
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
|
||||
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
|
||||
r3rwjFsQOoZotA==
|
||||
QXV0aDAeFw0yMzAxMDMxMDIwNDdaFw0zMzAxMDMxMDMwNDZaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxq6Wl1Ih
|
||||
hgvGdM8M2IVI7dwnv3yShJygZsnREQSEW0xeWJL5DtNeHCME5WByFUAlZKpePtW8
|
||||
TNwln9DYDtgNSMiWwvO/wR0mXsyU8Ma4ZBMlX0oOkWo1Ff/M/u8YY9X78Vvwdt62
|
||||
Yt7QmU5oUUW2HdAgh4AlhKJSjm3t0uDP5s54uvueL5bjChHwEb1ZGOtST9Zt86cj
|
||||
YA/xtVHnDXCJbhohpzQI6dK96NegONZVDaxEohVCyYYOgI1I14Bxu0ZCMm5GjwoO
|
||||
QohnUfEJ+BRgZqFpbsnYCE+PoVayVVFoLA+GMeqbQ2SHej1Pr1K0dbjUz6SAk8/+
|
||||
DL7h8d+YAtflATsMtdsVJ4WzEfvZbSbiYKYmlVC6zk6ooXWadvQ5+aezVes9WMpH
|
||||
YnAoScuKoeerRuKlsSU7u+XCmy/i7Hii5FwMrSvIL2GLtVE+tJFCTABA55OWZikt
|
||||
ULMQfg3P2Hk3GFIE35M10mSjKQkGhz06WC5UQ7f2Xl9GzO6PqRSorzugewgMK6L4
|
||||
SnN7XBFnUHHqx1bWNkUG8NPYB6Zs7UDHygemTWxqqxun43s501DNTSunCKIhwFbt
|
||||
1ol5gOvYAFG+BXxnggBT815Mgz1Zht3S9CuprAgz0grNEwAYjRTm1PSaX3t8I1kv
|
||||
oUUuMF6BzWLHJ66uZKOCsPs3ouGq+G3GfWUCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQCu46fF0Tr2tZz1wkYt2Ty3OU77jcG9
|
||||
zYU/7uxPqPC8OYIzQJrumXKLOkTYJXJ7k+7RQdsn/nbxdH1PbslNDD3Eid/sZsF/
|
||||
dFdGR1ZYwXVQbpYwEd19CvJTALn9CyAZpMS8J2RJrmdScAeSSb0+nAGTYP7GvPm+
|
||||
8ktOnrz3w8FtzTw+seuCW/DI/5UpfC9Jf+i/3XgxDozXWNW6YNOIw/CicyaqbBTk
|
||||
5WFcJ0WJN+8qQurw8n+sOvQcNsuDTO7K3Tqu0wGTDUQKou7kiMX0UISRvd8roNOl
|
||||
zvvokNQe4VgCGQA+Y2SxvSxVG1BaymYeNw/0Yxm7QiKSUI400V1iKIcpnIvIedJR
|
||||
j2bGIlslVSV/P6zkRuF1srRVxTxSf1imEfs8J8mMhHB6DkOsP4Y93z5s6JZ0sPiM
|
||||
eOb0CVKul/e1R0Kq23AdPf5eUv63RhfmokN1OsdarRKMFyHphWMxqGJXsSvRP+dl
|
||||
3DaKeTDx/91OSWiMc+glHHKKJveMYQLeJ7GXmcxhuoBm6o4Coowgw8NFKMCtAsp0
|
||||
ktvsQuhB3uFUterw/2ONsOChx7Ybu36Zk47TKBpktfxDQ578TVoZ7xWSAFqCPHvx
|
||||
A5VSwAg7tdBvORfqQjhiJRnhwr50RaNQABTLS0l5Vsn2mitApPs7iKiIts2ieWsU
|
||||
EsdgvPZR2e5IkA==
|
||||
-----END CERTIFICATE-----`
|
||||
caKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caCRL = `-----BEGIN X509 CRL-----
|
||||
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
|
||||
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
|
||||
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
|
||||
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
|
||||
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
|
||||
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
|
||||
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
|
||||
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
|
||||
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
|
||||
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
|
||||
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
|
||||
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
|
||||
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
|
||||
QbDK+MzhmbKfDxs=
|
||||
MIICpjCBjwIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjMwMTAzMTAzMzI4WhcNMjUwMTAyMTAzMzI4WjAjMCECEHUfHtKUGlg/86yMN/aM
|
||||
xxsXDTIzMDEwMzEwMzMyOFqgIzAhMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQAJf6MBMUc3xWKB6fy0VoPbXQjVTsL4
|
||||
Yjm5lKaCtvcRiJ6onaITfJL6V3OCy/MAe94sHynvK3DyyYvxJ0ms7y+kmEtFzHwz
|
||||
T+hBPHaEV/Ccamt+3zRZwndwEMomkQz5tBipwimOlsYXWqItjhXHcLLr84jWgqpD
|
||||
JHcfDmLswCeJVqe8xyYSYCnWMjQ3sn0h+arjm53SdHTULlsjgKeX/ao2IJwt1Ddr
|
||||
APYKZ/XBWq9vBq3l4l2Ufj16fUBY5NeHTjQcLLrkwmBwpSb0YS8+jBwmOwo1HwEF
|
||||
MEwADBTHI2jT4ygzzKefVETfcSk4CuIQ1ve0qQL7KY5Fg5AXwbRycev6R0vEHR82
|
||||
oOPAqg+dYgKtdkxK5QZrNLenloq6x0/3oEThwOg3J17+eCYjixBC+3PoUzLa+yfZ
|
||||
xSQ/kkcRJExEhadw5I9TI7sEUk1RjDCl6AtHg53LQifokiLLfMRptOiN3a4NlLJ2
|
||||
HNXfWUltRUnr6MCxk+G7U5Zaj1QtCN3Aldw+3xcJr7FOBU23VqRT22XbfW+B1gsr
|
||||
4eNlw5Kk/PDF/WZeGbrwy7fvpSoFsDYI8lpVlzKVwLampIZVhnWsfaf7jk/pc4T0
|
||||
6iZ+rnB6CP4P+LM34jKYiAtz+iufjEB6Ko0jN0ZWCznDGDVgMwnVynGJNKU+4bl8
|
||||
vC4nIbS2OhclcA==
|
||||
-----END X509 CRL-----`
|
||||
client1Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
|
||||
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
|
||||
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
|
||||
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
|
||||
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
|
||||
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
|
||||
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
|
||||
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
|
||||
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
|
||||
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
|
||||
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
|
||||
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
|
||||
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
|
||||
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
|
||||
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
|
||||
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
|
||||
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
|
||||
O/e3EH8=
|
||||
MIIEIDCCAgigAwIBAgIQWwKNgBzKWM8ewyS4K78uOTANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTFaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC+jwuaG0FSpPtE5mZPBgdacAhXXa51/TIT18HTm+QnOYUcGRel3AuZ
|
||||
OBWv3fOallW8iQX3i+M78cKeTMWOS5RGXCdDe866pYXEyUkZFRRSA/6573Dz5dJ/
|
||||
DZOCsgW+91JlSkM1+FYE9cpt4qLkdAjSRXIoebcA64K60wqZr1Js+pQrH3leT9wu
|
||||
33dM3KHkDHOeMj6X/V1me22htndD/DUlWmPc58jMFbcvxFG3oUBB9U65LJBwJNzr
|
||||
XWVcli2QirZ0fLkC7Lo2FIYuN1qeU/8A/T4TTInZb/eW3Faqv4RuhjWPXFLqkdIP
|
||||
4AzDxCNuhlWqyv9nfgegXAHOHpXZMDKxAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKloKnzI
|
||||
w7YYnjm1sKU+LgvT5dU0MB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQAeja0rK7I14ibgis9vSPXAGmmKpagIjvZiYCE6
|
||||
Ti/Rq6qbyQ6tKL08NxR2XPNjoXfxwGOGgboWR86S7WT93pz3HkftAjTfzUnxnXOx
|
||||
S7dWfq+g0uY/3ql6IFDQBpGKHu/KN8/1Pvn39FiYSdCaM66bwyFukcvBXace+aC1
|
||||
M6jzVsscxoCCjXhcZl++Tjpf6TzGMd8OFyArBQmOUCoFrTcMzLPKSAROAHp0k+Ju
|
||||
HHNgLdgXPQCfAgRbWnqq2o2moApe7+gzMS+1X0eKhIXYS7csK8rFvGzjH/ANDo9A
|
||||
8+AFcJh8YiIlEVI8nCb3ERdpAbu6G5xkfUDkqcWjCAhuokrbeFmU82RQOc3TQZc9
|
||||
NMHfTkCOPhaIdPI/kC+fZkdz+5ftDCl/radSljeMX+/y0DVQUOtrQzyT1PBN0vCx
|
||||
L+FCzj0fHJwdoDiFLxDLLN1pYWsxMnIichpg625CZM9r5i183yPErXxxQPydcDrX
|
||||
Y6Ps7rGiU7eGILhAfQnS1XUDvH0gNfLUvO5uWm6yO4yUEDWkA/wOTnrc8Z5Waza+
|
||||
kH+FmxnYpT1rMloTSoyiHIPvTq1nVJ8LILUODZAxW+ZHmccGgOpIN/DWuWunVRHG
|
||||
tuaTSgU1xjWl2q/SeoS2DpiEKTIAZZQ5CTD819oc8SnLTzK0ISRpBXKg13AF2uJD
|
||||
G9q7sA==
|
||||
-----END CERTIFICATE-----`
|
||||
client1Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
// client 2 crt is revoked
|
||||
client2Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
|
||||
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
|
||||
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
|
||||
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
|
||||
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
|
||||
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
|
||||
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
|
||||
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
|
||||
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
|
||||
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
|
||||
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
|
||||
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
|
||||
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
|
||||
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
|
||||
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
|
||||
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
|
||||
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
|
||||
6zdDidU=
|
||||
MIIEIDCCAgigAwIBAgIQdR8e0pQaWD/zrIw39ozHGzANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTRaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/UZqUxeP15lhXBmPmpS5SdI470R75fxmN14FhYwTS3FsDoT+evqRg
|
||||
II4Qo/wqbaGrk/BsbzB7ToVWqpkyZ58hYPdtLjKtBBHYsSCNCoKZEVJTz5JdW3sj
|
||||
CKRsG3zPVhFjJcYW9pKsr/CGIIDWAfkuuwR+R/NHkUFSjEP5N9qMAc9wBvskxV84
|
||||
YAJJykPD9rG8PjXHOKsfNUhH+/QfbqMkCeETJ1sp66o3ilql2aZ0m6K6x4gB7tM7
|
||||
NZnM4eztLZbAnQVQhNBYCR6i7DGI2dujujPbpCqmSqSb42n+3a2o844k6EnU76HJ
|
||||
RZwhd3ypy9CvTdkya5JbK+aKKo8fGFHbAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLItEDE1
|
||||
gVYfe7JSax5YAjEW8tmzMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQCZRStHCbwmhmH4tu7V5ammmQhn1TKcspV86cXz
|
||||
JQ4ZM11RvGpRLTmYuRkl5XloMuvB8yYAE1ihhkYOhgU6zSAj33kUQSx6cHXWau7T
|
||||
NjTchptKX+b17GR/yuFwIR3TugArBsnwyuUdts478gTY+MSgTOWWyOgWl3FujiEJ
|
||||
GJ7EgKde4jURXv2qjp6ZtSVqMlAa3y8C3S8nLdyt9Rf8CcSjEy/t8t0JhoMYCvxg
|
||||
o1k7QhMCfMYjjEIuEyDVOdCs2ExepG1zUVBP5h5239sXvLKrOZvgCZkslyTNd/m9
|
||||
vv4yR5gLgCdt0Ol1uip0p910PJoSqX6nZNfeCx3+Kgyc7crl8PrsnUAVoPgLxpVm
|
||||
FWF+KlUbh2KiYTuSi5cH0Ti9NtWT3Qi8d4WhmjFlu0zD3EJEUie0oYfHERiO9bVo
|
||||
5EAzERSVhgQdxVOLgIc2Hbe1JYFf7idyqASRw6KdVkW6YIC/V/5efrJ1LZ5QNrdv
|
||||
bmfJ5CznE6o1AH9JsQ8xMi+kmyn/It1uMWIwP/tYyjQ98dlOj2k9CHP2RzrvCCY9
|
||||
yZNjs2QC5cleNdSpNMb2J2EUYTNAnaH3H8YdbT0scMHDvre1G7p4AjeuRJ9mW7VK
|
||||
Dcqbw+VdSAPyAFdiCd9x8AU3sr28vYbPbPp+LsHQXIlYdnVR0hh2UKF5lR8iuqVx
|
||||
y05cAQ==
|
||||
-----END CERTIFICATE-----`
|
||||
client2Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
|
|
|
@ -211,6 +211,18 @@ func (t *BaseTransfer) SetCancelFn(cancelFn func()) {
|
|||
t.cancelFn = cancelFn
|
||||
}
|
||||
|
||||
// ConvertError accepts an error that occurs during a read or write and
|
||||
// converts it into a more understandable form for the client if it is a
|
||||
// well-known type of error
|
||||
func (t *BaseTransfer) ConvertError(err error) error {
|
||||
if t.Fs.IsNotExist(err) {
|
||||
return t.Connection.GetNotExistError()
|
||||
} else if t.Fs.IsPermission(err) {
|
||||
return t.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckRead returns an error if read if not allowed
|
||||
func (t *BaseTransfer) CheckRead() error {
|
||||
if t.transferQuota.AllowedDLSize == 0 && t.transferQuota.AllowedTotalSize == 0 {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -225,6 +226,10 @@ func TestTransferErrors(t *testing.T) {
|
|||
conn := NewBaseConnection("id", ProtocolSFTP, "", "", u)
|
||||
transfer := NewBaseTransfer(file, conn, nil, testFile, testFile, "/transfer_test_file", TransferUpload,
|
||||
0, 0, 0, 0, true, fs, dataprovider.TransferQuota{})
|
||||
err = transfer.ConvertError(os.ErrNotExist)
|
||||
assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
|
||||
err = transfer.ConvertError(os.ErrPermission)
|
||||
assert.ErrorIs(t, err, sftp.ErrSSHFxPermissionDenied)
|
||||
assert.Nil(t, transfer.cancelFn)
|
||||
assert.Equal(t, testFile, transfer.GetFsPath())
|
||||
transfer.SetCancelFn(cancelFn)
|
||||
|
|
|
@ -220,6 +220,7 @@ func Init() {
|
|||
ScoreInvalid: 2,
|
||||
ScoreValid: 1,
|
||||
ScoreLimitExceeded: 3,
|
||||
ScoreNoAuth: 2,
|
||||
ObservationTime: 30,
|
||||
EntriesSoftLimit: 100,
|
||||
EntriesHardLimit: 150,
|
||||
|
@ -253,6 +254,7 @@ func Init() {
|
|||
HostKeys: []string{},
|
||||
HostCertificates: []string{},
|
||||
HostKeyAlgorithms: []string{},
|
||||
Moduli: []string{},
|
||||
KexAlgorithms: []string{},
|
||||
Ciphers: []string{},
|
||||
MACs: []string{},
|
||||
|
@ -590,6 +592,9 @@ func HasServicesToStart() bool {
|
|||
if globalConf.WebDAVD.ShouldBind() {
|
||||
return true
|
||||
}
|
||||
if globalConf.HTTPDConfig.ShouldBind() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -1939,6 +1944,7 @@ func setViperDefaults() {
|
|||
viper.SetDefault("common.defender.score_invalid", globalConf.Common.DefenderConfig.ScoreInvalid)
|
||||
viper.SetDefault("common.defender.score_valid", globalConf.Common.DefenderConfig.ScoreValid)
|
||||
viper.SetDefault("common.defender.score_limit_exceeded", globalConf.Common.DefenderConfig.ScoreLimitExceeded)
|
||||
viper.SetDefault("common.defender.score_no_auth", globalConf.Common.DefenderConfig.ScoreNoAuth)
|
||||
viper.SetDefault("common.defender.observation_time", globalConf.Common.DefenderConfig.ObservationTime)
|
||||
viper.SetDefault("common.defender.entries_soft_limit", globalConf.Common.DefenderConfig.EntriesSoftLimit)
|
||||
viper.SetDefault("common.defender.entries_hard_limit", globalConf.Common.DefenderConfig.EntriesHardLimit)
|
||||
|
@ -1961,6 +1967,7 @@ func setViperDefaults() {
|
|||
viper.SetDefault("sftpd.host_keys", globalConf.SFTPD.HostKeys)
|
||||
viper.SetDefault("sftpd.host_certificates", globalConf.SFTPD.HostCertificates)
|
||||
viper.SetDefault("sftpd.host_key_algorithms", globalConf.SFTPD.HostKeyAlgorithms)
|
||||
viper.SetDefault("sftpd.moduli", globalConf.SFTPD.Moduli)
|
||||
viper.SetDefault("sftpd.kex_algorithms", globalConf.SFTPD.KexAlgorithms)
|
||||
viper.SetDefault("sftpd.ciphers", globalConf.SFTPD.Ciphers)
|
||||
viper.SetDefault("sftpd.macs", globalConf.SFTPD.MACs)
|
||||
|
|
|
@ -386,6 +386,10 @@ func TestServiceToStart(t *testing.T) {
|
|||
sftpdConf := config.GetSFTPDConfig()
|
||||
sftpdConf.Bindings[0].Port = 0
|
||||
config.SetSFTPDConfig(sftpdConf)
|
||||
// httpd service is enabled
|
||||
assert.True(t, config.HasServicesToStart())
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.Bindings[0].Port = 0
|
||||
assert.False(t, config.HasServicesToStart())
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
ftpdConf.Bindings[0].Port = 2121
|
||||
|
|
|
@ -15,14 +15,13 @@
|
|||
package dataprovider
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
|
@ -71,9 +70,9 @@ const (
|
|||
var (
|
||||
validAdminPerms = []string{PermAdminAny, PermAdminAddUsers, PermAdminChangeUsers, PermAdminDeleteUsers,
|
||||
PermAdminViewUsers, PermAdminManageGroups, PermAdminViewConnections, PermAdminCloseConnections,
|
||||
PermAdminViewServerStatus, PermAdminManageAdmins, PermAdminManageAPIKeys, PermAdminQuotaScans,
|
||||
PermAdminManageSystem, PermAdminManageDefender, PermAdminViewDefender, PermAdminRetentionChecks,
|
||||
PermAdminMetadataChecks, PermAdminViewEvents}
|
||||
PermAdminViewServerStatus, PermAdminManageAdmins, PermAdminManageEventRules, PermAdminManageAPIKeys,
|
||||
PermAdminQuotaScans, PermAdminManageSystem, PermAdminManageDefender, PermAdminViewDefender,
|
||||
PermAdminRetentionChecks, PermAdminMetadataChecks, PermAdminViewEvents}
|
||||
)
|
||||
|
||||
// AdminTOTPConfig defines the time-based one time password configuration
|
||||
|
@ -548,12 +547,9 @@ func (a *Admin) CanManageMFA() bool {
|
|||
}
|
||||
|
||||
// GetSignature returns a signature for this admin.
|
||||
// It could change after an update
|
||||
// It will change after an update
|
||||
func (a *Admin) GetSignature() string {
|
||||
data := []byte(a.Username)
|
||||
data = append(data, []byte(a.Password)...)
|
||||
signature := sha256.Sum256(data)
|
||||
return base64.StdEncoding.EncodeToString(signature[:])
|
||||
return strconv.FormatInt(a.UpdatedAt, 10)
|
||||
}
|
||||
|
||||
func (a *Admin) getACopy() Admin {
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/webdav"
|
||||
"github.com/drakkan/webdav"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
|
|
|
@ -524,36 +524,36 @@ func (c *Config) requireCustomTLSForMySQL() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *Config) doBackup() error {
|
||||
func (c *Config) doBackup() (string, error) {
|
||||
now := time.Now().UTC()
|
||||
outputFile := filepath.Join(c.BackupsPath, fmt.Sprintf("backup_%s_%d.json", now.Weekday(), now.Hour()))
|
||||
providerLog(logger.LevelDebug, "starting backup to file %q", outputFile)
|
||||
err := os.MkdirAll(filepath.Dir(outputFile), 0700)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelError, "unable to create backup dir %q: %v", outputFile, err)
|
||||
return fmt.Errorf("unable to create backup dir: %w", err)
|
||||
return outputFile, fmt.Errorf("unable to create backup dir: %w", err)
|
||||
}
|
||||
backup, err := DumpData()
|
||||
if err != nil {
|
||||
providerLog(logger.LevelError, "unable to execute backup: %v", err)
|
||||
return fmt.Errorf("unable to dump backup data: %w", err)
|
||||
return outputFile, fmt.Errorf("unable to dump backup data: %w", err)
|
||||
}
|
||||
dump, err := json.Marshal(backup)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelError, "unable to marshal backup as JSON: %v", err)
|
||||
return fmt.Errorf("unable to marshal backup data as JSON: %w", err)
|
||||
return outputFile, fmt.Errorf("unable to marshal backup data as JSON: %w", err)
|
||||
}
|
||||
err = os.WriteFile(outputFile, dump, 0600)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelError, "unable to save backup: %v", err)
|
||||
return fmt.Errorf("unable to save backup: %w", err)
|
||||
return outputFile, fmt.Errorf("unable to save backup: %w", err)
|
||||
}
|
||||
providerLog(logger.LevelDebug, "backup saved to %q", outputFile)
|
||||
return nil
|
||||
return outputFile, nil
|
||||
}
|
||||
|
||||
// ExecuteBackup executes a backup
|
||||
func ExecuteBackup() error {
|
||||
func ExecuteBackup() (string, error) {
|
||||
return config.doBackup()
|
||||
}
|
||||
|
||||
|
@ -833,6 +833,12 @@ func Initialize(cnf Config, basePath string, checkAdmins bool) error {
|
|||
if cnf.BackupsPath == "" {
|
||||
return fmt.Errorf("required directory is invalid, backup path %#v", cnf.BackupsPath)
|
||||
}
|
||||
absoluteBackupPath, err := util.GetAbsolutePath(cnf.BackupsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get absolute backup path: %w", err)
|
||||
}
|
||||
config.BackupsPath = absoluteBackupPath
|
||||
providerLog(logger.LevelDebug, "absolute backup path %q", config.BackupsPath)
|
||||
|
||||
if err := initializeHashingAlgo(&cnf); err != nil {
|
||||
return err
|
||||
|
@ -1866,10 +1872,6 @@ func GetUserVariants(username string) (User, User, error) {
|
|||
|
||||
// AddUser adds a new SFTPGo user.
|
||||
func AddUser(user *User, executor, ipAddress string) error {
|
||||
user.Filters.RecoveryCodes = nil
|
||||
user.Filters.TOTPConfig = UserTOTPConfig{
|
||||
Enabled: false,
|
||||
}
|
||||
user.Username = config.convertName(user.Username)
|
||||
err := provider.addUser(user)
|
||||
if err == nil {
|
||||
|
@ -1880,6 +1882,11 @@ func AddUser(user *User, executor, ipAddress string) error {
|
|||
|
||||
// UpdateUserPassword updates the user password
|
||||
func UpdateUserPassword(username, plainPwd, executor, ipAddress string) error {
|
||||
if config.PasswordValidation.Users.MinEntropy > 0 {
|
||||
if err := passwordvalidator.Validate(plainPwd, config.PasswordValidation.Users.MinEntropy); err != nil {
|
||||
return util.NewValidationError(err.Error())
|
||||
}
|
||||
}
|
||||
hashedPwd, err := hashPlainPassword(plainPwd)
|
||||
if err != nil {
|
||||
return util.NewGenericError(fmt.Sprintf("unable to set the new password: %v", err))
|
||||
|
|
|
@ -1354,6 +1354,12 @@ func (r *EventRule) hasUserAssociated(providerObjectType string) bool {
|
|||
return providerObjectType == actionObjectUser
|
||||
case EventTriggerFsEvent:
|
||||
return true
|
||||
default:
|
||||
if len(r.Actions) > 0 {
|
||||
// should we allow schedules where backup is not the first action?
|
||||
// maybe we could pass the action index and check before that index
|
||||
return r.Actions[0].Type == ActionTypeBackup
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -2423,8 +2423,11 @@ func (p *MemoryProvider) getNextRuleID() int64 {
|
|||
func (p *MemoryProvider) clear() {
|
||||
p.dbHandle.Lock()
|
||||
defer p.dbHandle.Unlock()
|
||||
|
||||
p.dbHandle.usernames = []string{}
|
||||
p.dbHandle.users = make(map[string]User)
|
||||
p.dbHandle.groupnames = []string{}
|
||||
p.dbHandle.groups = map[string]Group{}
|
||||
p.dbHandle.vfoldersNames = []string{}
|
||||
p.dbHandle.vfolders = make(map[string]vfs.BaseVirtualFolder)
|
||||
p.dbHandle.admins = make(map[string]Admin)
|
||||
|
@ -2433,6 +2436,10 @@ func (p *MemoryProvider) clear() {
|
|||
p.dbHandle.apiKeysIDs = []string{}
|
||||
p.dbHandle.shares = make(map[string]Share)
|
||||
p.dbHandle.sharesIDs = []string{}
|
||||
p.dbHandle.actions = map[string]BaseEventAction{}
|
||||
p.dbHandle.actionsNames = []string{}
|
||||
p.dbHandle.rules = map[string]EventRule{}
|
||||
p.dbHandle.rulesNames = []string{}
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) reloadConfig() error {
|
||||
|
|
|
@ -25,8 +25,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/httpclient"
|
||||
|
@ -128,12 +128,9 @@ func (n *Node) authenticate(token string) (string, error) {
|
|||
if token == "" {
|
||||
return "", ErrInvalidCredentials
|
||||
}
|
||||
t, err := jwt.Parse([]byte(token), jwt.WithVerify(jwa.HS256, []byte(n.Data.Key.GetPayload())))
|
||||
t, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.HS256, []byte(n.Data.Key.GetPayload())), jwt.WithValidate(true))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to parse token: %v", err)
|
||||
}
|
||||
if err := jwt.Validate(t); err != nil {
|
||||
return "", fmt.Errorf("unable to validate token: %v", err)
|
||||
return "", fmt.Errorf("unable to parse and validate token: %v", err)
|
||||
}
|
||||
if admin, ok := t.Get("admin"); ok {
|
||||
if val, ok := admin.(string); ok && val != "" {
|
||||
|
@ -169,7 +166,7 @@ func (n *Node) generateAuthToken(username string) (string, error) {
|
|||
t.Set(jwt.NotBeforeKey, now.Add(-30*time.Second)) //nolint:errcheck
|
||||
t.Set(jwt.ExpirationKey, now.Add(1*time.Minute)) //nolint:errcheck
|
||||
|
||||
payload, err := jwt.Sign(t, jwa.HS256, []byte(n.Data.Key.GetPayload()))
|
||||
payload, err := jwt.Sign(t, jwt.WithKey(jwa.HS256, []byte(n.Data.Key.GetPayload())))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to sign authentication token: %w", err)
|
||||
}
|
||||
|
|
|
@ -106,7 +106,8 @@ func checkCacheUpdates() {
|
|||
providerLog(logger.LevelError, "unable to get recently updated users: %v", err)
|
||||
return
|
||||
}
|
||||
for _, user := range users {
|
||||
for idx := range users {
|
||||
user := users[idx]
|
||||
providerLog(logger.LevelDebug, "invalidate caches for user %q", user.Username)
|
||||
if user.DeletedAt > 0 {
|
||||
deletedAt := util.GetTimeFromMsecSinceEpoch(user.DeletedAt)
|
||||
|
|
|
@ -987,6 +987,12 @@ func sqlCommonAddUser(user *User, dbHandle *sql.DB) error {
|
|||
defer cancel()
|
||||
|
||||
return sqlCommonExecuteTx(ctx, dbHandle, func(tx *sql.Tx) error {
|
||||
if config.IsShared == 1 {
|
||||
_, err := tx.ExecContext(ctx, getRemoveSoftDeletedUserQuery(), user.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
q := getAddUserQuery()
|
||||
_, err := tx.ExecContext(ctx, q, user.Username, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID,
|
||||
user.MaxSessions, user.QuotaSize, user.QuotaFiles, string(permissions), user.UploadBandwidth,
|
||||
|
@ -3127,6 +3133,12 @@ func sqlCommonAddEventRule(rule *EventRule, dbHandle *sql.DB) error {
|
|||
defer cancel()
|
||||
|
||||
return sqlCommonExecuteTx(ctx, dbHandle, func(tx *sql.Tx) error {
|
||||
if config.IsShared == 1 {
|
||||
_, err := tx.ExecContext(ctx, getRemoveSoftDeletedRuleQuery(), rule.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
q := getAddEventRuleQuery()
|
||||
_, err := tx.ExecContext(ctx, q, rule.Name, rule.Description, util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||
util.GetTimeAsMsSinceEpoch(time.Now()), rule.Trigger, string(conditions))
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -527,6 +527,10 @@ func getDeleteUserQuery(softDelete bool) string {
|
|||
return fmt.Sprintf(`DELETE FROM %s WHERE id = %s`, sqlTableUsers, sqlPlaceholders[0])
|
||||
}
|
||||
|
||||
func getRemoveSoftDeletedUserQuery() string {
|
||||
return fmt.Sprintf(`DELETE FROM %s WHERE username = %s AND deleted_at > 0`, sqlTableUsers, sqlPlaceholders[0])
|
||||
}
|
||||
|
||||
func getFolderByNameQuery() string {
|
||||
return fmt.Sprintf(`SELECT %s FROM %s WHERE name = %s`, selectFolderFields, sqlTableFolders, sqlPlaceholders[0])
|
||||
}
|
||||
|
@ -891,6 +895,10 @@ func getDeleteEventRuleQuery(softDelete bool) string {
|
|||
return fmt.Sprintf(`DELETE FROM %s WHERE name = %s`, sqlTableEventsRules, sqlPlaceholders[0])
|
||||
}
|
||||
|
||||
func getRemoveSoftDeletedRuleQuery() string {
|
||||
return fmt.Sprintf(`DELETE FROM %s WHERE name = %s AND deleted_at > 0`, sqlTableEventsRules, sqlPlaceholders[0])
|
||||
}
|
||||
|
||||
func getClearRuleActionMappingQuery() string {
|
||||
return fmt.Sprintf(`DELETE FROM %s WHERE rule_id = (SELECT id FROM %s WHERE name = %s)`, sqlTableRulesActionsMapping,
|
||||
sqlTableEventsRules, sqlPlaceholders[0])
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
package dataprovider
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -24,9 +22,11 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||
|
@ -600,7 +600,7 @@ func (u *User) GetVirtualFolderForPath(virtualPath string) (vfs.VirtualFolder, e
|
|||
// CheckMetadataConsistency checks the consistency between the metadata stored
|
||||
// in the configured metadata plugin and the filesystem
|
||||
func (u *User) CheckMetadataConsistency() error {
|
||||
fs, err := u.getRootFs("")
|
||||
fs, err := u.getRootFs(xid.New().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -621,7 +621,7 @@ func (u *User) CheckMetadataConsistency() error {
|
|||
// ScanQuota scans the user home dir and virtual folders, included in its quota,
|
||||
// and returns the number of files and their size
|
||||
func (u *User) ScanQuota() (int, int64, error) {
|
||||
fs, err := u.getRootFs("")
|
||||
fs, err := u.getRootFs(xid.New().String())
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
@ -945,6 +945,9 @@ func (u *User) IsPartialAuth(loginMethod string) bool {
|
|||
method == SSHLoginMethodPassword {
|
||||
continue
|
||||
}
|
||||
if method == LoginMethodPassword && util.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) {
|
||||
continue
|
||||
}
|
||||
if !util.Contains(SSHMultiStepsLoginMethods, method) {
|
||||
return false
|
||||
}
|
||||
|
@ -1131,12 +1134,9 @@ func (u *User) MustSetSecondFactorForProtocol(protocol string) bool {
|
|||
}
|
||||
|
||||
// GetSignature returns a signature for this admin.
|
||||
// It could change after an update
|
||||
// It will change after an update
|
||||
func (u *User) GetSignature() string {
|
||||
data := []byte(fmt.Sprintf("%v_%v_%v", u.Username, u.Status, u.ExpirationDate))
|
||||
data = append(data, []byte(u.Password)...)
|
||||
signature := sha256.Sum256(data)
|
||||
return base64.StdEncoding.EncodeToString(signature[:])
|
||||
return strconv.FormatInt(u.UpdatedAt, 10)
|
||||
}
|
||||
|
||||
// GetBandwidthForIP returns the upload and download bandwidth for the specified IP
|
||||
|
@ -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 {
|
||||
|
|
|
@ -1724,8 +1724,10 @@ func TestDefender(t *testing.T) {
|
|||
|
||||
cfg := config.GetCommonConfig()
|
||||
cfg.DefenderConfig.Enabled = true
|
||||
cfg.DefenderConfig.Threshold = 3
|
||||
cfg.DefenderConfig.Threshold = 4
|
||||
cfg.DefenderConfig.ScoreLimitExceeded = 2
|
||||
cfg.DefenderConfig.ScoreNoAuth = 1
|
||||
cfg.DefenderConfig.ScoreValid = 1
|
||||
|
||||
err := common.Initialize(cfg, 0)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1739,9 +1741,31 @@ func TestDefender(t *testing.T) {
|
|||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
// just dial without login
|
||||
ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
|
||||
client, err = ftp.Dial(ftpServerAddr, ftpOptions...)
|
||||
assert.NoError(t, err)
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
host := hosts[0]
|
||||
assert.Empty(t, host.GetBanTime())
|
||||
assert.Equal(t, 1, host.Score)
|
||||
}
|
||||
user.Password = "wrong_pwd"
|
||||
_, err = getFTPClient(user, false, nil)
|
||||
assert.Error(t, err)
|
||||
hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
host := hosts[0]
|
||||
assert.Empty(t, host.GetBanTime())
|
||||
assert.Equal(t, 2, host.Score)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
user.Password = "wrong_pwd"
|
||||
for i := 0; i < 2; i++ {
|
||||
_, err = getFTPClient(user, false, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
@ -408,7 +408,7 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, flags int, resolvedPath
|
|||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, fmt.Errorf("%w, denied by pre-upload action", ftpserver.ErrFileNameNotAllowed)
|
||||
}
|
||||
file, w, cancelFn, err := fs.Create(filePath, flags)
|
||||
file, w, cancelFn, err := fs.Create(filePath, flags, c.GetCreateChecks(requestPath, true))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error creating file %#v, flags %v: %+v", resolvedPath, flags, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
@ -463,7 +463,7 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve
|
|||
}
|
||||
}
|
||||
|
||||
file, w, cancelFn, err := fs.Create(filePath, flags)
|
||||
file, w, cancelFn, err := fs.Create(filePath, flags, c.GetCreateChecks(requestPath, false))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error opening existing file, flags: %v, source: %#v, err: %+v", flags, filePath, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
|
|
@ -65,205 +65,205 @@ CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
|
|||
-----END EC PRIVATE KEY-----`
|
||||
caCRT = `-----BEGIN CERTIFICATE-----
|
||||
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
|
||||
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
|
||||
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
|
||||
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
|
||||
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
|
||||
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
|
||||
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
|
||||
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
|
||||
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
|
||||
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
|
||||
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
|
||||
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
|
||||
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
|
||||
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
|
||||
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
|
||||
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
|
||||
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
|
||||
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
|
||||
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
|
||||
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
|
||||
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
|
||||
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
|
||||
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
|
||||
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
|
||||
r3rwjFsQOoZotA==
|
||||
QXV0aDAeFw0yMzAxMDMxMDIwNDdaFw0zMzAxMDMxMDMwNDZaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxq6Wl1Ih
|
||||
hgvGdM8M2IVI7dwnv3yShJygZsnREQSEW0xeWJL5DtNeHCME5WByFUAlZKpePtW8
|
||||
TNwln9DYDtgNSMiWwvO/wR0mXsyU8Ma4ZBMlX0oOkWo1Ff/M/u8YY9X78Vvwdt62
|
||||
Yt7QmU5oUUW2HdAgh4AlhKJSjm3t0uDP5s54uvueL5bjChHwEb1ZGOtST9Zt86cj
|
||||
YA/xtVHnDXCJbhohpzQI6dK96NegONZVDaxEohVCyYYOgI1I14Bxu0ZCMm5GjwoO
|
||||
QohnUfEJ+BRgZqFpbsnYCE+PoVayVVFoLA+GMeqbQ2SHej1Pr1K0dbjUz6SAk8/+
|
||||
DL7h8d+YAtflATsMtdsVJ4WzEfvZbSbiYKYmlVC6zk6ooXWadvQ5+aezVes9WMpH
|
||||
YnAoScuKoeerRuKlsSU7u+XCmy/i7Hii5FwMrSvIL2GLtVE+tJFCTABA55OWZikt
|
||||
ULMQfg3P2Hk3GFIE35M10mSjKQkGhz06WC5UQ7f2Xl9GzO6PqRSorzugewgMK6L4
|
||||
SnN7XBFnUHHqx1bWNkUG8NPYB6Zs7UDHygemTWxqqxun43s501DNTSunCKIhwFbt
|
||||
1ol5gOvYAFG+BXxnggBT815Mgz1Zht3S9CuprAgz0grNEwAYjRTm1PSaX3t8I1kv
|
||||
oUUuMF6BzWLHJ66uZKOCsPs3ouGq+G3GfWUCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQCu46fF0Tr2tZz1wkYt2Ty3OU77jcG9
|
||||
zYU/7uxPqPC8OYIzQJrumXKLOkTYJXJ7k+7RQdsn/nbxdH1PbslNDD3Eid/sZsF/
|
||||
dFdGR1ZYwXVQbpYwEd19CvJTALn9CyAZpMS8J2RJrmdScAeSSb0+nAGTYP7GvPm+
|
||||
8ktOnrz3w8FtzTw+seuCW/DI/5UpfC9Jf+i/3XgxDozXWNW6YNOIw/CicyaqbBTk
|
||||
5WFcJ0WJN+8qQurw8n+sOvQcNsuDTO7K3Tqu0wGTDUQKou7kiMX0UISRvd8roNOl
|
||||
zvvokNQe4VgCGQA+Y2SxvSxVG1BaymYeNw/0Yxm7QiKSUI400V1iKIcpnIvIedJR
|
||||
j2bGIlslVSV/P6zkRuF1srRVxTxSf1imEfs8J8mMhHB6DkOsP4Y93z5s6JZ0sPiM
|
||||
eOb0CVKul/e1R0Kq23AdPf5eUv63RhfmokN1OsdarRKMFyHphWMxqGJXsSvRP+dl
|
||||
3DaKeTDx/91OSWiMc+glHHKKJveMYQLeJ7GXmcxhuoBm6o4Coowgw8NFKMCtAsp0
|
||||
ktvsQuhB3uFUterw/2ONsOChx7Ybu36Zk47TKBpktfxDQ578TVoZ7xWSAFqCPHvx
|
||||
A5VSwAg7tdBvORfqQjhiJRnhwr50RaNQABTLS0l5Vsn2mitApPs7iKiIts2ieWsU
|
||||
EsdgvPZR2e5IkA==
|
||||
-----END CERTIFICATE-----`
|
||||
caKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caCRL = `-----BEGIN X509 CRL-----
|
||||
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
|
||||
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
|
||||
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
|
||||
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
|
||||
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
|
||||
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
|
||||
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
|
||||
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
|
||||
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
|
||||
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
|
||||
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
|
||||
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
|
||||
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
|
||||
QbDK+MzhmbKfDxs=
|
||||
MIICpjCBjwIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjMwMTAzMTAzMzI4WhcNMjUwMTAyMTAzMzI4WjAjMCECEHUfHtKUGlg/86yMN/aM
|
||||
xxsXDTIzMDEwMzEwMzMyOFqgIzAhMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQAJf6MBMUc3xWKB6fy0VoPbXQjVTsL4
|
||||
Yjm5lKaCtvcRiJ6onaITfJL6V3OCy/MAe94sHynvK3DyyYvxJ0ms7y+kmEtFzHwz
|
||||
T+hBPHaEV/Ccamt+3zRZwndwEMomkQz5tBipwimOlsYXWqItjhXHcLLr84jWgqpD
|
||||
JHcfDmLswCeJVqe8xyYSYCnWMjQ3sn0h+arjm53SdHTULlsjgKeX/ao2IJwt1Ddr
|
||||
APYKZ/XBWq9vBq3l4l2Ufj16fUBY5NeHTjQcLLrkwmBwpSb0YS8+jBwmOwo1HwEF
|
||||
MEwADBTHI2jT4ygzzKefVETfcSk4CuIQ1ve0qQL7KY5Fg5AXwbRycev6R0vEHR82
|
||||
oOPAqg+dYgKtdkxK5QZrNLenloq6x0/3oEThwOg3J17+eCYjixBC+3PoUzLa+yfZ
|
||||
xSQ/kkcRJExEhadw5I9TI7sEUk1RjDCl6AtHg53LQifokiLLfMRptOiN3a4NlLJ2
|
||||
HNXfWUltRUnr6MCxk+G7U5Zaj1QtCN3Aldw+3xcJr7FOBU23VqRT22XbfW+B1gsr
|
||||
4eNlw5Kk/PDF/WZeGbrwy7fvpSoFsDYI8lpVlzKVwLampIZVhnWsfaf7jk/pc4T0
|
||||
6iZ+rnB6CP4P+LM34jKYiAtz+iufjEB6Ko0jN0ZWCznDGDVgMwnVynGJNKU+4bl8
|
||||
vC4nIbS2OhclcA==
|
||||
-----END X509 CRL-----`
|
||||
client1Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
|
||||
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
|
||||
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
|
||||
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
|
||||
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
|
||||
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
|
||||
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
|
||||
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
|
||||
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
|
||||
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
|
||||
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
|
||||
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
|
||||
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
|
||||
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
|
||||
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
|
||||
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
|
||||
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
|
||||
O/e3EH8=
|
||||
MIIEIDCCAgigAwIBAgIQWwKNgBzKWM8ewyS4K78uOTANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTFaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC+jwuaG0FSpPtE5mZPBgdacAhXXa51/TIT18HTm+QnOYUcGRel3AuZ
|
||||
OBWv3fOallW8iQX3i+M78cKeTMWOS5RGXCdDe866pYXEyUkZFRRSA/6573Dz5dJ/
|
||||
DZOCsgW+91JlSkM1+FYE9cpt4qLkdAjSRXIoebcA64K60wqZr1Js+pQrH3leT9wu
|
||||
33dM3KHkDHOeMj6X/V1me22htndD/DUlWmPc58jMFbcvxFG3oUBB9U65LJBwJNzr
|
||||
XWVcli2QirZ0fLkC7Lo2FIYuN1qeU/8A/T4TTInZb/eW3Faqv4RuhjWPXFLqkdIP
|
||||
4AzDxCNuhlWqyv9nfgegXAHOHpXZMDKxAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKloKnzI
|
||||
w7YYnjm1sKU+LgvT5dU0MB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQAeja0rK7I14ibgis9vSPXAGmmKpagIjvZiYCE6
|
||||
Ti/Rq6qbyQ6tKL08NxR2XPNjoXfxwGOGgboWR86S7WT93pz3HkftAjTfzUnxnXOx
|
||||
S7dWfq+g0uY/3ql6IFDQBpGKHu/KN8/1Pvn39FiYSdCaM66bwyFukcvBXace+aC1
|
||||
M6jzVsscxoCCjXhcZl++Tjpf6TzGMd8OFyArBQmOUCoFrTcMzLPKSAROAHp0k+Ju
|
||||
HHNgLdgXPQCfAgRbWnqq2o2moApe7+gzMS+1X0eKhIXYS7csK8rFvGzjH/ANDo9A
|
||||
8+AFcJh8YiIlEVI8nCb3ERdpAbu6G5xkfUDkqcWjCAhuokrbeFmU82RQOc3TQZc9
|
||||
NMHfTkCOPhaIdPI/kC+fZkdz+5ftDCl/radSljeMX+/y0DVQUOtrQzyT1PBN0vCx
|
||||
L+FCzj0fHJwdoDiFLxDLLN1pYWsxMnIichpg625CZM9r5i183yPErXxxQPydcDrX
|
||||
Y6Ps7rGiU7eGILhAfQnS1XUDvH0gNfLUvO5uWm6yO4yUEDWkA/wOTnrc8Z5Waza+
|
||||
kH+FmxnYpT1rMloTSoyiHIPvTq1nVJ8LILUODZAxW+ZHmccGgOpIN/DWuWunVRHG
|
||||
tuaTSgU1xjWl2q/SeoS2DpiEKTIAZZQ5CTD819oc8SnLTzK0ISRpBXKg13AF2uJD
|
||||
G9q7sA==
|
||||
-----END CERTIFICATE-----`
|
||||
client1Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
// client 2 crt is revoked
|
||||
client2Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
|
||||
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
|
||||
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
|
||||
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
|
||||
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
|
||||
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
|
||||
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
|
||||
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
|
||||
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
|
||||
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
|
||||
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
|
||||
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
|
||||
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
|
||||
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
|
||||
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
|
||||
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
|
||||
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
|
||||
6zdDidU=
|
||||
MIIEIDCCAgigAwIBAgIQdR8e0pQaWD/zrIw39ozHGzANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTRaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/UZqUxeP15lhXBmPmpS5SdI470R75fxmN14FhYwTS3FsDoT+evqRg
|
||||
II4Qo/wqbaGrk/BsbzB7ToVWqpkyZ58hYPdtLjKtBBHYsSCNCoKZEVJTz5JdW3sj
|
||||
CKRsG3zPVhFjJcYW9pKsr/CGIIDWAfkuuwR+R/NHkUFSjEP5N9qMAc9wBvskxV84
|
||||
YAJJykPD9rG8PjXHOKsfNUhH+/QfbqMkCeETJ1sp66o3ilql2aZ0m6K6x4gB7tM7
|
||||
NZnM4eztLZbAnQVQhNBYCR6i7DGI2dujujPbpCqmSqSb42n+3a2o844k6EnU76HJ
|
||||
RZwhd3ypy9CvTdkya5JbK+aKKo8fGFHbAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLItEDE1
|
||||
gVYfe7JSax5YAjEW8tmzMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQCZRStHCbwmhmH4tu7V5ammmQhn1TKcspV86cXz
|
||||
JQ4ZM11RvGpRLTmYuRkl5XloMuvB8yYAE1ihhkYOhgU6zSAj33kUQSx6cHXWau7T
|
||||
NjTchptKX+b17GR/yuFwIR3TugArBsnwyuUdts478gTY+MSgTOWWyOgWl3FujiEJ
|
||||
GJ7EgKde4jURXv2qjp6ZtSVqMlAa3y8C3S8nLdyt9Rf8CcSjEy/t8t0JhoMYCvxg
|
||||
o1k7QhMCfMYjjEIuEyDVOdCs2ExepG1zUVBP5h5239sXvLKrOZvgCZkslyTNd/m9
|
||||
vv4yR5gLgCdt0Ol1uip0p910PJoSqX6nZNfeCx3+Kgyc7crl8PrsnUAVoPgLxpVm
|
||||
FWF+KlUbh2KiYTuSi5cH0Ti9NtWT3Qi8d4WhmjFlu0zD3EJEUie0oYfHERiO9bVo
|
||||
5EAzERSVhgQdxVOLgIc2Hbe1JYFf7idyqASRw6KdVkW6YIC/V/5efrJ1LZ5QNrdv
|
||||
bmfJ5CznE6o1AH9JsQ8xMi+kmyn/It1uMWIwP/tYyjQ98dlOj2k9CHP2RzrvCCY9
|
||||
yZNjs2QC5cleNdSpNMb2J2EUYTNAnaH3H8YdbT0scMHDvre1G7p4AjeuRJ9mW7VK
|
||||
Dcqbw+VdSAPyAFdiCd9x8AU3sr28vYbPbPp+LsHQXIlYdnVR0hh2UKF5lR8iuqVx
|
||||
y05cAQ==
|
||||
-----END CERTIFICATE-----`
|
||||
client2Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ func (t *transfer) Read(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil && err != io.EOF {
|
||||
t.TransferError(err)
|
||||
err = t.ConvertError(err)
|
||||
return
|
||||
}
|
||||
t.HandleThrottle()
|
||||
|
@ -85,6 +86,7 @@ func (t *transfer) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil {
|
||||
t.TransferError(err)
|
||||
err = t.ConvertError(err)
|
||||
return
|
||||
}
|
||||
t.HandleThrottle()
|
||||
|
|
|
@ -132,6 +132,7 @@ func loadDataFromRequest(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
if err := restoreBackup(content, "", scanQuota, mode, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
sendAPIResponse(w, r, err, "Data restored", http.StatusOK)
|
||||
}
|
||||
|
@ -170,6 +171,7 @@ func loadData(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
if err := restoreBackup(content, inputFile, scanQuota, mode, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
sendAPIResponse(w, r, err, "Data restored", http.StatusOK)
|
||||
}
|
||||
|
@ -444,7 +446,7 @@ func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota i
|
|||
err = dataprovider.UpdateUser(&user, executor, ipAddress)
|
||||
logger.Debug(logSender, "", "restoring existing user: %#v, dump file: %#v, error: %v", user.Username, inputFile, err)
|
||||
if mode == 2 && err == nil {
|
||||
disconnectUser(user.Username)
|
||||
disconnectUser(user.Username, executor)
|
||||
}
|
||||
} else {
|
||||
err = dataprovider.AddUser(&user, executor, ipAddress)
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/smtp"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
||||
|
@ -81,6 +82,10 @@ func addUser(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user.Filters.RecoveryCodes = nil
|
||||
user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
|
||||
Enabled: false,
|
||||
}
|
||||
err = dataprovider.AddUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
|
@ -186,7 +191,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
sendAPIResponse(w, r, err, "User updated", http.StatusOK)
|
||||
if disconnect == 1 {
|
||||
disconnectUser(user.Username)
|
||||
disconnectUser(user.Username, claims.Username)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +209,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
sendAPIResponse(w, r, err, "User deleted", http.StatusOK)
|
||||
disconnectUser(dataprovider.ConvertName(username))
|
||||
disconnectUser(dataprovider.ConvertName(username), claims.Username)
|
||||
}
|
||||
|
||||
func forgotUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -241,12 +246,24 @@ func resetUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "Password reset successful", http.StatusOK)
|
||||
}
|
||||
|
||||
func disconnectUser(username string) {
|
||||
func disconnectUser(username, admin string) {
|
||||
for _, stat := range common.Connections.GetStats() {
|
||||
if stat.Username == username {
|
||||
common.Connections.Close(stat.ConnectionID)
|
||||
}
|
||||
}
|
||||
for _, stat := range getNodesConnections(admin) {
|
||||
if stat.Username == username {
|
||||
n, err := dataprovider.GetNodeByName(stat.Node)
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "unable to disconnect user %q, error getting node %q: %v", username, stat.Node, err)
|
||||
continue
|
||||
}
|
||||
if err := n.SendDeleteRequest(admin, fmt.Sprintf("%s/%s", activeConnectionsPath, stat.ConnectionID)); err != nil {
|
||||
logger.Warn(logSender, "", "unable to disconnect user %q from node %q, error: %v", username, n.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
|
|
@ -67,6 +67,7 @@ func (f *httpdFile) Read(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil && err != io.EOF {
|
||||
f.TransferError(err)
|
||||
err = f.ConvertError(err)
|
||||
return
|
||||
}
|
||||
f.HandleThrottle()
|
||||
|
@ -91,6 +92,7 @@ func (f *httpdFile) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil {
|
||||
f.TransferError(err)
|
||||
err = f.ConvertError(err)
|
||||
return
|
||||
}
|
||||
f.HandleThrottle()
|
||||
|
|
|
@ -201,7 +201,7 @@ func (c *Connection) handleUploadFile(fs vfs.Fs, resolvedPath, filePath, request
|
|||
|
||||
maxWriteSize, _ := c.GetMaxWriteSize(diskQuota, false, fileSize, fs.IsUploadResumeSupported())
|
||||
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, c.GetCreateChecks(requestPath, isNewFile))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error opening existing file, source: %#v, err: %+v", filePath, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
|
|
@ -4516,7 +4516,7 @@ func TestUserSFTPFs(t *testing.T) {
|
|||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
user.FsConfig.Provider = sdk.SFTPFilesystemProvider
|
||||
user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1" // missing port
|
||||
user.FsConfig.SFTPConfig.Endpoint = "[::1]:22:22" // invalid endpoint
|
||||
user.FsConfig.SFTPConfig.Username = "sftp_user"
|
||||
user.FsConfig.SFTPConfig.Password = kms.NewPlainSecret("sftp_pwd")
|
||||
user.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(sftpPrivateKey)
|
||||
|
@ -4527,6 +4527,13 @@ func TestUserSFTPFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid endpoint")
|
||||
|
||||
user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1"
|
||||
_, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
|
||||
assert.Error(t, err)
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "127.0.0.1:22", user.FsConfig.SFTPConfig.Endpoint)
|
||||
|
||||
user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1:2022"
|
||||
user.FsConfig.SFTPConfig.DisableCouncurrentReads = true
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
|
@ -6367,6 +6374,7 @@ func TestDefenderAPI(t *testing.T) {
|
|||
cfg.DefenderConfig.Driver = driver
|
||||
cfg.DefenderConfig.Threshold = 3
|
||||
cfg.DefenderConfig.ScoreLimitExceeded = 2
|
||||
cfg.DefenderConfig.ScoreNoAuth = 0
|
||||
|
||||
err := common.Initialize(cfg, 0)
|
||||
assert.NoError(t, err)
|
||||
|
@ -6383,6 +6391,10 @@ func TestDefenderAPI(t *testing.T) {
|
|||
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
||||
hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, hosts, 0)
|
||||
common.AddDefenderEvent(ip, common.HostEventUserNotFound)
|
||||
hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
host := hosts[0]
|
||||
assert.Empty(t, host.GetBanTime())
|
||||
|
@ -6394,7 +6406,7 @@ func TestDefenderAPI(t *testing.T) {
|
|||
assert.Empty(t, host.GetBanTime())
|
||||
assert.Equal(t, 2, host.Score)
|
||||
|
||||
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
||||
common.AddDefenderEvent(ip, common.HostEventUserNotFound)
|
||||
hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
|
@ -6414,8 +6426,8 @@ func TestDefenderAPI(t *testing.T) {
|
|||
_, _, err = httpdtest.GetDefenderHostByIP(ip, http.StatusNotFound)
|
||||
assert.NoError(t, err)
|
||||
|
||||
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
||||
common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
|
||||
common.AddDefenderEvent(ip, common.HostEventUserNotFound)
|
||||
common.AddDefenderEvent(ip, common.HostEventUserNotFound)
|
||||
hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, hosts, 1)
|
||||
|
|
|
@ -40,8 +40,8 @@ import (
|
|||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -81,205 +81,205 @@ CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
|
|||
-----END EC PRIVATE KEY-----`
|
||||
caCRT = `-----BEGIN CERTIFICATE-----
|
||||
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
|
||||
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
|
||||
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
|
||||
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
|
||||
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
|
||||
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
|
||||
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
|
||||
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
|
||||
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
|
||||
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
|
||||
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
|
||||
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
|
||||
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
|
||||
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
|
||||
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
|
||||
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
|
||||
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
|
||||
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
|
||||
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
|
||||
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
|
||||
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
|
||||
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
|
||||
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
|
||||
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
|
||||
r3rwjFsQOoZotA==
|
||||
QXV0aDAeFw0yMzAxMDMxMDIwNDdaFw0zMzAxMDMxMDMwNDZaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxq6Wl1Ih
|
||||
hgvGdM8M2IVI7dwnv3yShJygZsnREQSEW0xeWJL5DtNeHCME5WByFUAlZKpePtW8
|
||||
TNwln9DYDtgNSMiWwvO/wR0mXsyU8Ma4ZBMlX0oOkWo1Ff/M/u8YY9X78Vvwdt62
|
||||
Yt7QmU5oUUW2HdAgh4AlhKJSjm3t0uDP5s54uvueL5bjChHwEb1ZGOtST9Zt86cj
|
||||
YA/xtVHnDXCJbhohpzQI6dK96NegONZVDaxEohVCyYYOgI1I14Bxu0ZCMm5GjwoO
|
||||
QohnUfEJ+BRgZqFpbsnYCE+PoVayVVFoLA+GMeqbQ2SHej1Pr1K0dbjUz6SAk8/+
|
||||
DL7h8d+YAtflATsMtdsVJ4WzEfvZbSbiYKYmlVC6zk6ooXWadvQ5+aezVes9WMpH
|
||||
YnAoScuKoeerRuKlsSU7u+XCmy/i7Hii5FwMrSvIL2GLtVE+tJFCTABA55OWZikt
|
||||
ULMQfg3P2Hk3GFIE35M10mSjKQkGhz06WC5UQ7f2Xl9GzO6PqRSorzugewgMK6L4
|
||||
SnN7XBFnUHHqx1bWNkUG8NPYB6Zs7UDHygemTWxqqxun43s501DNTSunCKIhwFbt
|
||||
1ol5gOvYAFG+BXxnggBT815Mgz1Zht3S9CuprAgz0grNEwAYjRTm1PSaX3t8I1kv
|
||||
oUUuMF6BzWLHJ66uZKOCsPs3ouGq+G3GfWUCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQCu46fF0Tr2tZz1wkYt2Ty3OU77jcG9
|
||||
zYU/7uxPqPC8OYIzQJrumXKLOkTYJXJ7k+7RQdsn/nbxdH1PbslNDD3Eid/sZsF/
|
||||
dFdGR1ZYwXVQbpYwEd19CvJTALn9CyAZpMS8J2RJrmdScAeSSb0+nAGTYP7GvPm+
|
||||
8ktOnrz3w8FtzTw+seuCW/DI/5UpfC9Jf+i/3XgxDozXWNW6YNOIw/CicyaqbBTk
|
||||
5WFcJ0WJN+8qQurw8n+sOvQcNsuDTO7K3Tqu0wGTDUQKou7kiMX0UISRvd8roNOl
|
||||
zvvokNQe4VgCGQA+Y2SxvSxVG1BaymYeNw/0Yxm7QiKSUI400V1iKIcpnIvIedJR
|
||||
j2bGIlslVSV/P6zkRuF1srRVxTxSf1imEfs8J8mMhHB6DkOsP4Y93z5s6JZ0sPiM
|
||||
eOb0CVKul/e1R0Kq23AdPf5eUv63RhfmokN1OsdarRKMFyHphWMxqGJXsSvRP+dl
|
||||
3DaKeTDx/91OSWiMc+glHHKKJveMYQLeJ7GXmcxhuoBm6o4Coowgw8NFKMCtAsp0
|
||||
ktvsQuhB3uFUterw/2ONsOChx7Ybu36Zk47TKBpktfxDQ578TVoZ7xWSAFqCPHvx
|
||||
A5VSwAg7tdBvORfqQjhiJRnhwr50RaNQABTLS0l5Vsn2mitApPs7iKiIts2ieWsU
|
||||
EsdgvPZR2e5IkA==
|
||||
-----END CERTIFICATE-----`
|
||||
caKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caCRL = `-----BEGIN X509 CRL-----
|
||||
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
|
||||
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
|
||||
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
|
||||
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
|
||||
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
|
||||
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
|
||||
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
|
||||
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
|
||||
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
|
||||
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
|
||||
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
|
||||
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
|
||||
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
|
||||
QbDK+MzhmbKfDxs=
|
||||
MIICpjCBjwIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjMwMTAzMTAzMzI4WhcNMjUwMTAyMTAzMzI4WjAjMCECEHUfHtKUGlg/86yMN/aM
|
||||
xxsXDTIzMDEwMzEwMzMyOFqgIzAhMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQAJf6MBMUc3xWKB6fy0VoPbXQjVTsL4
|
||||
Yjm5lKaCtvcRiJ6onaITfJL6V3OCy/MAe94sHynvK3DyyYvxJ0ms7y+kmEtFzHwz
|
||||
T+hBPHaEV/Ccamt+3zRZwndwEMomkQz5tBipwimOlsYXWqItjhXHcLLr84jWgqpD
|
||||
JHcfDmLswCeJVqe8xyYSYCnWMjQ3sn0h+arjm53SdHTULlsjgKeX/ao2IJwt1Ddr
|
||||
APYKZ/XBWq9vBq3l4l2Ufj16fUBY5NeHTjQcLLrkwmBwpSb0YS8+jBwmOwo1HwEF
|
||||
MEwADBTHI2jT4ygzzKefVETfcSk4CuIQ1ve0qQL7KY5Fg5AXwbRycev6R0vEHR82
|
||||
oOPAqg+dYgKtdkxK5QZrNLenloq6x0/3oEThwOg3J17+eCYjixBC+3PoUzLa+yfZ
|
||||
xSQ/kkcRJExEhadw5I9TI7sEUk1RjDCl6AtHg53LQifokiLLfMRptOiN3a4NlLJ2
|
||||
HNXfWUltRUnr6MCxk+G7U5Zaj1QtCN3Aldw+3xcJr7FOBU23VqRT22XbfW+B1gsr
|
||||
4eNlw5Kk/PDF/WZeGbrwy7fvpSoFsDYI8lpVlzKVwLampIZVhnWsfaf7jk/pc4T0
|
||||
6iZ+rnB6CP4P+LM34jKYiAtz+iufjEB6Ko0jN0ZWCznDGDVgMwnVynGJNKU+4bl8
|
||||
vC4nIbS2OhclcA==
|
||||
-----END X509 CRL-----`
|
||||
client1Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
|
||||
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
|
||||
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
|
||||
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
|
||||
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
|
||||
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
|
||||
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
|
||||
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
|
||||
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
|
||||
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
|
||||
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
|
||||
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
|
||||
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
|
||||
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
|
||||
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
|
||||
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
|
||||
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
|
||||
O/e3EH8=
|
||||
MIIEIDCCAgigAwIBAgIQWwKNgBzKWM8ewyS4K78uOTANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTFaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC+jwuaG0FSpPtE5mZPBgdacAhXXa51/TIT18HTm+QnOYUcGRel3AuZ
|
||||
OBWv3fOallW8iQX3i+M78cKeTMWOS5RGXCdDe866pYXEyUkZFRRSA/6573Dz5dJ/
|
||||
DZOCsgW+91JlSkM1+FYE9cpt4qLkdAjSRXIoebcA64K60wqZr1Js+pQrH3leT9wu
|
||||
33dM3KHkDHOeMj6X/V1me22htndD/DUlWmPc58jMFbcvxFG3oUBB9U65LJBwJNzr
|
||||
XWVcli2QirZ0fLkC7Lo2FIYuN1qeU/8A/T4TTInZb/eW3Faqv4RuhjWPXFLqkdIP
|
||||
4AzDxCNuhlWqyv9nfgegXAHOHpXZMDKxAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKloKnzI
|
||||
w7YYnjm1sKU+LgvT5dU0MB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQAeja0rK7I14ibgis9vSPXAGmmKpagIjvZiYCE6
|
||||
Ti/Rq6qbyQ6tKL08NxR2XPNjoXfxwGOGgboWR86S7WT93pz3HkftAjTfzUnxnXOx
|
||||
S7dWfq+g0uY/3ql6IFDQBpGKHu/KN8/1Pvn39FiYSdCaM66bwyFukcvBXace+aC1
|
||||
M6jzVsscxoCCjXhcZl++Tjpf6TzGMd8OFyArBQmOUCoFrTcMzLPKSAROAHp0k+Ju
|
||||
HHNgLdgXPQCfAgRbWnqq2o2moApe7+gzMS+1X0eKhIXYS7csK8rFvGzjH/ANDo9A
|
||||
8+AFcJh8YiIlEVI8nCb3ERdpAbu6G5xkfUDkqcWjCAhuokrbeFmU82RQOc3TQZc9
|
||||
NMHfTkCOPhaIdPI/kC+fZkdz+5ftDCl/radSljeMX+/y0DVQUOtrQzyT1PBN0vCx
|
||||
L+FCzj0fHJwdoDiFLxDLLN1pYWsxMnIichpg625CZM9r5i183yPErXxxQPydcDrX
|
||||
Y6Ps7rGiU7eGILhAfQnS1XUDvH0gNfLUvO5uWm6yO4yUEDWkA/wOTnrc8Z5Waza+
|
||||
kH+FmxnYpT1rMloTSoyiHIPvTq1nVJ8LILUODZAxW+ZHmccGgOpIN/DWuWunVRHG
|
||||
tuaTSgU1xjWl2q/SeoS2DpiEKTIAZZQ5CTD819oc8SnLTzK0ISRpBXKg13AF2uJD
|
||||
G9q7sA==
|
||||
-----END CERTIFICATE-----`
|
||||
client1Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
// client 2 crt is revoked
|
||||
client2Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
|
||||
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
|
||||
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
|
||||
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
|
||||
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
|
||||
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
|
||||
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
|
||||
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
|
||||
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
|
||||
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
|
||||
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
|
||||
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
|
||||
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
|
||||
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
|
||||
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
|
||||
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
|
||||
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
|
||||
6zdDidU=
|
||||
MIIEIDCCAgigAwIBAgIQdR8e0pQaWD/zrIw39ozHGzANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTRaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/UZqUxeP15lhXBmPmpS5SdI470R75fxmN14FhYwTS3FsDoT+evqRg
|
||||
II4Qo/wqbaGrk/BsbzB7ToVWqpkyZ58hYPdtLjKtBBHYsSCNCoKZEVJTz5JdW3sj
|
||||
CKRsG3zPVhFjJcYW9pKsr/CGIIDWAfkuuwR+R/NHkUFSjEP5N9qMAc9wBvskxV84
|
||||
YAJJykPD9rG8PjXHOKsfNUhH+/QfbqMkCeETJ1sp66o3ilql2aZ0m6K6x4gB7tM7
|
||||
NZnM4eztLZbAnQVQhNBYCR6i7DGI2dujujPbpCqmSqSb42n+3a2o844k6EnU76HJ
|
||||
RZwhd3ypy9CvTdkya5JbK+aKKo8fGFHbAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLItEDE1
|
||||
gVYfe7JSax5YAjEW8tmzMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQCZRStHCbwmhmH4tu7V5ammmQhn1TKcspV86cXz
|
||||
JQ4ZM11RvGpRLTmYuRkl5XloMuvB8yYAE1ihhkYOhgU6zSAj33kUQSx6cHXWau7T
|
||||
NjTchptKX+b17GR/yuFwIR3TugArBsnwyuUdts478gTY+MSgTOWWyOgWl3FujiEJ
|
||||
GJ7EgKde4jURXv2qjp6ZtSVqMlAa3y8C3S8nLdyt9Rf8CcSjEy/t8t0JhoMYCvxg
|
||||
o1k7QhMCfMYjjEIuEyDVOdCs2ExepG1zUVBP5h5239sXvLKrOZvgCZkslyTNd/m9
|
||||
vv4yR5gLgCdt0Ol1uip0p910PJoSqX6nZNfeCx3+Kgyc7crl8PrsnUAVoPgLxpVm
|
||||
FWF+KlUbh2KiYTuSi5cH0Ti9NtWT3Qi8d4WhmjFlu0zD3EJEUie0oYfHERiO9bVo
|
||||
5EAzERSVhgQdxVOLgIc2Hbe1JYFf7idyqASRw6KdVkW6YIC/V/5efrJ1LZ5QNrdv
|
||||
bmfJ5CznE6o1AH9JsQ8xMi+kmyn/It1uMWIwP/tYyjQ98dlOj2k9CHP2RzrvCCY9
|
||||
yZNjs2QC5cleNdSpNMb2J2EUYTNAnaH3H8YdbT0scMHDvre1G7p4AjeuRJ9mW7VK
|
||||
Dcqbw+VdSAPyAFdiCd9x8AU3sr28vYbPbPp+LsHQXIlYdnVR0hh2UKF5lR8iuqVx
|
||||
y05cAQ==
|
||||
-----END CERTIFICATE-----`
|
||||
client2Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
defaultAdminUsername = "admin"
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
|
||||
|
@ -299,10 +299,12 @@ func verifyCSRFHeader(next http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
if !util.Contains(token.Audience(), util.GetIPFromRemoteAddress(r.RemoteAddr)) {
|
||||
logger.Debug(logSender, "", "error validating CSRF header IP audience")
|
||||
sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden)
|
||||
return
|
||||
if tokenValidationMode != tokenValidationNoIPMatch {
|
||||
if !util.Contains(token.Audience(), util.GetIPFromRemoteAddress(r.RemoteAddr)) {
|
||||
logger.Debug(logSender, "", "error validating CSRF header IP audience")
|
||||
sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
|
|
|
@ -1366,14 +1366,14 @@ func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
|
|||
func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
|
||||
var err error
|
||||
config := vfs.S3FsConfig{}
|
||||
config.Bucket = r.Form.Get("s3_bucket")
|
||||
config.Region = r.Form.Get("s3_region")
|
||||
config.AccessKey = r.Form.Get("s3_access_key")
|
||||
config.RoleARN = r.Form.Get("s3_role_arn")
|
||||
config.Bucket = strings.TrimSpace(r.Form.Get("s3_bucket"))
|
||||
config.Region = strings.TrimSpace(r.Form.Get("s3_region"))
|
||||
config.AccessKey = strings.TrimSpace(r.Form.Get("s3_access_key"))
|
||||
config.RoleARN = strings.TrimSpace(r.Form.Get("s3_role_arn"))
|
||||
config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
|
||||
config.Endpoint = r.Form.Get("s3_endpoint")
|
||||
config.StorageClass = r.Form.Get("s3_storage_class")
|
||||
config.ACL = r.Form.Get("s3_acl")
|
||||
config.Endpoint = strings.TrimSpace(r.Form.Get("s3_endpoint"))
|
||||
config.StorageClass = strings.TrimSpace(r.Form.Get("s3_storage_class"))
|
||||
config.ACL = strings.TrimSpace(r.Form.Get("s3_acl"))
|
||||
config.KeyPrefix = r.Form.Get("s3_key_prefix")
|
||||
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
|
||||
if err != nil {
|
||||
|
@ -1407,9 +1407,9 @@ func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
|
|||
var err error
|
||||
config := vfs.GCSFsConfig{}
|
||||
|
||||
config.Bucket = r.Form.Get("gcs_bucket")
|
||||
config.StorageClass = r.Form.Get("gcs_storage_class")
|
||||
config.ACL = r.Form.Get("gcs_acl")
|
||||
config.Bucket = strings.TrimSpace(r.Form.Get("gcs_bucket"))
|
||||
config.StorageClass = strings.TrimSpace(r.Form.Get("gcs_storage_class"))
|
||||
config.ACL = strings.TrimSpace(r.Form.Get("gcs_acl"))
|
||||
config.KeyPrefix = r.Form.Get("gcs_key_prefix")
|
||||
autoCredentials := r.Form.Get("gcs_auto_credentials")
|
||||
if autoCredentials != "" {
|
||||
|
@ -1440,7 +1440,7 @@ func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
|
|||
func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
|
||||
var err error
|
||||
config := vfs.SFTPFsConfig{}
|
||||
config.Endpoint = r.Form.Get("sftp_endpoint")
|
||||
config.Endpoint = strings.TrimSpace(r.Form.Get("sftp_endpoint"))
|
||||
config.Username = r.Form.Get("sftp_username")
|
||||
config.Password = getSecretFromFormField(r, "sftp_password")
|
||||
config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
|
||||
|
@ -1463,7 +1463,7 @@ func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
|
|||
|
||||
func getHTTPFsConfig(r *http.Request) vfs.HTTPFsConfig {
|
||||
config := vfs.HTTPFsConfig{}
|
||||
config.Endpoint = r.Form.Get("http_endpoint")
|
||||
config.Endpoint = strings.TrimSpace(r.Form.Get("http_endpoint"))
|
||||
config.Username = r.Form.Get("http_username")
|
||||
config.SkipTLSVerify = r.Form.Get("http_skip_tls_verify") != ""
|
||||
config.Password = getSecretFromFormField(r, "http_password")
|
||||
|
@ -1479,13 +1479,13 @@ func getHTTPFsConfig(r *http.Request) vfs.HTTPFsConfig {
|
|||
func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
||||
var err error
|
||||
config := vfs.AzBlobFsConfig{}
|
||||
config.Container = r.Form.Get("az_container")
|
||||
config.AccountName = r.Form.Get("az_account_name")
|
||||
config.Container = strings.TrimSpace(r.Form.Get("az_container"))
|
||||
config.AccountName = strings.TrimSpace(r.Form.Get("az_account_name"))
|
||||
config.AccountKey = getSecretFromFormField(r, "az_account_key")
|
||||
config.SASURL = getSecretFromFormField(r, "az_sas_url")
|
||||
config.Endpoint = r.Form.Get("az_endpoint")
|
||||
config.Endpoint = strings.TrimSpace(r.Form.Get("az_endpoint"))
|
||||
config.KeyPrefix = r.Form.Get("az_key_prefix")
|
||||
config.AccessTier = r.Form.Get("az_access_tier")
|
||||
config.AccessTier = strings.TrimSpace(r.Form.Get("az_access_tier"))
|
||||
config.UseEmulator = r.Form.Get("az_use_emulator") != ""
|
||||
config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
|
||||
if err != nil {
|
||||
|
@ -2773,6 +2773,10 @@ func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Reques
|
|||
Password: user.Password,
|
||||
PublicKeys: user.PublicKeys,
|
||||
})
|
||||
user.Filters.RecoveryCodes = nil
|
||||
user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
|
||||
Enabled: false,
|
||||
}
|
||||
err = dataprovider.AddUser(&user, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
||||
|
@ -2832,7 +2836,7 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
|
|||
return
|
||||
}
|
||||
if r.Form.Get("disconnect") != "" {
|
||||
disconnectUser(user.Username)
|
||||
disconnectUser(user.Username, claims.Username)
|
||||
}
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
@ -113,10 +112,9 @@ func (p *authPlugin) initialize() error {
|
|||
return fmt.Errorf("invalid options for auth plugin %#v: %v", p.config.Cmd, err)
|
||||
}
|
||||
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: auth.Handshake,
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
|
@ -54,10 +53,9 @@ func (p *ipFilterPlugin) cleanup() {
|
|||
func (p *ipFilterPlugin) initialize() error {
|
||||
logger.Debug(logSender, "", "create new IP filter plugin %#v", p.config.Cmd)
|
||||
killProcess(p.config.Cmd)
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: ipfilter.Handshake,
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -75,10 +74,9 @@ func (p *kmsPlugin) initialize() error {
|
|||
if err := p.config.KMSOptions.validate(); err != nil {
|
||||
return fmt.Errorf("invalid options for kms plugin %#v: %v", p.config.Cmd, err)
|
||||
}
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: kmsplugin.Handshake,
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
|
@ -54,10 +53,9 @@ func (p *metadataPlugin) cleanup() {
|
|||
func (p *metadataPlugin) initialize() error {
|
||||
killProcess(p.config.Cmd)
|
||||
logger.Debug(logSender, "", "create new metadata plugin %#v", p.config.Cmd)
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: metadata.Handshake,
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
@ -138,10 +137,9 @@ func (p *notifierPlugin) initialize() error {
|
|||
if !p.config.NotifierOptions.hasActions() {
|
||||
return fmt.Errorf("no actions defined for the notifier plugin %#v", p.config.Cmd)
|
||||
}
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: notifier.Handshake,
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
@ -24,6 +26,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/sftpgo/sdk/plugin/auth"
|
||||
"github.com/sftpgo/sdk/plugin/eventsearcher"
|
||||
"github.com/sftpgo/sdk/plugin/ipfilter"
|
||||
|
@ -82,6 +85,20 @@ type Config struct {
|
|||
kmsID int
|
||||
}
|
||||
|
||||
func (c *Config) getSecureConfig() (*plugin.SecureConfig, error) {
|
||||
if c.SHA256Sum != "" {
|
||||
checksum, err := hex.DecodeString(c.SHA256Sum)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sha256 hash %q: %w", c.SHA256Sum, err)
|
||||
}
|
||||
return &plugin.SecureConfig{
|
||||
Checksum: checksum,
|
||||
Hash: sha256.New(),
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Config) newKMSPluginSecretProvider(base kms.BaseSecret, url, masterKey string) kms.SecretProvider {
|
||||
return &kmsPluginSecretProvider{
|
||||
BaseSecret: base,
|
||||
|
@ -774,16 +791,17 @@ func setLogLevel(logLevel string) {
|
|||
|
||||
func startCheckTicker() {
|
||||
logger.Debug(logSender, "", "start plugins checker")
|
||||
checker := time.NewTicker(30 * time.Second)
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-Handler.done:
|
||||
logger.Debug(logSender, "", "handler done, stop plugins checker")
|
||||
checker.Stop()
|
||||
return
|
||||
case <-checker.C:
|
||||
case <-ticker.C:
|
||||
Handler.checkCrashedPlugins()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
|
@ -54,10 +53,9 @@ func (p *searcherPlugin) cleanup() {
|
|||
func (p *searcherPlugin) initialize() error {
|
||||
killProcess(p.config.Cmd)
|
||||
logger.Debug(logSender, "", "create new searcher plugin %#v", p.config.Cmd)
|
||||
var secureConfig *plugin.SecureConfig
|
||||
if p.config.SHA256Sum != "" {
|
||||
secureConfig.Checksum = []byte(p.config.SHA256Sum)
|
||||
secureConfig.Hash = sha256.New()
|
||||
secureConfig, err := p.config.getSecureConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: eventsearcher.Handshake,
|
||||
|
|
|
@ -48,7 +48,7 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
|
|||
if s.PortableMode != 1 {
|
||||
return fmt.Errorf("service is not configured for portable mode")
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
rand.Seed(time.Now().UnixNano()) //nolint:staticcheck
|
||||
err := config.LoadConfig(s.ConfigDir, s.ConfigFile)
|
||||
if err != nil {
|
||||
fmt.Printf("error loading configuration file: %v using defaults\n", err)
|
||||
|
|
|
@ -182,9 +182,6 @@ func (c *Connection) handleFilewrite(request *sftp.Request) (sftp.WriterAtReader
|
|||
func (c *Connection) Filecmd(request *sftp.Request) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
c.Log(logger.LevelDebug, "new cmd, method: %v, sourcePath: %#v, targetPath: %#v", request.Method,
|
||||
request.Filepath, request.Target)
|
||||
|
||||
switch request.Method {
|
||||
case "Setstat":
|
||||
return c.handleSFTPSetstat(request)
|
||||
|
@ -410,7 +407,7 @@ func (c *Connection) handleSFTPUploadToNewFile(fs vfs.Fs, pflags sftp.FileOpenFl
|
|||
}
|
||||
|
||||
osFlags := getOSOpenFlags(pflags)
|
||||
file, w, cancelFn, err := fs.Create(filePath, osFlags)
|
||||
file, w, cancelFn, err := fs.Create(filePath, osFlags, c.GetCreateChecks(requestPath, true))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error creating file %#vm os flags %v, pflags %+v: %+v", resolvedPath, osFlags, pflags, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
@ -466,7 +463,7 @@ func (c *Connection) handleSFTPUploadToExistingFile(fs vfs.Fs, pflags sftp.FileO
|
|||
}
|
||||
}
|
||||
|
||||
file, w, cancelFn, err := fs.Create(filePath, osFlags)
|
||||
file, w, cancelFn, err := fs.Create(filePath, osFlags, c.GetCreateChecks(requestPath, false))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error opening existing file, os flags %v, pflags: %+v, source: %#v, err: %+v",
|
||||
osFlags, pflags, filePath, err)
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -1926,6 +1927,30 @@ func TestSupportedSecurityOptions(t *testing.T) {
|
|||
assert.Equal(t, supportedKexAlgos, serverConfig.KeyExchanges)
|
||||
}
|
||||
|
||||
func TestLoadModuli(t *testing.T) {
|
||||
dhGEXSha1 := "diffie-hellman-group-exchange-sha1"
|
||||
dhGEXSha256 := "diffie-hellman-group-exchange-sha256"
|
||||
c := Configuration{}
|
||||
c.Moduli = []string{".", "missing file"}
|
||||
err := c.loadModuli(configDir)
|
||||
assert.Error(t, err)
|
||||
assert.NotContains(t, supportedKexAlgos, dhGEXSha1)
|
||||
assert.NotContains(t, supportedKexAlgos, dhGEXSha256)
|
||||
assert.Len(t, supportedKexAlgos, 10)
|
||||
moduli := []byte("20220414072358 2 6 100 2047 5 F19C2D09AD49978F8A0C1B84168A4011A26F9CD516815934764A319FDC5975FA514AAF11B747D8CA6B3919532BEFB68FA118079473895674F3770F71FBB742F176883841EB3DE679BEF53C6AFE437A662F228B03C1E34B5A0D3909F608CEAA16C1F8131DE11E67878EFD918A89205E5E4DE323054010CA4711F25D466BB7727A016DD3F9F53BDBCE093055A4F2497ADEFB5A2500F9C5C3B0BCD88C6489F4C1CBC7CFB67BA6EABA0195794E4188CE9060F431041AD52FB9BAC4DF7FA536F585FBE67746CD57BFAD67567E9706C24D95C49BE95B759657C6BB5151E2AEA32F4CD557C40298A5C402101520EE8AAB8DFEED6FFC11AAF8036D6345923CFB5D1B922F")
|
||||
moduliFile := filepath.Join(os.TempDir(), "moduli")
|
||||
err = os.WriteFile(moduliFile, moduli, 0600)
|
||||
assert.NoError(t, err)
|
||||
c.Moduli = []string{moduliFile}
|
||||
err = c.loadModuli(configDir)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, supportedKexAlgos, dhGEXSha1)
|
||||
assert.Contains(t, supportedKexAlgos, dhGEXSha256)
|
||||
assert.Len(t, supportedKexAlgos, 12)
|
||||
err = os.Remove(moduliFile)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLoadHostKeys(t *testing.T) {
|
||||
serverConfig := &ssh.ServerConfig{}
|
||||
c := Configuration{}
|
||||
|
@ -1950,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)
|
||||
|
@ -2274,3 +2306,26 @@ func TestCanReadSymlink(t *testing.T) {
|
|||
err = connection.canReadLink("/denied/file.txt")
|
||||
assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
|
||||
}
|
||||
|
||||
func TestAuthenticationErrors(t *testing.T) {
|
||||
loginMethod := dataprovider.SSHLoginMethodPassword
|
||||
err := newAuthenticationError(fmt.Errorf("cannot validate credentials: %w", util.NewRecordNotFoundError("not found")), loginMethod)
|
||||
assert.ErrorIs(t, err, sftpAuthError)
|
||||
assert.ErrorIs(t, err, util.ErrNotFound)
|
||||
var sftpAuthErr *authenticationError
|
||||
if assert.ErrorAs(t, err, &sftpAuthErr) {
|
||||
assert.Equal(t, loginMethod, sftpAuthErr.getLoginMethod())
|
||||
}
|
||||
err = newAuthenticationError(fmt.Errorf("cannot validate credentials: %w", fs.ErrPermission), loginMethod)
|
||||
assert.ErrorIs(t, err, sftpAuthError)
|
||||
assert.NotErrorIs(t, err, util.ErrNotFound)
|
||||
err = newAuthenticationError(fmt.Errorf("cert has wrong type %d", ssh.HostCert), loginMethod)
|
||||
assert.ErrorIs(t, err, sftpAuthError)
|
||||
assert.NotErrorIs(t, err, util.ErrNotFound)
|
||||
err = newAuthenticationError(errors.New("ssh: certificate signed by unrecognized authority"), loginMethod)
|
||||
assert.ErrorIs(t, err, sftpAuthError)
|
||||
assert.NotErrorIs(t, err, util.ErrNotFound)
|
||||
err = newAuthenticationError(nil, loginMethod)
|
||||
assert.ErrorIs(t, err, sftpAuthError)
|
||||
assert.NotErrorIs(t, err, util.ErrNotFound)
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ func (c *scpCommand) handleUploadFile(fs vfs.Fs, resolvedPath, filePath string,
|
|||
|
||||
maxWriteSize, _ := c.connection.GetMaxWriteSize(diskQuota, false, fileSize, fs.IsUploadResumeSupported())
|
||||
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, c.connection.GetCreateChecks(requestPath, isNewFile))
|
||||
if err != nil {
|
||||
c.connection.Log(logger.LevelError, "error creating file %#v: %v", resolvedPath, err)
|
||||
c.sendErrorMessage(fs, err)
|
||||
|
|
|
@ -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,15 +87,25 @@ 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{},
|
||||
}
|
||||
|
||||
sftpAuthError = newAuthenticationError(nil, "")
|
||||
)
|
||||
|
||||
// Binding defines the configuration for a network listener
|
||||
|
@ -141,6 +155,12 @@ type Configuration struct {
|
|||
// HostKeyAlgorithms lists the public key algorithms that the server will accept for host
|
||||
// key authentication.
|
||||
HostKeyAlgorithms []string `json:"host_key_algorithms" mapstructure:"host_key_algorithms"`
|
||||
// Diffie-Hellman moduli files.
|
||||
// Each moduli file can be defined as a path relative to the configuration directory or an absolute one.
|
||||
// If set, "diffie-hellman-group-exchange-sha256" and "diffie-hellman-group-exchange-sha1" KEX algorithms
|
||||
// will be available, `diffie-hellman-group-exchange-sha256` will be enabled by default if you
|
||||
// don't explicitly set KEXs
|
||||
Moduli []string `json:"moduli" mapstructure:"moduli"`
|
||||
// KexAlgorithms specifies the available KEX (Key Exchange) algorithms in
|
||||
// preference order.
|
||||
KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
|
||||
|
@ -202,11 +222,31 @@ type Configuration struct {
|
|||
}
|
||||
|
||||
type authenticationError struct {
|
||||
err string
|
||||
err error
|
||||
loginMethod string
|
||||
}
|
||||
|
||||
func (e *authenticationError) Error() string {
|
||||
return fmt.Sprintf("Authentication error: %s", e.err)
|
||||
return fmt.Sprintf("Authentication error: %v", e.err)
|
||||
}
|
||||
|
||||
// Is reports if target matches
|
||||
func (e *authenticationError) Is(target error) bool {
|
||||
_, ok := target.(*authenticationError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error
|
||||
func (e *authenticationError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *authenticationError) getLoginMethod() string {
|
||||
return e.loginMethod
|
||||
}
|
||||
|
||||
func newAuthenticationError(err error, loginMethod string) *authenticationError {
|
||||
return &authenticationError{err: err, loginMethod: loginMethod}
|
||||
}
|
||||
|
||||
// ShouldBind returns true if there is at least a valid binding
|
||||
|
@ -230,7 +270,8 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig {
|
|||
return sp, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, &authenticationError{err: fmt.Sprintf("could not validate public key credentials: %v", err)}
|
||||
return nil, newAuthenticationError(fmt.Errorf("could not validate public key credentials: %w", err),
|
||||
dataprovider.SSHLoginMethodPublicKey)
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
|
@ -250,7 +291,8 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig {
|
|||
serverConfig.PasswordCallback = func(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
sp, err := c.validatePasswordCredentials(conn, pass)
|
||||
if err != nil {
|
||||
return nil, &authenticationError{err: fmt.Sprintf("could not validate password credentials: %v", err)}
|
||||
return nil, newAuthenticationError(fmt.Errorf("could not validate password credentials: %w", err),
|
||||
dataprovider.SSHLoginMethodPassword)
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
|
@ -285,12 +327,7 @@ func (c *Configuration) Initialize(configDir string) error {
|
|||
return common.ErrNoBinding
|
||||
}
|
||||
|
||||
if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
|
||||
serviceStatus.HostKeys = nil
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.initializeCertChecker(configDir); err != nil {
|
||||
if err := c.loadModuli(configDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -299,6 +336,14 @@ func (c *Configuration) Initialize(configDir string) error {
|
|||
if err := c.configureSecurityOptions(serverConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.checkAndLoadHostKeys(configDir, serverConfig); err != nil {
|
||||
serviceStatus.HostKeys = nil
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.initializeCertChecker(configDir); err != nil {
|
||||
return err
|
||||
}
|
||||
c.configureKeyboardInteractiveAuth(serverConfig)
|
||||
c.configureLoginBanner(serverConfig, configDir)
|
||||
c.checkSSHCommands()
|
||||
|
@ -385,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)
|
||||
}
|
||||
}
|
||||
serverConfig.KeyExchanges = c.KexAlgorithms
|
||||
}
|
||||
if len(c.Ciphers) > 0 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
serverConfig.Ciphers = c.Ciphers
|
||||
}
|
||||
if len(c.MACs) > 0 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
serverConfig.MACs = c.MACs
|
||||
}
|
||||
for _, mac := range c.MACs {
|
||||
if !util.Contains(supportedMACs, mac) {
|
||||
return fmt.Errorf("unsupported MAC algorithm %#v", mac)
|
||||
}
|
||||
}
|
||||
serverConfig.MACs = c.MACs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -460,7 +513,8 @@ func (c *Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.Serve
|
|||
serverConfig.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
|
||||
sp, err := c.validateKeyboardInteractiveCredentials(conn, client)
|
||||
if err != nil {
|
||||
return nil, &authenticationError{err: fmt.Sprintf("could not validate keyboard interactive credentials: %v", err)}
|
||||
return nil, newAuthenticationError(fmt.Errorf("could not validate keyboard interactive credentials: %w", err),
|
||||
dataprovider.SSHLoginMethodKeyboardInteractive)
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
|
@ -527,16 +581,14 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
|
|||
loginType := sconn.Permissions.Extensions["sftpgo_login_method"]
|
||||
connectionID := hex.EncodeToString(sconn.SessionID())
|
||||
|
||||
defer user.CloseFs() //nolint:errcheck
|
||||
if err = user.CheckFsRoot(connectionID); err != nil {
|
||||
errClose := user.CloseFs()
|
||||
logger.Warn(logSender, connectionID, "unable to check fs root: %v close fs error: %v", err, errClose)
|
||||
logger.Warn(logSender, connectionID, "unable to check fs root: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer user.CloseFs() //nolint:errcheck
|
||||
|
||||
logger.Log(logger.LevelInfo, common.ProtocolSSH, connectionID,
|
||||
"User %#v logged in with %#v, from ip %#v, client version %#v", user.Username, loginType,
|
||||
"User %q logged in with %q, from ip %q, client version %q", user.Username, loginType,
|
||||
ipAddr, string(sconn.ClientVersion()))
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
|
||||
|
@ -660,15 +712,15 @@ func checkAuthError(ip string, err error) {
|
|||
if authErrors, ok := err.(*ssh.ServerAuthError); ok {
|
||||
// check public key auth errors here
|
||||
for _, err := range authErrors.Errors {
|
||||
if err != nil {
|
||||
// these checks should be improved, we should check for error type and not error strings
|
||||
if strings.Contains(err.Error(), "public key credentials") {
|
||||
var sftpAuthErr *authenticationError
|
||||
if errors.As(err, &sftpAuthErr) {
|
||||
if sftpAuthErr.getLoginMethod() == dataprovider.SSHLoginMethodPublicKey {
|
||||
event := common.HostEventLoginFailed
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
if errors.Is(err, util.ErrNotFound) {
|
||||
event = common.HostEventUserNotFound
|
||||
}
|
||||
common.AddDefenderEvent(ip, event)
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -842,6 +894,47 @@ func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Configuration) loadModuli(configDir string) error {
|
||||
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) {
|
||||
logger.Warn(logSender, "", "unable to load invalid moduli file %q", m)
|
||||
logger.WarnToConsole("unable to load invalid host moduli file %q", m)
|
||||
continue
|
||||
}
|
||||
if !filepath.IsAbs(m) {
|
||||
m = filepath.Join(configDir, m)
|
||||
}
|
||||
logger.Info(logSender, "", "loading moduli file %q", m)
|
||||
if err := ssh.ParseModuli(m); err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
|
@ -876,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]
|
||||
|
@ -901,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) {
|
||||
|
@ -928,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
|
||||
}
|
||||
|
@ -1034,7 +1153,7 @@ func (c *Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubK
|
|||
cert.KeyId, cert.Serial, cert.Type(), ssh.FingerprintSHA256(cert.SignatureKey))
|
||||
}
|
||||
if user.IsPartialAuth(method) {
|
||||
logger.Debug(logSender, connectionID, "user %#v authenticated with partial success", conn.User())
|
||||
logger.Debug(logSender, connectionID, "user %q authenticated with partial success", conn.User())
|
||||
return certPerm, ssh.ErrPartialSuccess
|
||||
}
|
||||
sshPerm, err = loginUser(&user, method, keyID, conn)
|
||||
|
@ -1162,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}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -182,7 +182,7 @@ func TestMain(m *testing.M) {
|
|||
logFilePath = filepath.Join(configDir, "sftpgo_sftpd_test.log")
|
||||
loginBannerFileName := "login_banner"
|
||||
loginBannerFile := filepath.Join(configDir, loginBannerFileName)
|
||||
logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
|
||||
logger.InitLogger(logFilePath, 10, 1, 28, false, false, zerolog.DebugLevel)
|
||||
err := os.WriteFile(loginBannerFile, []byte("simple login banner\n"), os.ModePerm)
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("error creating login banner: %v", err)
|
||||
|
@ -401,6 +401,12 @@ func TestInitialization(t *testing.T) {
|
|||
assert.True(t, sftpdConf.Bindings[0].HasProxy())
|
||||
err = sftpdConf.Initialize(configDir)
|
||||
assert.Error(t, err)
|
||||
sftpdConf.Moduli = []string{"missing moduli file"}
|
||||
err = sftpdConf.Initialize(configDir)
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "unable to open moduli file")
|
||||
}
|
||||
sftpdConf.Moduli = nil
|
||||
sftpdConf.HostKeys = []string{"missing key"}
|
||||
err = sftpdConf.Initialize(configDir)
|
||||
assert.Error(t, err)
|
||||
|
@ -423,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) {
|
||||
|
@ -556,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) {
|
||||
|
@ -960,6 +969,7 @@ func TestDefender(t *testing.T) {
|
|||
cfg.DefenderConfig.Enabled = true
|
||||
cfg.DefenderConfig.Threshold = 3
|
||||
cfg.DefenderConfig.ScoreLimitExceeded = 2
|
||||
cfg.DefenderConfig.ScoreValid = 1
|
||||
|
||||
err := common.Initialize(cfg, 0)
|
||||
assert.NoError(t, err)
|
||||
|
@ -971,12 +981,23 @@ func TestDefender(t *testing.T) {
|
|||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
err = checkBasicSFTP(client)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
user.Password = "wrong_pwd"
|
||||
user.Password = "wrong_pwd"
|
||||
_, _, err = getSftpClient(user, usePubKey)
|
||||
assert.Error(t, err)
|
||||
hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
host := hosts[0]
|
||||
assert.Empty(t, host.GetBanTime())
|
||||
assert.Equal(t, 1, host.Score)
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
_, _, err = getSftpClient(user, usePubKey)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
@ -3038,7 +3059,7 @@ func TestPreLoginUserCreation(t *testing.T) {
|
|||
err = dataprovider.Initialize(providerConf, configDir, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
|
||||
_, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusNotFound)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(u, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
|
@ -3046,7 +3067,7 @@ func TestPreLoginUserCreation(t *testing.T) {
|
|||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
}
|
||||
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
|
||||
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -5149,7 +5170,7 @@ func TestVirtualFoldersQuotaLimit(t *testing.T) {
|
|||
err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), path.Join(vdirPath2, testFileName))
|
||||
assert.Error(t, err)
|
||||
err = client.Rename(path.Join(vdirPath1, testFileName+".rename"), testFileName)
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -5279,9 +5300,7 @@ func TestSFTPLoopVirtualFolders(t *testing.T) {
|
|||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
_, err = client.ReadDir("/vdir")
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "SFTP loop")
|
||||
}
|
||||
assert.Error(t, err)
|
||||
}
|
||||
// now make user2 a local account with an SFTP virtual folder to user1.
|
||||
// So we have:
|
||||
|
@ -5316,9 +5335,7 @@ func TestSFTPLoopVirtualFolders(t *testing.T) {
|
|||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
_, err = client.ReadDir("/vdir")
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "SFTP loop")
|
||||
}
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user1, http.StatusOK)
|
||||
|
@ -8268,6 +8285,12 @@ func TestUserPartialAuth(t *testing.T) {
|
|||
dataprovider.SSHLoginMethodPublicKey,
|
||||
}
|
||||
assert.False(t, user.IsPartialAuth(dataprovider.SSHLoginMethodPublicKey))
|
||||
user.Filters.DeniedLoginMethods = []string{
|
||||
dataprovider.SSHLoginMethodPassword,
|
||||
dataprovider.SSHLoginMethodPublicKey,
|
||||
dataprovider.SSHLoginMethodKeyboardInteractive,
|
||||
}
|
||||
assert.True(t, user.IsPartialAuth(dataprovider.SSHLoginMethodPublicKey))
|
||||
}
|
||||
|
||||
func TestUserGetNextAuthMethods(t *testing.T) {
|
||||
|
@ -10080,7 +10103,7 @@ func TestSCPNestedFolders(t *testing.T) {
|
|||
|
||||
// now change the password for the base user, so SFTP folder will not work
|
||||
baseUser.Password = defaultPassword + "_mod"
|
||||
_, _, err = httpdtest.UpdateUser(baseUser, http.StatusOK, "")
|
||||
_, _, err = httpdtest.UpdateUser(baseUser, http.StatusOK, "1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = scpUpload(filepath.Join(baseDir, "vdir"), remoteRootPath, true, false)
|
||||
|
|
|
@ -115,6 +115,7 @@ func (t *transfer) ReadAt(p []byte, off int64) (n int, err error) {
|
|||
if t.GetType() == common.TransferDownload {
|
||||
t.TransferError(err)
|
||||
}
|
||||
err = t.ConvertError(err)
|
||||
return
|
||||
}
|
||||
t.HandleThrottle()
|
||||
|
@ -139,6 +140,7 @@ func (t *transfer) WriteAt(p []byte, off int64) (n int, err error) {
|
|||
}
|
||||
if err != nil {
|
||||
t.TransferError(err)
|
||||
err = t.ConvertError(err)
|
||||
return
|
||||
}
|
||||
t.HandleThrottle()
|
||||
|
|
|
@ -181,6 +181,7 @@ func SendEmail(to []string, subject, body string, contentType EmailContentType,
|
|||
}
|
||||
|
||||
email := mail.NewMSG()
|
||||
email.AllowDuplicateAddress = true
|
||||
if from != "" {
|
||||
email.SetFrom(from)
|
||||
} else {
|
||||
|
|
|
@ -24,6 +24,11 @@ const (
|
|||
"sftpgo serve -c \"<path to dir containing the default config file and templates directory>\""
|
||||
)
|
||||
|
||||
// errors definitions
|
||||
var (
|
||||
ErrNotFound = NewRecordNotFoundError("")
|
||||
)
|
||||
|
||||
// ValidationError raised if input data is not valid
|
||||
type ValidationError struct {
|
||||
err string
|
||||
|
@ -55,6 +60,12 @@ func (e *RecordNotFoundError) Error() string {
|
|||
return fmt.Sprintf("not found: %s", e.err)
|
||||
}
|
||||
|
||||
// Is reports if target matches
|
||||
func (e *RecordNotFoundError) Is(target error) bool {
|
||||
_, ok := target.(*RecordNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewRecordNotFoundError returns a not found error
|
||||
func NewRecordNotFoundError(error string) *RecordNotFoundError {
|
||||
return &RecordNotFoundError{
|
||||
|
|
|
@ -125,6 +125,18 @@ func Contains[T comparable](elems []T, v T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Remove removes an element from a string slice and
|
||||
// returns the modified slice
|
||||
func Remove(elems []string, val string) []string {
|
||||
for idx, v := range elems {
|
||||
if v == val {
|
||||
elems[idx] = elems[len(elems)-1]
|
||||
return elems[:len(elems)-1]
|
||||
}
|
||||
}
|
||||
return elems
|
||||
}
|
||||
|
||||
// IsStringPrefixInSlice searches a string prefix in a slice and returns true
|
||||
// if a matching prefix is found
|
||||
func IsStringPrefixInSlice(obj string, list []string) bool {
|
||||
|
@ -301,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
|
||||
}
|
||||
|
@ -727,3 +739,19 @@ func PanicOnError(err error) {
|
|||
panic(fmt.Errorf("unexpected error: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetAbsolutePath returns an absolute path using the current dir as base
|
||||
// if name defines a relative path
|
||||
func GetAbsolutePath(name string) (string, error) {
|
||||
if name == "" {
|
||||
return name, errors.New("input path cannot be empty")
|
||||
}
|
||||
if filepath.IsAbs(name) {
|
||||
return name, nil
|
||||
}
|
||||
curDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
return filepath.Join(curDir, name), nil
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ package version
|
|||
|
||||
import "strings"
|
||||
|
||||
const version = "2.4.0-dev"
|
||||
const version = "2.4.6"
|
||||
|
||||
var (
|
||||
commit = ""
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -54,7 +53,7 @@ import (
|
|||
|
||||
const (
|
||||
azureDefaultEndpoint = "blob.core.windows.net"
|
||||
azBlobFsName = "AzureBlobFs"
|
||||
azFolderKey = "hdi_isfolder"
|
||||
)
|
||||
|
||||
// AzureBlobFs is a Fs implementation for Azure Blob storage.
|
||||
|
@ -181,7 +180,7 @@ func (fs *AzureBlobFs) Stat(name string) (os.FileInfo, error) {
|
|||
attrs, err := fs.headObject(name)
|
||||
if err == nil {
|
||||
contentType := util.GetStringFromPointer(attrs.ContentType)
|
||||
isDir := contentType == dirMimeType
|
||||
isDir := checkDirectoryMarkers(contentType, attrs.Metadata)
|
||||
metric.AZListObjectsCompleted(nil)
|
||||
return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, isDir,
|
||||
util.GetIntFromPointer(attrs.ContentLength),
|
||||
|
@ -228,7 +227,13 @@ func (fs *AzureBlobFs) Open(name string, offset int64) (File, *pipeat.PipeReader
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *AzureBlobFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (fs *AzureBlobFs) Create(name string, flag, checks int) (File, *PipeWriter, func(), error) {
|
||||
if checks&CheckParentDir != 0 {
|
||||
_, err := fs.Stat(path.Dir(name))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
r, w, err := pipeat.PipeInDir(fs.localTempDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
@ -238,8 +243,12 @@ func (fs *AzureBlobFs) Create(name string, flag int) (File, *PipeWriter, func(),
|
|||
p := NewPipeWriter(w)
|
||||
headers := blob.HTTPHeaders{}
|
||||
var contentType string
|
||||
var metadata map[string]*string
|
||||
if flag == -1 {
|
||||
contentType = dirMimeType
|
||||
metadata = map[string]*string{
|
||||
azFolderKey: util.NilIfEmpty("true"),
|
||||
}
|
||||
} else {
|
||||
contentType = mime.TypeByExtension(path.Ext(name))
|
||||
}
|
||||
|
@ -251,7 +260,7 @@ func (fs *AzureBlobFs) Create(name string, flag int) (File, *PipeWriter, func(),
|
|||
defer cancelFn()
|
||||
|
||||
blockBlob := fs.containerClient.NewBlockBlobClient(name)
|
||||
err := fs.handleMultipartUpload(ctx, r, blockBlob, &headers)
|
||||
err := fs.handleMultipartUpload(ctx, r, blockBlob, &headers, metadata)
|
||||
r.CloseWithError(err) //nolint:errcheck
|
||||
p.Done(err)
|
||||
fsLog(fs, logger.LevelDebug, "upload completed, path: %#v, readed bytes: %v, err: %+v", name, r.GetReadedBytes(), err)
|
||||
|
@ -270,6 +279,10 @@ func (fs *AzureBlobFs) Rename(source, target string) error {
|
|||
if source == target {
|
||||
return nil
|
||||
}
|
||||
_, err := fs.Stat(path.Dir(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, err := fs.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -282,43 +295,47 @@ func (fs *AzureBlobFs) Rename(source, target string) error {
|
|||
if hasContents {
|
||||
return fmt.Errorf("cannot rename non empty directory: %#v", source)
|
||||
}
|
||||
}
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxLongTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
srcBlob := fs.containerClient.NewBlockBlobClient(url.PathEscape(source))
|
||||
dstBlob := fs.containerClient.NewBlockBlobClient(target)
|
||||
resp, err := dstBlob.StartCopyFromURL(ctx, srcBlob.URL(), fs.getCopyOptions())
|
||||
if err != nil {
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
copyStatus := blob.CopyStatusType(util.GetStringFromPointer((*string)(resp.CopyStatus)))
|
||||
nErrors := 0
|
||||
for copyStatus == blob.CopyStatusTypePending {
|
||||
// Poll until the copy is complete.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
resp, err := dstBlob.GetProperties(ctx, &blob.GetPropertiesOptions{})
|
||||
if err != nil {
|
||||
// A GetProperties failure may be transient, so allow a couple
|
||||
// of them before giving up.
|
||||
nErrors++
|
||||
if ctx.Err() != nil || nErrors == 3 {
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
copyStatus = blob.CopyStatusType(util.GetStringFromPointer((*string)(resp.CopyStatus)))
|
||||
if err := fs.mkdirInternal(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if copyStatus != blob.CopyStatusTypeSuccess {
|
||||
err := fmt.Errorf("copy failed with status: %s", copyStatus)
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxLongTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
metric.AZCopyObjectCompleted(nil)
|
||||
fs.preserveModificationTime(source, target, fi)
|
||||
srcBlob := fs.containerClient.NewBlockBlobClient(source)
|
||||
dstBlob := fs.containerClient.NewBlockBlobClient(target)
|
||||
resp, err := dstBlob.StartCopyFromURL(ctx, srcBlob.URL(), fs.getCopyOptions())
|
||||
if err != nil {
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
copyStatus := blob.CopyStatusType(util.GetStringFromPointer((*string)(resp.CopyStatus)))
|
||||
nErrors := 0
|
||||
for copyStatus == blob.CopyStatusTypePending {
|
||||
// Poll until the copy is complete.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
resp, err := dstBlob.GetProperties(ctx, &blob.GetPropertiesOptions{})
|
||||
if err != nil {
|
||||
// A GetProperties failure may be transient, so allow a couple
|
||||
// of them before giving up.
|
||||
nErrors++
|
||||
if ctx.Err() != nil || nErrors == 3 {
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
copyStatus = blob.CopyStatusType(util.GetStringFromPointer((*string)(resp.CopyStatus)))
|
||||
}
|
||||
}
|
||||
if copyStatus != blob.CopyStatusTypeSuccess {
|
||||
err := fmt.Errorf("copy failed with status: %s", copyStatus)
|
||||
metric.AZCopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
|
||||
metric.AZCopyObjectCompleted(nil)
|
||||
fs.preserveModificationTime(source, target, fi)
|
||||
}
|
||||
return fs.Remove(source, fi.IsDir())
|
||||
}
|
||||
|
||||
|
@ -338,10 +355,21 @@ func (fs *AzureBlobFs) Remove(name string, isDir bool) error {
|
|||
defer cancelFn()
|
||||
|
||||
blobBlock := fs.containerClient.NewBlockBlobClient(name)
|
||||
deletSnapshots := blob.DeleteSnapshotsOptionTypeInclude
|
||||
var deletSnapshots blob.DeleteSnapshotsOptionType
|
||||
if !isDir {
|
||||
deletSnapshots = blob.DeleteSnapshotsOptionTypeInclude
|
||||
}
|
||||
_, err := blobBlock.Delete(ctx, &blob.DeleteOptions{
|
||||
DeleteSnapshots: &deletSnapshots,
|
||||
})
|
||||
if err != nil && isDir {
|
||||
if fs.isBadRequestError(err) {
|
||||
deletSnapshots = blob.DeleteSnapshotsOptionTypeInclude
|
||||
_, err = blobBlock.Delete(ctx, &blob.DeleteOptions{
|
||||
DeleteSnapshots: &deletSnapshots,
|
||||
})
|
||||
}
|
||||
}
|
||||
metric.AZDeleteObjectCompleted(err)
|
||||
if plugin.Handler.HasMetadater() && err == nil && !isDir {
|
||||
if errMetadata := plugin.Handler.RemoveMetadata(fs.getStorageID(), ensureAbsPath(name)); errMetadata != nil {
|
||||
|
@ -357,11 +385,7 @@ func (fs *AzureBlobFs) Mkdir(name string) error {
|
|||
if !fs.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
_, w, _, err := fs.Create(name, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
return fs.mkdirInternal(name)
|
||||
}
|
||||
|
||||
// Symlink creates source as a symbolic link to target.
|
||||
|
@ -424,8 +448,10 @@ func (fs *AzureBlobFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|||
prefixes := make(map[string]bool)
|
||||
|
||||
pager := fs.containerClient.NewListBlobsHierarchyPager("/", &container.ListBlobsHierarchyOptions{
|
||||
Include: container.ListBlobsInclude{},
|
||||
Prefix: &prefix,
|
||||
Include: container.ListBlobsInclude{
|
||||
//Metadata: true,
|
||||
},
|
||||
Prefix: &prefix,
|
||||
})
|
||||
|
||||
for pager.More() {
|
||||
|
@ -462,7 +488,7 @@ func (fs *AzureBlobFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|||
size = util.GetIntFromPointer(blobItem.Properties.ContentLength)
|
||||
modTime = util.GetTimeFromPointer(blobItem.Properties.LastModified)
|
||||
contentType := util.GetStringFromPointer(blobItem.Properties.ContentType)
|
||||
isDir = (contentType == dirMimeType)
|
||||
isDir = checkDirectoryMarkers(contentType, blobItem.Metadata)
|
||||
if isDir {
|
||||
// check if the dir is already included, it will be sent as blob prefix if it contains at least one item
|
||||
if _, ok := prefixes[name]; ok {
|
||||
|
@ -530,6 +556,17 @@ func (*AzureBlobFs) IsNotSupported(err error) bool {
|
|||
return err == ErrVfsUnsupported
|
||||
}
|
||||
|
||||
func (*AzureBlobFs) isBadRequestError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var respErr *azcore.ResponseError
|
||||
if errors.As(err, &respErr) {
|
||||
return respErr.StatusCode == http.StatusBadRequest
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckRootPath creates the specified local root directory if it does not exists
|
||||
func (fs *AzureBlobFs) CheckRootPath(username string, uid int, gid int) bool {
|
||||
// we need a local directory for temporary files
|
||||
|
@ -540,38 +577,7 @@ func (fs *AzureBlobFs) CheckRootPath(username string, uid int, gid int) bool {
|
|||
// ScanRootDirContents returns the number of files contained in the bucket,
|
||||
// and their size
|
||||
func (fs *AzureBlobFs) ScanRootDirContents() (int, int64, error) {
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
|
||||
pager := fs.containerClient.NewListBlobsFlatPager(&container.ListBlobsFlatOptions{
|
||||
Prefix: &fs.config.KeyPrefix,
|
||||
})
|
||||
|
||||
for pager.More() {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
resp, err := pager.NextPage(ctx)
|
||||
if err != nil {
|
||||
metric.AZListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
for _, blobItem := range resp.ListBlobsFlatSegmentResponse.Segment.BlobItems {
|
||||
if blobItem.Properties != nil {
|
||||
contentType := util.GetStringFromPointer(blobItem.Properties.ContentType)
|
||||
isDir := (contentType == dirMimeType)
|
||||
blobSize := util.GetIntFromPointer(blobItem.Properties.ContentLength)
|
||||
if isDir && blobSize == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += blobSize
|
||||
}
|
||||
}
|
||||
}
|
||||
metric.AZListObjectsCompleted(nil)
|
||||
|
||||
return numFiles, size, nil
|
||||
return fs.GetDirSize(fs.config.KeyPrefix)
|
||||
}
|
||||
|
||||
func (fs *AzureBlobFs) getFileNamesInPrefix(fsPrefix string) (map[string]bool, error) {
|
||||
|
@ -582,8 +588,10 @@ func (fs *AzureBlobFs) getFileNamesInPrefix(fsPrefix string) (map[string]bool, e
|
|||
}
|
||||
|
||||
pager := fs.containerClient.NewListBlobsHierarchyPager("/", &container.ListBlobsHierarchyOptions{
|
||||
Include: container.ListBlobsInclude{},
|
||||
Prefix: &prefix,
|
||||
Include: container.ListBlobsInclude{
|
||||
//Metadata: true,
|
||||
},
|
||||
Prefix: &prefix,
|
||||
})
|
||||
|
||||
for pager.More() {
|
||||
|
@ -600,7 +608,7 @@ func (fs *AzureBlobFs) getFileNamesInPrefix(fsPrefix string) (map[string]bool, e
|
|||
name = strings.TrimPrefix(name, prefix)
|
||||
if blobItem.Properties != nil {
|
||||
contentType := util.GetStringFromPointer(blobItem.Properties.ContentType)
|
||||
isDir := (contentType == dirMimeType)
|
||||
isDir := checkDirectoryMarkers(contentType, blobItem.Metadata)
|
||||
if isDir {
|
||||
continue
|
||||
}
|
||||
|
@ -620,8 +628,46 @@ func (fs *AzureBlobFs) CheckMetadata() error {
|
|||
|
||||
// GetDirSize returns the number of files and the size for a folder
|
||||
// including any subfolders
|
||||
func (*AzureBlobFs) GetDirSize(dirname string) (int, int64, error) {
|
||||
return 0, 0, ErrVfsUnsupported
|
||||
func (fs *AzureBlobFs) GetDirSize(dirname string) (int, int64, error) {
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
prefix := fs.getPrefix(dirname)
|
||||
|
||||
pager := fs.containerClient.NewListBlobsFlatPager(&container.ListBlobsFlatOptions{
|
||||
Include: container.ListBlobsInclude{
|
||||
Metadata: true,
|
||||
},
|
||||
Prefix: &prefix,
|
||||
})
|
||||
|
||||
for pager.More() {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
resp, err := pager.NextPage(ctx)
|
||||
if err != nil {
|
||||
metric.AZListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
for _, blobItem := range resp.ListBlobsFlatSegmentResponse.Segment.BlobItems {
|
||||
if blobItem.Properties != nil {
|
||||
contentType := util.GetStringFromPointer(blobItem.Properties.ContentType)
|
||||
isDir := checkDirectoryMarkers(contentType, blobItem.Metadata)
|
||||
blobSize := util.GetIntFromPointer(blobItem.Properties.ContentLength)
|
||||
if isDir && blobSize == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += blobSize
|
||||
if numFiles%1000 == 0 {
|
||||
fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
metric.AZListObjectsCompleted(nil)
|
||||
|
||||
return numFiles, size, nil
|
||||
}
|
||||
|
||||
// GetAtomicUploadPath returns the path to use for an atomic upload.
|
||||
|
@ -657,7 +703,10 @@ func (fs *AzureBlobFs) GetRelativePath(name string) string {
|
|||
func (fs *AzureBlobFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
prefix := fs.getPrefix(root)
|
||||
pager := fs.containerClient.NewListBlobsFlatPager(&container.ListBlobsFlatOptions{
|
||||
Prefix: &fs.config.KeyPrefix,
|
||||
Include: container.ListBlobsInclude{
|
||||
Metadata: true,
|
||||
},
|
||||
Prefix: &prefix,
|
||||
})
|
||||
|
||||
for pager.More() {
|
||||
|
@ -679,7 +728,7 @@ func (fs *AzureBlobFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
|||
isDir := false
|
||||
if blobItem.Properties != nil {
|
||||
contentType := util.GetStringFromPointer(blobItem.Properties.ContentType)
|
||||
isDir = (contentType == dirMimeType)
|
||||
isDir = checkDirectoryMarkers(contentType, blobItem.Metadata)
|
||||
blobSize = util.GetIntFromPointer(blobItem.Properties.ContentLength)
|
||||
lastModified = util.GetTimeFromPointer(blobItem.Properties.LastModified)
|
||||
}
|
||||
|
@ -792,6 +841,14 @@ func (fs *AzureBlobFs) setConfigDefaults() {
|
|||
}
|
||||
}
|
||||
|
||||
func (fs *AzureBlobFs) mkdirInternal(name string) error {
|
||||
_, w, _, err := fs.Create(name, -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
func (fs *AzureBlobFs) hasContents(name string) (bool, error) {
|
||||
result := false
|
||||
prefix := fs.getPrefix(name)
|
||||
|
@ -835,9 +892,9 @@ func (fs *AzureBlobFs) downloadPart(ctx context.Context, blockBlob *blockblob.Cl
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.BlobClientDownloadResponse.Body.Close()
|
||||
defer resp.DownloadResponse.Body.Close()
|
||||
|
||||
_, err = io.ReadAtLeast(resp.BlobClientDownloadResponse.Body, buf, int(count))
|
||||
_, err = io.ReadAtLeast(resp.DownloadResponse.Body, buf, int(count))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -928,7 +985,7 @@ func (fs *AzureBlobFs) handleMultipartDownload(ctx context.Context, blockBlob *b
|
|||
}
|
||||
|
||||
func (fs *AzureBlobFs) handleMultipartUpload(ctx context.Context, reader io.Reader,
|
||||
blockBlob *blockblob.Client, httpHeaders *blob.HTTPHeaders,
|
||||
blockBlob *blockblob.Client, httpHeaders *blob.HTTPHeaders, metadata map[string]*string,
|
||||
) error {
|
||||
partSize := fs.config.UploadPartSize
|
||||
guard := make(chan struct{}, fs.config.UploadConcurrency)
|
||||
|
@ -1018,6 +1075,7 @@ func (fs *AzureBlobFs) handleMultipartUpload(ctx context.Context, reader io.Read
|
|||
|
||||
commitOptions := blockblob.CommitBlockListOptions{
|
||||
HTTPHeaders: httpHeaders,
|
||||
Metadata: metadata,
|
||||
}
|
||||
if fs.config.AccessTier != "" {
|
||||
commitOptions.Tier = (*blob.AccessTier)(&fs.config.AccessTier)
|
||||
|
@ -1080,12 +1138,24 @@ func (fs *AzureBlobFs) getStorageID() string {
|
|||
return fmt.Sprintf("azblob://%v", fs.config.Container)
|
||||
}
|
||||
|
||||
func checkDirectoryMarkers(contentType string, metadata map[string]*string) bool {
|
||||
if contentType == dirMimeType {
|
||||
return true
|
||||
}
|
||||
for k, v := range metadata {
|
||||
if strings.ToLower(k) == azFolderKey {
|
||||
return util.GetStringFromPointer(v) == "true"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getAzContainerClientOptions() *container.ClientOptions {
|
||||
version := version.Get()
|
||||
return &container.ClientOptions{
|
||||
ClientOptions: azcore.ClientOptions{
|
||||
Telemetry: policy.TelemetryOptions{
|
||||
ApplicationID: fmt.Sprintf("SFTPGo-%v_%v", version.Version, version.CommitHash),
|
||||
ApplicationID: fmt.Sprintf("SFTPGo-%s", version.CommitHash),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ func (fs *CryptFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt,
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *CryptFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (fs *CryptFs) Create(name string, flag, _ int) (File, *PipeWriter, func(), error) {
|
||||
var err error
|
||||
var f *os.File
|
||||
if flag == 0 {
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
|
@ -213,7 +214,7 @@ func (v *VirtualFolder) GetFilesystem(connectionID string, forbiddenSelfUsers []
|
|||
// CheckMetadataConsistency checks the consistency between the metadata stored
|
||||
// in the configured metadata plugin and the filesystem
|
||||
func (v *VirtualFolder) CheckMetadataConsistency() error {
|
||||
fs, err := v.GetFilesystem("", nil)
|
||||
fs, err := v.GetFilesystem(xid.New().String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -227,7 +228,7 @@ func (v *VirtualFolder) ScanQuota() (int, int64, error) {
|
|||
if v.hasPathPlaceholder() {
|
||||
return 0, 0, errors.New("cannot scan quota: this folder has a path placeholder")
|
||||
}
|
||||
fs, err := v.GetFilesystem("", nil)
|
||||
fs, err := v.GetFilesystem(xid.New().String(), nil)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ import (
|
|||
|
||||
const (
|
||||
defaultGCSPageSize = 5000
|
||||
gcsfsName = "GCSFs"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -163,7 +162,13 @@ func (fs *GCSFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, fu
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *GCSFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (fs *GCSFs) Create(name string, flag, checks int) (File, *PipeWriter, func(), error) {
|
||||
if checks&CheckParentDir != 0 {
|
||||
_, err := fs.Stat(path.Dir(name))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
r, w, err := pipeat.PipeInDir(fs.localTempDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
@ -171,6 +176,19 @@ func (fs *GCSFs) Create(name string, flag int) (File, *PipeWriter, func(), error
|
|||
p := NewPipeWriter(w)
|
||||
bkt := fs.svc.Bucket(fs.config.Bucket)
|
||||
obj := bkt.Object(name)
|
||||
if flag == -1 {
|
||||
obj = obj.If(storage.Conditions{DoesNotExist: true})
|
||||
} else {
|
||||
attrs, statErr := fs.headObject(name)
|
||||
if statErr == nil {
|
||||
obj = obj.If(storage.Conditions{GenerationMatch: attrs.Generation})
|
||||
} else if fs.IsNotExist(statErr) {
|
||||
obj = obj.If(storage.Conditions{DoesNotExist: true})
|
||||
} else {
|
||||
fsLog(fs, logger.LevelWarn, "unable to set precondition for %q, stat err: %v", name, statErr)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithCancel(context.Background())
|
||||
objectWriter := obj.NewWriter(ctx)
|
||||
var contentType string
|
||||
|
@ -210,10 +228,14 @@ func (fs *GCSFs) Create(name string, flag int) (File, *PipeWriter, func(), error
|
|||
// rename all the contents too and this could take long time: think
|
||||
// about directories with thousands of files, for each file we should
|
||||
// execute a CopyObject call.
|
||||
func (fs *GCSFs) Rename(source, target string) error {
|
||||
func (fs *GCSFs) Rename(source, target string) error { //nolint:gocyclo
|
||||
if source == target {
|
||||
return nil
|
||||
}
|
||||
_, err := fs.Stat(path.Dir(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
realSourceName, fi, err := fs.getObjectStat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -226,38 +248,42 @@ func (fs *GCSFs) Rename(source, target string) error {
|
|||
if hasContents {
|
||||
return fmt.Errorf("cannot rename non empty directory: %#v", source)
|
||||
}
|
||||
if !strings.HasSuffix(target, "/") {
|
||||
target += "/"
|
||||
if err := fs.mkdirInternal(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
src := fs.svc.Bucket(fs.config.Bucket).Object(realSourceName)
|
||||
dst := fs.svc.Bucket(fs.config.Bucket).Object(target)
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
copier := dst.CopierFrom(src)
|
||||
if fs.config.StorageClass != "" {
|
||||
copier.StorageClass = fs.config.StorageClass
|
||||
}
|
||||
if fs.config.ACL != "" {
|
||||
copier.PredefinedACL = fs.config.ACL
|
||||
}
|
||||
var contentType string
|
||||
if fi.IsDir() {
|
||||
contentType = dirMimeType
|
||||
} else {
|
||||
contentType = mime.TypeByExtension(path.Ext(source))
|
||||
}
|
||||
if contentType != "" {
|
||||
copier.ContentType = contentType
|
||||
}
|
||||
_, err = copier.Run(ctx)
|
||||
metric.GCSCopyObjectCompleted(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plugin.Handler.HasMetadater() {
|
||||
if !fi.IsDir() {
|
||||
src := fs.svc.Bucket(fs.config.Bucket).Object(realSourceName)
|
||||
dst := fs.svc.Bucket(fs.config.Bucket).Object(target)
|
||||
attrs, statErr := fs.headObject(target)
|
||||
if statErr == nil {
|
||||
dst = dst.If(storage.Conditions{GenerationMatch: attrs.Generation})
|
||||
} else if fs.IsNotExist(statErr) {
|
||||
dst = dst.If(storage.Conditions{DoesNotExist: true})
|
||||
} else {
|
||||
fsLog(fs, logger.LevelWarn, "unable to set precondition for rename, target %q, stat err: %v",
|
||||
target, statErr)
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
copier := dst.CopierFrom(src)
|
||||
if fs.config.StorageClass != "" {
|
||||
copier.StorageClass = fs.config.StorageClass
|
||||
}
|
||||
if fs.config.ACL != "" {
|
||||
copier.PredefinedACL = fs.config.ACL
|
||||
}
|
||||
contentType := mime.TypeByExtension(path.Ext(source))
|
||||
if contentType != "" {
|
||||
copier.ContentType = contentType
|
||||
}
|
||||
_, err = copier.Run(ctx)
|
||||
metric.GCSCopyObjectCompleted(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plugin.Handler.HasMetadater() {
|
||||
err = plugin.Handler.SetModificationTime(fs.getStorageID(), ensureAbsPath(target),
|
||||
util.GetTimeAsMsSinceEpoch(fi.ModTime()))
|
||||
if err != nil {
|
||||
|
@ -283,11 +309,20 @@ func (fs *GCSFs) Remove(name string, isDir bool) error {
|
|||
name += "/"
|
||||
}
|
||||
}
|
||||
obj := fs.svc.Bucket(fs.config.Bucket).Object(name)
|
||||
attrs, statErr := fs.headObject(name)
|
||||
if statErr == nil {
|
||||
obj = obj.If(storage.Conditions{GenerationMatch: attrs.Generation})
|
||||
} else {
|
||||
fsLog(fs, logger.LevelWarn, "unable to set precondition for deleting %q, stat err: %v",
|
||||
name, statErr)
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
err := fs.svc.Bucket(fs.config.Bucket).Object(name).Delete(ctx)
|
||||
if fs.IsNotExist(err) && isDir {
|
||||
err := obj.Delete(ctx)
|
||||
if isDir && fs.IsNotExist(err) {
|
||||
// we can have directories without a trailing "/" (created using v2.1.0 and before)
|
||||
err = fs.svc.Bucket(fs.config.Bucket).Object(strings.TrimSuffix(name, "/")).Delete(ctx)
|
||||
}
|
||||
|
@ -306,14 +341,7 @@ func (fs *GCSFs) Mkdir(name string) error {
|
|||
if !fs.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
}
|
||||
_, w, _, err := fs.Create(name, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
return fs.mkdirInternal(name)
|
||||
}
|
||||
|
||||
// Symlink creates source as a symbolic link to target.
|
||||
|
@ -502,48 +530,7 @@ func (fs *GCSFs) CheckRootPath(username string, uid int, gid int) bool {
|
|||
// ScanRootDirContents returns the number of files contained in the bucket,
|
||||
// and their size
|
||||
func (fs *GCSFs) ScanRootDirContents() (int, int64, error) {
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
query := &storage.Query{Prefix: fs.config.KeyPrefix}
|
||||
err := query.SetAttrSelection(gcsDefaultFieldsSelection)
|
||||
if err != nil {
|
||||
return numFiles, size, err
|
||||
}
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxLongTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
bkt := fs.svc.Bucket(fs.config.Bucket)
|
||||
it := bkt.Objects(ctx, query)
|
||||
pager := iterator.NewPager(it, defaultGCSPageSize, "")
|
||||
|
||||
for {
|
||||
var objects []*storage.ObjectAttrs
|
||||
pageToken, err := pager.NextPage(&objects)
|
||||
if err != nil {
|
||||
metric.GCSListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
|
||||
for _, attrs := range objects {
|
||||
if !attrs.Deleted.IsZero() {
|
||||
continue
|
||||
}
|
||||
isDir := strings.HasSuffix(attrs.Name, "/") || attrs.ContentType == dirMimeType
|
||||
if isDir && attrs.Size == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += attrs.Size
|
||||
}
|
||||
|
||||
objects = nil
|
||||
if pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
metric.GCSListObjectsCompleted(nil)
|
||||
return numFiles, size, err
|
||||
return fs.GetDirSize(fs.config.KeyPrefix)
|
||||
}
|
||||
|
||||
func (fs *GCSFs) getFileNamesInPrefix(fsPrefix string) (map[string]bool, error) {
|
||||
|
@ -609,8 +596,54 @@ func (fs *GCSFs) CheckMetadata() error {
|
|||
|
||||
// GetDirSize returns the number of files and the size for a folder
|
||||
// including any subfolders
|
||||
func (*GCSFs) GetDirSize(dirname string) (int, int64, error) {
|
||||
return 0, 0, ErrVfsUnsupported
|
||||
func (fs *GCSFs) GetDirSize(dirname string) (int, int64, error) {
|
||||
prefix := fs.getPrefix(dirname)
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
|
||||
query := &storage.Query{Prefix: prefix}
|
||||
err := query.SetAttrSelection(gcsDefaultFieldsSelection)
|
||||
if err != nil {
|
||||
return numFiles, size, err
|
||||
}
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxLongTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
bkt := fs.svc.Bucket(fs.config.Bucket)
|
||||
it := bkt.Objects(ctx, query)
|
||||
pager := iterator.NewPager(it, defaultGCSPageSize, "")
|
||||
|
||||
for {
|
||||
var objects []*storage.ObjectAttrs
|
||||
pageToken, err := pager.NextPage(&objects)
|
||||
if err != nil {
|
||||
metric.GCSListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
|
||||
for _, attrs := range objects {
|
||||
if !attrs.Deleted.IsZero() {
|
||||
continue
|
||||
}
|
||||
isDir := strings.HasSuffix(attrs.Name, "/") || attrs.ContentType == dirMimeType
|
||||
if isDir && attrs.Size == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += attrs.Size
|
||||
if numFiles%1000 == 0 {
|
||||
fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
|
||||
}
|
||||
}
|
||||
|
||||
objects = nil
|
||||
if pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
metric.GCSListObjectsCompleted(nil)
|
||||
return numFiles, size, err
|
||||
}
|
||||
|
||||
// GetAtomicUploadPath returns the path to use for an atomic upload.
|
||||
|
@ -759,6 +792,17 @@ func (fs *GCSFs) getObjectStat(name string) (string, os.FileInfo, error) {
|
|||
return name + "/", info, err
|
||||
}
|
||||
|
||||
func (fs *GCSFs) mkdirInternal(name string) error {
|
||||
if !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
}
|
||||
_, w, _, err := fs.Create(name, -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
func (fs *GCSFs) hasContents(name string) (bool, error) {
|
||||
result := false
|
||||
prefix := fs.getPrefix(name)
|
||||
|
|
|
@ -330,7 +330,7 @@ func (fs *HTTPFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, f
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *HTTPFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (fs *HTTPFs) Create(name string, flag, _ int) (File, *PipeWriter, func(), error) {
|
||||
r, w, err := pipeat.PipeInDir(fs.localTempDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -103,7 +104,7 @@ func (*OsFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func()
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (*OsFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (*OsFs) Create(name string, flag, _ int) (File, *PipeWriter, func(), error) {
|
||||
var err error
|
||||
var f *os.File
|
||||
if flag == 0 {
|
||||
|
@ -382,6 +383,9 @@ func (fs *OsFs) GetDirSize(dirname string) (int, int64, error) {
|
|||
if info != nil && info.Mode().IsRegular() {
|
||||
size += info.Size()
|
||||
numFiles++
|
||||
if numFiles%1000 == 0 {
|
||||
fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
@ -403,6 +407,9 @@ func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) {
|
|||
for fs.IsNotExist(err) {
|
||||
results = append(results, parent)
|
||||
parent = filepath.Dir(parent)
|
||||
if util.Contains(results, parent) {
|
||||
break
|
||||
}
|
||||
_, err = os.Stat(parent)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
@ -57,7 +57,10 @@ const (
|
|||
// using this mime type for directories improves compatibility with s3fs-fuse
|
||||
s3DirMimeType = "application/x-directory"
|
||||
s3TransferBufferSize = 256 * 1024
|
||||
s3fsName = "S3Fs"
|
||||
)
|
||||
|
||||
var (
|
||||
s3DirMimeTypes = []string{s3DirMimeType, "httpd/unix-directory"}
|
||||
)
|
||||
|
||||
// S3Fs is a Fs implementation for AWS S3 compatible object storages
|
||||
|
@ -112,19 +115,6 @@ func NewS3Fs(connectionID, localTempDir, mountPath string, s3Config S3FsConfig)
|
|||
awsConfig.Credentials = aws.NewCredentialsCache(
|
||||
credentials.NewStaticCredentialsProvider(fs.config.AccessKey, fs.config.AccessSecret.GetPayload(), ""))
|
||||
}
|
||||
if fs.config.Endpoint != "" {
|
||||
endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...any) (aws.Endpoint, error) {
|
||||
return aws.Endpoint{
|
||||
URL: fs.config.Endpoint,
|
||||
HostnameImmutable: fs.config.ForcePathStyle,
|
||||
PartitionID: "aws",
|
||||
SigningRegion: fs.config.Region,
|
||||
Source: aws.EndpointSourceCustom,
|
||||
}, nil
|
||||
})
|
||||
awsConfig.EndpointResolverWithOptions = endpointResolver
|
||||
}
|
||||
|
||||
fs.setConfigDefaults()
|
||||
|
||||
if fs.config.RoleARN != "" {
|
||||
|
@ -134,6 +124,9 @@ func NewS3Fs(connectionID, localTempDir, mountPath string, s3Config S3FsConfig)
|
|||
}
|
||||
fs.svc = s3.NewFromConfig(awsConfig, func(o *s3.Options) {
|
||||
o.UsePathStyle = fs.config.ForcePathStyle
|
||||
if fs.config.Endpoint != "" {
|
||||
o.BaseEndpoint = aws.String(fs.config.Endpoint)
|
||||
}
|
||||
})
|
||||
return fs, nil
|
||||
}
|
||||
|
@ -159,8 +152,14 @@ func (fs *S3Fs) Stat(name string) (os.FileInfo, error) {
|
|||
}
|
||||
obj, err := fs.headObject(name)
|
||||
if err == nil {
|
||||
// a "dir" has a trailing "/" so we cannot have a directory here
|
||||
return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, false, obj.ContentLength,
|
||||
// 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 util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir {
|
||||
_, err = fs.headObject(name + "/")
|
||||
isDir = err == nil
|
||||
}
|
||||
return updateFileInfoModTime(fs.getStorageID(), name, NewFileInfo(name, isDir, util.GetIntFromPointer(obj.ContentLength),
|
||||
util.GetTimeFromPointer(obj.LastModified), false))
|
||||
}
|
||||
if !fs.IsNotExist(err) {
|
||||
|
@ -186,7 +185,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))
|
||||
}
|
||||
|
||||
|
@ -233,7 +232,13 @@ func (fs *S3Fs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, fun
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *S3Fs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
func (fs *S3Fs) Create(name string, flag, checks int) (File, *PipeWriter, func(), error) {
|
||||
if checks&CheckParentDir != 0 {
|
||||
_, err := fs.Stat(path.Dir(name))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
r, w, err := pipeat.PipeInDir(fs.localTempDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
|
@ -285,67 +290,61 @@ func (fs *S3Fs) Rename(source, target string) error {
|
|||
if source == target {
|
||||
return nil
|
||||
}
|
||||
_, err := fs.Stat(path.Dir(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi, err := fs.Stat(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copySource := fs.Join(fs.config.Bucket, source)
|
||||
if fi.IsDir() {
|
||||
hasContents, err := fs.hasContents(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasContents {
|
||||
return fmt.Errorf("cannot rename non empty directory: %#v", source)
|
||||
return fmt.Errorf("cannot rename non empty directory: %q", source)
|
||||
}
|
||||
if !strings.HasSuffix(copySource, "/") {
|
||||
copySource += "/"
|
||||
if err := fs.mkdirInternal(target); err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(target, "/") {
|
||||
target += "/"
|
||||
}
|
||||
}
|
||||
var contentType string
|
||||
if fi.IsDir() {
|
||||
contentType = s3DirMimeType
|
||||
} else {
|
||||
contentType = mime.TypeByExtension(path.Ext(source))
|
||||
}
|
||||
copySource = pathEscape(copySource)
|
||||
contentType := mime.TypeByExtension(path.Ext(source))
|
||||
copySource := pathEscape(fs.Join(fs.config.Bucket, source))
|
||||
|
||||
if fi.Size() > 500*1024*1024 {
|
||||
fsLog(fs, logger.LevelDebug, "renaming file %q with size %d using multipart copy",
|
||||
source, fi.Size())
|
||||
err = fs.doMultipartCopy(copySource, target, contentType, fi.Size())
|
||||
} else {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
if fi.Size() > 500*1024*1024 {
|
||||
fsLog(fs, logger.LevelDebug, "renaming file %q with size %d using multipart copy",
|
||||
source, fi.Size())
|
||||
err = fs.doMultipartCopy(copySource, target, contentType, fi.Size())
|
||||
} else {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
_, err = fs.svc.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
CopySource: aws.String(copySource),
|
||||
Key: aws.String(target),
|
||||
StorageClass: types.StorageClass(fs.config.StorageClass),
|
||||
ACL: types.ObjectCannedACL(fs.config.ACL),
|
||||
ContentType: util.NilIfEmpty(contentType),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
_, err = fs.svc.CopyObject(ctx, &s3.CopyObjectInput{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
CopySource: aws.String(copySource),
|
||||
Key: aws.String(target),
|
||||
StorageClass: types.StorageClass(fs.config.StorageClass),
|
||||
ACL: types.ObjectCannedACL(fs.config.ACL),
|
||||
ContentType: util.NilIfEmpty(contentType),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
metric.S3CopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
|
||||
waiter := s3.NewObjectExistsWaiter(fs.svc)
|
||||
err = waiter.Wait(context.Background(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
Key: aws.String(target),
|
||||
}, 10*time.Second)
|
||||
metric.S3CopyObjectCompleted(err)
|
||||
return err
|
||||
}
|
||||
|
||||
waiter := s3.NewObjectExistsWaiter(fs.svc)
|
||||
err = waiter.Wait(context.Background(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
Key: aws.String(target),
|
||||
}, 10*time.Second)
|
||||
metric.S3CopyObjectCompleted(err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plugin.Handler.HasMetadater() {
|
||||
if !fi.IsDir() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plugin.Handler.HasMetadater() {
|
||||
err = plugin.Handler.SetModificationTime(fs.getStorageID(), ensureAbsPath(target),
|
||||
util.GetTimeAsMsSinceEpoch(fi.ModTime()))
|
||||
if err != nil {
|
||||
|
@ -393,14 +392,7 @@ func (fs *S3Fs) Mkdir(name string) error {
|
|||
if !fs.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
}
|
||||
_, w, _, err := fs.Create(name, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
return fs.mkdirInternal(name)
|
||||
}
|
||||
|
||||
// Symlink creates source as a symbolic link to target.
|
||||
|
@ -490,6 +482,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
|
||||
|
@ -503,7 +496,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))
|
||||
}
|
||||
}
|
||||
|
@ -576,35 +569,7 @@ func (fs *S3Fs) CheckRootPath(username string, uid int, gid int) bool {
|
|||
// ScanRootDirContents returns the number of files contained in the bucket,
|
||||
// and their size
|
||||
func (fs *S3Fs) ScanRootDirContents() (int, int64, error) {
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
|
||||
paginator := s3.NewListObjectsV2Paginator(fs.svc, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
Prefix: aws.String(fs.config.KeyPrefix),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
page, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
metric.S3ListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
for _, fileObject := range page.Contents {
|
||||
isDir := strings.HasSuffix(util.GetStringFromPointer(fileObject.Key), "/")
|
||||
if isDir && fileObject.Size == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += fileObject.Size
|
||||
}
|
||||
}
|
||||
|
||||
metric.S3ListObjectsCompleted(nil)
|
||||
return numFiles, size, nil
|
||||
return fs.GetDirSize(fs.config.KeyPrefix)
|
||||
}
|
||||
|
||||
func (fs *S3Fs) getFileNamesInPrefix(fsPrefix string) (map[string]bool, error) {
|
||||
|
@ -652,8 +617,41 @@ func (fs *S3Fs) CheckMetadata() error {
|
|||
|
||||
// GetDirSize returns the number of files and the size for a folder
|
||||
// including any subfolders
|
||||
func (*S3Fs) GetDirSize(dirname string) (int, int64, error) {
|
||||
return 0, 0, ErrVfsUnsupported
|
||||
func (fs *S3Fs) GetDirSize(dirname string) (int, int64, error) {
|
||||
prefix := fs.getPrefix(dirname)
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
|
||||
paginator := s3.NewListObjectsV2Paginator(fs.svc, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(fs.config.Bucket),
|
||||
Prefix: aws.String(prefix),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
ctx, cancelFn := context.WithDeadline(context.Background(), time.Now().Add(fs.ctxTimeout))
|
||||
defer cancelFn()
|
||||
|
||||
page, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
metric.S3ListObjectsCompleted(err)
|
||||
return numFiles, size, err
|
||||
}
|
||||
for _, fileObject := range page.Contents {
|
||||
isDir := strings.HasSuffix(util.GetStringFromPointer(fileObject.Key), "/")
|
||||
objectSize := util.GetIntFromPointer(fileObject.Size)
|
||||
if isDir && objectSize == 0 {
|
||||
continue
|
||||
}
|
||||
numFiles++
|
||||
size += objectSize
|
||||
if numFiles%1000 == 0 {
|
||||
fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metric.S3ListObjectsCompleted(nil)
|
||||
return numFiles, size, nil
|
||||
}
|
||||
|
||||
// GetAtomicUploadPath returns the path to use for an atomic upload.
|
||||
|
@ -710,7 +708,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
|
||||
}
|
||||
|
@ -775,12 +774,24 @@ func (fs *S3Fs) setConfigDefaults() {
|
|||
}
|
||||
}
|
||||
|
||||
func (fs *S3Fs) mkdirInternal(name string) error {
|
||||
if !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
}
|
||||
_, w, _, err := fs.Create(name, -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -874,7 +885,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)),
|
||||
})
|
||||
|
@ -903,7 +914,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)
|
||||
|
@ -916,7 +927,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))
|
||||
|
|
|
@ -16,8 +16,10 @@ package vfs
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
|
@ -25,12 +27,15 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/rs/xid"
|
||||
"github.com/sftpgo/sdk"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -43,11 +48,16 @@ import (
|
|||
|
||||
const (
|
||||
// sftpFsName is the name for the SFTP Fs implementation
|
||||
sftpFsName = "sftpfs"
|
||||
sftpFsName = "sftpfs"
|
||||
logSenderSFTPCache = "sftpCache"
|
||||
maxSessionsPerConnection = 5
|
||||
)
|
||||
|
||||
// ErrSFTPLoop defines the error to return if an SFTP loop is detected
|
||||
var ErrSFTPLoop = errors.New("SFTP loop or nested local SFTP folders detected")
|
||||
var (
|
||||
// ErrSFTPLoop defines the error to return if an SFTP loop is detected
|
||||
ErrSFTPLoop = errors.New("SFTP loop or nested local SFTP folders detected")
|
||||
sftpConnsCache = newSFTPConnectionCache()
|
||||
)
|
||||
|
||||
// SFTPFsConfig defines the configuration for SFTP based filesystem
|
||||
type SFTPFsConfig struct {
|
||||
|
@ -145,6 +155,9 @@ func (c *SFTPFsConfig) validate() error {
|
|||
if c.Endpoint == "" {
|
||||
return errors.New("endpoint cannot be empty")
|
||||
}
|
||||
if !strings.Contains(c.Endpoint, ":") {
|
||||
c.Endpoint += ":22"
|
||||
}
|
||||
_, _, err := net.SplitHostPort(c.Endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid endpoint: %v", err)
|
||||
|
@ -220,17 +233,36 @@ func (c *SFTPFsConfig) ValidateAndEncryptCredentials(additionalData string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
// getUniqueID returns an hash of the settings used to connect to the SFTP server
|
||||
func (c *SFTPFsConfig) getUniqueID(partition int) uint64 {
|
||||
h := fnv.New64a()
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteString(c.Endpoint)
|
||||
b.WriteString(c.Username)
|
||||
b.WriteString(strings.Join(c.Fingerprints, ""))
|
||||
b.WriteString(strconv.FormatBool(c.DisableCouncurrentReads))
|
||||
b.WriteString(strconv.FormatInt(c.BufferSize, 10))
|
||||
b.WriteString(c.Password.GetPayload())
|
||||
b.WriteString(c.PrivateKey.GetPayload())
|
||||
b.WriteString(c.KeyPassphrase.GetPayload())
|
||||
if allowSelfConnections != 0 {
|
||||
b.WriteString(strings.Join(c.forbiddenSelfUsernames, ""))
|
||||
}
|
||||
b.WriteString(strconv.Itoa(partition))
|
||||
|
||||
h.Write(b.Bytes())
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
// SFTPFs is a Fs implementation for SFTP backends
|
||||
type SFTPFs struct {
|
||||
sync.Mutex
|
||||
connectionID string
|
||||
// if not empty this fs is mouted as virtual folder in the specified path
|
||||
mountPath string
|
||||
localTempDir string
|
||||
config *SFTPFsConfig
|
||||
sshClient *ssh.Client
|
||||
sftpClient *sftp.Client
|
||||
err chan error
|
||||
conn *sftpConnection
|
||||
}
|
||||
|
||||
// NewSFTPFs returns an SFTPFs object that allows to interact with an SFTP server
|
||||
|
@ -266,15 +298,18 @@ func NewSFTPFs(connectionID, mountPath, localTempDir string, forbiddenSelfUserna
|
|||
mountPath: getMountPath(mountPath),
|
||||
localTempDir: localTempDir,
|
||||
config: &config,
|
||||
err: make(chan error, 1),
|
||||
conn: sftpConnsCache.Get(&config, connectionID),
|
||||
}
|
||||
err := sftpFs.createConnection()
|
||||
if err != nil {
|
||||
sftpFs.Close() //nolint:errcheck
|
||||
}
|
||||
return sftpFs, err
|
||||
}
|
||||
|
||||
// Name returns the name for the Fs implementation
|
||||
func (fs *SFTPFs) Name() string {
|
||||
return fmt.Sprintf("%v %#v", sftpFsName, fs.config.Endpoint)
|
||||
return fmt.Sprintf(`%s %q@%q`, sftpFsName, fs.config.Username, fs.config.Endpoint)
|
||||
}
|
||||
|
||||
// ConnectionID returns the connection ID associated to this Fs implementation
|
||||
|
@ -284,26 +319,29 @@ func (fs *SFTPFs) ConnectionID() string {
|
|||
|
||||
// Stat returns a FileInfo describing the named file
|
||||
func (fs *SFTPFs) Stat(name string) (os.FileInfo, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.sftpClient.Stat(name)
|
||||
return client.Stat(name)
|
||||
}
|
||||
|
||||
// Lstat returns a FileInfo describing the named file
|
||||
func (fs *SFTPFs) Lstat(name string) (os.FileInfo, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.sftpClient.Lstat(name)
|
||||
return client.Lstat(name)
|
||||
}
|
||||
|
||||
// Open opens the named file for reading
|
||||
func (fs *SFTPFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
f, err := fs.sftpClient.Open(name)
|
||||
f, err := client.Open(name)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
@ -336,22 +374,22 @@ func (fs *SFTPFs) Open(name string, offset int64) (File, *pipeat.PipeReaderAt, f
|
|||
}
|
||||
|
||||
// Create creates or opens the named file for writing
|
||||
func (fs *SFTPFs) Create(name string, flag int) (File, *PipeWriter, func(), error) {
|
||||
err := fs.checkConnection()
|
||||
func (fs *SFTPFs) Create(name string, flag, _ int) (File, *PipeWriter, func(), error) {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if fs.config.BufferSize == 0 {
|
||||
var f File
|
||||
if flag == 0 {
|
||||
f, err = fs.sftpClient.Create(name)
|
||||
f, err = client.Create(name)
|
||||
} else {
|
||||
f, err = fs.sftpClient.OpenFile(name, flag)
|
||||
f, err = client.OpenFile(name, flag)
|
||||
}
|
||||
return f, nil, nil, err
|
||||
}
|
||||
// buffering is enabled
|
||||
f, err := fs.sftpClient.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
f, err := client.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
@ -393,48 +431,53 @@ func (fs *SFTPFs) Rename(source, target string) error {
|
|||
if source == target {
|
||||
return nil
|
||||
}
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := fs.sftpClient.HasExtension("posix-rename@openssh.com"); ok {
|
||||
return fs.sftpClient.PosixRename(source, target)
|
||||
if _, ok := client.HasExtension("posix-rename@openssh.com"); ok {
|
||||
return client.PosixRename(source, target)
|
||||
}
|
||||
return fs.sftpClient.Rename(source, target)
|
||||
return client.Rename(source, target)
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
func (fs *SFTPFs) Remove(name string, isDir bool) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isDir {
|
||||
return fs.sftpClient.RemoveDirectory(name)
|
||||
return client.RemoveDirectory(name)
|
||||
}
|
||||
return fs.sftpClient.Remove(name)
|
||||
return client.Remove(name)
|
||||
}
|
||||
|
||||
// Mkdir creates a new directory with the specified name and default permissions
|
||||
func (fs *SFTPFs) Mkdir(name string) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Mkdir(name)
|
||||
return client.Mkdir(name)
|
||||
}
|
||||
|
||||
// Symlink creates source as a symbolic link to target.
|
||||
func (fs *SFTPFs) Symlink(source, target string) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Symlink(source, target)
|
||||
return client.Symlink(source, target)
|
||||
}
|
||||
|
||||
// Readlink returns the destination of the named symbolic link
|
||||
func (fs *SFTPFs) Readlink(name string) (string, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolved, err := fs.sftpClient.ReadLink(name)
|
||||
resolved, err := client.ReadLink(name)
|
||||
if err != nil {
|
||||
return resolved, err
|
||||
}
|
||||
|
@ -448,43 +491,48 @@ func (fs *SFTPFs) Readlink(name string) (string, error) {
|
|||
|
||||
// Chown changes the numeric uid and gid of the named file.
|
||||
func (fs *SFTPFs) Chown(name string, uid int, gid int) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Chown(name, uid, gid)
|
||||
return client.Chown(name, uid, gid)
|
||||
}
|
||||
|
||||
// Chmod changes the mode of the named file to mode.
|
||||
func (fs *SFTPFs) Chmod(name string, mode os.FileMode) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Chmod(name, mode)
|
||||
return client.Chmod(name, mode)
|
||||
}
|
||||
|
||||
// Chtimes changes the access and modification times of the named file.
|
||||
func (fs *SFTPFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Chtimes(name, atime, mtime)
|
||||
return client.Chtimes(name, atime, mtime)
|
||||
}
|
||||
|
||||
// Truncate changes the size of the named file.
|
||||
func (fs *SFTPFs) Truncate(name string, size int64) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.sftpClient.Truncate(name, size)
|
||||
return client.Truncate(name, size)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries.
|
||||
func (fs *SFTPFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.sftpClient.ReadDir(dirname)
|
||||
return client.ReadDir(dirname)
|
||||
}
|
||||
|
||||
// IsUploadResumeSupported returns true if resuming uploads is supported.
|
||||
|
@ -528,11 +576,12 @@ func (fs *SFTPFs) CheckRootPath(username string, uid int, gid int) bool {
|
|||
if fs.config.Prefix == "/" {
|
||||
return true
|
||||
}
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if err := fs.sftpClient.MkdirAll(fs.config.Prefix); err != nil {
|
||||
fsLog(fs, logger.LevelDebug, "error creating root directory %#v for user %#v: %v", fs.config.Prefix, username, err)
|
||||
if err := client.MkdirAll(fs.config.Prefix); err != nil {
|
||||
fsLog(fs, logger.LevelDebug, "error creating root directory %q for user %q: %v", fs.config.Prefix, username, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -581,10 +630,11 @@ func (fs *SFTPFs) GetRelativePath(name string) string {
|
|||
// Walk walks the file tree rooted at root, calling walkFn for each file or
|
||||
// directory in the tree, including root
|
||||
func (fs *SFTPFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
walker := fs.sftpClient.Walk(root)
|
||||
walker := client.Walk(root)
|
||||
for walker.Step() {
|
||||
err := walker.Err()
|
||||
if err != nil {
|
||||
|
@ -620,9 +670,6 @@ func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) {
|
|||
if fs.config.Prefix != "/" && fsPath != "/" {
|
||||
// we need to check if this path is a symlink outside the given prefix
|
||||
// or a file/dir inside a dir symlinked outside the prefix
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var validatedPath string
|
||||
var err error
|
||||
validatedPath, err = fs.getRealPath(fsPath)
|
||||
|
@ -657,10 +704,11 @@ func (fs *SFTPFs) ResolvePath(virtualPath string) (string, error) {
|
|||
|
||||
// RealPath implements the FsRealPather interface
|
||||
func (fs *SFTPFs) RealPath(p string) (string, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolved, err := fs.sftpClient.RealPath(p)
|
||||
resolved, err := client.RealPath(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -676,16 +724,20 @@ func (fs *SFTPFs) RealPath(p string) (string, error) {
|
|||
|
||||
// getRealPath returns the real remote path trying to resolve symbolic links if any
|
||||
func (fs *SFTPFs) getRealPath(name string) (string, error) {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
linksWalked := 0
|
||||
for {
|
||||
info, err := fs.sftpClient.Lstat(name)
|
||||
info, err := client.Lstat(name)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink == 0 {
|
||||
return name, nil
|
||||
}
|
||||
resolvedLink, err := fs.sftpClient.ReadLink(name)
|
||||
resolvedLink, err := client.ReadLink(name)
|
||||
if err != nil {
|
||||
return name, fmt.Errorf("unable to resolve link to %q: %w", name, err)
|
||||
}
|
||||
|
@ -723,12 +775,13 @@ func (fs *SFTPFs) isSubDir(name string) error {
|
|||
func (fs *SFTPFs) GetDirSize(dirname string) (int, int64, error) {
|
||||
numFiles := 0
|
||||
size := int64(0)
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return numFiles, size, err
|
||||
}
|
||||
isDir, err := isDirectory(fs, dirname)
|
||||
if err == nil && isDir {
|
||||
walker := fs.sftpClient.Walk(dirname)
|
||||
walker := client.Walk(dirname)
|
||||
for walker.Step() {
|
||||
err := walker.Err()
|
||||
if err != nil {
|
||||
|
@ -737,6 +790,9 @@ func (fs *SFTPFs) GetDirSize(dirname string) (int, int64, error) {
|
|||
if walker.Stat().Mode().IsRegular() {
|
||||
size += walker.Stat().Size()
|
||||
numFiles++
|
||||
if numFiles%1000 == 0 {
|
||||
fsLog(fs, logger.LevelDebug, "dirname %q scan in progress, files: %d, size: %d", dirname, numFiles, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -745,10 +801,11 @@ func (fs *SFTPFs) GetDirSize(dirname string) (int, int64, error) {
|
|||
|
||||
// GetMimeType returns the content type
|
||||
func (fs *SFTPFs) GetMimeType(name string) (string, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := fs.sftpClient.OpenFile(name, os.O_RDONLY)
|
||||
f, err := client.OpenFile(name, os.O_RDONLY)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -766,31 +823,20 @@ func (fs *SFTPFs) GetMimeType(name string) (string, error) {
|
|||
|
||||
// GetAvailableDiskSize returns the available size for the specified path
|
||||
func (fs *SFTPFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
client, err := fs.conn.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := fs.sftpClient.HasExtension("statvfs@openssh.com"); !ok {
|
||||
if _, ok := client.HasExtension("statvfs@openssh.com"); !ok {
|
||||
return nil, ErrStorageSizeUnavailable
|
||||
}
|
||||
return fs.sftpClient.StatVFS(dirName)
|
||||
return client.StatVFS(dirName)
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
func (fs *SFTPFs) Close() error {
|
||||
fs.Lock()
|
||||
defer fs.Unlock()
|
||||
|
||||
var sftpErr, sshErr error
|
||||
if fs.sftpClient != nil {
|
||||
sftpErr = fs.sftpClient.Close()
|
||||
}
|
||||
if fs.sshClient != nil {
|
||||
sshErr = fs.sshClient.Close()
|
||||
}
|
||||
if sftpErr != nil {
|
||||
return sftpErr
|
||||
}
|
||||
return sshErr
|
||||
fs.conn.RemoveSession(fs.connectionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
|
@ -825,64 +871,98 @@ func (fs *SFTPFs) copy(dst io.Writer, src io.Reader) (written int64, err error)
|
|||
return written, err
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) checkConnection() error {
|
||||
err := fs.closed()
|
||||
if err == nil {
|
||||
return nil
|
||||
func (fs *SFTPFs) createConnection() error {
|
||||
err := fs.conn.OpenConnection()
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelError, "error opening connection: %v", err)
|
||||
return err
|
||||
}
|
||||
return fs.createConnection()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) createConnection() error {
|
||||
fs.Lock()
|
||||
defer fs.Unlock()
|
||||
type sftpConnection struct {
|
||||
config *SFTPFsConfig
|
||||
logSender string
|
||||
sshClient *ssh.Client
|
||||
sftpClient *sftp.Client
|
||||
mu sync.RWMutex
|
||||
isConnected bool
|
||||
sessions map[string]bool
|
||||
lastActivity time.Time
|
||||
}
|
||||
|
||||
var err error
|
||||
func newSFTPConnection(config *SFTPFsConfig, sessionID string) *sftpConnection {
|
||||
c := &sftpConnection{
|
||||
config: config,
|
||||
logSender: fmt.Sprintf(`%s "%s@%s"`, sftpFsName, config.Username, config.Endpoint),
|
||||
isConnected: false,
|
||||
sessions: map[string]bool{},
|
||||
lastActivity: time.Now().UTC(),
|
||||
}
|
||||
c.sessions[sessionID] = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *sftpConnection) OpenConnection() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.openConnNoLock()
|
||||
}
|
||||
|
||||
func (c *sftpConnection) openConnNoLock() error {
|
||||
if c.isConnected {
|
||||
logger.Debug(c.logSender, "", "reusing connection")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Debug(c.logSender, "", "try to open a new connection")
|
||||
clientConfig := &ssh.ClientConfig{
|
||||
User: fs.config.Username,
|
||||
User: c.config.Username,
|
||||
HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error {
|
||||
fp := ssh.FingerprintSHA256(key)
|
||||
if util.Contains(sftpFingerprints, fp) {
|
||||
if allowSelfConnections == 0 {
|
||||
fsLog(fs, logger.LevelError, "SFTP self connections not allowed")
|
||||
logger.Log(logger.LevelError, c.logSender, "", "SFTP self connections not allowed")
|
||||
return ErrSFTPLoop
|
||||
}
|
||||
if util.Contains(fs.config.forbiddenSelfUsernames, fs.config.Username) {
|
||||
fsLog(fs, logger.LevelError, "SFTP loop or nested local SFTP folders detected, mount path %q, username %q, forbidden usernames: %+v",
|
||||
fs.mountPath, fs.config.Username, fs.config.forbiddenSelfUsernames)
|
||||
if util.Contains(c.config.forbiddenSelfUsernames, c.config.Username) {
|
||||
logger.Log(logger.LevelError, c.logSender, "",
|
||||
"SFTP loop or nested local SFTP folders detected, username %q, forbidden usernames: %+v",
|
||||
c.config.Username, c.config.forbiddenSelfUsernames)
|
||||
return ErrSFTPLoop
|
||||
}
|
||||
}
|
||||
if len(fs.config.Fingerprints) > 0 {
|
||||
for _, provided := range fs.config.Fingerprints {
|
||||
if len(c.config.Fingerprints) > 0 {
|
||||
for _, provided := range c.config.Fingerprints {
|
||||
if provided == fp {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("invalid fingerprint %#v", fp)
|
||||
return fmt.Errorf("invalid fingerprint %q", fp)
|
||||
}
|
||||
fsLog(fs, logger.LevelWarn, "login without host key validation, please provide at least a fingerprint!")
|
||||
logger.Log(logger.LevelWarn, c.logSender, "", "login without host key validation, please provide at least a fingerprint!")
|
||||
return nil
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version),
|
||||
}
|
||||
if fs.config.PrivateKey.GetPayload() != "" {
|
||||
if c.config.PrivateKey.GetPayload() != "" {
|
||||
var signer ssh.Signer
|
||||
if fs.config.KeyPassphrase.GetPayload() != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(fs.config.PrivateKey.GetPayload()),
|
||||
[]byte(fs.config.KeyPassphrase.GetPayload()))
|
||||
var err error
|
||||
if c.config.KeyPassphrase.GetPayload() != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(c.config.PrivateKey.GetPayload()),
|
||||
[]byte(c.config.KeyPassphrase.GetPayload()))
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey([]byte(fs.config.PrivateKey.GetPayload()))
|
||||
signer, err = ssh.ParsePrivateKey([]byte(c.config.PrivateKey.GetPayload()))
|
||||
}
|
||||
if err != nil {
|
||||
fs.err <- err
|
||||
return fmt.Errorf("sftpfs: unable to parse the private key: %w", err)
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
|
||||
}
|
||||
if fs.config.Password.GetPayload() != "" {
|
||||
clientConfig.Auth = append(clientConfig.Auth, ssh.Password(fs.config.Password.GetPayload()))
|
||||
if c.config.Password.GetPayload() != "" {
|
||||
clientConfig.Auth = append(clientConfig.Auth, ssh.Password(c.config.Password.GetPayload()))
|
||||
}
|
||||
// add more ciphers, KEXs and MACs, they are negotiated according to the order
|
||||
clientConfig.Ciphers = []string{"aes128-gcm@openssh.com", "aes256-gcm@openssh.com", "chacha20-poly1305@openssh.com",
|
||||
|
@ -895,52 +975,225 @@ func (fs *SFTPFs) createConnection() error {
|
|||
clientConfig.MACs = []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256",
|
||||
"hmac-sha2-512-etm@openssh.com", "hmac-sha2-512",
|
||||
"hmac-sha1", "hmac-sha1-96"}
|
||||
fs.sshClient, err = ssh.Dial("tcp", fs.config.Endpoint, clientConfig)
|
||||
sshClient, err := ssh.Dial("tcp", c.config.Endpoint, clientConfig)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelError, "unable to connect: %v", err)
|
||||
fs.err <- err
|
||||
return err
|
||||
return fmt.Errorf("sftpfs: unable to connect: %w", err)
|
||||
}
|
||||
fs.sftpClient, err = sftp.NewClient(fs.sshClient)
|
||||
sftpClient, err := sftp.NewClient(sshClient, c.getClientOptions()...)
|
||||
if err != nil {
|
||||
fsLog(fs, logger.LevelError, "unable to create SFTP client: %v", err)
|
||||
fs.sshClient.Close()
|
||||
fs.err <- err
|
||||
return err
|
||||
sshClient.Close()
|
||||
return fmt.Errorf("sftpfs: unable to create SFTP client: %w", err)
|
||||
}
|
||||
if fs.config.DisableCouncurrentReads {
|
||||
fsLog(fs, logger.LevelDebug, "disabling concurrent reads")
|
||||
opt := sftp.UseConcurrentReads(false)
|
||||
opt(fs.sftpClient) //nolint:errcheck
|
||||
}
|
||||
if fs.config.BufferSize > 0 {
|
||||
fsLog(fs, logger.LevelDebug, "enabling concurrent writes")
|
||||
opt := sftp.UseConcurrentWrites(true)
|
||||
opt(fs.sftpClient) //nolint:errcheck
|
||||
}
|
||||
go fs.wait()
|
||||
c.sshClient = sshClient
|
||||
c.sftpClient = sftpClient
|
||||
c.isConnected = true
|
||||
go c.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) wait() {
|
||||
func (c *sftpConnection) getClientOptions() []sftp.ClientOption {
|
||||
var options []sftp.ClientOption
|
||||
if c.config.DisableCouncurrentReads {
|
||||
options = append(options, sftp.UseConcurrentReads(false))
|
||||
logger.Debug(c.logSender, "", "disabling concurrent reads")
|
||||
}
|
||||
if c.config.BufferSize > 0 {
|
||||
options = append(options, sftp.UseConcurrentWrites(true))
|
||||
logger.Debug(c.logSender, "", "enabling concurrent writes")
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (c *sftpConnection) getClient() (*sftp.Client, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.isConnected {
|
||||
return c.sftpClient, nil
|
||||
}
|
||||
err := c.openConnNoLock()
|
||||
return c.sftpClient, err
|
||||
}
|
||||
|
||||
func (c *sftpConnection) Wait() {
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
var watchdogInProgress atomic.Bool
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if watchdogInProgress.Load() {
|
||||
logger.Error(c.logSender, "", "watchdog still in progress, closing hanging connection")
|
||||
c.sshClient.Close()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
watchdogInProgress.Store(true)
|
||||
defer watchdogInProgress.Store(false)
|
||||
|
||||
_, err := c.sftpClient.Getwd()
|
||||
if err != nil {
|
||||
logger.Error(c.logSender, "", "watchdog error: %v", err)
|
||||
}
|
||||
}()
|
||||
case <-done:
|
||||
logger.Debug(c.logSender, "", "quitting watchdog")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// we wait on the sftp client otherwise if the channel is closed but not the connection
|
||||
// we don't detect the event.
|
||||
fs.err <- fs.sftpClient.Wait()
|
||||
fsLog(fs, logger.LevelDebug, "sftp channel closed")
|
||||
err := c.sftpClient.Wait()
|
||||
logger.Log(logger.LevelDebug, c.logSender, "", "sftp channel closed: %v", err)
|
||||
close(done)
|
||||
|
||||
fs.Lock()
|
||||
defer fs.Unlock()
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if fs.sshClient != nil {
|
||||
fs.sshClient.Close()
|
||||
c.isConnected = false
|
||||
if c.sshClient != nil {
|
||||
c.sshClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) closed() error {
|
||||
select {
|
||||
case err := <-fs.err:
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
func (c *sftpConnection) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
logger.Debug(c.logSender, "", "closing connection")
|
||||
var sftpErr, sshErr error
|
||||
if c.sftpClient != nil {
|
||||
sftpErr = c.sftpClient.Close()
|
||||
}
|
||||
if c.sshClient != nil {
|
||||
sshErr = c.sshClient.Close()
|
||||
}
|
||||
if sftpErr != nil {
|
||||
return sftpErr
|
||||
}
|
||||
c.isConnected = false
|
||||
return sshErr
|
||||
}
|
||||
|
||||
func (c *sftpConnection) AddSession(sessionID string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.sessions[sessionID] = true
|
||||
logger.Debug(c.logSender, "", "added session %s, active sessions: %d", sessionID, len(c.sessions))
|
||||
}
|
||||
|
||||
func (c *sftpConnection) RemoveSession(sessionID string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
delete(c.sessions, sessionID)
|
||||
logger.Debug(c.logSender, "", "removed session %s, active sessions: %d", sessionID, len(c.sessions))
|
||||
if len(c.sessions) == 0 {
|
||||
c.lastActivity = time.Now().UTC()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sftpConnection) ActiveSessions() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
return len(c.sessions)
|
||||
}
|
||||
|
||||
func (c *sftpConnection) GetLastActivity() time.Time {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if len(c.sessions) > 0 {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
logger.Debug(c.logSender, "", "last activity %s", c.lastActivity)
|
||||
return c.lastActivity
|
||||
}
|
||||
|
||||
type sftpConnectionsCache struct {
|
||||
scheduler *cron.Cron
|
||||
sync.RWMutex
|
||||
items map[uint64]*sftpConnection
|
||||
}
|
||||
|
||||
func newSFTPConnectionCache() *sftpConnectionsCache {
|
||||
c := &sftpConnectionsCache{
|
||||
scheduler: cron.New(),
|
||||
items: make(map[uint64]*sftpConnection),
|
||||
}
|
||||
_, err := c.scheduler.AddFunc("@every 1m", c.Cleanup)
|
||||
util.PanicOnError(err)
|
||||
c.scheduler.Start()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *sftpConnectionsCache) Get(config *SFTPFsConfig, sessionID string) *sftpConnection {
|
||||
partition := 0
|
||||
key := config.getUniqueID(partition)
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
var oldKey uint64
|
||||
for {
|
||||
if val, ok := c.items[key]; ok {
|
||||
activeSessions := val.ActiveSessions()
|
||||
if activeSessions < maxSessionsPerConnection || key == oldKey {
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"reusing connection for session ID %q, key: %d, active sessions %d, active connections: %d",
|
||||
sessionID, key, activeSessions+1, len(c.items))
|
||||
val.AddSession(sessionID)
|
||||
return val
|
||||
}
|
||||
partition++
|
||||
oldKey = key
|
||||
key = config.getUniqueID(partition)
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"connection full, generated new key for partition: %d, active sessions: %d, key: %d, old key: %d",
|
||||
partition, activeSessions, oldKey, key)
|
||||
} else {
|
||||
conn := newSFTPConnection(config, sessionID)
|
||||
c.items[key] = conn
|
||||
logger.Debug(logSenderSFTPCache, "",
|
||||
"adding new connection for session ID %q, partition: %d, key: %d, active connections: %d",
|
||||
sessionID, partition, key, len(c.items))
|
||||
return conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sftpConnectionsCache) Remove(key uint64) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if conn, ok := c.items[key]; ok {
|
||||
delete(c.items, key)
|
||||
logger.Debug(logSenderSFTPCache, "", "removed connection with key %d, active connections: %d", key, len(c.items))
|
||||
|
||||
defer conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sftpConnectionsCache) Cleanup() {
|
||||
c.RLock()
|
||||
|
||||
for k, conn := range c.items {
|
||||
if val := conn.GetLastActivity(); val.Before(time.Now().Add(-30 * time.Second)) {
|
||||
logger.Debug(conn.logSender, "", "removing inactive connection, last activity %s", val)
|
||||
|
||||
defer func(key uint64) {
|
||||
c.Remove(key)
|
||||
}(k)
|
||||
}
|
||||
}
|
||||
|
||||
c.RUnlock()
|
||||
}
|
||||
|
|
|
@ -38,7 +38,17 @@ import (
|
|||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
)
|
||||
|
||||
const dirMimeType = "inode/directory"
|
||||
const (
|
||||
dirMimeType = "inode/directory"
|
||||
s3fsName = "S3Fs"
|
||||
gcsfsName = "GCSFs"
|
||||
azBlobFsName = "AzureBlobFs"
|
||||
)
|
||||
|
||||
// Additional checks for files
|
||||
const (
|
||||
CheckParentDir = 1
|
||||
)
|
||||
|
||||
var (
|
||||
validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
|
||||
|
@ -78,7 +88,7 @@ type Fs interface {
|
|||
Stat(name string) (os.FileInfo, error)
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
Open(name string, offset int64) (File, *pipeat.PipeReaderAt, func(), error)
|
||||
Create(name string, flag int) (File, *PipeWriter, func(), error)
|
||||
Create(name string, flag, checks int) (File, *PipeWriter, func(), error)
|
||||
Rename(source, target string) error
|
||||
Remove(name string, isDir bool) error
|
||||
Mkdir(name string) error
|
||||
|
|
|
@ -26,8 +26,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/drakkan/webdav"
|
||||
"github.com/eikenb/pipeat"
|
||||
"golang.org/x/net/webdav"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -84,6 +84,9 @@ type webDavFileInfo struct {
|
|||
// ContentType implements webdav.ContentTyper interface
|
||||
func (fi *webDavFileInfo) ContentType(ctx context.Context) (string, error) {
|
||||
extension := path.Ext(fi.virtualPath)
|
||||
if extension == "" || extension == ".dat" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
contentType := mime.TypeByExtension(extension)
|
||||
if contentType != "" {
|
||||
return contentType, nil
|
||||
|
@ -105,20 +108,19 @@ func (f *webDavFile) Readdir(count int) ([]os.FileInfo, error) {
|
|||
if !f.Connection.User.HasPerm(dataprovider.PermListItems, f.GetVirtualPath()) {
|
||||
return nil, f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
fileInfos, err := f.Connection.ListDir(f.GetVirtualPath())
|
||||
entries, err := f.Connection.ListDir(f.GetVirtualPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]os.FileInfo, 0, len(fileInfos))
|
||||
for _, fileInfo := range fileInfos {
|
||||
result = append(result, &webDavFileInfo{
|
||||
FileInfo: fileInfo,
|
||||
for idx, info := range entries {
|
||||
entries[idx] = &webDavFileInfo{
|
||||
FileInfo: info,
|
||||
Fs: f.Fs,
|
||||
virtualPath: path.Join(f.GetVirtualPath(), fileInfo.Name()),
|
||||
fsPath: f.Fs.Join(f.GetFsPath(), fileInfo.Name()),
|
||||
})
|
||||
virtualPath: path.Join(f.GetVirtualPath(), info.Name()),
|
||||
fsPath: f.Fs.Join(f.GetFsPath(), info.Name()),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// Stat the handle
|
||||
|
@ -131,7 +133,7 @@ func (f *webDavFile) Stat() (os.FileInfo, error) {
|
|||
f.Unlock()
|
||||
if f.GetType() == common.TransferUpload && errUpload == nil {
|
||||
info := &webDavFileInfo{
|
||||
FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, f.BytesReceived.Load(), time.Unix(0, 0), false),
|
||||
FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, f.BytesReceived.Load(), time.Now(), false),
|
||||
Fs: f.Fs,
|
||||
virtualPath: f.GetVirtualPath(),
|
||||
fsPath: f.GetFsPath(),
|
||||
|
@ -140,7 +142,7 @@ func (f *webDavFile) Stat() (os.FileInfo, error) {
|
|||
}
|
||||
info, err := f.Fs.Stat(f.GetFsPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, f.Connection.GetFsError(f.Fs, err)
|
||||
}
|
||||
if vfs.IsCryptOsFs(f.Fs) {
|
||||
info = f.Fs.(*vfs.CryptFs).ConvertFileInfo(info)
|
||||
|
@ -154,33 +156,38 @@ func (f *webDavFile) Stat() (os.FileInfo, error) {
|
|||
return fi, nil
|
||||
}
|
||||
|
||||
func (f *webDavFile) checkFirstRead() error {
|
||||
if !f.Connection.User.HasPerm(dataprovider.PermDownload, path.Dir(f.GetVirtualPath())) {
|
||||
return f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
transferQuota := f.BaseTransfer.GetTransferQuota()
|
||||
if !transferQuota.HasDownloadSpace() {
|
||||
f.Connection.Log(logger.LevelInfo, "denying file read due to quota limits")
|
||||
return f.Connection.GetReadQuotaExceededError()
|
||||
}
|
||||
if ok, policy := f.Connection.User.IsFileAllowed(f.GetVirtualPath()); !ok {
|
||||
f.Connection.Log(logger.LevelWarn, "reading file %#v is not allowed", f.GetVirtualPath())
|
||||
return f.Connection.GetErrorForDeniedFile(policy)
|
||||
}
|
||||
err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
|
||||
if err != nil {
|
||||
f.Connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", f.GetVirtualPath(), err)
|
||||
return f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
f.readTryed.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads the contents to downloads.
|
||||
func (f *webDavFile) Read(p []byte) (n int, err error) {
|
||||
if f.AbortTransfer.Load() {
|
||||
return 0, errTransferAborted
|
||||
}
|
||||
if !f.readTryed.Load() {
|
||||
if !f.Connection.User.HasPerm(dataprovider.PermDownload, path.Dir(f.GetVirtualPath())) {
|
||||
return 0, f.Connection.GetPermissionDeniedError()
|
||||
if err := f.checkFirstRead(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
transferQuota := f.BaseTransfer.GetTransferQuota()
|
||||
if !transferQuota.HasDownloadSpace() {
|
||||
f.Connection.Log(logger.LevelInfo, "denying file read due to quota limits")
|
||||
return 0, f.Connection.GetReadQuotaExceededError()
|
||||
}
|
||||
|
||||
if ok, policy := f.Connection.User.IsFileAllowed(f.GetVirtualPath()); !ok {
|
||||
f.Connection.Log(logger.LevelWarn, "reading file %#v is not allowed", f.GetVirtualPath())
|
||||
return 0, f.Connection.GetErrorForDeniedFile(policy)
|
||||
}
|
||||
err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
|
||||
if err != nil {
|
||||
f.Connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", f.GetVirtualPath(), err)
|
||||
return 0, f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
f.readTryed.Store(true)
|
||||
}
|
||||
|
||||
f.Connection.UpdateLastActivity()
|
||||
|
||||
// the file is read sequentially we don't need to check for concurrent reads and so
|
||||
|
@ -190,17 +197,23 @@ func (f *webDavFile) Read(p []byte) (n int, err error) {
|
|||
f.TransferError(common.ErrOpUnsupported)
|
||||
return 0, common.ErrOpUnsupported
|
||||
}
|
||||
_, r, cancelFn, e := f.Fs.Open(f.GetFsPath(), 0)
|
||||
file, r, cancelFn, e := f.Fs.Open(f.GetFsPath(), 0)
|
||||
f.Lock()
|
||||
if e == nil {
|
||||
f.reader = r
|
||||
if file != nil {
|
||||
f.File = file
|
||||
f.writer = f.File
|
||||
f.reader = f.File
|
||||
} else if r != nil {
|
||||
f.reader = r
|
||||
}
|
||||
f.BaseTransfer.SetCancelFn(cancelFn)
|
||||
}
|
||||
f.ErrTransfer = e
|
||||
f.startOffset = 0
|
||||
f.Unlock()
|
||||
if e != nil {
|
||||
return 0, e
|
||||
return 0, f.Connection.GetFsError(f.Fs, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,6 +224,7 @@ func (f *webDavFile) Read(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil && err != io.EOF {
|
||||
f.TransferError(err)
|
||||
err = f.ConvertError(err)
|
||||
return
|
||||
}
|
||||
f.HandleThrottle()
|
||||
|
@ -233,6 +247,7 @@ func (f *webDavFile) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
if err != nil {
|
||||
f.TransferError(err)
|
||||
err = f.ConvertError(err)
|
||||
return
|
||||
}
|
||||
f.HandleThrottle()
|
||||
|
@ -263,18 +278,41 @@ func (f *webDavFile) updateTransferQuotaOnSeek() {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *webDavFile) checkFile() error {
|
||||
if f.File == nil && vfs.IsLocalOrUnbufferedSFTPFs(f.Fs) {
|
||||
file, _, _, err := f.Fs.Open(f.GetFsPath(), 0)
|
||||
if err != nil {
|
||||
f.Connection.Log(logger.LevelWarn, "could not open file %q for seeking: %v",
|
||||
f.GetFsPath(), err)
|
||||
f.TransferError(err)
|
||||
return err
|
||||
}
|
||||
f.File = file
|
||||
f.reader = file
|
||||
f.writer = file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *webDavFile) seekFile(offset int64, whence int) (int64, error) {
|
||||
ret, err := f.File.Seek(offset, whence)
|
||||
if err != nil {
|
||||
f.TransferError(err)
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Seek sets the offset for the next Read or Write on the writer to offset,
|
||||
// interpreted according to whence: 0 means relative to the origin of the file,
|
||||
// 1 means relative to the current offset, and 2 means relative to the end.
|
||||
// It returns the new offset and an error, if any.
|
||||
func (f *webDavFile) Seek(offset int64, whence int) (int64, error) {
|
||||
f.Connection.UpdateLastActivity()
|
||||
if err := f.checkFile(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if f.File != nil {
|
||||
ret, err := f.File.Seek(offset, whence)
|
||||
if err != nil {
|
||||
f.TransferError(err)
|
||||
}
|
||||
return ret, err
|
||||
return f.seekFile(offset, whence)
|
||||
}
|
||||
if f.GetType() == common.TransferDownload {
|
||||
readOffset := f.startOffset + f.BytesSent.Load()
|
||||
|
|
|
@ -21,8 +21,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"golang.org/x/net/webdav"
|
||||
"github.com/drakkan/webdav"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -134,25 +133,13 @@ func (c *Connection) OpenFile(ctx context.Context, name string, flag int, perm o
|
|||
}
|
||||
|
||||
func (c *Connection) getFile(fs vfs.Fs, fsPath, virtualPath string) (webdav.File, error) {
|
||||
var err error
|
||||
var file vfs.File
|
||||
var r *pipeat.PipeReaderAt
|
||||
var cancelFn func()
|
||||
|
||||
// for cloud fs we open the file when we receive the first read to avoid to download the first part of
|
||||
// the file if it was opened only to do a stat or a readdir and so it is not a real download
|
||||
if vfs.IsLocalOrUnbufferedSFTPFs(fs) {
|
||||
file, r, cancelFn, err = fs.Open(fsPath, 0)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "could not open file %#v for reading: %+v", fsPath, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
}
|
||||
}
|
||||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, fsPath, fsPath, virtualPath,
|
||||
// we open the file when we receive the first read so we only open the file if necessary
|
||||
baseTransfer := common.NewBaseTransfer(nil, c.BaseConnection, cancelFn, fsPath, fsPath, virtualPath,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, c.GetTransferQuota())
|
||||
|
||||
return newWebDavFile(baseTransfer, nil, r), nil
|
||||
return newWebDavFile(baseTransfer, nil, nil), nil
|
||||
}
|
||||
|
||||
func (c *Connection) putFile(fs vfs.Fs, fsPath, virtualPath string) (webdav.File, error) {
|
||||
|
@ -202,7 +189,7 @@ func (c *Connection) handleUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, re
|
|||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, c.GetCreateChecks(requestPath, true))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error creating file %#v: %+v", resolvedPath, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
@ -247,7 +234,7 @@ func (c *Connection) handleUploadToExistingFile(fs vfs.Fs, resolvedPath, filePat
|
|||
}
|
||||
}
|
||||
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
|
||||
file, w, cancelFn, err := fs.Create(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, c.GetCreateChecks(requestPath, false))
|
||||
if err != nil {
|
||||
c.Log(logger.LevelError, "error creating file %#v: %+v", resolvedPath, err)
|
||||
return nil, c.GetFsError(fs, err)
|
||||
|
|
|
@ -30,10 +30,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/drakkan/webdav"
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/sftpgo/sdk"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/webdav"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -69,205 +70,205 @@ CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
|
|||
-----END EC PRIVATE KEY-----`
|
||||
caCRT = `-----BEGIN CERTIFICATE-----
|
||||
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
|
||||
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
|
||||
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
|
||||
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
|
||||
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
|
||||
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
|
||||
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
|
||||
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
|
||||
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
|
||||
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
|
||||
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
|
||||
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
|
||||
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
|
||||
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
|
||||
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
|
||||
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
|
||||
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
|
||||
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
|
||||
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
|
||||
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
|
||||
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
|
||||
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
|
||||
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
|
||||
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
|
||||
r3rwjFsQOoZotA==
|
||||
QXV0aDAeFw0yMzAxMDMxMDIwNDdaFw0zMzAxMDMxMDMwNDZaMBMxETAPBgNVBAMT
|
||||
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxq6Wl1Ih
|
||||
hgvGdM8M2IVI7dwnv3yShJygZsnREQSEW0xeWJL5DtNeHCME5WByFUAlZKpePtW8
|
||||
TNwln9DYDtgNSMiWwvO/wR0mXsyU8Ma4ZBMlX0oOkWo1Ff/M/u8YY9X78Vvwdt62
|
||||
Yt7QmU5oUUW2HdAgh4AlhKJSjm3t0uDP5s54uvueL5bjChHwEb1ZGOtST9Zt86cj
|
||||
YA/xtVHnDXCJbhohpzQI6dK96NegONZVDaxEohVCyYYOgI1I14Bxu0ZCMm5GjwoO
|
||||
QohnUfEJ+BRgZqFpbsnYCE+PoVayVVFoLA+GMeqbQ2SHej1Pr1K0dbjUz6SAk8/+
|
||||
DL7h8d+YAtflATsMtdsVJ4WzEfvZbSbiYKYmlVC6zk6ooXWadvQ5+aezVes9WMpH
|
||||
YnAoScuKoeerRuKlsSU7u+XCmy/i7Hii5FwMrSvIL2GLtVE+tJFCTABA55OWZikt
|
||||
ULMQfg3P2Hk3GFIE35M10mSjKQkGhz06WC5UQ7f2Xl9GzO6PqRSorzugewgMK6L4
|
||||
SnN7XBFnUHHqx1bWNkUG8NPYB6Zs7UDHygemTWxqqxun43s501DNTSunCKIhwFbt
|
||||
1ol5gOvYAFG+BXxnggBT815Mgz1Zht3S9CuprAgz0grNEwAYjRTm1PSaX3t8I1kv
|
||||
oUUuMF6BzWLHJ66uZKOCsPs3ouGq+G3GfWUCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQCu46fF0Tr2tZz1wkYt2Ty3OU77jcG9
|
||||
zYU/7uxPqPC8OYIzQJrumXKLOkTYJXJ7k+7RQdsn/nbxdH1PbslNDD3Eid/sZsF/
|
||||
dFdGR1ZYwXVQbpYwEd19CvJTALn9CyAZpMS8J2RJrmdScAeSSb0+nAGTYP7GvPm+
|
||||
8ktOnrz3w8FtzTw+seuCW/DI/5UpfC9Jf+i/3XgxDozXWNW6YNOIw/CicyaqbBTk
|
||||
5WFcJ0WJN+8qQurw8n+sOvQcNsuDTO7K3Tqu0wGTDUQKou7kiMX0UISRvd8roNOl
|
||||
zvvokNQe4VgCGQA+Y2SxvSxVG1BaymYeNw/0Yxm7QiKSUI400V1iKIcpnIvIedJR
|
||||
j2bGIlslVSV/P6zkRuF1srRVxTxSf1imEfs8J8mMhHB6DkOsP4Y93z5s6JZ0sPiM
|
||||
eOb0CVKul/e1R0Kq23AdPf5eUv63RhfmokN1OsdarRKMFyHphWMxqGJXsSvRP+dl
|
||||
3DaKeTDx/91OSWiMc+glHHKKJveMYQLeJ7GXmcxhuoBm6o4Coowgw8NFKMCtAsp0
|
||||
ktvsQuhB3uFUterw/2ONsOChx7Ybu36Zk47TKBpktfxDQ578TVoZ7xWSAFqCPHvx
|
||||
A5VSwAg7tdBvORfqQjhiJRnhwr50RaNQABTLS0l5Vsn2mitApPs7iKiIts2ieWsU
|
||||
EsdgvPZR2e5IkA==
|
||||
-----END CERTIFICATE-----`
|
||||
caKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caCRL = `-----BEGIN X509 CRL-----
|
||||
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
|
||||
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
|
||||
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
|
||||
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
|
||||
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
|
||||
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
|
||||
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
|
||||
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
|
||||
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
|
||||
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
|
||||
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
|
||||
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
|
||||
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
|
||||
QbDK+MzhmbKfDxs=
|
||||
MIICpjCBjwIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||
MjMwMTAzMTAzMzI4WhcNMjUwMTAyMTAzMzI4WjAjMCECEHUfHtKUGlg/86yMN/aM
|
||||
xxsXDTIzMDEwMzEwMzMyOFqgIzAhMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feo
|
||||
t1eIHWmIMA0GCSqGSIb3DQEBCwUAA4ICAQAJf6MBMUc3xWKB6fy0VoPbXQjVTsL4
|
||||
Yjm5lKaCtvcRiJ6onaITfJL6V3OCy/MAe94sHynvK3DyyYvxJ0ms7y+kmEtFzHwz
|
||||
T+hBPHaEV/Ccamt+3zRZwndwEMomkQz5tBipwimOlsYXWqItjhXHcLLr84jWgqpD
|
||||
JHcfDmLswCeJVqe8xyYSYCnWMjQ3sn0h+arjm53SdHTULlsjgKeX/ao2IJwt1Ddr
|
||||
APYKZ/XBWq9vBq3l4l2Ufj16fUBY5NeHTjQcLLrkwmBwpSb0YS8+jBwmOwo1HwEF
|
||||
MEwADBTHI2jT4ygzzKefVETfcSk4CuIQ1ve0qQL7KY5Fg5AXwbRycev6R0vEHR82
|
||||
oOPAqg+dYgKtdkxK5QZrNLenloq6x0/3oEThwOg3J17+eCYjixBC+3PoUzLa+yfZ
|
||||
xSQ/kkcRJExEhadw5I9TI7sEUk1RjDCl6AtHg53LQifokiLLfMRptOiN3a4NlLJ2
|
||||
HNXfWUltRUnr6MCxk+G7U5Zaj1QtCN3Aldw+3xcJr7FOBU23VqRT22XbfW+B1gsr
|
||||
4eNlw5Kk/PDF/WZeGbrwy7fvpSoFsDYI8lpVlzKVwLampIZVhnWsfaf7jk/pc4T0
|
||||
6iZ+rnB6CP4P+LM34jKYiAtz+iufjEB6Ko0jN0ZWCznDGDVgMwnVynGJNKU+4bl8
|
||||
vC4nIbS2OhclcA==
|
||||
-----END X509 CRL-----`
|
||||
client1Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
|
||||
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
|
||||
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
|
||||
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
|
||||
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
|
||||
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
|
||||
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
|
||||
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
|
||||
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
|
||||
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
|
||||
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
|
||||
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
|
||||
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
|
||||
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
|
||||
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
|
||||
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
|
||||
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
|
||||
O/e3EH8=
|
||||
MIIEIDCCAgigAwIBAgIQWwKNgBzKWM8ewyS4K78uOTANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTFaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC+jwuaG0FSpPtE5mZPBgdacAhXXa51/TIT18HTm+QnOYUcGRel3AuZ
|
||||
OBWv3fOallW8iQX3i+M78cKeTMWOS5RGXCdDe866pYXEyUkZFRRSA/6573Dz5dJ/
|
||||
DZOCsgW+91JlSkM1+FYE9cpt4qLkdAjSRXIoebcA64K60wqZr1Js+pQrH3leT9wu
|
||||
33dM3KHkDHOeMj6X/V1me22htndD/DUlWmPc58jMFbcvxFG3oUBB9U65LJBwJNzr
|
||||
XWVcli2QirZ0fLkC7Lo2FIYuN1qeU/8A/T4TTInZb/eW3Faqv4RuhjWPXFLqkdIP
|
||||
4AzDxCNuhlWqyv9nfgegXAHOHpXZMDKxAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFKloKnzI
|
||||
w7YYnjm1sKU+LgvT5dU0MB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQAeja0rK7I14ibgis9vSPXAGmmKpagIjvZiYCE6
|
||||
Ti/Rq6qbyQ6tKL08NxR2XPNjoXfxwGOGgboWR86S7WT93pz3HkftAjTfzUnxnXOx
|
||||
S7dWfq+g0uY/3ql6IFDQBpGKHu/KN8/1Pvn39FiYSdCaM66bwyFukcvBXace+aC1
|
||||
M6jzVsscxoCCjXhcZl++Tjpf6TzGMd8OFyArBQmOUCoFrTcMzLPKSAROAHp0k+Ju
|
||||
HHNgLdgXPQCfAgRbWnqq2o2moApe7+gzMS+1X0eKhIXYS7csK8rFvGzjH/ANDo9A
|
||||
8+AFcJh8YiIlEVI8nCb3ERdpAbu6G5xkfUDkqcWjCAhuokrbeFmU82RQOc3TQZc9
|
||||
NMHfTkCOPhaIdPI/kC+fZkdz+5ftDCl/radSljeMX+/y0DVQUOtrQzyT1PBN0vCx
|
||||
L+FCzj0fHJwdoDiFLxDLLN1pYWsxMnIichpg625CZM9r5i183yPErXxxQPydcDrX
|
||||
Y6Ps7rGiU7eGILhAfQnS1XUDvH0gNfLUvO5uWm6yO4yUEDWkA/wOTnrc8Z5Waza+
|
||||
kH+FmxnYpT1rMloTSoyiHIPvTq1nVJ8LILUODZAxW+ZHmccGgOpIN/DWuWunVRHG
|
||||
tuaTSgU1xjWl2q/SeoS2DpiEKTIAZZQ5CTD819oc8SnLTzK0ISRpBXKg13AF2uJD
|
||||
G9q7sA==
|
||||
-----END CERTIFICATE-----`
|
||||
client1Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
// client 2 crt is revoked
|
||||
client2Crt = `-----BEGIN CERTIFICATE-----
|
||||
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
|
||||
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
|
||||
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
|
||||
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
|
||||
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
|
||||
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
|
||||
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
|
||||
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
|
||||
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
|
||||
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
|
||||
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
|
||||
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
|
||||
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
|
||||
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
|
||||
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
|
||||
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
|
||||
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
|
||||
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
|
||||
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
|
||||
6zdDidU=
|
||||
MIIEIDCCAgigAwIBAgIQdR8e0pQaWD/zrIw39ozHGzANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwhDZXJ0QXV0aDAeFw0yMzAxMDMxMDIzMTRaFw0zMzAxMDMxMDMw
|
||||
NDVaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/UZqUxeP15lhXBmPmpS5SdI470R75fxmN14FhYwTS3FsDoT+evqRg
|
||||
II4Qo/wqbaGrk/BsbzB7ToVWqpkyZ58hYPdtLjKtBBHYsSCNCoKZEVJTz5JdW3sj
|
||||
CKRsG3zPVhFjJcYW9pKsr/CGIIDWAfkuuwR+R/NHkUFSjEP5N9qMAc9wBvskxV84
|
||||
YAJJykPD9rG8PjXHOKsfNUhH+/QfbqMkCeETJ1sp66o3ilql2aZ0m6K6x4gB7tM7
|
||||
NZnM4eztLZbAnQVQhNBYCR6i7DGI2dujujPbpCqmSqSb42n+3a2o844k6EnU76HJ
|
||||
RZwhd3ypy9CvTdkya5JbK+aKKo8fGFHbAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwID
|
||||
uDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLItEDE1
|
||||
gVYfe7JSax5YAjEW8tmzMB8GA1UdIwQYMBaAFCj8lmcR7loB9zAP/feot1eIHWmI
|
||||
MA0GCSqGSIb3DQEBCwUAA4ICAQCZRStHCbwmhmH4tu7V5ammmQhn1TKcspV86cXz
|
||||
JQ4ZM11RvGpRLTmYuRkl5XloMuvB8yYAE1ihhkYOhgU6zSAj33kUQSx6cHXWau7T
|
||||
NjTchptKX+b17GR/yuFwIR3TugArBsnwyuUdts478gTY+MSgTOWWyOgWl3FujiEJ
|
||||
GJ7EgKde4jURXv2qjp6ZtSVqMlAa3y8C3S8nLdyt9Rf8CcSjEy/t8t0JhoMYCvxg
|
||||
o1k7QhMCfMYjjEIuEyDVOdCs2ExepG1zUVBP5h5239sXvLKrOZvgCZkslyTNd/m9
|
||||
vv4yR5gLgCdt0Ol1uip0p910PJoSqX6nZNfeCx3+Kgyc7crl8PrsnUAVoPgLxpVm
|
||||
FWF+KlUbh2KiYTuSi5cH0Ti9NtWT3Qi8d4WhmjFlu0zD3EJEUie0oYfHERiO9bVo
|
||||
5EAzERSVhgQdxVOLgIc2Hbe1JYFf7idyqASRw6KdVkW6YIC/V/5efrJ1LZ5QNrdv
|
||||
bmfJ5CznE6o1AH9JsQ8xMi+kmyn/It1uMWIwP/tYyjQ98dlOj2k9CHP2RzrvCCY9
|
||||
yZNjs2QC5cleNdSpNMb2J2EUYTNAnaH3H8YdbT0scMHDvre1G7p4AjeuRJ9mW7VK
|
||||
Dcqbw+VdSAPyAFdiCd9x8AU3sr28vYbPbPp+LsHQXIlYdnVR0hh2UKF5lR8iuqVx
|
||||
y05cAQ==
|
||||
-----END CERTIFICATE-----`
|
||||
client2Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
|
@ -286,7 +287,10 @@ func (fs *MockOsFs) Name() string {
|
|||
|
||||
// Open returns nil
|
||||
func (fs *MockOsFs) Open(name string, offset int64) (vfs.File, *pipeat.PipeReaderAt, func(), error) {
|
||||
return nil, fs.reader, nil, nil
|
||||
if fs.reader != nil {
|
||||
return nil, fs.reader, nil, nil
|
||||
}
|
||||
return fs.Fs.Open(name, offset)
|
||||
}
|
||||
|
||||
// IsUploadResumeSupported returns true if resuming uploads is supported
|
||||
|
@ -314,14 +318,18 @@ func (fs *MockOsFs) Rename(source, target string) error {
|
|||
|
||||
// GetMimeType returns the content type
|
||||
func (fs *MockOsFs) GetMimeType(name string) (string, error) {
|
||||
if fs.err != nil {
|
||||
return "", fs.err
|
||||
}
|
||||
return "application/custom-mime", nil
|
||||
}
|
||||
|
||||
func newMockOsFs(atomicUpload bool, connectionID, rootDir string, reader *pipeat.PipeReaderAt) vfs.Fs {
|
||||
func newMockOsFs(atomicUpload bool, connectionID, rootDir string, reader *pipeat.PipeReaderAt, err error) vfs.Fs {
|
||||
return &MockOsFs{
|
||||
Fs: vfs.NewOsFs(connectionID, rootDir, ""),
|
||||
isAtomicUploadSupported: atomicUpload,
|
||||
reader: reader,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,38 +560,31 @@ func TestFileAccessErrors(t *testing.T) {
|
|||
missingPath := "missing path"
|
||||
fsMissingPath := filepath.Join(user.HomeDir, missingPath)
|
||||
err := connection.RemoveAll(ctx, missingPath)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||
}
|
||||
_, err = connection.getFile(fs, fsMissingPath, missingPath)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||
}
|
||||
_, err = connection.getFile(fs, fsMissingPath, missingPath)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||
}
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
davFile, err := connection.getFile(fs, fsMissingPath, missingPath)
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 64)
|
||||
_, err = davFile.Read(buf)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
err = davFile.Close()
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
p := filepath.Join(user.HomeDir, "adir", missingPath)
|
||||
_, err = connection.handleUploadToNewFile(fs, p, p, path.Join("adir", missingPath))
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||
}
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
_, err = connection.handleUploadToExistingFile(fs, p, "_"+p, 0, path.Join("adir", missingPath))
|
||||
if assert.Error(t, err) {
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
fs = newMockOsFs(false, fs.ConnectionID(), user.HomeDir, nil)
|
||||
fs = newMockOsFs(false, fs.ConnectionID(), user.HomeDir, nil, nil)
|
||||
_, err = connection.handleUploadToExistingFile(fs, p, p, 0, path.Join("adir", missingPath))
|
||||
if assert.Error(t, err) {
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
}
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
|
||||
f, err := os.CreateTemp("", "temp")
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
davFile, err := connection.handleUploadToExistingFile(fs, f.Name(), f.Name(), 123, f.Name())
|
||||
davFile, err = connection.handleUploadToExistingFile(fs, f.Name(), f.Name(), 123, f.Name())
|
||||
if assert.NoError(t, err) {
|
||||
transfer := davFile.(*webDavFile)
|
||||
transfers := connection.GetTransfers()
|
||||
|
@ -636,6 +637,31 @@ func TestFileAccessErrors(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCheckRequestMethodWithPrefix(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
Permissions: map[string][]string{
|
||||
"/": {dataprovider.PermAny},
|
||||
},
|
||||
},
|
||||
}
|
||||
fs := vfs.NewOsFs("connID", user.HomeDir, "")
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user),
|
||||
}
|
||||
server := webDavServer{
|
||||
binding: Binding{
|
||||
Prefix: "/dav",
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, "/../dav", nil)
|
||||
require.NoError(t, err)
|
||||
server.checkRequestMethod(context.Background(), req, connection)
|
||||
require.Equal(t, "PROPFIND", req.Method)
|
||||
require.Equal(t, "1", req.Header.Get("Depth"))
|
||||
}
|
||||
|
||||
func TestContentType(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
|
@ -650,9 +676,9 @@ func TestContentType(t *testing.T) {
|
|||
}
|
||||
testFilePath := filepath.Join(user.HomeDir, testFile)
|
||||
ctx := context.Background()
|
||||
baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
|
||||
baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".unknown",
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
fs = newMockOsFs(false, fs.ConnectionID(), user.GetHomeDir(), nil)
|
||||
fs = newMockOsFs(false, fs.ConnectionID(), user.GetHomeDir(), nil, nil)
|
||||
err := os.WriteFile(testFilePath, []byte(""), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
davFile := newWebDavFile(baseTransfer, nil, nil)
|
||||
|
@ -668,6 +694,8 @@ func TestContentType(t *testing.T) {
|
|||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".unknown1",
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "")
|
||||
fi, err = davFile.Stat()
|
||||
|
@ -679,9 +707,53 @@ func TestContentType(t *testing.T) {
|
|||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
fi.(*webDavFileInfo).fsPath = "missing"
|
||||
_, err = fi.(*webDavFileInfo).ContentType(ctx)
|
||||
assert.EqualError(t, err, webdav.ErrNotImplemented.Error())
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "")
|
||||
fi, err = davFile.Stat()
|
||||
if assert.NoError(t, err) {
|
||||
ctype, err := fi.(*webDavFileInfo).ContentType(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "application/octet-stream", ctype)
|
||||
}
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
// the second time the cache will be used
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".custom",
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = vfs.NewOsFs("id", user.HomeDir, "")
|
||||
fi, err = davFile.Stat()
|
||||
if assert.NoError(t, err) {
|
||||
ctype, err := fi.(*webDavFileInfo).ContentType(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "text/plain; charset=utf-8", ctype)
|
||||
}
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile+".unknown2",
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{})
|
||||
fs = newMockOsFs(false, fs.ConnectionID(), user.GetHomeDir(), nil, os.ErrInvalid)
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = fs
|
||||
fi, err = davFile.Stat()
|
||||
if assert.NoError(t, err) {
|
||||
ctype, err := fi.(*webDavFileInfo).ContentType(ctx)
|
||||
assert.EqualError(t, err, webdav.ErrNotImplemented.Error(), "unexpected content type %q", ctype)
|
||||
}
|
||||
cache := mimeCache{
|
||||
maxSize: 10,
|
||||
mimeTypes: map[string]string{},
|
||||
}
|
||||
cache.addMimeToCache("", "")
|
||||
cache.RLock()
|
||||
assert.Len(t, cache.mimeTypes, 0)
|
||||
cache.RUnlock()
|
||||
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
@ -750,7 +822,7 @@ func TestTransferReadWriteErrors(t *testing.T) {
|
|||
|
||||
r, w, err = pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
mockFs := newMockOsFs(false, fs.ConnectionID(), user.HomeDir, r)
|
||||
mockFs := newMockOsFs(false, fs.ConnectionID(), user.HomeDir, r, nil)
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, mockFs, dataprovider.TransferQuota{})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
|
@ -790,7 +862,7 @@ func TestTransferSeek(t *testing.T) {
|
|||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
fs := vfs.NewOsFs("connID", user.HomeDir, "")
|
||||
fs := newMockOsFs(true, "connID", user.HomeDir, nil, nil)
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, "", "", user),
|
||||
}
|
||||
|
@ -831,6 +903,8 @@ func TestTransferSeek(t *testing.T) {
|
|||
res, err := davFile.Seek(0, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), res)
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
davFile.Connection.RemoveTransfer(davFile.BaseTransfer)
|
||||
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
|
@ -838,7 +912,9 @@ func TestTransferSeek(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(len(testFileContents)), res)
|
||||
err = davFile.updateStatInfo()
|
||||
assert.Nil(t, err)
|
||||
assert.NoError(t, err)
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath+"1", testFilePath+"1", testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
|
||||
|
@ -847,26 +923,42 @@ func TestTransferSeek(t *testing.T) {
|
|||
assert.True(t, fs.IsNotExist(err))
|
||||
davFile.Connection.RemoveTransfer(davFile.BaseTransfer)
|
||||
|
||||
fs = vfs.NewOsFs(fs.ConnectionID(), user.GetHomeDir(), "")
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath+"1", testFilePath+"1", testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
_, err = davFile.Seek(0, io.SeekEnd)
|
||||
assert.True(t, fs.IsNotExist(err))
|
||||
davFile.Connection.RemoveTransfer(davFile.BaseTransfer)
|
||||
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFilePath, testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.reader = f
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
|
||||
r, _, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), r, nil)
|
||||
res, err = davFile.Seek(2, io.SeekStart)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), res)
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
r, _, err = pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), r, nil)
|
||||
res, err = davFile.Seek(2, io.SeekEnd)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(5), res)
|
||||
err = davFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath+"1", testFilePath+"1", testFile,
|
||||
common.TransferDownload, 0, 0, 0, 0, false, fs, dataprovider.TransferQuota{AllowedTotalSize: 100})
|
||||
|
||||
davFile = newWebDavFile(baseTransfer, nil, nil)
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil)
|
||||
davFile.Fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir(), nil, nil)
|
||||
res, err = davFile.Seek(2, io.SeekEnd)
|
||||
assert.True(t, fs.IsNotExist(err))
|
||||
assert.Equal(t, int64(0), res)
|
||||
|
|
|
@ -26,12 +26,13 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drakkan/webdav"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/xid"
|
||||
"golang.org/x/net/webdav"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/common"
|
||||
"github.com/drakkan/sftpgo/v2/internal/dataprovider"
|
||||
|
@ -137,6 +138,9 @@ func (s *webDavServer) checkRequestMethod(ctx context.Context, r *http.Request,
|
|||
// see RFC4918, section 9.4
|
||||
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
||||
p := path.Clean(r.URL.Path)
|
||||
if s.binding.Prefix != "" {
|
||||
p = strings.TrimPrefix(p, s.binding.Prefix)
|
||||
}
|
||||
info, err := connection.Stat(ctx, p)
|
||||
if err == nil && info.IsDir() {
|
||||
if r.Method == http.MethodHead {
|
||||
|
@ -188,7 +192,6 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
user, isCached, lockSystem, loginMethod, err := s.authenticate(r, ipAddr)
|
||||
if err != nil {
|
||||
updateLoginMetrics(&user, ipAddr, loginMethod, err)
|
||||
if !s.binding.DisableWWWAuthHeader {
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"")
|
||||
}
|
||||
|
|
|
@ -620,7 +620,8 @@ func TestBasicHandling(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
status := webdavd.GetStatus()
|
||||
assert.True(t, status.IsActive)
|
||||
}
|
||||
|
@ -701,7 +702,8 @@ func TestBasicHandlingCryptFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestLoginEmptyPassword(t *testing.T) {
|
||||
|
@ -922,12 +924,13 @@ func TestPropPatch(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestLoginInvalidPwd(t *testing.T) {
|
||||
|
@ -992,6 +995,7 @@ func TestDefender(t *testing.T) {
|
|||
cfg.DefenderConfig.Enabled = true
|
||||
cfg.DefenderConfig.Threshold = 3
|
||||
cfg.DefenderConfig.ScoreLimitExceeded = 2
|
||||
cfg.DefenderConfig.ScoreValid = 1
|
||||
|
||||
err := common.Initialize(cfg, 0)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1001,8 +1005,18 @@ func TestDefender(t *testing.T) {
|
|||
client := getWebDavClient(user, true, nil)
|
||||
assert.NoError(t, checkBasicFunc(client))
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
user.Password = "wrong_pwd"
|
||||
user.Password = "wrong_pwd"
|
||||
client = getWebDavClient(user, false, nil)
|
||||
assert.Error(t, checkBasicFunc(client))
|
||||
hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hosts, 1) {
|
||||
host := hosts[0]
|
||||
assert.Empty(t, host.GetBanTime())
|
||||
assert.Equal(t, 1, host.Score)
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
client = getWebDavClient(user, false, nil)
|
||||
assert.Error(t, checkBasicFunc(client))
|
||||
}
|
||||
|
@ -1308,7 +1322,8 @@ func TestPreDownloadHook(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
|
||||
common.Config.Actions.ExecuteOn = []string{common.OperationPreDownload}
|
||||
common.Config.Actions.Hook = preDownloadPath
|
||||
|
@ -1357,7 +1372,8 @@ func TestPreUploadHook(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
|
||||
common.Config.Actions.ExecuteOn = oldExecuteOn
|
||||
common.Config.Actions.Hook = oldHook
|
||||
|
@ -1419,7 +1435,8 @@ func TestMaxConnections(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
|
||||
common.Config.MaxTotalConnections = oldValue
|
||||
}
|
||||
|
@ -1450,7 +1467,8 @@ func TestMaxPerHostConnections(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
|
||||
common.Config.MaxPerHostConnections = oldValue
|
||||
}
|
||||
|
@ -1475,7 +1493,8 @@ func TestMaxSessions(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestLoginWithIPilters(t *testing.T) {
|
||||
|
@ -1898,6 +1917,11 @@ func TestClientClose(t *testing.T) {
|
|||
common.Connections.Close(stat.ConnectionID)
|
||||
}
|
||||
wg.Wait()
|
||||
// for the sftp user a stat is done after the failed upload and
|
||||
// this triggers a new connection
|
||||
for _, stat := range common.Connections.GetStats() {
|
||||
common.Connections.Close(stat.ConnectionID)
|
||||
}
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
|
||||
|
@ -2104,6 +2128,19 @@ func TestBytesRangeRequests(t *testing.T) {
|
|||
assert.Equal(t, "file", string(bodyBytes))
|
||||
}
|
||||
}
|
||||
// seek on a missing file
|
||||
remotePath = fmt.Sprintf("http://%v/%v", webDavServerAddr, testFileName+"_missing")
|
||||
req, err = http.NewRequest(http.MethodGet, remotePath, nil)
|
||||
if assert.NoError(t, err) {
|
||||
httpClient := httpclient.GetHTTPClient()
|
||||
req.SetBasicAuth(user.Username, defaultPassword)
|
||||
req.Header.Set("Range", "bytes=5-")
|
||||
resp, err := httpClient.Do(req)
|
||||
if assert.NoError(t, err) {
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
@ -2326,28 +2363,24 @@ func TestOsErrors(t *testing.T) {
|
|||
info, err := client.Stat(vdir)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, info.IsDir())
|
||||
// now remove the folder mapped to vdir. It should not appear in directory listing
|
||||
// now remove the folder mapped to vdir. It still appear in directory listing
|
||||
// virtual folders are automatically added
|
||||
err = os.RemoveAll(mappedPath)
|
||||
assert.NoError(t, err)
|
||||
files, err = client.ReadDir(".")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, files, 0)
|
||||
assert.Len(t, files, 1)
|
||||
err = createTestFile(filepath.Join(user.GetHomeDir(), testFileName), 32768)
|
||||
assert.NoError(t, err)
|
||||
files, err = client.ReadDir(".")
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, files, 1) {
|
||||
assert.Equal(t, testFileName, files[0].Name())
|
||||
}
|
||||
if runtime.GOOS != osWindows {
|
||||
// if the file cannot be accessed it should not appear in directory listing
|
||||
err = os.Chmod(filepath.Join(user.GetHomeDir(), testFileName), 0001)
|
||||
assert.NoError(t, err)
|
||||
files, err = client.ReadDir(".")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, files, 0)
|
||||
err = os.Chmod(filepath.Join(user.GetHomeDir(), testFileName), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, files, 2) {
|
||||
var names []string
|
||||
for _, info := range files {
|
||||
names = append(names, info.Name())
|
||||
}
|
||||
assert.Contains(t, names, testFileName)
|
||||
assert.Contains(t, names, "vdir")
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
|
@ -2803,9 +2836,18 @@ func TestSFTPLoopVirtualFolders(t *testing.T) {
|
|||
|
||||
contents, err := client.ReadDir("/")
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, contents, 1) {
|
||||
assert.Equal(t, testDir, contents[0].Name())
|
||||
assert.True(t, contents[0].IsDir())
|
||||
if assert.Len(t, contents, 2) {
|
||||
expected := 0
|
||||
for _, info := range contents {
|
||||
switch info.Name() {
|
||||
case testDir, "vdir":
|
||||
assert.True(t, info.IsDir())
|
||||
expected++
|
||||
default:
|
||||
t.Errorf("unexpected file/dir %q", info.Name())
|
||||
}
|
||||
}
|
||||
assert.Equal(t, expected, 2)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user1, http.StatusOK)
|
||||
|
@ -2922,7 +2964,8 @@ func TestNestedVirtualFolders(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(localUser.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
assert.Eventually(t, func() bool { return len(common.Connections.GetStats()) == 0 },
|
||||
1*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func checkBasicFunc(client *gowebdav.Client) error {
|
||||
|
|
|
@ -33,7 +33,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FileInfo'
|
||||
401:
|
||||
|
@ -343,7 +343,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
|
@ -377,7 +377,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -417,7 +417,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -452,7 +452,7 @@ paths:
|
|||
200:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StatVFS'
|
||||
401:
|
||||
|
@ -472,61 +472,61 @@ components:
|
|||
OKResponse:
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
BadRequest:
|
||||
description: Bad Request
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Unauthorized:
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Forbidden:
|
||||
description: Forbidden
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
NotFound:
|
||||
description: Not Found
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
NotImplemented:
|
||||
description: Not Implemented
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
Conflict:
|
||||
description: Conflict
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
RequestEntityTooLarge:
|
||||
description: Request Entity Too Large, max allowed size exceeded
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
InternalServerError:
|
||||
description: Internal Server Error
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
DefaultResponse:
|
||||
description: Unexpected Error
|
||||
content:
|
||||
application/json:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
schemas:
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue