Compare commits

...

46 commits
main ... 2.3.x

Author SHA1 Message Date
Nicola Murino
66c14bebd8
set version to 2.3.6
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-12 17:41:17 +02:00
Nicola Murino
b4ef24e23d
FTPD: fix APPE to new files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-12 11:45:29 +02:00
Nicola Murino
e617dc9c0a
azblob: use UUIDs as block IDs
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-10-07 07:38:12 +02:00
Nicola Murino
80fb56bc48
WebClient: validate PDF files before rendering
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-23 16:53:15 +02:00
Nicola Murino
b65fc0bdc2
ftpd: return relative paths for NLST reponses
Fixes #993

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-17 16:47:29 +02:00
Nicola Murino
cb98f8fd6d
set version to 2.3.5
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-16 19:28:17 +02:00
Nicola Murino
cbef217cfa
WebClient: improve HTML escaping
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-12 20:09:14 +02:00
Nicola Murino
4a34ae6662
WebClient: properly escape files/directories names
Fixes #981

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-12 12:14:40 +02:00
Nicola Murino
3ff7acc1b4
CI: add commit info in vendored sources
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 17:33:42 +02:00
Nicola Murino
fc648454df
CI: use Docker to build x86_64 Linux packages
therefore Linux packages are compiled with Docker for all supported
architectures

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 13:41:00 +02:00
Nicola Murino
836b36b816
WebClient/HTTP API: ensure to check home dir, when needed, in multi-node setups
Behind a load balancer with no sticky sessions enabled is not enough to check
the home dir only when the client logs in

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-08 11:55:08 +02:00
Nicola Murino
2d19817431
CI: improve workflows
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-03 19:02:33 +02:00
Nicola Murino
df84c42e7e
set version to 2.3.4
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-09-01 12:28:13 +02:00
Nicola Murino
c304143eb3
MFA: allow recovery codes only if two-factor auth is enabled
Fixes #965

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-31 10:07:45 +02:00
Nicola Murino
d2acc6f5c1
FTP: always generate a defender event if the client does not authenticate
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-30 17:50:12 +02:00
Nicola Murino
531ed852f5
ftpd: prefix MLST entries with a space
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-21 19:28:22 +02:00
Nicola Murino
303a723b04
backport from main
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-17 22:23:42 +02:00
Nicola Murino
88bfdb9910
OIDC: allow to get the role field from a sub-struct
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-11 13:02:06 +02:00
Nicola Murino
d3a523ba13
docker: add a variant with official plugins included
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-11 12:57:55 +02:00
Nicola Murino
665016ed1e
set version to 2.3.3
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-05 09:47:00 +02:00
Nicola Murino
97d5680d1e
azblob: fix SAS URL with embedded container name
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-01 21:52:32 +02:00
Nicola Murino
e7866047aa
allow to edit profile to users logged in via OIDC
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-08-01 21:49:53 +02:00
Nicola Murino
5f313cc6be
macOS: add config file search path
this way the default config file is used in brew package if no config file is
set

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-29 17:55:01 +02:00
Nicola Murino
3c2c703408
user templates: apply placeholders also for start directory
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-27 19:09:54 +02:00
Nicola Murino
78a399eed4
download as zip: improve filename
include username and also filename/directory name if the user downloads
a single file/directory

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-26 17:53:04 +02:00
Nicola Murino
e6d434654d
backport from main
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-24 08:56:31 +02:00
Nicola Murino
d34446e6e9
web client: add HTML5 player
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-23 16:30:27 +02:00
Nicola Murino
2da19ef233
backport OIDC related changes from main
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-23 15:31:57 +02:00
Nicola Murino
b34bc2b818
add license header to source files
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-18 13:43:25 +02:00
Nicola Murino
378995147b
try to better highlight donations and sponsorships options ...
... and to better explain why they are required.

