wings/router/router_transfer.go

274 lines
7.1 KiB
Go
Raw Permalink Normal View History

package router
import (
2022-11-15 01:25:01 +00:00
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
2022-11-15 01:25:01 +00:00
"errors"
"fmt"
2021-01-10 01:22:39 +00:00
"io"
2022-11-15 01:25:01 +00:00
"mime"
"mime/multipart"
2021-01-10 01:22:39 +00:00
"net/http"
"os"
"strings"
2020-09-04 03:29:53 +00:00
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
2021-01-26 04:28:24 +00:00
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
2022-11-15 01:25:01 +00:00
"github.com/pterodactyl/wings/server/installer"
"github.com/pterodactyl/wings/server/transfer"
)
2022-11-15 01:25:01 +00:00
// postTransfers .
func postTransfers(c *gin.Context) {
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
if len(auth) != 2 || auth[0] != "Bearer" {
c.Header("WWW-Authenticate", "Bearer")
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "The required authorization heads were not present in the request.",
})
return
}
token := tokens.TransferPayload{}
if err := tokens.ParseToken([]byte(auth[1]), &token); err != nil {
middleware.CaptureAndAbort(c, err)
return
}
2022-11-15 01:25:01 +00:00
manager := middleware.ExtractManager(c)
u, err := uuid.Parse(token.Subject)
if err != nil {
middleware.CaptureAndAbort(c, err)
return
}
2022-11-15 01:25:01 +00:00
// Get or create a new transfer instance for this server.
var (
ctx context.Context
cancel context.CancelFunc
)
trnsfr := transfer.Incoming().Get(u.String())
if trnsfr == nil {
// TODO: should this use the request context?
trnsfr = transfer.New(c, nil)
ctx, cancel = context.WithCancel(trnsfr.Context())
defer cancel()
2021-03-07 18:02:03 +00:00
2022-11-15 01:25:01 +00:00
i, err := installer.New(ctx, manager, installer.ServerDetails{
UUID: u.String(),
StartOnCompletion: false,
})
if err != nil {
if err := manager.Client().SetTransferStatus(context.Background(), trnsfr.Server.ID(), false); err != nil {
trnsfr.Log().WithField("status", false).WithError(err).Error("failed to set transfer status")
}
middleware.CaptureAndAbort(c, err)
return
}
2022-11-15 01:25:01 +00:00
i.Server().SetTransferring(true)
manager.Add(i.Server())
2022-11-15 01:25:01 +00:00
// We add the transfer to the list of transfers once we have a server instance to use.
trnsfr.Server = i.Server()
transfer.Incoming().Add(trnsfr)
} else {
ctx, cancel = context.WithCancel(trnsfr.Context())
defer cancel()
}
2022-11-15 01:25:01 +00:00
// Any errors past this point (until the transfer is complete) will abort
// the transfer.
2022-11-15 01:25:01 +00:00
successful := false
defer func(ctx context.Context, trnsfr *transfer.Transfer) {
// Remove the transfer from the list of incoming transfers.
transfer.Incoming().Remove(trnsfr)
2022-11-15 01:25:01 +00:00
if !successful {
trnsfr.Server.Events().Publish(server.TransferStatusEvent, "failure")
manager.Remove(func(match *server.Server) bool {
return match.ID() == trnsfr.Server.ID()
})
}
2022-11-15 01:25:01 +00:00
if err := manager.Client().SetTransferStatus(context.Background(), trnsfr.Server.ID(), successful); err != nil {
// Only delete the files if the transfer actually failed, otherwise we could have
// unrecoverable data-loss.
if !successful && err != nil {
// Delete all extracted files.
go func(trnsfr *transfer.Transfer) {
if err := os.RemoveAll(trnsfr.Server.Filesystem().Path()); err != nil && !os.IsNotExist(err) {
trnsfr.Log().WithError(err).Warn("failed to delete local server files")
}
}(trnsfr)
}
2022-11-15 01:25:01 +00:00
trnsfr.Log().WithField("status", successful).WithError(err).Error("failed to set transfer status on panel")
return
}
2022-11-15 01:25:01 +00:00
trnsfr.Server.SetTransferring(false)
trnsfr.Server.Events().Publish(server.TransferStatusEvent, "success")
}(ctx, trnsfr)
2022-11-15 01:25:01 +00:00
mediaType, params, err := mime.ParseMediaType(c.GetHeader("Content-Type"))
if err != nil {
trnsfr.Log().Debug("failed to parse content type header")
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
if !strings.HasPrefix(mediaType, "multipart/") {
trnsfr.Log().Debug("invalid content type")
middleware.CaptureAndAbort(c, fmt.Errorf("invalid content type \"%s\", expected \"multipart/form-data\"", mediaType))
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
// Used to calculate the hash of the file as it is being uploaded.
h := sha256.New()
2022-11-15 01:25:01 +00:00
// Used to read the file and checksum from the request body.
mr := multipart.NewReader(c.Request.Body, params["boundary"])
// Loop through the parts of the request body and process them.
var (
hasArchive bool
hasChecksum bool
checksumVerified bool
)
out:
for {
select {
case <-ctx.Done():
break out
default:
p, err := mr.NextPart()
if err == io.EOF {
break out
}
if err != nil {
middleware.CaptureAndAbort(c, err)
return
}
2022-11-15 01:25:01 +00:00
name := p.FormName()
switch name {
case "archive":
trnsfr.Log().Debug("received archive")
2022-11-15 01:25:01 +00:00
if err := trnsfr.Server.EnsureDataDirectoryExists(); err != nil {
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
tee := io.TeeReader(p, h)
if err := trnsfr.Server.Filesystem().ExtractStreamUnsafe(ctx, "/", tee); err != nil {
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
hasArchive = true
case "checksum":
trnsfr.Log().Debug("received checksum")
2021-03-07 18:02:03 +00:00
2022-11-15 01:25:01 +00:00
if !hasArchive {
middleware.CaptureAndAbort(c, errors.New("archive must be sent before the checksum"))
return
}
2022-11-15 01:25:01 +00:00
hasChecksum = true
2022-11-15 01:25:01 +00:00
v, err := io.ReadAll(p)
if err != nil {
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
expected := make([]byte, hex.DecodedLen(len(v)))
n, err := hex.Decode(expected, v)
if err != nil {
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
actual := h.Sum(nil)
2022-11-15 01:25:01 +00:00
trnsfr.Log().WithFields(log.Fields{
"expected": hex.EncodeToString(expected),
"actual": hex.EncodeToString(actual),
}).Debug("checksums")
2022-11-15 01:25:01 +00:00
if !bytes.Equal(expected[:n], actual) {
middleware.CaptureAndAbort(c, errors.New("checksums don't match"))
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
trnsfr.Log().Debug("checksums match")
checksumVerified = true
default:
continue
}
}
2020-12-12 00:24:35 +00:00
}
2022-11-15 01:25:01 +00:00
if !hasArchive || !hasChecksum {
middleware.CaptureAndAbort(c, errors.New("missing archive or checksum"))
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
if !checksumVerified {
middleware.CaptureAndAbort(c, errors.New("checksums don't match"))
return
}
2022-11-15 01:25:01 +00:00
// Transfer is almost complete, we just want to ensure the environment is
// configured correctly. We might want to not fail the transfer at this
// stage, but we will just to be safe.
2022-11-15 01:25:01 +00:00
// Ensure the server environment gets configured.
if err := trnsfr.Server.CreateEnvironment(); err != nil {
middleware.CaptureAndAbort(c, err)
2022-11-15 01:25:01 +00:00
return
}
2022-11-15 01:25:01 +00:00
// Changing this causes us to notify the panel about a successful transfer,
// rather than failing the transfer like we do by default.
successful = true
// The rest of the logic for ensuring the server is unlocked and everything
// is handled in the deferred function above.
trnsfr.Log().Debug("done!")
}
2022-11-15 01:25:01 +00:00
// deleteTransfer cancels an incoming transfer for a server.
func deleteTransfer(c *gin.Context) {
s := ExtractServer(c)
if !s.IsTransferring() {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{
"error": "Server is not currently being transferred.",
})
return
}
2022-11-15 01:25:01 +00:00
trnsfr := transfer.Incoming().Get(s.ID())
if trnsfr == nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{
"error": "Server is not currently being transferred.",
})
return
}
2022-11-15 01:25:01 +00:00
trnsfr.Cancel()
c.Status(http.StatusAccepted)
}