Compare commits

..

1 Commits

Author SHA1 Message Date
Pterodactyl CI
480df5f59d bump version for release 2022-02-05 17:40:50 +00:00
31 changed files with 387 additions and 660 deletions

View File

@@ -1,37 +1,5 @@
# Changelog # Changelog
## v1.6.4
### Fixed
* Fixes a bug causing CPU limiting to not be properly applied to servers.
* Fixes a bug causing zip archives to decompress without taking into account nested folder structures.
## v1.6.3
### Fixed
* Fixes SFTP authentication failing for administrative users due to a permissions adjustment on the Panel.
## v1.6.2
### Fixed
* Fixes file upload size not being properly enforced.
* Fixes a bug that prevented listing a directory when it contained a named pipe. Also added a check to prevent attempting to read a named pipe directly.
* Fixes a bug with the archiver logic that would include folders that had the same name prefix. (for example, requesting only `map` would also include `map2` and `map3`)
* Requests to the Panel that return a client error (4xx response code) no longer trigger an exponential backoff, they immediately stop the request.
### Changed
* CPU limit fields are only set on the Docker container if they have been specified for the server — otherwise they are left empty.
### Added
* Added the ability to define the location of the temporary folder used by Wings — defaults to `/tmp/pterodactyl`.
* Adds the ability to authenticate for SFTP using public keys (requires `Panel@1.8.0`).
## v1.6.1
### Fixed
* Fixes error that would sometimes occur when starting a server that would cause the temporary power action lock to never be released due to a blocked channel.
* Fixes a bug causing the CPU usage of Wings to get stuck at 100% when a server is deleted while the installation process is running.
### Changed
* Cleans up a lot of the logic for handling events between the server and environment process to make it easier to make modifications to down the road.
* Cleans up logic handling the `StopAndWait` logic for stopping a server gracefully before terminating the process if it does not respond.
## v1.6.0 ## v1.6.0
### Fixed ### Fixed
* Internal logic for processing a server start event has been adjusted to attach to the Docker container before attempting to start the container. This should fix issues where a server would get stuck after pulling the container image. * Internal logic for processing a server start event has been adjusted to attach to the Docker container before attempting to start the container. This should fix issues where a server would get stuck after pulling the container image.

View File

@@ -1,18 +1,19 @@
# Stage 1 (Build) # Stage 1 (Build)
FROM golang:1.17-alpine AS builder FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS builder
ARG VERSION ARG VERSION
RUN apk add --update --no-cache git make RUN apk add --update --no-cache git make upx
WORKDIR /app/ WORKDIR /app/
COPY go.mod go.sum /app/ COPY go.mod go.sum /app/
RUN go mod download RUN go mod download
COPY . /app/ COPY . /app/
RUN CGO_ENABLED=0 go build \ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=$VERSION" \ -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=$VERSION" \
-v \ -v \
-trimpath \ -trimpath \
-o wings \ -o wings \
wings.go wings.go
RUN upx wings
RUN echo "ID=\"distroless\"" > /etc/os-release RUN echo "ID=\"distroless\"" > /etc/os-release
# Stage 2 (Final) # Stage 2 (Final)

View File