Please don't say "someone else will help the project, I'll just use it"

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-16 20:29:10 +02:00
Nicola Murino
6b995db864
oidc: allow to configure oauth2 scopes
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-16 19:25:04 +02:00
Nicola Murino
371012a46e
backport some fixes from main
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-07-15 20:09:06 +02:00
Nicola Murino
d3d788c8d0
s3: improve rename performance
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-30 18:25:40 +02:00
maximethebault
756b122ab8
S3: Fix timeout error when renaming large files (#899)
Remove AWS SDK Transport ResponseHeaderTimeout (finer-grained timeout are already handled by the callers)
Lower the threshold for MultipartCopy (5GB -> 500MB) to improve copy performance and reduce chance of hitting Single part copy timeout

Fixes #898

Signed-off-by: Maxime Thébault <contact@maximethebault.me>
2022-06-30 10:25:04 +02:00
Nicola Murino
e244ba37b2
config: fix replace from env vars for some sub list
ensure to merge configuration from files with configuration from env

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-28 19:17:16 +02:00
Nicola Murino
5610b98d19
fix get branding from env
Fixes #895

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-28 10:46:25 +02:00
Nicola Murino
b3ca20b5e6
dataprovider: fix sql tables prefix handling
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-24 12:26:43 +02:00
Nicola Murino
d0b6ca8d2f
backup: include folders set on groups
Fixes #885

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-21 14:13:25 +02:00
Nicola Murino
550158ff4b
fix database reset
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-13 19:40:24 +02:00
Nicola Murino
14a3803c8f
OpenAPI schema: improve compatibility with some generators
Fixes #875

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-11 19:00:04 +02:00
Nicola Murino
ca4da2f64e
set version to 2.3.1
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-10 18:42:13 +02:00
Nicola Murino
049c2b7430
mysql: groups is a reserved keyfrom since MySQL 8.0.2
add mysql to CI

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-10 17:36:26 +02:00
Nicola Murino
7fd5558400
parse IP proxy header also if listening on UNIX domain socket
Fixes #867

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 09:48:39 +02:00
Nicola Murino
b60255752f
web UIs: fix date formatting on Safari
Fixes #869

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 09:47:02 +02:00
Nicola Murino
37f79650c8
APT and YUM repo are now available
This is possible thanks to the Oregon State University's free
mirroring service

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-09 09:46:57 +02:00
Nicola Murino
8988d6542b
create branch 2.3.x
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
2022-06-06 19:01:24 +02:00
274 changed files with 7496 additions and 1272 deletions

View file

@ -2,7 +2,7 @@ name: CI
on:
push:
branches: [main]
branches: [2.3.x]
pull_request:
jobs:
@ -32,7 +32,7 @@ jobs:
- name: Build for Linux/macOS x86_64
if: startsWith(matrix.os, 'windows-') != true
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
cd tests/eventsearcher
go build -trimpath -ldflags "-s -w" -o eventsearcher
cd -
@ -42,12 +42,12 @@ jobs:
- name: Build for macOS arm64
if: startsWith(matrix.os, 'macos-') == true
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
- name: Build for Windows
if: startsWith(matrix.os, 'windows-')
run: |
$GIT_COMMIT = (git describe --always --dirty) | Out-String
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
$LATEST_TAG = ((git describe --tags $(git rev-list --tags --max-count=1)) | Out-String).Trim()
$REV_LIST=$LATEST_TAG+"..HEAD"
@ -283,6 +283,21 @@ jobs:
ports:
- 3307:3306
mysql:
image: mysql:latest
env:
MYSQL_ROOT_PASSWORD: mysql
MYSQL_DATABASE: sftpgo
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-interval 10s
--health-timeout 5s
--health-retries 6
ports:
- 3308:3306
steps:
- uses: actions/checkout@v3
@ -312,6 +327,17 @@ jobs:
SFTPGO_DATA_PROVIDER__PASSWORD: postgres
- name: Run tests using MySQL provider
run: |
go test -v -p 1 -timeout 15m ./... -covermode=atomic
env:
SFTPGO_DATA_PROVIDER__DRIVER: mysql
SFTPGO_DATA_PROVIDER__NAME: sftpgo
SFTPGO_DATA_PROVIDER__HOST: localhost
SFTPGO_DATA_PROVIDER__PORT: 3308
SFTPGO_DATA_PROVIDER__USERNAME: sftpgo
SFTPGO_DATA_PROVIDER__PASSWORD: sftpgo
- name: Run tests using MariaDB provider
run: |
go test -v -p 1 -timeout 15m ./... -covermode=atomic
env:
@ -321,6 +347,7 @@ jobs:
SFTPGO_DATA_PROVIDER__PORT: 3307
SFTPGO_DATA_PROVIDER__USERNAME: sftpgo
SFTPGO_DATA_PROVIDER__PASSWORD: sftpgo
SFTPGO_DATA_PROVIDER__SQL_TABLES_PREFIX: prefix_
- name: Run tests using CockroachDB provider
run: |
@ -336,15 +363,17 @@ jobs:
SFTPGO_DATA_PROVIDER__PORT: 26257
SFTPGO_DATA_PROVIDER__USERNAME: root
SFTPGO_DATA_PROVIDER__PASSWORD:
SFTPGO_DATA_PROVIDER__SQL_TABLES_PREFIX: prefix_
build-linux-packages:
name: Build Linux packages
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: amd64
go: 1.18
distro: ubuntu:18.04
go: latest
go-arch: amd64
- arch: aarch64
distro: ubuntu18.04
@ -362,16 +391,36 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
if: ${{ matrix.arch == 'amd64' }}
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Get commit SHA
id: get_commit
run: echo ::set-output name=COMMIT::${GITHUB_SHA::8}
shell: bash
- name: Build on amd64
if: ${{ matrix.arch == 'amd64' }}
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
echo '#!/bin/bash' > build.sh
echo '' >> build.sh
echo 'set -e' >> build.sh
echo 'apt-get update -q -y' >> build.sh
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
else
echo 'GO_VERSION=${{ matrix.go }}' >> build.sh
fi
echo 'GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}' >> build.sh
echo 'curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/${GO_VERSION}.linux-${GO_DOWNLOAD_ARCH}.tar.gz' >> build.sh
echo 'tar -C /usr/local -xzf go.tar.gz' >> build.sh
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
echo 'go version' >> build.sh
echo 'cd /usr/local/src' >> build.sh
echo 'go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
chmod 755 build.sh
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/
cp -r templates output/
@ -398,7 +447,7 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git
apt-get install -q -y curl gcc
if [ ${{ matrix.go }} == 'latest' ]
then
GO_VERSION=$(curl -L https://go.dev/VERSION?m=text)
@ -414,11 +463,12 @@ jobs:
tar -C /usr/local -xzf go.tar.gz
run: |
export PATH=$PATH:/usr/local/go/bin
go version
if [ ${{ matrix.arch}} == 'armv7' ]
then
export GOARM=7
fi
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${{ steps.get_commit.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,bash_completion,zsh_completion}
cp sftpgo.json output/
cp -r templates output/

View file

@ -5,7 +5,7 @@ on:
# - cron: '0 4 * * *' # everyday at 4:00 AM UTC
push:
branches:
- main
- 2.3.x
tags:
- v*
pull_request:
@ -28,6 +28,9 @@ jobs:
- os: ubuntu-latest
docker_pkg: distroless
optional_deps: false
- os: ubuntu-latest
docker_pkg: debian-plugins
optional_deps: true
steps:
- name: Checkout
uses: actions/checkout@v3
@ -64,6 +67,9 @@ jobs:
VERSION="${VERSION}-distroless"
VERSION_SLIM="${VERSION}-slim"
DOCKERFILE=Dockerfile.distroless
elif [[ $DOCKER_PKG == debian-plugins ]]; then
VERSION="${VERSION}-plugins"
VERSION_SLIM="${VERSION}-slim"
fi
DOCKER_IMAGES=("drakkan/sftpgo" "ghcr.io/drakkan/sftpgo")
TAGS="${DOCKER_IMAGES[0]}:${VERSION}"
@ -89,6 +95,13 @@ jobs:
fi
TAGS="${TAGS},${DOCKER_IMAGE}:distroless"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:distroless-slim"
elif [[ $DOCKER_PKG == debian-plugins ]]; then
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-plugins,${DOCKER_IMAGE}:${MAJOR}-plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:${MINOR}-plugins-slim,${DOCKER_IMAGE}:${MAJOR}-plugins-slim"
fi
TAGS="${TAGS},${DOCKER_IMAGE}:plugins"
TAGS_SLIM="${TAGS_SLIM},${DOCKER_IMAGE}:plugins-slim"
else
if [[ -n $MAJOR && -n $MINOR ]]; then
TAGS="${TAGS},${DOCKER_IMAGE}:${MINOR}-alpine,${DOCKER_IMAGE}:${MAJOR}-alpine"
@ -109,6 +122,11 @@ jobs:
echo ::set-output name=tags::${TAGS_SLIM}
echo ::set-output name=full::false
fi
if [[ $DOCKER_PKG == debian-plugins ]]; then
echo ::set-output name=plugins::true
else
echo ::set-output name=plugins::false
fi
echo ::set-output name=dockerfile::${DOCKERFILE}
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
echo ::set-output name=sha::${GITHUB_SHA::8}
@ -150,6 +168,7 @@ jobs:
build-args: |
COMMIT_SHA=${{ steps.info.outputs.sha }}
INSTALL_OPTIONAL_PACKAGES=${{ steps.info.outputs.full }}
DOWNLOAD_PLUGINS=${{ steps.info.outputs.plugins }}
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

View file

@ -5,7 +5,7 @@ on:
tags: 'v*'
env:
GO_VERSION: 1.18.3
GO_VERSION: 1.18.7
jobs:
prepare-sources-with-deps:
@ -26,6 +26,7 @@ jobs:
run: |
go mod vendor
echo "${SFTPGO_VERSION}" > VERSION.txt
echo "${GITHUB_SHA::8}" >> VERSION.txt
tar cJvf sftpgo_${SFTPGO_VERSION}_src_with_deps.tar.xz *
env:
SFTPGO_VERSION: ${{ steps.get_version.outputs.VERSION }}
@ -71,16 +72,16 @@ jobs:
- name: Build for macOS x86_64
if: startsWith(matrix.os, 'windows-') != true
run: go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
run: go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
- name: Build for macOS arm64
if: startsWith(matrix.os, 'macos-') == true
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
run: CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 SDKROOT=$(xcrun --sdk macosx --show-sdk-path) go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo_arm64
- name: Build for Windows
if: startsWith(matrix.os, 'windows-')
run: |
$GIT_COMMIT = (git describe --always --dirty) | Out-String
$GIT_COMMIT = (git describe --always --abbrev=8 --dirty) | Out-String
$DATE_TIME = ([datetime]::Now.ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ")) | Out-String
$FILE_VERSION = $Env:SFTPGO_VERSION.substring(1) + ".0"
go install github.com/tc-hib/go-winres@latest
@ -254,11 +255,12 @@ jobs:
prepare-linux:
name: Prepare Linux binaries
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: amd64
distro: ubuntu:18.04
go-arch: amd64
deb-arch: amd64
rpm-arch: x86_64
@ -284,17 +286,13 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Go
if: ${{ matrix.arch == 'amd64' }}
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Get versions
id: get_version
run: |
echo ::set-output name=SFTPGO_VERSION::${GITHUB_REF/refs\/tags\//}
echo ::set-output name=GO_VERSION::${GO_VERSION}
echo ::set-output name=COMMIT::${GITHUB_SHA::8}
shell: bash
env:
GO_VERSION: ${{ env.GO_VERSION }}
@ -302,7 +300,20 @@ jobs:
- name: Build on amd64
if: ${{ matrix.arch == 'amd64' }}
run: |
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
echo '#!/bin/bash' > build.sh
echo '' >> build.sh
echo 'set -e' >> build.sh
echo 'apt-get update -q -y' >> build.sh
echo 'apt-get install -q -y curl gcc' >> build.sh
echo 'curl --retry 5 --retry-delay 2 --connect-timeout 10 -o go.tar.gz -L https://go.dev/dl/go${{ steps.get_version.outputs.GO_VERSION }}.linux-${{ matrix.go-arch }}.tar.gz' >> build.sh
echo 'tar -C /usr/local -xzf go.tar.gz' >> build.sh
echo 'export PATH=$PATH:/usr/local/go/bin' >> build.sh
echo 'go version' >> build.sh
echo 'cd /usr/local/src' >> build.sh
echo 'go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo' >> build.sh
chmod 755 build.sh
docker run --rm --name ubuntu-build --mount type=bind,source=`pwd`,target=/usr/local/src ${{ matrix.distro }} /usr/local/src/build.sh
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt
@ -340,7 +351,7 @@ jobs:
shell: /bin/bash
install: |
apt-get update -q -y
apt-get install -q -y curl gcc git xz-utils
apt-get install -q -y curl gcc xz-utils
GO_DOWNLOAD_ARCH=${{ matrix.go-arch }}
if [ ${{ matrix.arch}} == 'armv7' ]
then
@ -350,7 +361,8 @@ jobs:
tar -C /usr/local -xzf go.tar.gz
run: |
export PATH=$PATH:/usr/local/go/bin
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go version
go build -buildvcs=false -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=${{ steps.get_version.outputs.COMMIT }} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
mkdir -p output/{init,sqlite,bash_completion,zsh_completion}
echo "For documentation please take a look here:" > output/README.txt
echo "" >> output/README.txt

View file

@ -20,9 +20,14 @@ ARG FEATURES
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
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/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo
# Set to "true" to download the "official" plugins in /usr/local/bin
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
FROM debian:bullseye-slim
# Set to "true" to install jq and the optional git and rsync dependencies
@ -43,7 +48,7 @@ COPY --from=builder /workspace/sftpgo.json /etc/sftpgo/sftpgo.json
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
COPY --from=builder /workspace/sftpgo /usr/local/bin/
COPY --from=builder /workspace/sftpgo /usr/local/bin/sftpgo-plugin-* /usr/local/bin/
# Log to the stdout so the logs will be available using docker logs
ENV SFTPGO_LOG_FILE_PATH=""

View file

@ -22,7 +22,7 @@ ARG FEATURES
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
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/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo

View file

@ -20,7 +20,7 @@ ARG FEATURES=nosqlite
COPY . .
RUN set -xe && \
export COMMIT_SHA=${COMMIT_SHA:-$(git describe --always --dirty)} && \
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/version.commit=${COMMIT_SHA} -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -v -o sftpgo
# Modify the default configuration file

View file

@ -11,6 +11,25 @@
Fully featured and highly configurable SFTP server with optional HTTP/S, FTP/S and WebDAV support.
Several storage backends are supported: local filesystem, encrypted local filesystem, S3 (compatible) Object Storage, Google Cloud Storage, Azure Blob Storage, SFTP.
## Sponsors
If you find SFTPGo useful please consider supporting this Open Source project.
Maintaining and evolving SFTPGo is a lot of work - easily the equivalent of a full time job - for me.
I'd like to make SFTPGo into a sustainable long term project and would not like to introduce a dual licensing option and limit some features to the proprietary version only.
If you use SFTPGo, it is in your best interest to ensure that the project you rely on stays healthy and well maintained.
This can only happen with your donations and [sponsorships](https://github.com/sponsors/drakkan) :heart:
If you just take and don't return anything back, the project will die in the long run and you will be forced to pay for a similar proprietary solution.
More [info](https://github.com/drakkan/sftpgo/issues/452).
Thank you to our sponsors!
[<img src="https://www.7digital.com/wp-content/themes/sevendigital/images/top_logo.png" alt="7digital logo">](https://www.7digital.com/)
## Features
- Support for serving local filesystem, encrypted local filesystem, S3 Compatible Object Storage, Google Cloud Storage, Azure Blob Storage or other SFTP accounts over SFTP/SCP/FTP/WebDAV.
@ -93,6 +112,8 @@ An official Docker image is available. Documentation is [here](./docker/README.m
</details>
APT and YUM repositories are [available](./docs/repo.md).
SFTPGo is also available on [AWS Marketplace](https://aws.amazon.com/marketplace/seller-profile?id=6e849ab8-70a6-47de-9a43-13c3fa849335) and [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/prasselsrl1645470739547.sftpgo_linux), purchasing from there will help keep SFTPGo a long-term sustainable project.
<details><summary>Windows packages</summary>
@ -308,14 +329,6 @@ We are very grateful to all the people who contributed with ideas and/or pull re
Thank you [ysura](https://www.ysura.com/) for granting me stable access to a test AWS S3 account.
## Sponsors
I'd like to make SFTPGo into a sustainable long term project and your [sponsorship](https://github.com/sponsors/drakkan) will really help :heart:
Thank you to our sponsors!
[<img src="https://www.7digital.com/wp-content/themes/sevendigital/images/top_logo.png" alt="7digital logo">](https://www.7digital.com/)
## License
GNU AGPLv3

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package acme
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package acme provides automatic access to certificates from Let's Encrypt and any other ACME-based CA
// The code here is largely coiped from https://github.com/go-acme/lego/tree/master/cmd
// This package is intended to provide basic functionality for obtaining and renewing certificates

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build awscontainer
// +build awscontainer

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !awscontainer
// +build !awscontainer

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import "github.com/spf13/cobra"

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
@ -34,6 +48,7 @@ To initialize/update the data provider from the configuration directory simply u
$ sftpgo initprovider
Any defined action is ignored.
Please take a look at the usage below to customize the options.`,
Run: func(cmd *cobra.Command, args []string) {
logger.DisableLogger()
@ -51,6 +66,10 @@ Please take a look at the usage below to customize the options.`,
os.Exit(1)
}
providerConf := config.GetProviderConf()
// ignore actions
providerConf.Actions.Hook = ""
providerConf.Actions.ExecuteFor = nil
providerConf.Actions.ExecuteOn = nil
logger.InfoToConsole("Initializing provider: %#v config file: %#v", providerConf.Driver, viper.ConfigFileUsed())
err = dataprovider.InitializeDatabase(providerConf, configDir)
if err == nil {

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !noportable
// +build !noportable

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build noportable
// +build noportable

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package cmd provides Command Line Interface support
package cmd

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (

View file

@ -1,3 +1,18 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package command provides command configuration for SFTPGo hooks
package command
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package command
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (
@ -28,8 +42,17 @@ var (
errUnconfiguredAction = errors.New("no hook is configured for this action")
errNoHook = errors.New("unable to execute action, no hook defined")
errUnexpectedHTTResponse = errors.New("unexpected HTTP response code")
hooksConcurrencyGuard = make(chan struct{}, 150)
)
func startNewHook() {
hooksConcurrencyGuard <- struct{}{}
}
func hookEnded() {
<-hooksConcurrencyGuard
}
// ProtocolActions defines the action to execute on file operations and SSH commands
type ProtocolActions struct {
// Valid values are download, upload, pre-delete, delete, rename, ssh_cmd. Empty slice to disable
@ -102,7 +125,12 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
return
}
go actionHandler.Handle(notification) //nolint:errcheck
go func() {
startNewHook()
defer hookEnded()
actionHandler.Handle(notification) //nolint:errcheck
}()
}
}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package common defines code shared among file transfer packages and protocols
package common
@ -593,6 +607,9 @@ func (c *Configuration) ExecuteStartupHook() error {
}
func (c *Configuration) executePostDisconnectHook(remoteAddr, protocol, username, connID string, connectionTime time.Time) {
startNewHook()
defer hookEnded()
ipAddr := util.GetIPFromRemoteAddress(remoteAddr)
connDuration := int64(time.Since(connectionTime) / time.Millisecond)
@ -845,6 +862,15 @@ 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() == "" {
ip := util.GetIPFromRemoteAddress(conn.GetRemoteAddress())
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, conn.GetProtocol(),
dataprovider.ErrNoAuthTryed.Error())
metric.AddNoAuthTryed()
AddDefenderEvent(ip, HostEventNoLoginTried)
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip,
conn.GetProtocol(), dataprovider.ErrNoAuthTryed)
}
Config.checkPostDisconnectHook(conn.GetRemoteAddress(), conn.GetProtocol(), conn.GetUsername(),
conn.GetID(), conn.GetConnectionTime())
return
@ -933,19 +959,11 @@ func (conns *ActiveConnections) checkIdles() {
isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && c.GetUsername() == "")
if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
defer func(conn ActiveConnection, isFTPNoAuth bool) {
defer func(conn ActiveConnection) {
err := conn.Disconnect()
logger.Debug(conn.GetProtocol(), conn.GetID(), "close idle connection, idle time: %v, username: %#v close err: %v",
time.Since(conn.GetLastActivity()), conn.GetUsername(), err)
if isFTPNoAuth {
ip := util.GetIPFromRemoteAddress(c.GetRemoteAddress())
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, c.GetProtocol(), "client idle")
metric.AddNoAuthTryed()
AddDefenderEvent(ip, HostEventNoLoginTried)
dataprovider.ExecutePostLoginHook(&dataprovider.User{}, dataprovider.LoginMethodNoAuthTryed, ip, c.GetProtocol(),
dataprovider.ErrNoAuthTryed)
}
}(c, isUnauthenticatedFTPUser)
}(c)
}
}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (
@ -248,7 +262,7 @@ func (c *RetentionCheck) cleanupFolder(folderPath string) error {
result.Elapsed = time.Since(startTime)
result.Info = "data retention check skipped: no permissions"
c.conn.Log(logger.LevelInfo, "user %#v does not have permissions to check retention on %#v, retention check skipped",
c.conn.User, folderPath)
c.conn.User.Username, folderPath)
return nil
}
@ -400,6 +414,9 @@ func (c *RetentionCheck) sendEmailNotification(elapsed time.Duration, errCheck e
}
func (c *RetentionCheck) sendHookNotification(elapsed time.Duration, errCheck error) error {
startNewHook()
defer hookEnded()
data := make(map[string]any)
totalDeletedFiles := 0
totalDeletedSize := int64(0)

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common_test
import (
@ -2699,7 +2713,7 @@ func TestDelayedQuotaUpdater(t *testing.T) {
Name: "folder",
MappedPath: filepath.Join(os.TempDir(), "p"),
}
err = dataprovider.AddFolder(&folder)
err = dataprovider.AddFolder(&folder, "", "")
assert.NoError(t, err)
err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, false)

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package common
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package config manages the configuration
package config
@ -85,6 +99,7 @@ var (
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: true,
EnabledLoginMethods: 0,
EnableHTTPS: false,
CertificateFile: "",
CertificateKeyFile: "",
@ -105,7 +120,9 @@ var (
UsernameField: "",
RoleField: "",
ImplicitRoles: false,
Scopes: []string{"openid", "profile", "email"},
CustomFields: []string{},
Debug: false,
},
Security: httpd.SecurityConf{
Enabled: false,
@ -374,6 +391,7 @@ func Init() {
InstallationCode: "",
InstallationCodeHint: defaultInstallCodeHint,
},
HideSupportLink: false,
},
HTTPConfig: httpclient.Config{
Timeout: 20,
@ -957,9 +975,7 @@ func getPluginsFromEnv(idx int) {
}
func getSFTPDBindindFromEnv(idx int) {
binding := sftpd.Binding{
ApplyProxyConfig: true,
}
binding := defaultSFTPDBinding
if len(globalConf.SFTPD.Bindings) > idx {
binding = globalConf.SFTPD.Bindings[idx]
}
@ -995,9 +1011,17 @@ func getSFTPDBindindFromEnv(idx int) {
func getFTPDPassiveIPOverridesFromEnv(idx int) []ftpd.PassiveIPOverride {
var overrides []ftpd.PassiveIPOverride
if len(globalConf.FTPD.Bindings) > idx {
overrides = globalConf.FTPD.Bindings[idx].PassiveIPOverrides
}
for subIdx := 0; subIdx < 10; subIdx++ {
var override ftpd.PassiveIPOverride
var replace bool
if len(globalConf.FTPD.Bindings) > idx && len(globalConf.FTPD.Bindings[idx].PassiveIPOverrides) > subIdx {
override = globalConf.FTPD.Bindings[idx].PassiveIPOverrides[subIdx]
replace = true
}
ip, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__PASSIVE_IP_OVERRIDES__%v__IP", idx, subIdx))
if ok {
@ -1011,18 +1035,19 @@ func getFTPDPassiveIPOverridesFromEnv(idx int) []ftpd.PassiveIPOverride {
}
if len(override.Networks) > 0 {
if replace {
overrides[subIdx] = override
} else {
overrides = append(overrides, override)
}
}
}
return overrides
}
func getDefaultFTPDBinding(idx int) ftpd.Binding {
binding := ftpd.Binding{
ApplyProxyConfig: true,
MinTLSVersion: 12,
}
binding := defaultFTPDBinding
if len(globalConf.FTPD.Bindings) > idx {
binding = globalConf.FTPD.Bindings[idx]
}
@ -1155,9 +1180,7 @@ func getWebDAVDBindingProxyConfigsFromEnv(idx int, binding *webdavd.Binding) boo
}
func getWebDAVDBindingFromEnv(idx int) {
binding := webdavd.Binding{
MinTLSVersion: 12,
}
binding := defaultWebDAVDBinding
if len(globalConf.WebDAVD.Bindings) > idx {
binding = globalConf.WebDAVD.Bindings[idx]
}
@ -1233,22 +1256,44 @@ func getWebDAVDBindingFromEnv(idx int) {
func getHTTPDSecurityProxyHeadersFromEnv(idx int) []httpd.HTTPSProxyHeader {
var httpsProxyHeaders []httpd.HTTPSProxyHeader
if len(globalConf.HTTPDConfig.Bindings) > idx {
httpsProxyHeaders = globalConf.HTTPDConfig.Bindings[idx].Security.HTTPSProxyHeaders
}
for subIdx := 0; subIdx < 10; subIdx++ {
proxyKey, _ := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_PROXY_HEADERS__%v__KEY", idx, subIdx))
proxyVal, _ := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_PROXY_HEADERS__%v__VALUE", idx, subIdx))
if proxyKey != "" && proxyVal != "" {
httpsProxyHeaders = append(httpsProxyHeaders, httpd.HTTPSProxyHeader{
Key: proxyKey,
Value: proxyVal,
})
var httpsProxyHeader httpd.HTTPSProxyHeader
var replace bool
if len(globalConf.HTTPDConfig.Bindings) > idx &&
len(globalConf.HTTPDConfig.Bindings[idx].Security.HTTPSProxyHeaders) > subIdx {
httpsProxyHeader = httpsProxyHeaders[subIdx]
replace = true
}
proxyKey, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_PROXY_HEADERS__%v__KEY",
idx, subIdx))
if ok {
httpsProxyHeader.Key = proxyKey
}
proxyVal, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_PROXY_HEADERS__%v__VALUE",
idx, subIdx))
if ok {
httpsProxyHeader.Value = proxyVal
}
if httpsProxyHeader.Key != "" && httpsProxyHeader.Value != "" {
if replace {
httpsProxyHeaders[subIdx] = httpsProxyHeader
} else {
httpsProxyHeaders = append(httpsProxyHeaders, httpsProxyHeader)
}
}
}
return httpsProxyHeaders
}
func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint:gocyclo
var result httpd.SecurityConf
result := defaultHTTPDBinding.Security
if len(globalConf.HTTPDConfig.Bindings) > idx {
result = globalConf.HTTPDConfig.Bindings[idx].Security
}
isSet := false
enabled, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__ENABLED", idx))
@ -1296,6 +1341,7 @@ func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint:
stsSeconds, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__STS_SECONDS", idx))
if ok {
result.STSSeconds = stsSeconds
isSet = true
}
stsIncludeSubDomains, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__STS_INCLUDE_SUBDOMAINS", idx))
@ -1344,7 +1390,10 @@ func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint:
}
func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) {
var result httpd.OIDC
result := defaultHTTPDBinding.OIDC
if len(globalConf.HTTPDConfig.Bindings) > idx {
result = globalConf.HTTPDConfig.Bindings[idx].OIDC
}
isSet := false
clientID, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__CLIENT_ID", idx))
@ -1377,6 +1426,12 @@ func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) {
isSet = true
}
scopes, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__SCOPES", idx))
if ok {
result.Scopes = scopes
isSet = true
}
roleField, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__ROLE_FIELD", idx))
if ok {
result.RoleField = roleField
@ -1395,81 +1450,90 @@ func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) {
isSet = true
}
debug, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__DEBUG", idx))
if ok {
result.Debug = debug
isSet = true
}
return result, isSet
}
func getHTTPDUIBrandingFromEnv(prefix string) (httpd.UIBranding, bool) {
var result httpd.UIBranding
func getHTTPDUIBrandingFromEnv(prefix string, branding httpd.UIBranding) (httpd.UIBranding, bool) {
isSet := false
name, ok := os.LookupEnv(fmt.Sprintf("%s__NAME", prefix))
if ok {
result.Name = name
branding.Name = name
isSet = true
}
shortName, ok := os.LookupEnv(fmt.Sprintf("%s__SHORT_NAME", prefix))
if ok {
result.ShortName = shortName
branding.ShortName = shortName
isSet = true
}
faviconPath, ok := os.LookupEnv(fmt.Sprintf("%s__FAVICON_PATH", prefix))
if ok {
result.FaviconPath = faviconPath
branding.FaviconPath = faviconPath
isSet = true
}
logoPath, ok := os.LookupEnv(fmt.Sprintf("%s__LOGO_PATH", prefix))
if ok {
result.LogoPath = logoPath
branding.LogoPath = logoPath
isSet = true
}
loginImagePath, ok := os.LookupEnv(fmt.Sprintf("%s__LOGIN_IMAGE_PATH", prefix))
if ok {
result.LoginImagePath = loginImagePath
branding.LoginImagePath = loginImagePath
isSet = true
}
disclaimerName, ok := os.LookupEnv(fmt.Sprintf("%s__DISCLAIMER_NAME", prefix))
if ok {
result.DisclaimerName = disclaimerName
branding.DisclaimerName = disclaimerName
isSet = true
}
disclaimerPath, ok := os.LookupEnv(fmt.Sprintf("%s__DISCLAIMER_PATH", prefix))
if ok {
result.DisclaimerPath = disclaimerPath
branding.DisclaimerPath = disclaimerPath
isSet = true
}
defaultCSSPath, ok := os.LookupEnv(fmt.Sprintf("%s__DEFAULT_CSS", prefix))
if ok {
result.DefaultCSS = defaultCSSPath
branding.DefaultCSS = defaultCSSPath
isSet = true
}
extraCSS, ok := lookupStringListFromEnv(fmt.Sprintf("%s__EXTRA_CSS", prefix))
if ok {
result.ExtraCSS = extraCSS
branding.ExtraCSS = extraCSS
isSet = true
}
return result, isSet
return branding, isSet
}
func getHTTPDBrandingFromEnv(idx int) (httpd.Branding, bool) {
var result httpd.Branding
result := defaultHTTPDBinding.Branding
if len(globalConf.HTTPDConfig.Bindings) > idx {
result = globalConf.HTTPDConfig.Bindings[idx].Branding
}
isSet := false
webAdmin, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_ADMIN", idx))
webAdmin, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_ADMIN", idx),
result.WebAdmin)
if ok {
result.WebAdmin = webAdmin
isSet = true
}
webClient, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_CLIENT", idx))
webClient, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_CLIENT", idx),
result.WebClient)
if ok {
result.WebClient = webClient
isSet = true
@ -1480,9 +1544,18 @@ func getHTTPDBrandingFromEnv(idx int) (httpd.Branding, bool) {
func getHTTPDWebClientIntegrationsFromEnv(idx int) []httpd.WebClientIntegration {
var integrations []httpd.WebClientIntegration
if len(globalConf.HTTPDConfig.Bindings) > idx {
integrations = globalConf.HTTPDConfig.Bindings[idx].WebClientIntegrations
}
for subIdx := 0; subIdx < 10; subIdx++ {
var integration httpd.WebClientIntegration
var replace bool
if len(globalConf.HTTPDConfig.Bindings) > idx &&
len(globalConf.HTTPDConfig.Bindings[idx].WebClientIntegrations) > subIdx {
integration = integrations[subIdx]
replace = true
}
url, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__WEB_CLIENT_INTEGRATIONS__%v__URL", idx, subIdx))
if ok {
@ -1495,21 +1568,20 @@ func getHTTPDWebClientIntegrationsFromEnv(idx int) []httpd.WebClientIntegration
integration.FileExtensions = extensions
}
if url != "" && len(extensions) > 0 {
if integration.URL != "" && len(integration.FileExtensions) > 0 {
if replace {
integrations[subIdx] = integration
} else {
integrations = append(integrations, integration)
}
}
}
return integrations
}
func getDefaultHTTPBinding(idx int) httpd.Binding {
binding := httpd.Binding{
EnableWebAdmin: true,
EnableWebClient: true,
RenderOpenAPI: true,
MinTLSVersion: 12,
}
binding := defaultHTTPDBinding
if len(globalConf.HTTPDConfig.Bindings) > idx {
binding = globalConf.HTTPDConfig.Bindings[idx]
}
@ -1540,6 +1612,7 @@ func getHTTPDNestedObjectsFromEnv(idx int, binding *httpd.Binding) bool {
brandingConf, ok := getHTTPDBrandingFromEnv(idx)
if ok {
binding.Branding = brandingConf
isSet = true
}
return isSet
@ -1569,7 +1642,7 @@ func getHTTPDBindingProxyConfigsFromEnv(idx int, binding *httpd.Binding) bool {
return isSet
}
func getHTTPDBindingFromEnv(idx int) {
func getHTTPDBindingFromEnv(idx int) { //nolint:gocyclo
binding := getDefaultHTTPBinding(idx)
isSet := false
@ -1609,6 +1682,12 @@ func getHTTPDBindingFromEnv(idx int) {
isSet = true
}
enabledLoginMethods, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLED_LOGIN_METHODS", idx))
if ok {
binding.EnabledLoginMethods = int(enabledLoginMethods)
isSet = true
}
renderOpenAPI, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__RENDER_OPENAPI", idx))
if ok {
binding.RenderOpenAPI = renderOpenAPI
@ -1668,6 +1747,9 @@ func setHTTPDBinding(isSet bool, binding httpd.Binding, idx int) {
func getHTTPClientCertificatesFromEnv(idx int) {
tlsCert := httpclient.TLSKeyPair{}
if len(globalConf.HTTPConfig.Certificates) > idx {
tlsCert = globalConf.HTTPConfig.Certificates[idx]
}
cert, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTP__CERTIFICATES__%v__CERT", idx))
if ok {
@ -1893,9 +1975,10 @@ func setViperDefaults() {
viper.SetDefault("httpd.cors.allowed_headers", globalConf.HTTPDConfig.Cors.AllowedHeaders)
viper.SetDefault("httpd.cors.exposed_headers", globalConf.HTTPDConfig.Cors.ExposedHeaders)
viper.SetDefault("httpd.cors.allow_credentials", globalConf.HTTPDConfig.Cors.AllowCredentials)
viper.SetDefault("httpd.cors.max_age", globalConf.HTTPDConfig.Cors.MaxAge)
viper.SetDefault("httpd.setup.installation_code", globalConf.HTTPDConfig.Setup.InstallationCode)
viper.SetDefault("httpd.setup.installation_code_hint", globalConf.HTTPDConfig.Setup.InstallationCodeHint)
viper.SetDefault("httpd.cors.max_age", globalConf.HTTPDConfig.Cors.MaxAge)
viper.SetDefault("httpd.hide_support_link", globalConf.HTTPDConfig.HideSupportLink)
viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout)
viper.SetDefault("http.retry_wait_min", globalConf.HTTPConfig.RetryWaitMin)
viper.SetDefault("http.retry_wait_max", globalConf.HTTPConfig.RetryWaitMax)

25
config/config_darwin.go Normal file
View file

@ -0,0 +1,25 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build darwin
// +build darwin
package config
import "github.com/spf13/viper"
// macOS specific config search path
func setViperAdditionalConfigPaths() {
viper.AddConfigPath("/usr/local/etc/sftpgo")
}

20
config/config_fallback.go Normal file
View file

@ -0,0 +1,20 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !linux && !darwin
// +build !linux,!darwin
package config
func setViperAdditionalConfigPaths() {}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build linux
// +build linux
@ -9,4 +23,5 @@ import "github.com/spf13/viper"
func setViperAdditionalConfigPaths() {
viper.AddConfigPath("$HOME/.config/sftpgo")
viper.AddConfigPath("/etc/sftpgo")
viper.AddConfigPath("/usr/local/etc/sftpgo")
}

View file

@ -1,6 +0,0 @@
//go:build !linux
// +build !linux
package config
func setViperAdditionalConfigPaths() {}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config_test
import (
@ -485,6 +499,126 @@ func TestDisabledMFAConfig(t *testing.T) {
assert.NoError(t, err)
}
func TestFTPDOverridesFromEnv(t *testing.T) {
reset()
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP", "192.168.1.1")
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__NETWORKS", "192.168.1.0/24, 192.168.3.0/25")
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__1__IP", "192.168.2.1")
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__1__NETWORKS", "192.168.2.0/24")
cleanup := func() {
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP")
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__NETWORKS")
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__1__IP")
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__1__NETWORKS")
}
t.Cleanup(cleanup)
configDir := ".."
err := config.LoadConfig(configDir, "")
assert.NoError(t, err)
ftpdConf := config.GetFTPDConfig()
require.Len(t, ftpdConf.Bindings, 1)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides, 2)
require.Equal(t, "192.168.1.1", ftpdConf.Bindings[0].PassiveIPOverrides[0].IP)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides[0].Networks, 2)
require.Equal(t, "192.168.2.1", ftpdConf.Bindings[0].PassiveIPOverrides[1].IP)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides[1].Networks, 1)
cleanup()
cfg := make(map[string]any)
cfg["ftpd"] = ftpdConf
configAsJSON, err := json.Marshal(cfg)
require.NoError(t, err)
confName := tempConfigName + ".json"
configFilePath := filepath.Join(configDir, confName)
err = os.WriteFile(configFilePath, configAsJSON, os.ModePerm)
assert.NoError(t, err)
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__0__IP", "192.168.1.2")
os.Setenv("SFTPGO_FTPD__BINDINGS__0__PASSIVE_IP_OVERRIDES__1__NETWORKS", "192.168.2.0/24,192.168.4.0/25")
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
ftpdConf = config.GetFTPDConfig()
require.Len(t, ftpdConf.Bindings, 1)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides, 2)
require.Equal(t, "192.168.1.2", ftpdConf.Bindings[0].PassiveIPOverrides[0].IP)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides[0].Networks, 2)
require.Equal(t, "192.168.2.1", ftpdConf.Bindings[0].PassiveIPOverrides[1].IP)
require.Len(t, ftpdConf.Bindings[0].PassiveIPOverrides[1].Networks, 2)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
func TestHTTPDSubObjectsFromEnv(t *testing.T) {
reset()
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__SECURITY__HTTPS_PROXY_HEADERS__0__KEY", "X-Forwarded-Proto")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__SECURITY__HTTPS_PROXY_HEADERS__0__VALUE", "https")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__URL", "http://127.0.0.1/")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__FILE_EXTENSIONS", ".pdf, .txt")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CLIENT_ID", "client_id")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CLIENT_SECRET", "client_secret")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CONFIG_URL", "config_url")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__REDIRECT_BASE_URL", "redirect_base_url")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__USERNAME_FIELD", "email")
cleanup := func() {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__SECURITY__HTTPS_PROXY_HEADERS__0__KEY")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__SECURITY__HTTPS_PROXY_HEADERS__0__VALUE")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__URL")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__FILE_EXTENSIONS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CLIENT_ID")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CLIENT_SECRET")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CONFIG_URL")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__REDIRECT_BASE_URL")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__USERNAME_FIELD")
}
t.Cleanup(cleanup)
configDir := ".."
err := config.LoadConfig(configDir, "")
assert.NoError(t, err)
httpdConf := config.GetHTTPDConfig()
require.Len(t, httpdConf.Bindings, 1)
require.Len(t, httpdConf.Bindings[0].Security.HTTPSProxyHeaders, 1)
require.Len(t, httpdConf.Bindings[0].WebClientIntegrations, 1)
require.Equal(t, "client_id", httpdConf.Bindings[0].OIDC.ClientID)
require.Equal(t, "client_secret", httpdConf.Bindings[0].OIDC.ClientSecret)
require.Equal(t, "config_url", httpdConf.Bindings[0].OIDC.ConfigURL)
require.Equal(t, "redirect_base_url", httpdConf.Bindings[0].OIDC.RedirectBaseURL)
require.Equal(t, "email", httpdConf.Bindings[0].OIDC.UsernameField)
cleanup()
cfg := make(map[string]any)
cfg["httpd"] = httpdConf
configAsJSON, err := json.Marshal(cfg)
require.NoError(t, err)
confName := tempConfigName + ".json"
configFilePath := filepath.Join(configDir, confName)
err = os.WriteFile(configFilePath, configAsJSON, os.ModePerm)
assert.NoError(t, err)
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__SECURITY__HTTPS_PROXY_HEADERS__0__VALUE", "http")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__URL", "http://127.0.1.1/")
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__OIDC__CLIENT_SECRET", "new_client_secret")
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
httpdConf = config.GetHTTPDConfig()
require.Len(t, httpdConf.Bindings, 1)
require.Len(t, httpdConf.Bindings[0].Security.HTTPSProxyHeaders, 1)
require.Equal(t, "http", httpdConf.Bindings[0].Security.HTTPSProxyHeaders[0].Value)
require.Len(t, httpdConf.Bindings[0].WebClientIntegrations, 1)
require.Equal(t, "http://127.0.1.1/", httpdConf.Bindings[0].WebClientIntegrations[0].URL)
require.Equal(t, "client_id", httpdConf.Bindings[0].OIDC.ClientID)
require.Equal(t, "new_client_secret", httpdConf.Bindings[0].OIDC.ClientSecret)
require.Equal(t, "config_url", httpdConf.Bindings[0].OIDC.ConfigURL)
require.Equal(t, "redirect_base_url", httpdConf.Bindings[0].OIDC.RedirectBaseURL)
require.Equal(t, "email", httpdConf.Bindings[0].OIDC.UsernameField)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
func TestPluginsFromEnv(t *testing.T) {
reset()
@ -545,7 +679,9 @@ func TestPluginsFromEnv(t *testing.T) {
require.Equal(t, kms.SecretStatusAWS, pluginConf.KMSOptions.EncryptedStatus)
require.Equal(t, 14, pluginConf.AuthOptions.Scope)
configAsJSON, err := json.Marshal(pluginsConf)
cfg := make(map[string]any)
cfg["plugins"] = pluginConf
configAsJSON, err := json.Marshal(cfg)
require.NoError(t, err)
confName := tempConfigName + ".json"
configFilePath := filepath.Join(configDir, confName)
@ -919,6 +1055,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS", "3")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1 ")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__MIN_TLS_VERSION", "13")
@ -938,8 +1075,10 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__REDIRECT_BASE_URL", "redirect base url")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__USERNAME_FIELD", "preferred_username")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__ROLE_FIELD", "sftpgo_role")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__SCOPES", "openid")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__IMPLICIT_ROLES", "1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__CUSTOM_FIELDS", "field1,field2")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__DEBUG", "1")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ENABLED", "true")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS", "*.example.com,*.example.net")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX", "1")
@ -985,6 +1124,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__MIN_TLS_VERSION")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES")
@ -1002,8 +1142,10 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__REDIRECT_BASE_URL")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__USERNAME_FIELD")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__ROLE_FIELD")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__SCOPES")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__IMPLICIT_ROLES")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__CUSTOM_FIELDS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__DEBUG")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ENABLED")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX")
@ -1044,6 +1186,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 12, bindings[0].MinTLSVersion)
require.True(t, bindings[0].EnableWebAdmin)
require.True(t, bindings[0].EnableWebClient)
require.Equal(t, 0, bindings[0].EnabledLoginMethods)
require.True(t, bindings[0].RenderOpenAPI)
require.Len(t, bindings[0].TLSCipherSuites, 1)
require.Empty(t, bindings[0].OIDC.ConfigURL)
@ -1051,16 +1194,21 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 0, bindings[0].HideLoginURL)
require.False(t, bindings[0].Security.Enabled)
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
require.Len(t, bindings[0].OIDC.Scopes, 3)
require.False(t, bindings[0].OIDC.Debug)
require.Equal(t, 8000, bindings[1].Port)
require.Equal(t, "127.0.0.1", bindings[1].Address)
require.False(t, bindings[1].EnableHTTPS)
require.Equal(t, 12, bindings[0].MinTLSVersion)
require.True(t, bindings[1].EnableWebAdmin)
require.True(t, bindings[1].EnableWebClient)
require.Equal(t, 0, bindings[1].EnabledLoginMethods)
require.True(t, bindings[1].RenderOpenAPI)
require.Nil(t, bindings[1].TLSCipherSuites)
require.Equal(t, 1, bindings[1].HideLoginURL)
require.Empty(t, bindings[1].OIDC.ClientID)
require.Len(t, bindings[1].OIDC.Scopes, 3)
require.False(t, bindings[1].OIDC.Debug)
require.False(t, bindings[1].Security.Enabled)
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
require.Equal(t, "WebClient", bindings[1].Branding.WebClient.ShortName)
@ -1071,6 +1219,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 13, bindings[2].MinTLSVersion)
require.False(t, bindings[2].EnableWebAdmin)
require.False(t, bindings[2].EnableWebClient)
require.Equal(t, 3, bindings[2].EnabledLoginMethods)
require.False(t, bindings[2].RenderOpenAPI)
require.Equal(t, 1, bindings[2].ClientAuthType)
require.Len(t, bindings[2].TLSCipherSuites, 2)
@ -1091,10 +1240,13 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, "redirect base url", bindings[2].OIDC.RedirectBaseURL)
require.Equal(t, "preferred_username", bindings[2].OIDC.UsernameField)
require.Equal(t, "sftpgo_role", bindings[2].OIDC.RoleField)
require.Len(t, bindings[2].OIDC.Scopes, 1)
require.Equal(t, "openid", bindings[2].OIDC.Scopes[0])
require.True(t, bindings[2].OIDC.ImplicitRoles)
require.Len(t, bindings[2].OIDC.CustomFields, 2)
require.Equal(t, "field1", bindings[2].OIDC.CustomFields[0])
require.Equal(t, "field2", bindings[2].OIDC.CustomFields[1])
require.True(t, bindings[2].OIDC.Debug)
require.True(t, bindings[2].Security.Enabled)
require.Len(t, bindings[2].Security.AllowedHosts, 2)
require.Equal(t, "*.example.com", bindings[2].Security.AllowedHosts[0])

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (
@ -29,12 +43,17 @@ const (
const (
actionObjectUser = "user"
actionObjectFolder = "folder"
actionObjectGroup = "group"
actionObjectAdmin = "admin"
actionObjectAPIKey = "api_key"
actionObjectShare = "share"
)
var (
actionsConcurrencyGuard = make(chan struct{}, 100)
)
func executeAction(operation, executor, ip, objectType, objectName string, object plugin.Renderer) {
if plugin.Handler.HasNotifiers() {
plugin.Handler.NotifyProviderEvent(&notifier.ProviderEvent{
@ -55,6 +74,11 @@ func executeAction(operation, executor, ip, objectType, objectName string, objec
}
go func() {
actionsConcurrencyGuard <- struct{}{}
defer func() {
<-actionsConcurrencyGuard
}()
dataAsJSON, err := object.RenderAsJSON(operation != operationDelete)
if err != nil {
providerLog(logger.LevelError, "unable to serialize user as JSON for operation %#v: %v", operation, err)

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !nobolt
// +build !nobolt
@ -1030,9 +1044,7 @@ func (p *BoltProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
})
}
func (p *BoltProvider) deleteFolderMappings(tx *bolt.Tx, folder vfs.BaseVirtualFolder, usersBucket,
groupsBucket *bolt.Bucket,
) error {
func (p *BoltProvider) deleteFolderMappings(folder vfs.BaseVirtualFolder, usersBucket, groupsBucket *bolt.Bucket) error {
for _, username := range folder.Users {
var u []byte
if u = usersBucket.Get([]byte(username)); u == nil {
@ -1112,7 +1124,7 @@ func (p *BoltProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
if err != nil {
return err
}
if err = p.deleteFolderMappings(tx, folder, usersBucket, groupsBucket); err != nil {
if err = p.deleteFolderMappings(folder, usersBucket, groupsBucket); err != nil {
return err
}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build nobolt
// +build nobolt

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package dataprovider provides data access.
// It abstracts different data providers and exposes a common API.
package dataprovider
@ -90,7 +104,7 @@ const (
operationDelete = "delete"
sqlPrefixValidChars = "abcdefghijklmnopqrstuvwxyz_0123456789"
maxHookResponseSize = 1048576 // 1MB
iso8601UTCFormat = "2006-01-02 15:04:05Z"
iso8601UTCFormat = "2006-01-02T15:04:05Z"
)
// Supported algorithms for hashing passwords.
@ -156,6 +170,28 @@ var (
sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
logSender = "dataprovider"
credentialsDirPath string
sqlTableUsers string
sqlTableFolders string
sqlTableFoldersMapping string
sqlTableUsersFoldersMapping string
sqlTableAdmins string
sqlTableAPIKeys string
sqlTableShares string
sqlTableDefenderHosts string
sqlTableDefenderEvents string
sqlTableActiveTransfers string
sqlTableGroups string
sqlTableUsersGroupsMapping string
sqlTableGroupsFoldersMapping string
sqlTableSharedSessions string
sqlTableSchemaVersion string
argon2Params *argon2id.Params
lastLoginMinDelay = 10 * time.Minute
usernameRegex = regexp.MustCompile("^[a-zA-Z0-9-_.~]+$")
tempPath string
)
func initSQLTables() {
sqlTableUsers = "users"
sqlTableFolders = "folders"
sqlTableFoldersMapping = "folders_mapping"
@ -171,11 +207,7 @@ var (
sqlTableGroupsFoldersMapping = "groups_folders_mapping"
sqlTableSharedSessions = "shared_sessions"
sqlTableSchemaVersion = "schema_version"
argon2Params *argon2id.Params
lastLoginMinDelay = 10 * time.Minute
usernameRegex = regexp.MustCompile("^[a-zA-Z0-9-_.~]+$")
tempPath string
)
}
type schemaVersion struct {
Version int
@ -218,6 +250,24 @@ type PasswordValidation struct {
Users PasswordValidationRules `json:"users" mapstructure:"users"`
}
type wrappedFolder struct {
Folder vfs.BaseVirtualFolder
}
func (w *wrappedFolder) RenderAsJSON(reload bool) ([]byte, error) {
if reload {
folder, err := provider.getFolderByName(w.Folder.Name)
if err != nil {
providerLog(logger.LevelError, "unable to reload folder before rendering as json: %v", err)
return nil, err
}
folder.PrepareForRendering()
return json.Marshal(folder)
}
w.Folder.PrepareForRendering()
return json.Marshal(w.Folder)
}
// ObjectsActions defines the action to execute on user create, update, delete for the specified objects
type ObjectsActions struct {
// Valid values are add, update, delete. Empty slice to disable
@ -707,11 +757,18 @@ func SetTempPath(fsPath string) {
tempPath = fsPath
}
func checkSharedMode() {
if !util.Contains(sharedProviders, config.Driver) {
config.IsShared = 0
}
}
// Initialize the data provider.
// An error is returned if the configured driver is invalid or if the data provider cannot be initialized
func Initialize(cnf Config, basePath string, checkAdmins bool) error {
var err error
config = cnf
checkSharedMode()
config.Actions.ExecuteOn = util.RemoveDuplicates(config.Actions.ExecuteOn, true)
config.Actions.ExecuteFor = util.RemoveDuplicates(config.Actions.ExecuteFor, true)
@ -822,6 +879,7 @@ func initializeHashingAlgo(cnf *Config) error {
}
func validateSQLTablesPrefix() error {
initSQLTables()
if config.SQLTablesPrefix != "" {
for _, char := range config.SQLTablesPrefix {
if !strings.Contains(sqlPrefixValidChars, strings.ToLower(string(char))) {
@ -831,17 +889,24 @@ func validateSQLTablesPrefix() error {
sqlTableUsers = config.SQLTablesPrefix + sqlTableUsers
sqlTableFolders = config.SQLTablesPrefix + sqlTableFolders
sqlTableFoldersMapping = config.SQLTablesPrefix + sqlTableFoldersMapping
sqlTableUsersFoldersMapping = config.SQLTablesPrefix + sqlTableUsersFoldersMapping
sqlTableAdmins = config.SQLTablesPrefix + sqlTableAdmins
sqlTableAPIKeys = config.SQLTablesPrefix + sqlTableAPIKeys
sqlTableShares = config.SQLTablesPrefix + sqlTableShares
sqlTableDefenderEvents = config.SQLTablesPrefix + sqlTableDefenderEvents
sqlTableDefenderHosts = config.SQLTablesPrefix + sqlTableDefenderHosts
sqlTableActiveTransfers = config.SQLTablesPrefix + sqlTableActiveTransfers
sqlTableGroups = config.SQLTablesPrefix + sqlTableGroups
sqlTableUsersGroupsMapping = config.SQLTablesPrefix + sqlTableUsersGroupsMapping
sqlTableGroupsFoldersMapping = config.SQLTablesPrefix + sqlTableGroupsFoldersMapping
sqlTableSharedSessions = config.SQLTablesPrefix + sqlTableSharedSessions
sqlTableSchemaVersion = config.SQLTablesPrefix + sqlTableSchemaVersion
providerLog(logger.LevelDebug, "sql table for users %#v, folders %#v folders mapping %#v admins %#v "+
"api keys %#v shares %#v defender hosts %#v defender events %#v transfers %#v schema version %#v",
sqlTableUsers, sqlTableFolders, sqlTableFoldersMapping, sqlTableAdmins, sqlTableAPIKeys,
sqlTableShares, sqlTableDefenderHosts, sqlTableDefenderEvents, sqlTableActiveTransfers, sqlTableSchemaVersion)
providerLog(logger.LevelDebug, "sql table for users %q, folders %q users folders mapping %q admins %q "+
"api keys %q shares %q defender hosts %q defender events %q transfers %q groups %q "+
"users groups mapping %q groups folders mapping %q shared sessions %q schema version %q",
sqlTableUsers, sqlTableFolders, sqlTableUsersFoldersMapping, sqlTableAdmins, sqlTableAPIKeys,
sqlTableShares, sqlTableDefenderHosts, sqlTableDefenderEvents, sqlTableActiveTransfers, sqlTableGroups,
sqlTableUsersGroupsMapping, sqlTableGroupsFoldersMapping, sqlTableSharedSessions, sqlTableSchemaVersion)
}
return nil
}
@ -934,6 +999,9 @@ func CheckAdminAndPass(username, password, ip string) (Admin, error) {
// CheckCachedUserCredentials checks the credentials for a cached user
func CheckCachedUserCredentials(user *CachedUser, password, loginMethod, protocol string, tlsCert *x509.Certificate) error {
if err := user.User.CheckLoginConditions(); err != nil {
return err
}
if loginMethod != LoginMethodPassword {
_, err := checkUserAndTLSCertificate(&user.User, protocol, tlsCert)
if err != nil {
@ -946,9 +1014,6 @@ func CheckCachedUserCredentials(user *CachedUser, password, loginMethod, protoco
return nil
}
}
if err := user.User.CheckLoginConditions(); err != nil {
return err
}
if password == "" {
return ErrInvalidCredentials
}
@ -1726,15 +1791,20 @@ func GetUsersForQuotaCheck(toFetch map[string]bool) ([]User, error) {
}
// AddFolder adds a new virtual folder.
func AddFolder(folder *vfs.BaseVirtualFolder) error {
func AddFolder(folder *vfs.BaseVirtualFolder, executor, ipAddress string) error {
folder.Name = config.convertName(folder.Name)
return provider.addFolder(folder)
err := provider.addFolder(folder)
if err == nil {
executeAction(operationAdd, executor, ipAddress, actionObjectFolder, folder.Name, &wrappedFolder{Folder: *folder})
}
return err
}
// UpdateFolder updates the specified virtual folder
func UpdateFolder(folder *vfs.BaseVirtualFolder, users []string, groups []string, executor, ipAddress string) error {
err := provider.updateFolder(folder)
if err == nil {
executeAction(operationUpdate, executor, ipAddress, actionObjectFolder, folder.Name, &wrappedFolder{Folder: *folder})
usersInGroups, errGrp := provider.getUsersInGroups(groups)
if errGrp == nil {
users = append(users, usersInGroups...)
@ -1765,6 +1835,7 @@ func DeleteFolder(folderName, executor, ipAddress string) error {
}
err = provider.deleteFolder(folder)
if err == nil {
executeAction(operationDelete, executor, ipAddress, actionObjectFolder, folder.Name, &wrappedFolder{Folder: folder})
users := folder.Users
usersInGroups, errGrp := provider.getUsersInGroups(folder.Groups)
if errGrp == nil {
@ -3326,6 +3397,11 @@ func ExecutePostLoginHook(user *User, loginMethod, ip, protocol string, err erro
}
go func() {
actionsConcurrencyGuard <- struct{}{}
defer func() {
<-actionsConcurrencyGuard
}()
status := "0"
if err == nil {
status = "1"

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (
@ -1993,7 +2007,7 @@ func (p *MemoryProvider) restoreFolders(dump *BackupData) error {
}
} else {
folder.Users = nil
err = AddFolder(&folder)
err = AddFolder(&folder, ActionExecutorSystem, "")
if err != nil {
providerLog(logger.LevelError, "error adding folder %#v: %v", folder.Name, err)
return err

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !nomysql
// +build !nomysql

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build nomysql
// +build nomysql

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !nopgsql
// +build !nopgsql
@ -145,7 +159,7 @@ ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folder
FOREIGN KEY ("folder_id") REFERENCES "{{folders}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}groups_folders_mapping_group_id_idx" ON "{{groups_folders_mapping}}" ("group_id");
ALTER TABLE "{{groups_folders_mapping}}" ADD CONSTRAINT "{{prefix}}groups_folders_mapping_group_id_fk_groups_id"
FOREIGN KEY ("group_id") REFERENCES "groups" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
FOREIGN KEY ("group_id") REFERENCES "{{groups}}" ("id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE CASCADE;
CREATE INDEX "{{prefix}}groups_updated_at_idx" ON "{{groups}}" ("updated_at");
`
pgsqlV17DownSQL = `DROP TABLE "{{users_groups_mapping}}" CASCADE;

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build nopgsql
// +build nopgsql

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (
@ -55,6 +69,7 @@ func sqlReplaceAll(sql string) string {
sql = strings.ReplaceAll(sql, "{{defender_events}}", sqlTableDefenderEvents)
sql = strings.ReplaceAll(sql, "{{defender_hosts}}", sqlTableDefenderHosts)
sql = strings.ReplaceAll(sql, "{{active_transfers}}", sqlTableActiveTransfers)
sql = strings.ReplaceAll(sql, "{{shared_sessions}}", sqlTableSharedSessions)
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
return sql
}
@ -655,10 +670,13 @@ func sqlCommonDumpGroups(dbHandle sqlQuerier) ([]Group, error) {
if err != nil {
return groups, err
}
group.PrepareForRendering()
groups = append(groups, group)
}
return groups, rows.Err()
err = rows.Err()
if err != nil {
return groups, err
}
return getGroupsWithVirtualFolders(ctx, groups, dbHandle)
}
func sqlCommonGetUsersInGroups(names []string, dbHandle sqlQuerier) ([]string, error) {

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build !nosqlite
// +build !nosqlite
@ -115,12 +129,12 @@ CREATE TABLE "{{groups_folders_mapping}}" ("id" integer NOT NULL PRIMARY KEY AUT
"virtual_path" text NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL,
CONSTRAINT "{{prefix}}unique_group_folder_mapping" UNIQUE ("group_id", "folder_id"));
CREATE TABLE "{{users_groups_mapping}}" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" integer NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"group_id" integer NOT NULL REFERENCES "groups" ("id") ON DELETE NO ACTION,
"user_id" integer NOT NULL REFERENCES "{{users}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"group_id" integer NOT NULL REFERENCES "{{groups}}" ("id") ON DELETE NO ACTION,
"group_type" integer NOT NULL, CONSTRAINT "{{prefix}}unique_user_group_mapping" UNIQUE ("user_id", "group_id"));
CREATE TABLE "new__folders_mapping" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" integer NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"folder_id" integer NOT NULL REFERENCES "folders" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"user_id" integer NOT NULL REFERENCES "{{users}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"folder_id" integer NOT NULL REFERENCES "{{folders}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"virtual_path" text NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL,
CONSTRAINT "{{prefix}}unique_user_folder_mapping" UNIQUE ("user_id", "folder_id"));
INSERT INTO "new__folders_mapping" ("id", "virtual_path", "quota_size", "quota_files", "folder_id", "user_id") SELECT "id",
@ -139,8 +153,8 @@ CREATE INDEX "{{prefix}}groups_folders_mapping_group_id_idx" ON "{{groups_folder
DROP TABLE "{{groups_folders_mapping}}";
DROP TABLE "{{groups}}";
CREATE TABLE "new__folders_mapping" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"user_id" integer NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"folder_id" integer NOT NULL REFERENCES "folders" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"user_id" integer NOT NULL REFERENCES "{{users}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"folder_id" integer NOT NULL REFERENCES "{{folders}}" ("id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
"virtual_path" text NOT NULL, "quota_size" bigint NOT NULL, "quota_files" integer NOT NULL,
CONSTRAINT "{{prefix}}unique_folder_mapping" UNIQUE ("user_id", "folder_id"));
INSERT INTO "new__folders_mapping" ("id", "virtual_path", "quota_size", "quota_files", "folder_id", "user_id") SELECT "id",
@ -652,6 +666,7 @@ func updateSQLiteDatabaseFrom16To17(dbHandle *sql.DB) error {
}
sql := strings.ReplaceAll(sqliteV17SQL, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{groups}}", sqlTableGroups)
sql = strings.ReplaceAll(sql, "{{users}}", sqlTableUsers)
sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
sql = strings.ReplaceAll(sql, "{{folders_mapping}}", sqlTableFoldersMapping)
sql = strings.ReplaceAll(sql, "{{users_folders_mapping}}", sqlTableUsersFoldersMapping)

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build nosqlite
// +build nosqlite

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (
@ -33,6 +47,14 @@ func getSQLPlaceholders() []string {
return placeholders
}
func getSQLTableGroups() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("`%s`", sqlTableGroups)
}
return sqlTableGroups
}
func getAddSessionQuery() string {
if config.Driver == MySQLDataProviderName {
return fmt.Sprintf("INSERT INTO %s (`key`,`data`,`type`,`timestamp`) VALUES (%s,%s,%s,%s) "+
@ -139,7 +161,7 @@ func getDefenderEventsCleanupQuery() string {
}
func getGroupByNameQuery() string {
return fmt.Sprintf(`SELECT %s FROM %s WHERE name = %s`, selectGroupFields, sqlTableGroups, sqlPlaceholders[0])
return fmt.Sprintf(`SELECT %s FROM %s WHERE name = %s`, selectGroupFields, getSQLTableGroups(), sqlPlaceholders[0])
}
func getGroupsQuery(order string, minimal bool) string {
@ -149,7 +171,7 @@ func getGroupsQuery(order string, minimal bool) string {
} else {
fieldSelection = selectGroupFields
}
return fmt.Sprintf(`SELECT %s FROM %s ORDER BY name %s LIMIT %v OFFSET %v`, fieldSelection, sqlTableGroups,
return fmt.Sprintf(`SELECT %s FROM %s ORDER BY name %s LIMIT %v OFFSET %v`, fieldSelection, getSQLTableGroups(),
order, sqlPlaceholders[0], sqlPlaceholders[1])
}
@ -168,7 +190,7 @@ func getGroupsWithNamesQuery(numArgs int) string {
} else {
sb.WriteString("('')")
}
return fmt.Sprintf(`SELECT %s FROM %s WHERE name in %s`, selectGroupFields, sqlTableGroups, sb.String())
return fmt.Sprintf(`SELECT %s FROM %s WHERE name in %s`, selectGroupFields, getSQLTableGroups(), sb.String())
}
func getUsersInGroupsQuery(numArgs int) string {
@ -187,27 +209,27 @@ func getUsersInGroupsQuery(numArgs int) string {
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)))`,
sqlTableUsers, sqlTableUsersGroupsMapping, sqlTableGroups, sb.String())
sqlTableUsers, sqlTableUsersGroupsMapping, getSQLTableGroups(), sb.String())
}
func getDumpGroupsQuery() string {
return fmt.Sprintf(`SELECT %s FROM %s`, selectGroupFields, sqlTableGroups)
return fmt.Sprintf(`SELECT %s FROM %s`, selectGroupFields, getSQLTableGroups())
}
func getAddGroupQuery() string {
return fmt.Sprintf(`INSERT INTO %s (name,description,created_at,updated_at,user_settings)
VALUES (%v,%v,%v,%v,%v)`, sqlTableGroups, sqlPlaceholders[0], sqlPlaceholders[1],
VALUES (%v,%v,%v,%v,%v)`, getSQLTableGroups(), sqlPlaceholders[0], sqlPlaceholders[1],
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4])
}
func getUpdateGroupQuery() string {
return fmt.Sprintf(`UPDATE %s SET description=%v,user_settings=%v,updated_at=%v
WHERE name = %s`, sqlTableGroups, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
WHERE name = %s`, getSQLTableGroups(), sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
sqlPlaceholders[3])
}
func getDeleteGroupQuery() string {
return fmt.Sprintf(`DELETE FROM %s WHERE name = %s`, sqlTableGroups, sqlPlaceholders[0])
return fmt.Sprintf(`DELETE FROM %s WHERE name = %s`, getSQLTableGroups(), sqlPlaceholders[0])
}
func getAdminByUsernameQuery() string {
@ -519,19 +541,19 @@ func getClearUserGroupMappingQuery() string {
func getAddUserGroupMappingQuery() string {
return fmt.Sprintf(`INSERT INTO %v (user_id,group_id,group_type) VALUES ((SELECT id FROM %v WHERE username = %v),
(SELECT id FROM %v WHERE name = %v),%v)`,
sqlTableUsersGroupsMapping, sqlTableUsers, sqlPlaceholders[0], sqlTableGroups, sqlPlaceholders[1], sqlPlaceholders[2])
sqlTableUsersGroupsMapping, sqlTableUsers, sqlPlaceholders[0], getSQLTableGroups(), sqlPlaceholders[1], sqlPlaceholders[2])
}
func getClearGroupFolderMappingQuery() string {
return fmt.Sprintf(`DELETE FROM %v WHERE group_id = (SELECT id FROM %v WHERE name = %v)`, sqlTableGroupsFoldersMapping,
sqlTableGroups, sqlPlaceholders[0])
getSQLTableGroups(), sqlPlaceholders[0])
}
func getAddGroupFolderMappingQuery() string {
return fmt.Sprintf(`INSERT INTO %v (virtual_path,quota_size,quota_files,folder_id,group_id)
VALUES (%v,%v,%v,(SELECT id FROM %v WHERE name = %v),(SELECT id FROM %v WHERE name = %v))`,
sqlTableGroupsFoldersMapping, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlTableFolders,
sqlPlaceholders[3], sqlTableGroups, sqlPlaceholders[4])
sqlPlaceholders[3], getSQLTableGroups(), sqlPlaceholders[4])
}
func getClearUserFolderMappingQuery() string {
@ -585,7 +607,7 @@ func getRelatedGroupsForUsersQuery(users []User) string {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT g.name,ug.group_type,ug.user_id FROM %v g INNER JOIN %v ug ON g.id = ug.group_id WHERE
ug.user_id IN %v ORDER BY ug.user_id`, sqlTableGroups, sqlTableUsersGroupsMapping, sb.String())
ug.user_id IN %v ORDER BY ug.user_id`, getSQLTableGroups(), sqlTableUsersGroupsMapping, sb.String())
}
func getRelatedFoldersForUsersQuery(users []User) string {
@ -637,7 +659,7 @@ func getRelatedGroupsForFoldersQuery(folders []vfs.BaseVirtualFolder) string {
sb.WriteString(")")
}
return fmt.Sprintf(`SELECT fm.folder_id,g.name FROM %v fm INNER JOIN %v g ON fm.group_id = g.id
WHERE fm.folder_id IN %v ORDER BY fm.folder_id`, sqlTableGroupsFoldersMapping, sqlTableGroups, sb.String())
WHERE fm.folder_id IN %v ORDER BY fm.folder_id`, sqlTableGroupsFoldersMapping, getSQLTableGroups(), sb.String())
}
func getRelatedUsersForGroupsQuery(groups []Group) string {

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package dataprovider
import (
@ -194,6 +208,26 @@ func (u *User) checkDirWithParents(virtualDirPath, connectionID string) error {
return nil
}
func (u *User) checkLocalHomeDir(connectionID string) {
switch u.FsConfig.Provider {
case sdk.LocalFilesystemProvider, sdk.CryptedFilesystemProvider:
return
default:
osFs := vfs.NewOsFs(connectionID, u.GetHomeDir(), "")
osFs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
}
}
func (u *User) checkRootPath(connectionID string) error {
fs, err := u.GetFilesystemForPath("/", connectionID)
if err != nil {
logger.Warn(logSender, connectionID, "could not create main filesystem for user %q err: %v", u.Username, err)
return fmt.Errorf("could not create root filesystem: %w", err)
}
fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
return nil
}
// CheckFsRoot check the root directory for the main fs and the virtual folders.
// It returns an error if the main filesystem cannot be created
func (u *User) CheckFsRoot(connectionID string) error {
@ -209,15 +243,16 @@ func (u *User) CheckFsRoot(connectionID string) error {
}
if isLastActivityRecent(u.LastLogin, delay) {
if u.LastLogin > u.UpdatedAt {
if config.IsShared == 1 {
u.checkLocalHomeDir(connectionID)
}
return nil
}
}
fs, err := u.GetFilesystemForPath("/", connectionID)
err := u.checkRootPath(connectionID)
if err != nil {
logger.Warn(logSender, connectionID, "could not create main filesystem for user %#v err: %v", u.Username, err)
return err
}
fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
if u.Filters.StartDirectory != "" {
err = u.checkDirWithParents(u.Filters.StartDirectory, connectionID)
if err != nil {
@ -227,7 +262,7 @@ func (u *User) CheckFsRoot(connectionID string) error {
}
for idx := range u.VirtualFolders {
v := &u.VirtualFolders[idx]
fs, err = u.GetFilesystemForPath(v.VirtualPath, connectionID)
fs, err := u.GetFilesystemForPath(v.VirtualPath, connectionID)
if err == nil {
fs.CheckRootPath(u.Username, u.GetUID(), u.GetGID())
}

View file

@ -4,12 +4,14 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
## Supported tags and respective Dockerfile links
- [v2.3.0, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile)
- [v2.3.0-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.alpine)
- [v2.3.0-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile)
- [v2.3.0-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.alpine)
- [v2.3.0-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.0/Dockerfile.distroless)
- [v2.3.6, v2.3, v2, latest](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile)
- [v2.3.6-plugins, v2.3-plugins, v2-plugins, plugins](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile)
- [v2.3.6-alpine, v2.3-alpine, v2-alpine, alpine](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile.alpine)
- [v2.3.6-slim, v2.3-slim, v2-slim, slim](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile)
- [v2.3.6-alpine-slim, v2.3-alpine-slim, v2-alpine-slim, alpine-slim](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile.alpine)
- [v2.3.6-distroless-slim, v2.3-distroless-slim, v2-distroless-slim, distroless-slim](https://github.com/drakkan/sftpgo/blob/v2.3.6/Dockerfile.distroless)
- [edge](../Dockerfile)
- [edge-plugins](../Dockerfile)
- [edge-alpine](../Dockerfile.alpine)
- [edge-slim](../Dockerfile)
- [edge-alpine-slim](../Dockerfile.alpine)
@ -197,7 +199,11 @@ We only provide the slim variant and so the optional `git` dependency is not ava
### `sftpgo:<suite>-slim`
These tags provide a slimmer image that does not include the optional `git` dependency.
These tags provide a slimmer image that does not include `jq` and the optional `git` and `rsync` dependencies.
### `sftpgo:<suite>-plugins`
These tags provide the standard image with the addition of all "official" plugins installed in `/usr/local/bin`.
## Helm Chart

View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
ARCH=`uname -m`
case ${ARCH} in
"x86_64")
SUFFIX=amd64
;;
"aarch64")
SUFFIX=arm64
;;
*)
SUFFIX=ppc64le
;;
esac
echo "download plugins for arch ${SUFFIX}"
for PLUGIN in geoipfilter kms pubsub eventstore eventsearch 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}"
chmod 755 "/usr/local/bin/sftpgo-plugin-${PLUGIN}"
done

View file

@ -29,7 +29,7 @@ Version info, such as git commit and build date, can be embedded setting the fol
For example, you can build using the following command:
```bash
go build -tags nogcs,nos3,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
go build -tags nogcs,nos3,nosqlite -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=`git describe --always --abbrev=8 --dirty` -X github.com/drakkan/sftpgo/v2/version.date=`date -u +%FT%TZ`" -o sftpgo
```
You should get a version that includes git commit, build date and available features like this one:

View file

@ -83,6 +83,8 @@ The `actions` struct inside the `data_provider` configuration section allows you
The supported object types are:
- `user`
- `folder`
- `group`
- `admin`
- `api_key`

View file

@ -220,7 +220,7 @@ The configuration file contains the following sections:
- `users_base_dir`, string. Users default base directory. If no home dir is defined while adding a new user, and this value is a valid absolute path, then the user home dir will be automatically defined as the path obtained joining the base dir and the username
- `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See [Custom Actions](./custom-actions.md) for more details
- `execute_on`, list of strings. Valid values are `add`, `update`, `delete`. `update` action will not be fired for internal updates such as the last login or the user quota fields.
- `execute_for`, list of strings. Defines the provider objects that trigger the action. Valid values are `user`, `admin`, `api_key`.
- `execute_for`, list of strings. Defines the provider objects that trigger the action. Valid values are `user`, `folder`, `group`, `admin`, `api_key`, `share`.
- `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
- `external_auth_hook`, string. Absolute path to an external program or an HTTP URL to invoke for users authentication. See [External Authentication](./external-auth.md) for more details. Leave empty to disable.
- `external_auth_scope`, integer. 0 means all supported authentication scopes (passwords, public keys and keyboard interactive). 1 means passwords only. 2 means public keys only. 4 means key keyboard interactive only. 8 means TLS certificate. The flags can be combined, for example 6 means public keys and keyboard interactive
@ -260,6 +260,8 @@ The configuration file contains the following sections:
- `enable_web_admin`, boolean. Set to `false` to disable the built-in web admin for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web admin interface. Default `true`.
- `enable_web_client`, boolean. Set to `false` to disable the built-in web client for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web client interface. Default `true`.
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
- `enabled_login_methods`, integer. Defines the login methods available for the WebAdmin and WebClient UIs. `0` means any configured method: username/password login form and OIDC, if enabled. `1` means OIDC for the WebAdmin UI. `2` means OIDC for the WebClient UI. `4` means login form for the WebAdmin UI. `8` means login form for the WebClient UI. You can combine the values. For example `3` means that you can only login using OIDC on both WebClient and WebAdmin UI. Default: `0`.
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
- `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Binding specific private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If not set the global ones will be used, if any.
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
@ -279,9 +281,11 @@ The configuration file contains the following sections:
- `client_secret`, string. Defines the application's secret. Default: blank.
- `redirect_base_url`, string. Defines the base URL to redirect to after OpenID authentication. The suffix `/web/oidc/redirect` will be added to this base URL, adding also the `web_root` if configured. Default: blank.
- `username_field`, string. Defines the ID token claims field to map to the SFTPGo username. Default: blank.
- `role_field`, string. Defines the optional ID token claims field to map to a SFTPGo role. If the defined ID token claims field is set to `admin` the authenticated user is mapped to an SFTPGo admin. You don't need to specify this field if you want to use OpenID only for the Web Client UI. Default: blank.
- `scopes`, list of strings. Request the OAuth provider to provide the scope information from an authenticated users. The `openid` scope is mandatory. Default: `"openid", "profile", "email"`.
- `role_field`, string. Defines the optional ID token claims field to map to a SFTPGo role. If the defined ID token claims field is set to `admin` the authenticated user is mapped to an SFTPGo admin. You don't need to specify this field if you want to use OpenID only for the Web Client UI. If the field is inside a nested structure, you can use the dot notation to traverse the structures. Default: blank.
- `implicit_roles`, boolean. If set, the `role_field` is ignored and the SFTPGo role is assumed based on the login link used. Default: `false`.
- `custom_fields`, list of strings. Custom token claims fields to pass to the pre-login hook. Default: empty.
- `debug`, boolean. If set, the received id tokens will be logged at debug level. Default: `false`.
- `security`, struct. Defines security headers to add to HTTP responses and allows to restrict allowed hosts. The following parameters are supported:
- `enabled`, boolean. Set to `true` to enable security configurations. Default: `false`.
- `allowed_hosts`, list of strings. Fully qualified domain names that are allowed. An empty list allows any and all host names. Default: empty.
@ -330,6 +334,7 @@ The configuration file contains the following sections:
- `setup` struct containing configurations for the initial setup screen
- `installation_code`, string. If set, this installation code will be required when creating the first admin account. Please note that even if set using an environment variable this field is read at SFTPGo startup and not at runtime. This is not a license key or similar, the purpose here is to prevent anyone who can access to the initial setup screen from creating an admin user. Default: blank.
- `installation_code_hint`, string. Description for the installation code input field. Default: `Installation code`.
- `hide_support_link`, boolean. If set, the link to the [sponsors section](../README.md#sponsors) will not appear on the setup screen page. Default: `false`.
- **"telemetry"**, the configuration for the telemetry server, more details [below](#telemetry-server)
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 0
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: `127.0.0.1`

View file

@ -1,6 +1,7 @@
# OpenID Connect
OpenID Connect integration allows you to map your identity provider users to SFTPGo admins/users and so you can login to SFTPGo Web Client and Web Admin user interfaces using your identity provider.
OpenID Connect integration allows you to map your identity provider users to SFTPGo admins/users,
so you can login to SFTPGo Web Client and Web Admin user interfaces, using your own identity provider.
SFTPGo allows to configure per-binding OpenID Connect configurations. The supported configuration parameters are documented within the `oidc` section [here](./full-configuration.md).
@ -41,6 +42,11 @@ Add the following configuration parameters to the SFTPGo configuration file (or
"client_secret": "jRsmE0SWnuZjP7djBqNq0mrf8QN77j2c",
"config_url": "http://192.168.1.12:8086/auth/realms/sftpgo",
"redirect_base_url": "http://192.168.1.50:8080",
"scopes": [
"openid",
"profile",
"email"
],
"username_field": "preferred_username",
"role_field": "sftpgo_role",
"implicit_roles": false,
@ -104,8 +110,12 @@ And the following is an example ID token which allows the SFTPGo user `user1` to
```
SFTPGo users (not admins) can be created/updated after successful OpenID authentication by defining a [pre-login hook](./dynamic-user-mod.md).
You can use the `custom_fields` configuration parameter to define the token claims field names to pass to the pre-login hook, these fields are useful for implementing custom logic when creating/updating the SFTPGo user within the hook.
For example you can set the field `sftpgo_home_dir` in your identity provider and add it to the `custom_fields` in the SFTPGo configuration like this:
You can use `scopes` configuration to request additional information (claims) about authenticated users (See your provider's own documentation for more information).
By default the scopes `"openid", "profile", "email"` are retrieved.
The `custom_fields` configuration parameter can be used to define claim field names to pass to the pre-login hook,
these fields can be used e.g. for implementing custom logic when creating/updating the SFTPGo user within the hook.
For example, if you have created a scope with name `sftpgo` in your identity provider to provide a claim for `sftpgo_home_dir` ,
then you can add it to the `custom_fields` in the SFTPGo configuration like this:
```json
...
@ -115,6 +125,7 @@ For example you can set the field `sftpgo_home_dir` in your identity provider an
"config_url": "http://192.168.1.12:8086/auth/realms/sftpgo",
"redirect_base_url": "http://192.168.1.50:8080",
"username_field": "preferred_username",
"scopes": [ "openid", "profile", "email", "sftpgo" ],
"role_field": "sftpgo_role",
"custom_fields": ["sftpgo_home_dir"]
}

61
docs/repo.md Normal file
View file

@ -0,0 +1,61 @@
# SFTPGo repositories
These repositories are available through Oregon State University's free mirroring service. Special thanks to Lance Albertson, Director of the Oregon State University Open Source Lab, who helped me with the initial setup.
## APT repo
Supported distributions:
- Debian 10 "buster"
- Debian 11 "bullseye"
Import the public key used by the package management system using the following command:
```shell
curl -sS https://ftp.osuosl.org/pub/sftpgo/apt/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/sftpgo-archive-keyring.gpg
```
If you receive an error indicating that `gnupg` is not installed, you can install it using the following command:
```shell
sudo apt install gnupg
```
Create the SFTPGo source list file:
```shell
CODENAME=`lsb_release -c -s`
echo "deb [signed-by=/usr/share/keyrings/sftpgo-archive-keyring.gpg] https://ftp.osuosl.org/pub/sftpgo/apt ${CODENAME} main" | sudo tee /etc/apt/sources.list.d/sftpgo.list
```
Reload the package database and install SFTPGo:
```shell
sudo apt update
sudo apt install sftpgo
```
## YUM repo
The YUM repository supports generic Red Hat based distributions.
Create the SFTPGo repository using the following command:
```shell
ARCH=`uname -m`
curl -sS https://ftp.osuosl.org/pub/sftpgo/yum/${ARCH}/sftpgo.repo | sudo tee /etc/yum.repos.d/sftpgo.repo
```
Reload the package database and install SFTPGo:
```shell
sudo yum update
sudo yum install sftpgo
```
Start the SFTPGo service and enable it to start at system boot:
```shell
sudo systemctl start sftpgo
sudo systemctl enable sftpgo
```

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ftpd_test
import (

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Package ftpd implements the FTP protocol
package ftpd

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ftpd_test
import (
@ -77,154 +91,154 @@ 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==
QXV0aDAeFw0yMjA3MDQxNTQzMTFaFw0yNDAxMDQxNTUzMDhaMBMxETAPBgNVBAMT
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4eyDJkmW
D4OVYo7ddgiZkd6QQdPyLcsa31Wc9jdR2/peEabyNT8jSWteS6ouY84GRlnhfFeZ
mpXgbaUJu/Z8Y/8riPxwL8XF4vCScQDMywpQnVUd6E9x2/+/uaD4p/BBswgKqKPe
uDcHZn7MkD4QlquUhMElDrBUi1Dv/AVHnQ6iP4vd5Jlv0F+40jdq/8Wa7yhW7Pu5
iNvPwCk8HjENBKVur/re+Acif8A2TlbCsuOnVduSQNmnWH+iZmB9upyBZtUszGS0
JhUwtSnwUX/JapF70Pwte/PV3RK8cJ5FjuAPNeTyJvSuMTELFSAyCeiNynFGgyhW
cqbEiPu6BURLculyVkmh4dOrhTrYZv/n3UJAhyxkdYrbh3INHmTa4izvclcuwoEo
lFlJp3l77D0lIi+pbtcBV6ys7reyuxUAkBNwnpt2pWfCQoi4QYKcNbHm47c2phOb
QSojQ8SsNU5bnlY2MDzkKo5DPav/i4d0HpndphUpx4f8hA0KylLevDRkMz9TAH7H
uDssn0CxFOGHiveEAGGbn+doHjNWM339x/cdLbK0vuieDKby8YYcBY1JML57Dl9f
rs52ySnDZbMqOb9zF66mQpC2FZoAj713xSkDSnSCUekrqgck1EA1ifxAviHt+p26
JwaEDL7Lk01EEdYN4csSd1fezbCqTrG8ffUCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPirPBPO01zUuf7xC+ds
bOOY5QvAMA0GCSqGSIb3DQEBCwUAA4ICAQBUYa+ydfTPKjTN4lXyEZgchZQ+juny
aMy1xosLz6Evj0us2Bwczmy6X2Zvaw/KteFlgKaU1Ex2UkU7FfAlaH0HtwTLFMVM
p9nB7ZzStvg0n8zFM29SEkOFwZ9FRonxx4sY3FdvI4QvAWyDyqgOl8+Eedg0kC4+
M7hxarTFmZZ7POZl8Hio592yx3asMmSCcmb7oUCKVI98qsf9fuL+LIZSpn4fE7av
AiNBcOqCZ10CRnl4VSgAW2LH4oqROYdUv+me1u1YRwh7fCF/R7VjOLuaDzv0mp/g
hzG9U+Yso3WV4b28MsctwUmGTK8Zc5QaANKgmI3ulkta37wN5KjrUuescHC7MqZg
vN9n60801be1EoUL83KUx57Bix95YZR02Zge0gYdYTb+E2bwaZ4GMlf7cs6qmC6A
ZPLR7Tffw2J4dPTcfEx3rPZ91s3MkAdPzYYGdGlbKp8RCFnezZ7rw2z57rnT0zDr
LuL3Q6ADBfothoos/EBIC5ekXb9czp8gig+nJXLC6jlqcQpCLrV88oS3+8zACmx1
d6tje9uuAqPgiQGddKZj4b4BlHmAMXq0PufQsZVoyzboTewZiLVCtTR9/iF7Cepg
6EVv57p61pFhPu8lNRAi0aH/po9yt+7435FGpn2kan6k9aDIVdaqeuxxITwsqJ4R
WwSa13hh6yjoDQ==
-----END CERTIFICATE-----`
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=
MjIwNzA0MTU1MzU4WhcNMjQwNzAzMTU1MzU4WjAkMCICEQDZo5Q3lhxFuDUsxGNm
794YFw0yMjA3MDQxNTUzNThaoCMwITAfBgNVHSMEGDAWgBT4qzwTztNc1Ln+8Qvn
bGzjmOULwDANBgkqhkiG9w0BAQsFAAOCAgEA1lK6g8qmhyY6myx8342dDuaauY03
0iojkxpasuYcytK6XRm96YqjZK9EETxsHHViVU0vCXES60D6wJ9gw4fTWn3WxEdx
nIwbGyjUGHh2y+R3uQsfvwxsdYvDsTLAnOLwOo68dAHWmMDZRmgTuGNoYFxVQRGR
Cn90ZR7LPLpCScclWM8FE/W1B90x3ZE8EhJiCI/WyyTh3EgshmB7A5GoDrFZfmvR
dzoTKO+F9p2XjtmgfiBE3czWQysfATmbutZUbG/ZRb89u+ZEUyPoC94mg8fhNWoX
1d5G9QAkZFHp957/5QHLq9OHNfnWXoohhebjF4VWqZH7w+RtLc8t0PIog2lX4t1o
5N/xFk9akvuoyNGg/fYuJBmN162Q0MdeYfYKDGWdXxf6fpHxVr5v2JrIx6gOwubb
cIKP22ZBv/PYOeFsAZ755lTl4OTFUjU5ZJEPD6pUc1daaIqfxsxu8gDZP92FZjsB
zaalMbh30n2OhagSMBzSLg5rE6WmBzlQX0ZN8YrW4l2Vq6twnnFHY+UyblRZS+d4
oHBaoOaxPEkLxNZ8ulzJS4B6c4D1CXOaBEf++snVzRRUOEdX3x7TvkkrLvIsm06R
ux0L1zJb9LbZ/1rhuv70z/kIlD55sqYuRqu3RpgTgZuTERU//rYIqWd03Y5Qon8i
VoC6Yp9DPldQJrk=
-----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=
MIIEITCCAgmgAwIBAgIRAJla/m/UkZMifNwG+DxFr2MwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjIwNzA0MTU0MzM3WhcNMjQwMTA0MTU1
MzA3WjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA8xM5v+2QfdzfwnNT5cl+6oEy2fZoI2YG6L6c25rG0pr+yl1IHKdM
Zcvn93uat7hlbzxeOLfJRM7+QK1lLaxuppq9p+gT+1x9eG3E4X7e0pdbjrpJGbvN
ji0hwDBLDWD8mHNq/SCk9FKtGnfZqrNB5BLw2uIKjJzVGXVlsjN6geBDm2hVjTSm
zMr39CfLUdtvMaZhpIPJzbH+sNfp1zKavFIpmwCd77p/z0QAiQ9NaIvzv4PZDDEE
MUHzmVAU6bUjD8GToXaMbRiz694SU8aAwvvcdjGexdbHnfSAfLOl2wTPPxvePncR
aa656ZeZWxY9pRCItP+v43nm7d4sAyRD4QIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQbwDqF
aja3ifZHm6mtSeTK9IHc+zAfBgNVHSMEGDAWgBT4qzwTztNc1Ln+8QvnbGzjmOUL
wDANBgkqhkiG9w0BAQsFAAOCAgEAprE/zV6u8UIH8g4Jb73wtUD/eIL3iBJ7mNYa
lqwCyJrWH7/F9fcovJnF9WO1QPTeHxhoD9rlQK70GitUAeboYw611yNWDS4tDlaL
sjpJKykUxBgBR7QSLZCrPtQ3fP2WvlZzLGqB28rASTLphShqTuGp4gJaxGHfbCU7
mlV9QYi+InQxOICJJPebXUOwx5wYkFQWJ9qE1AK3QrWPi8QYFznJvHgkNAaMBEmI
jAlggOzpveVvy8f4z3QG9o29LIwp7JvtJQs7QXL80FZK98/8US/3gONwTrBz2Imx
28ywvwCq7fpMyPgxX4sXtxphCNim+vuHcqDn2CvLS9p/6L6zzqbFNxpmMkJDLrOc
YqtHE4TLWIaXpb5JNrYJgNCZyJuYDICVTbivtMacHpSwYtXQ4iuzY2nIr0+4y9i9
MNpqv3W47xnvgUQa5vbTbIqo2NSY24A84mF5EyjhaNgNtDlN56+qTQ6HLZNVr6pv
eUCCWnY4GkaZUEU1M8/uNtKaZKv1WA7gJxZDQHj8+R110mPtzm1C5jqg7jSjGy9C
8PhAwBqIXkVLNayFEtyZZobTxMH5qY1yFkI3sic7S9ZyXt3quY1Q1UT3liRteIm/
sZHC5zEoidsHObkTeU44hqZVPkbvrfmgW01xTJjddnMPBH+yqjCCc94yCbW79j/2
7LEmxYg=
-----END CERTIFICATE-----`
client1Key = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiV
bJtHXVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd
20jPyhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1
UHw43Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZm
H859DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0
habTcDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABAoIBAEBSjVFqtbsp0byR
aXvyrtLX1Ng7h++at2jca85Ihq//jyqbHTje8zPuNAKI6eNbmb0YGr5OuEa4pD9N
ssDmMsKSoG/lRwwcm7h4InkSvBWpFShvMgUaohfHAHzsBYxfnh+TfULsi0y7c2n6
t/2OZcOTRkkUDIITnXYiw93ibHHv2Mv2bBDu35kGrcK+c2dN5IL5ZjTjMRpbJTe2
44RBJbdTxHBVSgoGBnugF+s2aEma6Ehsj70oyfoVpM6Aed5kGge0A5zA1JO7WCn9
Ay/DzlULRXHjJIoRWd2NKvx5n3FNppUc9vJh2plRHalRooZ2+MjSf8HmXlvG2Hpb
ScvmWgECgYEA1G+A/2KnxWsr/7uWIJ7ClcGCiNLdk17Pv3DZ3G4qUsU2ITftfIbb
tU0Q/b19na1IY8Pjy9ptP7t74/hF5kky97cf1FA8F+nMj/k4+wO8QDI8OJfzVzh9
PwielA5vbE+xmvis5Hdp8/od1Yrc/rPSy2TKtPFhvsqXjqoUmOAjDP8CgYEAwZjH
9dt1sc2lx/rMxihlWEzQ3JPswKW9/LJAmbRBoSWF9FGNjbX7uhWtXRKJkzb8ZAwa
88azluNo2oftbDD/+jw8b2cDgaJHlLAkSD4O1D1RthW7/LKD15qZ/oFsRb13NV85
ZNKtwslXGbfVNyGKUVFm7fVA8vBAOUey+LKDFj8CgYEAg8WWstOzVdYguMTXXuyb
ruEV42FJaDyLiSirOvxq7GTAKuLSQUg1yMRBIeQEo2X1XU0JZE3dLodRVhuO4EXP
g7Dn4X7Th9HSvgvNuIacowWGLWSz4Qp9RjhGhXhezUSx2nseY6le46PmFavJYYSR
4PBofMyt4PcyA6Cknh+KHmkCgYEAnTriG7ETE0a7v4DXUpB4TpCEiMCy5Xs2o8Z5
ZNva+W+qLVUWq+MDAIyechqeFSvxK6gRM69LJ96lx+XhU58wJiFJzAhT9rK/g+jS
bsHH9WOfu0xHkuHA5hgvvV2Le9B2wqgFyva4HJy82qxMxCu/VG/SMqyfBS9OWbb7
ibQhdq0CgYAl53LUWZsFSZIth1vux2LVOsI8C3X1oiXDGpnrdlQ+K7z57hq5EsRq
GC+INxwXbvKNqp5h0z2MvmKYPDlGVTgw8f8JjM7TkN17ERLcydhdRrMONUryZpo8
1xTob+8blyJgfxZUIAKbMbMbIiU0WAF0rfD/eJJwS4htOW/Hfv4TGA==
MIIEpAIBAAKCAQEA8xM5v+2QfdzfwnNT5cl+6oEy2fZoI2YG6L6c25rG0pr+yl1I
HKdMZcvn93uat7hlbzxeOLfJRM7+QK1lLaxuppq9p+gT+1x9eG3E4X7e0pdbjrpJ
GbvNji0hwDBLDWD8mHNq/SCk9FKtGnfZqrNB5BLw2uIKjJzVGXVlsjN6geBDm2hV
jTSmzMr39CfLUdtvMaZhpIPJzbH+sNfp1zKavFIpmwCd77p/z0QAiQ9NaIvzv4PZ
DDEEMUHzmVAU6bUjD8GToXaMbRiz694SU8aAwvvcdjGexdbHnfSAfLOl2wTPPxve
PncRaa656ZeZWxY9pRCItP+v43nm7d4sAyRD4QIDAQABAoIBADE17zcgDWSt1s8z
MgUPahZn2beu3x5rhXKRRIhhKWdx4atufy7t39WsFmZQK96OAlsmyZyJ+MFpdqf5
csZwZmZsZYEcxw7Yhr5e2sEcQlg4NF0M8ce38cGa+X5DSK6IuBrVIw/kEAE2y7zU
Dsk0SV63RvPJV4FoLuxcjB4rtd2c+JBduNUXQYVppz/KhsXN+9CbPbZ7wo1cB5fo
Iu/VswvvW6EAxVx39zZcwSGdkss9XUktU8akx7T/pepIH6fwkm7uXSNez6GH9d1I
8qOiORk/gAtqPL1TJgConyYheWMM9RbXP/IwL0BV8U4ZVG53S8jx2XpP4OJQ+k35
WYvz8JECgYEA+9OywKOG2lMiiUB1qZfmXB80PngNsz+L6xUWkrw58gSqYZIg0xyH
Sfr7HBo0yn/PB0oMMWPpNfYvG8/kSMIWiVlsYz9fdsUuqIvN+Kh9VF6o2wn+gnJk
sBE3KVMofcgwgLE6eMVv2MSQlBoXhGPNlCBHS1gorQdYE82dxDPBBzsCgYEA9xpm
c3C9LxiVbw9ZZ5D2C+vzwIG2+ZeDwKSizM1436MAnzNQgQTMzQ20uFGNBD562VjI
rHFlZYr3KCtSIw5gvCSuox0YB64Yq/WAtGZtH9JyKRz4h4juq6iM4FT7nUwM4DF9
3CUiDS8DGoqvCNpY50GvzSR5QVT1DKTZsMunh5MCgYEAyIWMq7pK0iQqtvG9/3o1
8xrhxfBgsF+kcV+MZvE8jstKRIFQY+oujCkutPTlHm3hE2PSC64L8G0Em/fRRmJO
AbZUCT9YK8HdYlZYf2zix0DM4gW2RHcEV/KNYvmVn3q9rGvzLGHCqu/yVAvmuAOk
mhON0Z/0W7siVjp/KtEvHisCgYA/cfTaMRkyDXLY6C0BbXPvTa7xP5z2atO2U89F
HICrkxOmzKsf5VacU6eSJ8Y4T76FLcmglSD+uHaLRsw5Ggj2Zci9MswntKi7Bjb8
msvr/sG3EqwxSJRXWNiLBObx1UP9EFgLfTFIB0kZuIAGmuF2xyPXXUUQ5Dpi+7S1
MyUZpwKBgQDg+AIPvk41vQ4Cz2CKrQX5/uJSW4bOhgP1yk7ruIH4Djkag3ZzTnHM
zA9/pLzRfz1ENc5I/WaYSh92eKw3j6tUtMJlE2AbfCpgOQtRUNs3IBmzCWrY8J01
W/8bwB+KhfFxNYwvszYsvvOq51NgahYQkgThVm38UixB3PFpEf+NiQ==
-----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=
MIIEITCCAgmgAwIBAgIRANmjlDeWHEW4NSzEY2bv3hgwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjIwNzA0MTU0MzUxWhcNMjQwMTA0MTU1
MzA3WjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAzNl7q7yS8MSaQs6zRbuqrsUuwEJ5ZH85vf7zHZKgOW3zNniXLOmH
JdtQ3jKZQ1BCIsJFvez2GxGIMWbXaSPw4bL0J3vl5oItChsjGg34IvqcDxWuIk2a
muRdMh7r1ryVs2ir2cQ5YHzI59BEpUWKQg3bD4yragdkb6BRc7lVgzCbrM1Eq758
HHbaLwlsfpqOvheaum4IG113CeD/HHrw42W6g/qQWL+FHlYqV3plHZ8Bj+bhcZI5
jdU4paGEzeY0a0NlnyH4gXGPjLKvPKFZHy4D6RiRlLHvHeiRyDtTu4wFkAiXxzGs
E4UBbykmYUB85zgwpjaktOaoe36IM1T8CQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRdYIEk
gxh+vTaMpAbqaPGRKGGBpTAfBgNVHSMEGDAWgBT4qzwTztNc1Ln+8QvnbGzjmOUL
wDANBgkqhkiG9w0BAQsFAAOCAgEABSR/PbPfiNZ6FOrt91/I0g6LviwICDcuXhfr
re4UsWp1kxXeS3CB2G71qXv3hswN8phG2hdsij0/FBEGUTLS3FTCmLmqmcVqPj3/
677PMFDoACBKgT5iIwpnNvdD+4ROM8JFjUwy7aTWx85a5yoPFGnB+ORMfLCYjr2S
D02KFvKuSXWCjXphqJ41cFGne4oeh/JMkN0RNArm7wTT8yWCGgO1k4OON8dphuTV
48Wm6I9UBSWuLk1vcIlgb/8YWVwy9rBNmjOBDGuroL6PSmfZD+e9Etii0X2znZ+t
qDpXJB7V5U0DbsBCtGM/dHaFz/LCoBYX9z6th1iPUHksUTM3RzN9L24r9/28dY/a
shBpn5rK3ui/2mPBpO26wX14Kl/DUkdKUV9dJllSlmwo8Z0RluY9S4xnCrna/ODH
FbhWmlTSs+odCZl6Lc0nuw+WQ2HnlTVJYBSFAGfsGQQ3pzk4DC5VynnxY0UniUgD
WYPR8JEYa+BpH3rIQ9jmnOKWLtyc7lFPB9ab63pQBBiwRvWo+tZ2vybqjeHPuu5N
BuKvvtu3RKKdSCnIo5Rs5zw4JYCjvlx/NVk9jtpa1lIHYHilvBmCcRX5DkE/yH/x
IjEKhCOQpGR6D5Kkca9xNL7zNcat3bzLn+d7Wo4m09uWi9ifPdchxed0w5d9ihx1
enqNrFI=
-----END CERTIFICATE-----`
client2Key = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY
+6hijcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN
/4jQtNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2Hk
O/xGoZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB
1YFMs8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhts
C871nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABAoIBAFatstVb1KdQXsq0
cFpui8zTKOUiduJOrDkWzTygAmlEhYtrccdfXu7OWz0x0lvBLDVGK3a0I/TGrAzj
4BuFY+FM/egxTVt9in6fmA3et4BS1OAfCryzUdfK6RV//8L+t+zJZ/qKQzWnugpy
QYjDo8ifuMFwtvEoXizaIyBNLAhEp9hnrv+Tyi2O2gahPvCHsD48zkyZRCHYRstD
NH5cIrwz9/RJgPO1KI+QsJE7Nh7stR0sbr+5TPU4fnsL2mNhMUF2TJrwIPrc1yp+
YIUjdnh3SO88j4TQT3CIrWi8i4pOy6N0dcVn3gpCRGaqAKyS2ZYUj+yVtLO4KwxZ
SZ1lNvECgYEA78BrF7f4ETfWSLcBQ3qxfLs7ibB6IYo2x25685FhZjD+zLXM1AKb
FJHEXUm3mUYrFJK6AFEyOQnyGKBOLs3S6oTAswMPbTkkZeD1Y9O6uv0AHASLZnK6
pC6ub0eSRF5LUyTQ55Jj8D7QsjXJueO8v+G5ihWhNSN9tB2UA+8NBmkCgYEA+weq
cvoeMIEMBQHnNNLy35bwfqrceGyPIRBcUIvzQfY1vk7KW6DYOUzC7u+WUzy/hA52
DjXVVhua2eMQ9qqtOav7djcMc2W9RbLowxvno7K5qiCss013MeWk64TCWy+WMp5A
AVAtOliC3hMkIKqvR2poqn+IBTh1449agUJQqTMCgYEAu06IHGq1GraV6g9XpGF5
wqoAlMzUTdnOfDabRilBf/YtSr+J++ThRcuwLvXFw7CnPZZ4TIEjDJ7xjj3HdxeE
fYYjineMmNd40UNUU556F1ZLvJfsVKizmkuCKhwvcMx+asGrmA+tlmds4p3VMS50
KzDtpKzLWlmU/p/RINWlRmkCgYBy0pHTn7aZZx2xWKqCDg+L2EXPGqZX6wgZDpu7
OBifzlfM4ctL2CmvI/5yPmLbVgkgBWFYpKUdiujsyyEiQvWTUKhn7UwjqKDHtcsk
G6p7xS+JswJrzX4885bZJ9Oi1AR2yM3sC9l0O7I4lDbNPmWIXBLeEhGMmcPKv/Kc
91Ff4wKBgQCF3ur+Vt0PSU0ucrPVHjCe7tqazm0LJaWbPXL1Aw0pzdM2EcNcW/MA
w0kqpr7MgJ94qhXCBcVcfPuFN9fBOadM3UBj1B45Cz3pptoK+ScI8XKno6jvVK/p
xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
MIIEowIBAAKCAQEAzNl7q7yS8MSaQs6zRbuqrsUuwEJ5ZH85vf7zHZKgOW3zNniX
LOmHJdtQ3jKZQ1BCIsJFvez2GxGIMWbXaSPw4bL0J3vl5oItChsjGg34IvqcDxWu
Ik2amuRdMh7r1ryVs2ir2cQ5YHzI59BEpUWKQg3bD4yragdkb6BRc7lVgzCbrM1E
q758HHbaLwlsfpqOvheaum4IG113CeD/HHrw42W6g/qQWL+FHlYqV3plHZ8Bj+bh
cZI5jdU4paGEzeY0a0NlnyH4gXGPjLKvPKFZHy4D6RiRlLHvHeiRyDtTu4wFkAiX
xzGsE4UBbykmYUB85zgwpjaktOaoe36IM1T8CQIDAQABAoIBAETHMJK0udFE8VZE
+EQNgn0zj0LWDtQDM2vrUc04Ebu2gtZjHr7hmZLIVBqGepbzN4FcIPZnvSnRdRzB
HsoaWyIsZ3VqUAJY6q5d9iclUY7M/eDCsripvaML0Y6meyCaKNkX57sx+uG+g+Xx
M1saQhVzeX17CYKMANjJxw9HxsJI0aBPyiBbILHMwfRfsJU8Ou72HH1sIQuPdH2H
/c9ru8YZAno6oVq1zuC/pCis+h50U9HzTnt3/4NNS6cWG/y2YLztCvm9uGo4MTd/
mA9s4cxVhvQW6gCDHgGn6zj661OL/d2rpak1eWizhZvZ8jsIN/sM87b0AJeVT4zH
6xA3egECgYEA1nI5EsCetQbFBp7tDovSp3fbitwoQtdtHtLn2u4DfvmbLrgSoq0Z
L+9N13xML/l8lzWai2gI69uA3c2+y1O64LkaiSeDqbeBp9b6fKMlmwIVbklEke1w
XVTIWOYTTF5/8+tUOlsgme5BhLAWnQ7+SoitzHtl5e1vEYaAGamE2DECgYEA9Is2
BbTk2YCqkcsB7D9q95JbY0SZpecvTv0rLR+acz3T8JrAASdmvqdBOlPWc+0ZaEdS
PcJaOEw3yxYJ33cR/nLBaR2/Uu5qQebyPALs3B2pjjTFdGvcpeFxO55fowwsfR/e
0H+HeiFj5Y4S+kFWT+3FRmJ6GUB828LJYaVhQ1kCgYEA1bdsTdYN1Vfzz89fbZnH
zQLUl6UlssfDhm6mhzeh4E+eaocke1+LtIwHxfOocj9v/bp8VObPzU8rNOIxfa3q
lr+jRIFO5DtwSfckGEb32W3QMeNvJQe/biRqrr5NCVU8q7kibi4XZZFfVn+vacNh
hqKEoz9vpCBnCs5CqFCbhmECgYAG8qWYR+lwnI08Ey58zdh2LDxYd6x94DGh5uOB
JrK2r30ECwGFht8Ob6YUyCkBpizgn5YglxMFInU7Webx6GokdpI0MFotOwTd1nfv
aI3eOyGEHs+1XRMpy1vyO6+v7DqfW3ZzKgxpVeWGsiCr54tSPgkq1MVvTju96qza
D17SEQKBgCKC0GjDjnt/JvujdzHuBt1sWdOtb+B6kQvA09qVmuDF/Dq36jiaHDjg
XMf5HU3ThYqYn3bYypZZ8nQ7BXVh4LqGNqG29wR4v6l+dLO6odXnLzfApGD9e+d4
2tmlLP54LaN35hQxRjhT8lCN0BkrNF44+bh8frwm/kuxSd8wT2S+
-----END RSA PRIVATE KEY-----`
testFileName = "test_file_ftp.dat"
testDLFileName = "test_download_ftp.dat"
@ -600,8 +614,18 @@ func TestListDirWithWildcards(t *testing.T) {
assert.NoError(t, err)
sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
assert.NoError(t, err)
defer func() {
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}()
for _, user := range []dataprovider.User{localUser, sftpUser} {
client, err := getFTPClient(user, true, nil)
client, err := getFTPClient(user, true, nil, ftp.DialWithDisabledMLSD(true))
if assert.NoError(t, err) {
dir1 := "test.dir"
dir2 := "test.dir1"
@ -619,58 +643,96 @@ func TestListDirWithWildcards(t *testing.T) {
localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
err = ftpDownloadFile(fileName, localDownloadPath, testFileSize, client, 0)
assert.NoError(t, err)
entries, err := client.NameList(fileName)
if assert.NoError(t, err) {
entries, err := client.List(fileName)
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Equal(t, fileName, entries[0].Name)
nListEntries, err := client.NameList(fileName)
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, fileName)
entries, err = client.List(".")
require.NoError(t, err)
require.Len(t, entries, 3)
nListEntries, err = client.NameList(".")
require.NoError(t, err)
require.Len(t, nListEntries, 3)
entries, err = client.List("/test.*")
require.NoError(t, err)
require.Len(t, entries, 2)
found := 0
for _, e := range entries {
switch e.Name {
case dir1, dir2:
found++
}
}
assert.Equal(t, 2, found)
nListEntries, err = client.NameList("/test.*")
require.NoError(t, err)
require.Len(t, entries, 2)
assert.Contains(t, nListEntries, dir1)
assert.Contains(t, nListEntries, dir2)
entries, err = client.List("/*.dir?")
require.NoError(t, err)
assert.Len(t, entries, 1)
assert.Contains(t, entries, fileName)
}
entries, err = client.NameList(".")
assert.NoError(t, err)
assert.Len(t, entries, 3)
entries, err = client.NameList("/test.*")
if assert.NoError(t, err) {
assert.Len(t, entries, 2)
assert.Contains(t, entries, dir1)
assert.Contains(t, entries, dir2)
}
entries, err = client.NameList("/*.dir?")
if assert.NoError(t, err) {
assert.Len(t, entries, 1)
assert.Contains(t, entries, dir2)
}
entries, err = client.NameList("/test.???")
if assert.NoError(t, err) {
assert.Len(t, entries, 1)
assert.Contains(t, entries, dir1)
}
assert.Equal(t, dir2, entries[0].Name)
nListEntries, err = client.NameList("/*.dir?")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, dir2)
entries, err = client.List("/test.???")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Equal(t, dir1, entries[0].Name)
nListEntries, err = client.NameList("/test.???")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, dir1)
_, err = client.NameList("/missingdir/test.*")
assert.Error(t, err)
_, err = client.List("/missingdir/test.*")
assert.Error(t, err)
_, err = client.NameList("test[-]")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), path.ErrBadPattern.Error())
}
_, err = client.List("test[-]")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), path.ErrBadPattern.Error())
}
subDir := path.Join(dir1, "sub.d")
err = client.MakeDir(subDir)
assert.NoError(t, err)
err = client.ChangeDir(path.Dir(subDir))
assert.NoError(t, err)
entries, err = client.NameList("sub.?")
if assert.NoError(t, err) {
assert.Len(t, entries, 1)
assert.Contains(t, entries, path.Base(subDir))
}
entries, err = client.NameList("../*.dir?")
if assert.NoError(t, err) {
assert.Len(t, entries, 1)
assert.Contains(t, entries, path.Join("../", dir2))
}
entries, err = client.List("sub.?")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, path.Base(subDir), entries[0].Name)
nListEntries, err = client.NameList("sub.?")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, path.Base(subDir))
entries, err = client.List("../*.dir?")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Equal(t, path.Join("../", dir2), entries[0].Name)
nListEntries, err = client.NameList("../*.dir?")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, path.Join("../", dir2))
err = client.ChangeDir("/")
assert.NoError(t, err)
entries, err = client.NameList(path.Join(dir1, "sub.*"))
if assert.NoError(t, err) {
assert.Len(t, entries, 1)
assert.Contains(t, entries, path.Join(dir1, "sub.d"))
}
entries, err = client.List(path.Join(dir1, "sub.*"))
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Equal(t, path.Join(dir1, "sub.d"), entries[0].Name)
nListEntries, err = client.NameList(path.Join(dir1, "sub.*"))
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Contains(t, nListEntries, path.Join(dir1, "sub.d"))
err = client.RemoveDir(subDir)
assert.NoError(t, err)
err = client.RemoveDir(dir1)
@ -685,13 +747,6 @@ func TestListDirWithWildcards(t *testing.T) {
assert.NoError(t, err)
}
}
_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}
func TestStartDirectory(t *testing.T) {
@ -875,7 +930,7 @@ func TestLoginExternalAuth(t *testing.T) {
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 0
@ -899,7 +954,7 @@ func TestLoginExternalAuth(t *testing.T) {
Type: sdk.GroupTypePrimary,
},
}
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
assert.NoError(t, err)
_, err = getFTPClient(u, true, nil)
if !assert.Error(t, err) {
@ -910,7 +965,7 @@ func TestLoginExternalAuth(t *testing.T) {
}
u.Groups = nil
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
assert.NoError(t, err)
u.Username = defaultUsername + "1"
client, err = getFTPClient(u, true, nil)
@ -1651,6 +1706,23 @@ func TestResume(t *testing.T) {
expected := append(data, data...)
assert.Equal(t, expected, readed)
}
// append to a new file
srcFile, err = os.Open(testFilePath)
if assert.NoError(t, err) {
newFileName := testFileName + "_new"
err = client.Append(newFileName, srcFile)
assert.NoError(t, err)
err = srcFile.Close()
assert.NoError(t, err)
size, err := client.FileSize(newFileName)
assert.NoError(t, err)
assert.Equal(t, int64(len(data)), size)
err = ftpDownloadFile(newFileName, localDownloadPath, int64(len(data)), client, 0)
assert.NoError(t, err)
readed, err = os.ReadFile(localDownloadPath)
assert.NoError(t, err)
assert.Equal(t, data, readed)
}
err = client.Quit()
assert.NoError(t, err)
err = os.Remove(testFilePath)
@ -3008,7 +3080,7 @@ func TestExternalAuthWithClientCert(t *testing.T) {
err = config.LoadConfig(configDir, "")
assert.NoError(t, err)
providerConf := config.GetProviderConf()
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u, false, ""), os.ModePerm)
err = os.WriteFile(extAuthPath, getExtAuthScriptContent(u), os.ModePerm)
assert.NoError(t, err)
providerConf.ExternalAuthHook = extAuthPath
providerConf.ExternalAuthScope = 8
@ -3340,8 +3412,10 @@ func getFTPClientImplicitTLS(user dataprovider.User) (*ftp.ServerConn, error) {
return client, err
}
func getFTPClient(user dataprovider.User, useTLS bool, tlsConfig *tls.Config) (*ftp.ServerConn, error) {
func getFTPClient(user dataprovider.User, useTLS bool, tlsConfig *tls.Config, dialOptions ...ftp.DialOption,
) (*ftp.ServerConn, error) {
ftpOptions := []ftp.DialOption{ftp.DialWithTimeout(5 * time.Second)}
ftpOptions = append(ftpOptions, dialOptions...)
if useTLS {
if tlsConfig == nil {
tlsConfig = &tls.Config{
@ -3422,24 +3496,13 @@ func getTestSFTPUser() dataprovider.User {
return u
}
func getExtAuthScriptContent(user dataprovider.User, nonJSONResponse bool, username string) []byte {
func getExtAuthScriptContent(user dataprovider.User) []byte {
extAuthContent := []byte("#!/bin/sh\n\n")
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("if test \"$SFTPGO_AUTHD_USERNAME\" = \"%v\"; then\n", user.Username))...)
if len(username) > 0 {
user.Username = username
}
u, _ := json.Marshal(user)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte(fmt.Sprintf("echo '%v'\n", string(u)))...)
}
extAuthContent = append(extAuthContent, []byte("else\n")...)
if nonJSONResponse {
extAuthContent = append(extAuthContent, []byte("echo 'text response'\n")...)
} else {
extAuthContent = append(extAuthContent, []byte("echo '{\"username\":\"\"}'\n")...)
}
extAuthContent = append(extAuthContent, []byte("fi\n")...)
return extAuthContent
}

View file

@ -1,3 +1,17 @@
// Copyright (C) 2019-2022 Nicola Murino
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ftpd
import (
@ -28,6 +42,7 @@ var (
type Connection struct {
*common.BaseConnection
clientContext ftpserver.ClientContext
doWildcardListDir bool
}
func (c *Connection) getFTPMode() string {
@ -140,6 +155,7 @@ func (c *Connection) Rename(oldname, newname string) error {
// if any happens
func (c *Connection) Stat(name string) (os.FileInfo, error) {
c.UpdateLastActivity()
c.doWildcardListDir = false
if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(name)) {
return nil, c.GetPermissionDeniedError()
@ -147,7 +163,8 @@ func (c *Connection) Stat(name string) (os.FileInfo, error) {
fi, err := c.DoStat(name, 0, true)
if err != nil {
if c.isListDirWithWildcards(path.Base(name), os.ErrNotExist) {
if c.isListDirWithWildcards(path.Base(name)) {
c.doWildcardListDir = true
return vfs.NewFileInfo(name, true, 0, time.Now(), false), nil
}
return nil, err
@ -277,17 +294,18 @@ func (c *Connection) Symlink(oldname, newname string) error {
func (c *Connection) ReadDir(name string) ([]os.FileInfo, error) {
c.UpdateLastActivity()
files, err := c.ListDir(name)
if err != nil {
if c.doWildcardListDir {
c.doWildcardListDir = false
baseName := path.Base(name)
if c.isListDirWithWildcards(baseName, err) {
// we only support wildcards for the last path level, for example:
// - *.xml is supported
// - dir*/*.xml is not supported
return c.getListDirWithWildcards(path.Dir(name), baseName)
name = path.Dir(name)
c.clientContext.SetListPath(name)
return c.getListDirWithWildcards(name, baseName)
}
}
return files, err
return c.ListDir(name)
}
// GetHandle implements ClientDriverExtentionFileTransfer
@ -494,7 +512,10 @@ func (c *Connection) getListDirWithWildcards(dirName, pattern string) ([]os.File
return files, err
}
validIdx := 0
relativeBase := getPathRelativeTo(c.clientContext.Path(), dirName)
var relativeBase string
if c.clientContext.GetLastCommand() != "NLST" {
relativeBase = getPathRelativeTo(c.clientContext.Path(), dirName)
}
for _, fi := range files {
match, err := path.Match(pattern, fi.Name())
if err != nil {
@ -510,12 +531,10 @@ func (c *Connection) getListDirWithWildcards(dirName, pattern string) ([]os.File
return files[:validIdx], nil
}
func (c *Connection) isListDirWithWildcards(name string, err error) bool {
if errors.Is(err, c.GetNotExistError()) {
func (c *Connection) isListDirWithWildcards(name string) bool {
if strings.ContainsAny(name, "*?[]") {
lastCommand := c.clientContext.GetLastCommand()
if lastCommand == "LIST" || lastCommand == "NLST" {
return strings.ContainsAny(name, "*?[]")
}
return lastCommand == "LIST" || lastCommand == "NLST"
}
return false
}

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