@@ -19,16 +19,22 @@ I would like to extend my sincere thanks to the following sponsors for helping f
| Company | About | | Company | About |
| ------- | ----- | | ------- | ----- |
| [**WISP**](https://wisp.gg) | Extra features. | | [**WISP**](https://wisp.gg) | Extra features. |
| [**MixmlHosting**](https://mixmlhosting.com) | MixmlHosting provides high quality Virtual Private Servers along with game servers, all at a affordable price. |
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | | [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
| [**Tempest**](https://tempest.net/) | Tempest Hosting is a subsidiary of Path Network, Inc. offering unmetered DDoS protected 10Gbps dedicated servers, starting at just $80/month. Full anycast, tons of filters. |
| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. | | [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. |
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | | [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | | [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHostings reliable servers and network. Easy to use, provisioned in a couple of minutes. |
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. |
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | | [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | | [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
| [**HostEZ**](https://hostez.io) | Providing North America Valheim, Minecraft and other popular games with low latency, high uptime and maximum availability. EZ! |
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.| | [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
| [**Gamenodes**](https://gamenodes.nl) | Gamenodes love quality. For Minecraft, Discord Bots and other services, among others. With our own programmers, we provide just that little bit of extra service! | | [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! |
## Documentation ## Documentation
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)

View File

@@ -89,8 +89,8 @@ type ApiConfiguration struct {
// servers. // servers.
DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"` DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
// The maximum size for files uploaded through the Panel in MB. // The maximum size for files uploaded through the Panel in bytes.
UploadLimit int64 `default:"100" json:"upload_limit" yaml:"upload_limit"` UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
} }
// RemoteQueryConfiguration defines the configuration settings for remote requests // RemoteQueryConfiguration defines the configuration settings for remote requests
@@ -132,10 +132,6 @@ type SystemConfiguration struct {
// Directory where local backups will be stored on the machine. // Directory where local backups will be stored on the machine.
BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"` BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"`
// TmpDirectory specifies where temporary files for Pterodactyl installation processes
// should be created. This supports environments running docker-in-docker.
TmpDirectory string `default:"/tmp/pterodactyl" yaml:"tmp_directory"`
// The user that should own all of the server files, and be used for containers. // The user that should own all of the server files, and be used for containers.
Username string `default:"pterodactyl" yaml:"username"` Username string `default:"pterodactyl" yaml:"username"`

View File

@@ -116,4 +116,4 @@ func parseErrorFromResponse(res *http.Response, body []byte) error {
} }
return errors.Wrap(errors.New(emsg), "Error response from daemon") return errors.Wrap(errors.New(emsg), "Error response from daemon")
} }

View File

@@ -480,3 +480,21 @@ func (e *Environment) convertMounts() []mount.Mount {
return out return out
} }
func (e *Environment) resources() container.Resources {
l := e.Configuration.Limits()
pids := l.ProcessLimit()
return container.Resources{
Memory: l.BoundedMemoryLimit(),
MemoryReservation: l.MemoryLimit * 1_000_000,
MemorySwap: l.ConvertedSwap(),
CPUQuota: l.ConvertedCpuLimit(),
CPUPeriod: 100_000,
CPUShares: 1024,
BlkioWeight: l.IoWeight,
OomKillDisable: &l.OOMDisabled,
CpusetCpus: l.Threads,
PidsLimit: &pids,
}
}

View File

@@ -26,7 +26,7 @@ type Metadata struct {
var _ environment.ProcessEnvironment = (*Environment)(nil) var _ environment.ProcessEnvironment = (*Environment)(nil)
type Environment struct { type Environment struct {
mu sync.RWMutex mu sync.RWMutex
// The public identifier for this environment. In this case it is the Docker container // The public identifier for this environment. In this case it is the Docker container
// name that will be used for all instances created under it. // name that will be used for all instances created under it.

View File

@@ -218,7 +218,7 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
} }
}() }()
doTermination := func(s string) error { doTermination := func (s string) error {
e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...") e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...")
return e.Terminate(ctx, os.Kill) return e.Terminate(ctx, os.Kill)
} }

View File

@@ -99,36 +99,21 @@ func (l Limits) ProcessLimit() int64 {
return config.Get().Docker.ContainerPidLimit return config.Get().Docker.ContainerPidLimit
} }
// AsContainerResources returns the available resources for a container in a format
// that Docker understands.
func (l Limits) AsContainerResources() container.Resources { func (l Limits) AsContainerResources() container.Resources {
pids := l.ProcessLimit() pids := l.ProcessLimit()
resources := container.Resources{
return container.Resources{
Memory: l.BoundedMemoryLimit(), Memory: l.BoundedMemoryLimit(),
MemoryReservation: l.MemoryLimit * 1_000_000, MemoryReservation: l.MemoryLimit * 1_000_000,
MemorySwap: l.ConvertedSwap(), MemorySwap: l.ConvertedSwap(),
CPUQuota: l.ConvertedCpuLimit(),
CPUPeriod: 100_000,
CPUShares: 1024,
BlkioWeight: l.IoWeight, BlkioWeight: l.IoWeight,
OomKillDisable: &l.OOMDisabled, OomKillDisable: &l.OOMDisabled,
CpusetCpus: l.Threads,
PidsLimit: &pids, PidsLimit: &pids,
} }
// If the CPU Limit is not set, don't send any of these fields through. Providing
// them seems to break some Java services that try to read the available processors.
//
// @see https://github.com/pterodactyl/panel/issues/3988
if l.CpuLimit > 0 {
resources.CPUQuota = l.CpuLimit * 1_000
resources.CPUPeriod = 100_000
resources.CPUShares = 1024
}
// Similar to above, don't set the specific assigned CPUs if we didn't actually limit
// the server to any of them.
if l.Threads != "" {
resources.CpusetCpus = l.Threads
}
return resources
} }
type Variables map[string]interface{} type Variables map[string]interface{}

105
go.mod
View File

@@ -3,113 +3,116 @@ module github.com/pterodactyl/wings
go 1.17 go 1.17
require ( require (
emperror.dev/errors v0.8.1 emperror.dev/errors v0.8.0
github.com/AlecAivazis/survey/v2 v2.3.4 github.com/AlecAivazis/survey/v2 v2.2.15
github.com/Jeffail/gabs/v2 v2.6.1 github.com/Jeffail/gabs/v2 v2.6.1
github.com/NYTimes/logrotate v1.0.0 github.com/NYTimes/logrotate v1.0.0
github.com/apex/log v1.9.0 github.com/apex/log v1.9.0
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/buger/jsonparser v1.1.1 github.com/buger/jsonparser v1.1.1
github.com/cenkalti/backoff/v4 v4.1.2 github.com/cenkalti/backoff/v4 v4.1.1
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249 github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
github.com/creasty/defaults v1.5.2 github.com/creasty/defaults v1.5.1
github.com/docker/docker v20.10.14+incompatible github.com/docker/docker v20.10.7+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/fatih/color v1.13.0 github.com/fatih/color v1.12.0
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
github.com/gabriel-vasile/mimetype v1.4.0 github.com/gabriel-vasile/mimetype v1.3.1
github.com/gammazero/workerpool v1.1.2 github.com/gammazero/workerpool v1.1.2
github.com/gbrlsnchs/jwt/v3 v3.0.1 github.com/gbrlsnchs/jwt/v3 v3.0.1
github.com/gin-gonic/gin v1.7.7 github.com/gin-gonic/gin v1.7.2
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.4.2
github.com/iancoleman/strcase v0.2.0 github.com/iancoleman/strcase v0.2.0
github.com/icza/dyno v0.0.0-20210726202311-f1bafe5d9996 github.com/icza/dyno v0.0.0-20210726202311-f1bafe5d9996
github.com/juju/ratelimit v1.0.1 github.com/juju/ratelimit v1.0.1
github.com/karrick/godirwalk v1.16.1 github.com/karrick/godirwalk v1.16.1
github.com/klauspost/pgzip v1.2.5 github.com/klauspost/pgzip v1.2.5
github.com/magiconair/properties v1.8.6 github.com/magiconair/properties v1.8.5
github.com/mattn/go-colorable v0.1.12 github.com/mattn/go-colorable v0.1.8
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.0
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/sftp v1.13.4 github.com/pkg/profile v1.6.0
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/pkg/sftp v1.13.2
github.com/spf13/cobra v1.4.0 github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/ini.v1 v1.66.4 gopkg.in/ini.v1 v1.62.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require github.com/goccy/go-json v0.9.6 require github.com/goccy/go-json v0.9.4
require golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect require golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/hcsshim v0.9.2 // indirect github.com/Microsoft/hcsshim v0.8.20 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/brotli v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/containerd/containerd v1.6.2 // indirect github.com/containerd/containerd v1.5.5 // indirect
github.com/containerd/fifo v1.0.0 // indirect github.com/containerd/fifo v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dsnet/compress v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gammazero/deque v0.1.1 // indirect github.com/gammazero/deque v0.1.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.10.1 // indirect github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/mux v1.7.4 // indirect github.com/gorilla/mux v1.7.4 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.11 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.1 // indirect github.com/klauspost/compress v1.13.2 // indirect
github.com/kr/fs v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/magefile/mage v1.13.0 // indirect github.com/magefile/mage v1.11.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nwaples/rardecode v1.1.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/ugorji/go/codec v1.1.7 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect github.com/ulikunitz/xz v0.5.10 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.7.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect google.golang.org/genproto v0.0.0-20210729151513-df9385d47c1b // indirect
google.golang.org/grpc v1.45.0 // indirect google.golang.org/grpc v1.39.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )

474
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -142,10 +142,12 @@ func (c *client) request(ctx context.Context, method, path string, body io.Reade
if r.HasError() { if r.HasError() {
// Close the request body after returning the error to free up resources. // Close the request body after returning the error to free up resources.
defer r.Body.Close() defer r.Body.Close()
// Don't keep attempting to access this endpoint if the response is a 4XX // Don't keep spamming the endpoint if we've already made too many requests or
// level error which indicates a client mistake. Only retry when the error // if we're not even authenticated correctly. Retrying generally won't fix either
// is due to a server issue (5XX error). // of these issues.
if r.StatusCode >= 400 && r.StatusCode < 500 { if r.StatusCode == http.StatusForbidden ||
r.StatusCode == http.StatusTooManyRequests ||
r.StatusCode == http.StatusUnauthorized {
return backoff.Permanent(r.Error()) return backoff.Permanent(r.Error())
} }
return r.Error() return r.Error()

View File

@@ -11,11 +11,6 @@ import (
"github.com/pterodactyl/wings/parser" "github.com/pterodactyl/wings/parser"
) )
const (
SftpAuthPassword = SftpAuthRequestType("password")
SftpAuthPublicKey = SftpAuthRequestType("public_key")
)
// A generic type allowing for easy binding use when making requests to API // A generic type allowing for easy binding use when making requests to API
// endpoints that only expect a singular argument or something that would not // endpoints that only expect a singular argument or something that would not
// benefit from being a typed struct. // benefit from being a typed struct.
@@ -68,17 +63,14 @@ type RawServerData struct {
ProcessConfiguration json.RawMessage `json:"process_configuration"` ProcessConfiguration json.RawMessage `json:"process_configuration"`
} }
type SftpAuthRequestType string
// SftpAuthRequest defines the request details that are passed along to the Panel // SftpAuthRequest defines the request details that are passed along to the Panel
// when determining if the credentials provided to Wings are valid. // when determining if the credentials provided to Wings are valid.
type SftpAuthRequest struct { type SftpAuthRequest struct {
Type SftpAuthRequestType `json:"type"` User string `json:"username"`
User string `json:"username"` Pass string `json:"password"`
Pass string `json:"password"` IP string `json:"ip"`
IP string `json:"ip"` SessionID []byte `json:"session_id"`
SessionID []byte `json:"session_id"` ClientVersion []byte `json:"client_version"`
ClientVersion []byte `json:"client_version"`
} }
// SftpAuthResponse is returned by the Panel when a pair of SFTP credentials // SftpAuthResponse is returned by the Panel when a pair of SFTP credentials
@@ -87,6 +79,7 @@ type SftpAuthRequest struct {
// user for the SFTP subsystem. // user for the SFTP subsystem.
type SftpAuthResponse struct { type SftpAuthResponse struct {
Server string `json:"server"` Server string `json:"server"`
Token string `json:"token"`
Permissions []string `json:"permissions"` Permissions []string `json:"permissions"`
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"mime"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -14,8 +13,8 @@ import (
"time" "time"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/goccy/go-json"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/goccy/go-json"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
) )
@@ -78,13 +77,10 @@ func (c *Counter) Write(p []byte) (int, error) {
type DownloadRequest struct { type DownloadRequest struct {
Directory string Directory string
URL *url.URL URL *url.URL
FileName string
UseHeader bool
} }
type Download struct { type Download struct {
Identifier string Identifier string
path string
mu sync.RWMutex mu sync.RWMutex
req DownloadRequest req DownloadRequest
server *server.Server server *server.Server
@@ -176,28 +172,8 @@ func (dl *Download) Execute() error {
} }
} }
if dl.req.UseHeader { fnameparts := strings.Split(dl.req.URL.Path, "/")
if contentDisposition := res.Header.Get("Content-Disposition"); contentDisposition != "" { p := filepath.Join(dl.req.Directory, fnameparts[len(fnameparts)-1])
_, params, err := mime.ParseMediaType(contentDisposition)
if err != nil {
return errors.WrapIf(err, "downloader: invalid \"Content-Disposition\" header")
}
if v, ok := params["filename"]; ok {
dl.path = v
}
}
}
if dl.path == "" {
if dl.req.FileName != "" {
dl.path = dl.req.FileName
} else {
parts := strings.Split(dl.req.URL.Path, "/")
dl.path = parts[len(parts)-1]
}
}
p := dl.Path()
dl.server.Log().WithField("path", p).Debug("writing remote file to disk") dl.server.Log().WithField("path", p).Debug("writing remote file to disk")
r := io.TeeReader(res.Body, dl.counter(res.ContentLength)) r := io.TeeReader(res.Body, dl.counter(res.ContentLength))
@@ -229,10 +205,6 @@ func (dl *Download) Progress() float64 {
return dl.progress return dl.progress
} }
func (dl *Download) Path() string {
return filepath.Join(dl.req.Directory, dl.path)
}
// Handles a write event by updating the progress completed percentage and firing off // Handles a write event by updating the progress completed percentage and firing off
// events to the server websocket as needed. // events to the server websocket as needed.
func (dl *Download) counter(contentLength int64) *Counter { func (dl *Download) counter(contentLength int64) *Counter {

View File

@@ -13,8 +13,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/pterodactyl/wings/config"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -37,15 +35,6 @@ func getServerFileContents(c *gin.Context) {
return return
} }
defer f.Close() defer f.Close()
// Don't allow a named pipe to be opened.
//
// @see https://github.com/pterodactyl/panel/issues/4059
if st.Mode()&os.ModeNamedPipe != 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Cannot open files of this type.",
})
return
}
c.Header("X-Mime-Type", st.Mimetype) c.Header("X-Mime-Type", st.Mimetype)
c.Header("Content-Length", strconv.Itoa(int(st.Size()))) c.Header("Content-Length", strconv.Itoa(int(st.Size())))
@@ -131,10 +120,6 @@ func putServerRenameFiles(c *gin.Context) {
// Return nil if the error is an is not exists. // Return nil if the error is an is not exists.
// NOTE: os.IsNotExist() does not work if the error is wrapped. // NOTE: os.IsNotExist() does not work if the error is wrapped.
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
s.Log().WithField("error", err).
WithField("from_path", pf).
WithField("to_path", pt).
Warn("failed to rename: source or target does not exist")
return nil return nil
} }
return err return err
@@ -270,12 +255,9 @@ func postServerPullRemoteFile(c *gin.Context) {
s := ExtractServer(c) s := ExtractServer(c)
var data struct { var data struct {
// Deprecated // Deprecated
Directory string `binding:"required_without=RootPath,omitempty" json:"directory"` Directory string `binding:"required_without=RootPath,omitempty" json:"directory"`
RootPath string `binding:"required_without=Directory,omitempty" json:"root"` RootPath string `binding:"required_without=Directory,omitempty" json:"root"`
URL string `binding:"required" json:"url"` URL string `binding:"required" json:"url"`
FileName string `json:"file_name"`
UseHeader bool `json:"use_header"`
Foreground bool `json:"foreground"`
} }
if err := c.BindJSON(&data); err != nil { if err := c.BindJSON(&data); err != nil {
return return
@@ -313,41 +295,21 @@ func postServerPullRemoteFile(c *gin.Context) {
dl := downloader.New(s, downloader.DownloadRequest{ dl := downloader.New(s, downloader.DownloadRequest{
Directory: data.RootPath, Directory: data.RootPath,
URL: u, URL: u,
FileName: data.FileName,
UseHeader: data.UseHeader,
}) })
download := func() error { // Execute this pull in a separate thread since it may take a long time to complete.
go func() {
s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk") s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk")
if err := dl.Execute(); err != nil { if err := dl.Execute(); err != nil {
s.Log().WithField("download_id", dl.Identifier).WithField("error", err).Error("failed to pull remote file") s.Log().WithField("download_id", dl.Identifier).WithField("error", err).Error("failed to pull remote file")
return err
} else { } else {
s.Log().WithField("download_id", dl.Identifier).Info("completed pull of remote file") s.Log().WithField("download_id", dl.Identifier).Info("completed pull of remote file")
} }
return nil }()
}
if !data.Foreground {
go func() {
_ = download()
}()
c.JSON(http.StatusAccepted, gin.H{
"identifier": dl.Identifier,
})
return
}
if err := download(); err != nil { c.JSON(http.StatusAccepted, gin.H{
NewServerError(err, s).Abort(c) "identifier": dl.Identifier,
return })
}
st, err := s.Filesystem().Stat(dl.Path())
if err != nil {
NewServerError(err, s).AbortFilesystemError(c)
return
}
c.JSON(http.StatusOK, &st)
} }
// Stops a remote file download if it exists and belongs to this server. // Stops a remote file download if it exists and belongs to this server.
@@ -575,16 +537,8 @@ func postServerUploadFiles(c *gin.Context) {
directory := c.Query("directory") directory := c.Query("directory")
maxFileSize := config.Get().Api.UploadLimit
maxFileSizeBytes := maxFileSize * 1024 * 1024
var totalSize int64 var totalSize int64
for _, header := range headers { for _, header := range headers {
if header.Size > maxFileSizeBytes {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "File " + header.Filename + " is larger than the maximum file upload size of " + strconv.FormatInt(maxFileSize, 10) + " MB.",
})
return
}
totalSize += header.Size totalSize += header.Size
} }

View File

@@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/goccy/go-json"
ws "github.com/gorilla/websocket" ws "github.com/gorilla/websocket"
"github.com/goccy/go-json"
"github.com/pterodactyl/wings/router/middleware" "github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/websocket" "github.com/pterodactyl/wings/router/websocket"

View File

@@ -19,7 +19,7 @@ func TestName(t *testing.T) {
}) })
g.It("calls strike once per time period", func() { g.It("calls strike once per time period", func() {
t := newConsoleThrottle(1, time.Millisecond*20) t := newConsoleThrottle(1, time.Millisecond * 20)
var times int var times int
t.strike = func() { t.strike = func() {
@@ -53,10 +53,10 @@ func TestName(t *testing.T) {
} }
func BenchmarkConsoleThrottle(b *testing.B) { func BenchmarkConsoleThrottle(b *testing.B) {
t := newConsoleThrottle(10, time.Millisecond*10) t := newConsoleThrottle(10, time.Millisecond * 10)
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
t.Allow() t.Allow()
} }
} }

View File

@@ -52,4 +52,4 @@ func (s *Server) DestroyAllSinks() {
for _, sink := range s.sinks { for _, sink := range s.sinks {
sink.Destroy() sink.Destroy()
} }
} }

View File

@@ -130,7 +130,7 @@ func (a *Archive) withFilesCallback(tw *tar.Writer) func(path string, de *godirw
for _, f := range a.Files { for _, f := range a.Files {
// If the given doesn't match, or doesn't have the same prefix continue // If the given doesn't match, or doesn't have the same prefix continue
// to the next item in the loop. // to the next item in the loop.
if p != f && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", f) { if p != f && !strings.HasPrefix(p, f) {
continue continue
} }

View File

@@ -5,12 +5,9 @@ import (
"archive/zip" "archive/zip"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
gzip2 "github.com/klauspost/compress/gzip"
zip2 "github.com/klauspost/compress/zip"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -175,26 +172,13 @@ func ExtractNameFromArchive(f archiver.File) string {
return f.Name() return f.Name()
} }
switch s := sys.(type) { switch s := sys.(type) {
case *zip.FileHeader:
return s.Name
case *zip2.FileHeader:
return s.Name
case *tar.Header: case *tar.Header:
return s.Name return s.Name
case *gzip.Header: case *gzip.Header:
return s.Name return s.Name
case *gzip2.Header: case *zip.FileHeader:
return s.Name return s.Name
default: default:
// At this point we cannot figure out what type of archive this might be so
// just try to find the name field in the struct. If it is found return it.
field := reflect.Indirect(reflect.ValueOf(sys)).FieldByName("Name")
if field.IsValid() {
return field.String()
}
// Fallback to the basename of the file at this point. There is nothing we can really
// do to try and figure out what the underlying directory of the file is supposed to
// be since it didn't implement a name field.
return f.Name() return f.Name()
} }
} }

View File

@@ -115,6 +115,19 @@ func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) {
return f, nil return f, nil
} }
// Reads a file on the system and returns it as a byte representation in a file
// reader. This is not the most memory efficient usage since it will be reading the
// entirety of the file into memory.
func (fs *Filesystem) Readfile(p string, w io.Writer) error {
file, _, err := fs.File(p)
if err != nil {
return err
}
defer file.Close()
_, err = bufio.NewReader(file).WriteTo(w)
return err
}
// Writefile writes a file to the system. If the file does not already exist one // Writefile writes a file to the system. If the file does not already exist one
// will be created. This will also properly recalculate the disk space used by // will be created. This will also properly recalculate the disk space used by
// the server when writing new files or modifying existing ones. // the server when writing new files or modifying existing ones.
@@ -171,16 +184,16 @@ func (fs *Filesystem) CreateDirectory(name string, p string) error {
return os.MkdirAll(cleaned, 0o755) return os.MkdirAll(cleaned, 0o755)
} }
// Rename moves (or renames) a file or directory. // Moves (or renames) a file or directory.
func (fs *Filesystem) Rename(from string, to string) error { func (fs *Filesystem) Rename(from string, to string) error {
cleanedFrom, err := fs.SafePath(from) cleanedFrom, err := fs.SafePath(from)
if err != nil { if err != nil {
return errors.WithStack(err) return err
} }
cleanedTo, err := fs.SafePath(to) cleanedTo, err := fs.SafePath(to)
if err != nil { if err != nil {
return errors.WithStack(err) return err
} }
// If the target file or directory already exists the rename function will fail, so just // If the target file or directory already exists the rename function will fail, so just
@@ -202,10 +215,7 @@ func (fs *Filesystem) Rename(from string, to string) error {
} }
} }
if err := os.Rename(cleanedFrom, cleanedTo); err != nil { return os.Rename(cleanedFrom, cleanedTo)
return errors.WithStack(err)
}
return nil
} }
// Recursively iterates over a file or directory and sets the permissions on all of the // Recursively iterates over a file or directory and sets the permissions on all of the
@@ -482,11 +492,7 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
cleanedp, _ = fs.SafePath(filepath.Join(cleaned, f.Name())) cleanedp, _ = fs.SafePath(filepath.Join(cleaned, f.Name()))
} }
// Don't try to detect the type on a pipe — this will just hang the application and if cleanedp != "" {
// you'll never get a response back.
//
// @see https://github.com/pterodactyl/panel/issues/4059
if cleanedp != "" && f.Mode()&os.ModeNamedPipe == 0 {
m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name())) m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name()))
} else { } else {
// Just pass this for an unknown type because the file could not safely be resolved within // Just pass this for an unknown type because the file could not safely be resolved within

View File

@@ -1,7 +1,6 @@
package filesystem package filesystem
import ( import (
"bufio"
"bytes" "bytes"
"errors" "errors"
"math/rand" "math/rand"
@@ -45,14 +44,6 @@ type rootFs struct {
root string root string
} }
func getFileContent(file *os.File) string {
var w bytes.Buffer
if _, err := bufio.NewReader(file).WriteTo(&w); err != nil {
panic(err)
}
return w.String()
}
func (rfs *rootFs) CreateServerFile(p string, c []byte) error { func (rfs *rootFs) CreateServerFile(p string, c []byte) error {
f, err := os.Create(filepath.Join(rfs.root, "/server", p)) f, err := os.Create(filepath.Join(rfs.root, "/server", p))
@@ -84,6 +75,54 @@ func (rfs *rootFs) reset() {
} }
} }
func TestFilesystem_Readfile(t *testing.T) {
g := Goblin(t)
fs, rfs := NewFs()
g.Describe("Readfile", func() {
buf := &bytes.Buffer{}
g.It("opens a file if it exists on the system", func() {
err := rfs.CreateServerFileFromString("test.txt", "testing")
g.Assert(err).IsNil()
err = fs.Readfile("test.txt", buf)
g.Assert(err).IsNil()
g.Assert(buf.String()).Equal("testing")
})
g.It("returns an error if the file does not exist", func() {
err := fs.Readfile("test.txt", buf)
g.Assert(err).IsNotNil()
g.Assert(errors.Is(err, os.ErrNotExist)).IsTrue()
})
g.It("returns an error if the \"file\" is a directory", func() {
err := os.Mkdir(filepath.Join(rfs.root, "/server/test.txt"), 0o755)
g.Assert(err).IsNil()
err = fs.Readfile("test.txt", buf)
g.Assert(err).IsNotNil()
g.Assert(IsErrorCode(err, ErrCodeIsDirectory)).IsTrue()
})
g.It("cannot open a file outside the root directory", func() {
err := rfs.CreateServerFileFromString("/../test.txt", "testing")
g.Assert(err).IsNil()
err = fs.Readfile("/../test.txt", buf)
g.Assert(err).IsNotNil()
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
})
g.AfterEach(func() {
buf.Truncate(0)
atomic.StoreInt64(&fs.diskUsed, 0)
rfs.reset()
})
})
}
func TestFilesystem_Writefile(t *testing.T) { func TestFilesystem_Writefile(t *testing.T) {
g := Goblin(t) g := Goblin(t)
fs, rfs := NewFs() fs, rfs := NewFs()
@@ -101,10 +140,9 @@ func TestFilesystem_Writefile(t *testing.T) {
err := fs.Writefile("test.txt", r) err := fs.Writefile("test.txt", r)
g.Assert(err).IsNil() g.Assert(err).IsNil()
f, _, err := fs.File("test.txt") err = fs.Readfile("test.txt", buf)
g.Assert(err).IsNil() g.Assert(err).IsNil()
defer f.Close() g.Assert(buf.String()).Equal("test file content")
g.Assert(getFileContent(f)).Equal("test file content")
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(r.Size()) g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(r.Size())
}) })
@@ -114,10 +152,9 @@ func TestFilesystem_Writefile(t *testing.T) {
err := fs.Writefile("/some/nested/test.txt", r) err := fs.Writefile("/some/nested/test.txt", r)
g.Assert(err).IsNil() g.Assert(err).IsNil()
f, _, err := fs.File("/some/nested/test.txt") err = fs.Readfile("/some/nested/test.txt", buf)
g.Assert(err).IsNil() g.Assert(err).IsNil()
defer f.Close() g.Assert(buf.String()).Equal("test file content")
g.Assert(getFileContent(f)).Equal("test file content")
}) })
g.It("can create a new file inside a nested directory without a trailing slash", func() { g.It("can create a new file inside a nested directory without a trailing slash", func() {
@@ -126,10 +163,9 @@ func TestFilesystem_Writefile(t *testing.T) {
err := fs.Writefile("some/../foo/bar/test.txt", r) err := fs.Writefile("some/../foo/bar/test.txt", r)
g.Assert(err).IsNil() g.Assert(err).IsNil()
f, _, err := fs.File("foo/bar/test.txt") err = fs.Readfile("foo/bar/test.txt", buf)
g.Assert(err).IsNil() g.Assert(err).IsNil()
defer f.Close() g.Assert(buf.String()).Equal("test file content")
g.Assert(getFileContent(f)).Equal("test file content")
}) })
g.It("cannot create a file outside the root directory", func() { g.It("cannot create a file outside the root directory", func() {
@@ -154,6 +190,28 @@ func TestFilesystem_Writefile(t *testing.T) {
g.Assert(IsErrorCode(err, ErrCodeDiskSpace)).IsTrue() g.Assert(IsErrorCode(err, ErrCodeDiskSpace)).IsTrue()
}) })
/*g.It("updates the total space used when a file is appended to", func() {
atomic.StoreInt64(&fs.diskUsed, 100)
b := make([]byte, 100)
_, _ = rand.Read(b)
r := bytes.NewReader(b)
err := fs.Writefile("test.txt", r)
g.Assert(err).IsNil()
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(200))
// If we write less data than already exists, we should expect the total
// disk used to be decremented.
b = make([]byte, 50)
_, _ = rand.Read(b)
r = bytes.NewReader(b)
err = fs.Writefile("test.txt", r)
g.Assert(err).IsNil()
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(150))
})*/
g.It("truncates the file when writing new contents", func() { g.It("truncates the file when writing new contents", func() {
r := bytes.NewReader([]byte("original data")) r := bytes.NewReader([]byte("original data"))
err := fs.Writefile("test.txt", r) err := fs.Writefile("test.txt", r)
@@ -163,10 +221,9 @@ func TestFilesystem_Writefile(t *testing.T) {
err = fs.Writefile("test.txt", r) err = fs.Writefile("test.txt", r)
g.Assert(err).IsNil() g.Assert(err).IsNil()
f, _, err := fs.File("test.txt") err = fs.Readfile("test.txt", buf)
g.Assert(err).IsNil() g.Assert(err).IsNil()
defer f.Close() g.Assert(buf.String()).Equal("new data")
g.Assert(getFileContent(f)).Equal("new data")
}) })
g.AfterEach(func() { g.AfterEach(func() {

View File

@@ -119,6 +119,16 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
panic(err) panic(err)
} }
g.Describe("Readfile", func() {
g.It("cannot read a file symlinked outside the root", func() {
b := bytes.Buffer{}
err := fs.Readfile("symlinked.txt", &b)
g.Assert(err).IsNotNil()
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
})
})
g.Describe("Writefile", func() { g.Describe("Writefile", func() {
g.It("cannot write to a file symlinked outside the root", func() { g.It("cannot write to a file symlinked outside the root", func() {
r := bytes.NewReader([]byte("testing")) r := bytes.NewReader([]byte("testing"))

View File

@@ -34,7 +34,7 @@ func (s *Server) Install(sync bool) error {
if sync { if sync {
s.Log().Info("syncing server state with remote source before executing installation process") s.Log().Info("syncing server state with remote source before executing installation process")
if err := s.Sync(); err != nil { if err := s.Sync(); err != nil {
return errors.WrapIf(err, "install: failed to sync server state with Panel") return err
} }
} }
@@ -58,7 +58,7 @@ func (s *Server) Install(sync bool) error {
// error to this log entry. Otherwise ignore it in this log since whatever is calling // error to this log entry. Otherwise ignore it in this log since whatever is calling
// this function should handle the error and will end up logging the same one. // this function should handle the error and will end up logging the same one.
if err == nil { if err == nil {
l.WithField("error", err) l.WithField("error", serr)
} }
l.Warn("failed to notify panel of server install state") l.Warn("failed to notify panel of server install state")
@@ -72,7 +72,7 @@ func (s *Server) Install(sync bool) error {
// the install is completed. // the install is completed.
s.Events().Publish(InstallCompletedEvent, "") s.Events().Publish(InstallCompletedEvent, "")
return errors.WithStackIf(err) return err
} }
// Reinstalls a server's software by utilizing the install script for the server egg. This // Reinstalls a server's software by utilizing the install script for the server egg. This
@@ -81,7 +81,7 @@ func (s *Server) Reinstall() error {
if s.Environment.State() != environment.ProcessOfflineState { if s.Environment.State() != environment.ProcessOfflineState {
s.Log().Debug("waiting for server instance to enter a stopped state") s.Log().Debug("waiting for server instance to enter a stopped state")
if err := s.Environment.WaitForStop(s.Context(), time.Second*10, true); err != nil { if err := s.Environment.WaitForStop(s.Context(), time.Second*10, true); err != nil {
return errors.WrapIf(err, "install: failed to stop running environment") return err
} }
} }
@@ -204,7 +204,7 @@ func (ip *InstallationProcess) Run() error {
// Returns the location of the temporary data for the installation process. // Returns the location of the temporary data for the installation process.
func (ip *InstallationProcess) tempDir() string { func (ip *InstallationProcess) tempDir() string {
return filepath.Join(config.Get().System.TmpDirectory, ip.Server.ID()) return filepath.Join(os.TempDir(), "pterodactyl/", ip.Server.ID())
} }
// Writes the installation script to a temporary file on the host machine so that it // Writes the installation script to a temporary file on the host machine so that it

View File

@@ -288,10 +288,14 @@ func (h *Handler) can(permission string) bool {
return false return false
} }
// SFTPServer owners and super admins have their permissions returned as '[*]' via the Panel
// API, so for the sake of speed do an initial check for that before iterating over the
// entire array of permissions.
if len(h.permissions) == 1 && h.permissions[0] == "*" {
return true
}
for _, p := range h.permissions { for _, p := range h.permissions {
// If we match the permission specifically, or the user has been granted the "*" if p == permission {
// permission because they're an admin, let them through.
if p == permission || p == "*" {
return true return true
} }
} }

View File

@@ -68,14 +68,9 @@ func (c *SFTPServer) Run() error {
} }
conf := &ssh.ServerConfig{ conf := &ssh.ServerConfig{
NoClientAuth: false, NoClientAuth: false,
MaxAuthTries: 6, MaxAuthTries: 6,
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { PasswordCallback: c.passwordCallback,
return c.makeCredentialsRequest(conn, remote.SftpAuthPassword, string(password))
},
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
return c.makeCredentialsRequest(conn, remote.SftpAuthPublicKey, string(ssh.MarshalAuthorizedKey(key)))
},
} }
conf.AddHostKey(private) conf.AddHostKey(private)
@@ -182,17 +177,17 @@ func (c *SFTPServer) generateED25519PrivateKey() error {
return nil return nil
} }
func (c *SFTPServer) makeCredentialsRequest(conn ssh.ConnMetadata, t remote.SftpAuthRequestType, p string) (*ssh.Permissions, error) { // A function capable of validating user credentials with the Panel API.
func (c *SFTPServer) passwordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
request := remote.SftpAuthRequest{ request := remote.SftpAuthRequest{
Type: t,
User: conn.User(), User: conn.User(),
Pass: p, Pass: string(pass),
IP: conn.RemoteAddr().String(), IP: conn.RemoteAddr().String(),
SessionID: conn.SessionID(), SessionID: conn.SessionID(),
ClientVersion: conn.ClientVersion(), ClientVersion: conn.ClientVersion(),
} }
logger := log.WithFields(log.Fields{"subsystem": "sftp", "method": request.Type, "username": request.User, "ip": request.IP}) logger := log.WithFields(log.Fields{"subsystem": "sftp", "username": conn.User(), "ip": conn.RemoteAddr().String()})
logger.Debug("validating credentials for SFTP connection") logger.Debug("validating credentials for SFTP connection")
if !validUsernameRegexp.MatchString(request.User) { if !validUsernameRegexp.MatchString(request.User) {
@@ -211,7 +206,7 @@ func (c *SFTPServer) makeCredentialsRequest(conn ssh.ConnMetadata, t remote.Sftp
} }
logger.WithField("server", resp.Server).Debug("credentials validated and matched to server instance") logger.WithField("server", resp.Server).Debug("credentials validated and matched to server instance")
permissions := ssh.Permissions{ sshPerm := &ssh.Permissions{
Extensions: map[string]string{ Extensions: map[string]string{
"uuid": resp.Server, "uuid": resp.Server,
"user": conn.User(), "user": conn.User(),
@@ -219,7 +214,7 @@ func (c *SFTPServer) makeCredentialsRequest(conn ssh.ConnMetadata, t remote.Sftp
}, },
} }
return &permissions, nil return sshPerm, nil
} }
// PrivateKeyPath returns the path the host private key for this server instance. // PrivateKeyPath returns the path the host private key for this server instance.

View File

@@ -1,3 +1,3 @@
package system package system
var Version = "develop" var Version = "1.6.1"

View File

@@ -42,6 +42,7 @@ func (l *Locker) Acquire() error {
return nil return nil
} }
// TryAcquire will attempt to acquire a power-lock until the context provided // TryAcquire will attempt to acquire a power-lock until the context provided
// is canceled. // is canceled.
func (l *Locker) TryAcquire(ctx context.Context) error { func (l *Locker) TryAcquire(ctx context.Context) error {

View File

@@ -95,7 +95,7 @@ func TestPower(t *testing.T) {
l.Acquire() l.Acquire()
go func() { go func() {
time.AfterFunc(time.Millisecond*50, func() { time.AfterFunc(time.Millisecond * 50, func() {
l.Release() l.Release()
}) })
}() }()

View File

@@ -44,7 +44,7 @@ func (r *Rate) Try() bool {
// Reset resets the internal state of the rate limiter back to zero. // Reset resets the internal state of the rate limiter back to zero.
func (r *Rate) Reset() { func (r *Rate) Reset() {
r.mu.Lock() r.mu.Lock()
r.count = 0 r.count = 0
r.last = time.Now() r.last = time.Now()
r.mu.Unlock() r.mu.Unlock()
} }

View File

@@ -47,7 +47,7 @@ func TestRate(t *testing.T) {
g.It("resets back to zero when called", func() { g.It("resets back to zero when called", func() {
r := NewRate(10, time.Second) r := NewRate(10, time.Second)
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
if i%10 == 0 { if i % 10 == 0 {
r.Reset() r.Reset()
} }
g.Assert(r.Try()).IsTrue() g.Assert(r.Try()).IsTrue()