mirror of
https://github.com/safing/portmaster
synced 2025-04-21 03:19:10 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
209 lines
5.5 KiB
Go
209 lines
5.5 KiB
Go
package sync
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
|
|
|
"github.com/safing/jess/filesig"
|
|
"github.com/safing/portmaster/base/api"
|
|
"github.com/safing/structures/container"
|
|
"github.com/safing/structures/dsd"
|
|
)
|
|
|
|
// Type is the type of an export.
|
|
type Type string
|
|
|
|
// Export Types.
|
|
const (
|
|
TypeProfile = "profile"
|
|
TypeSettings = "settings"
|
|
TypeSingleSetting = "single-setting"
|
|
)
|
|
|
|
// Export IDs.
|
|
const (
|
|
ExportTargetGlobal = "global"
|
|
)
|
|
|
|
// Messages.
|
|
var (
|
|
MsgNone = ""
|
|
MsgValid = "Import is valid."
|
|
MsgSuccess = "Import successful."
|
|
MsgRequireRestart = "Import successful. Restart required for setting to take effect."
|
|
)
|
|
|
|
// ExportRequest is a request for an export.
|
|
type ExportRequest struct {
|
|
From string `json:"from"`
|
|
Keys []string `json:"keys"`
|
|
}
|
|
|
|
// ImportRequest is a request to import an export.
|
|
type ImportRequest struct {
|
|
// Where the export should be import to.
|
|
Target string `json:"target"`
|
|
// Only validate, but do not actually change anything.
|
|
ValidateOnly bool `json:"validateOnly"`
|
|
|
|
RawExport string `json:"rawExport"`
|
|
RawMime string `json:"rawMime"`
|
|
}
|
|
|
|
// ImportResult is returned by successful import operations.
|
|
type ImportResult struct {
|
|
RestartRequired bool `json:"restartRequired"`
|
|
ReplacesExisting bool `json:"replacesExisting"`
|
|
ContainsUnknown bool `json:"containsUnknown"`
|
|
}
|
|
|
|
// Errors.
|
|
var (
|
|
ErrMismatch = api.ErrorWithStatus(
|
|
errors.New("the supplied export cannot be imported here"),
|
|
http.StatusPreconditionFailed,
|
|
)
|
|
ErrSettingNotFound = api.ErrorWithStatus(
|
|
errors.New("setting not found"),
|
|
http.StatusPreconditionFailed,
|
|
)
|
|
ErrTargetNotFound = api.ErrorWithStatus(
|
|
errors.New("import/export target does not exist"),
|
|
http.StatusGone,
|
|
)
|
|
ErrUnchanged = api.ErrorWithStatus(
|
|
errors.New("cannot export unchanged setting"),
|
|
http.StatusGone,
|
|
)
|
|
ErrNotSettablePerApp = api.ErrorWithStatus(
|
|
errors.New("cannot be set per app"),
|
|
http.StatusGone,
|
|
)
|
|
ErrInvalidImportRequest = api.ErrorWithStatus(
|
|
errors.New("invalid import request"),
|
|
http.StatusUnprocessableEntity,
|
|
)
|
|
ErrInvalidSettingValue = api.ErrorWithStatus(
|
|
errors.New("invalid setting value"),
|
|
http.StatusUnprocessableEntity,
|
|
)
|
|
ErrInvalidProfileData = api.ErrorWithStatus(
|
|
errors.New("invalid profile data"),
|
|
http.StatusUnprocessableEntity,
|
|
)
|
|
ErrImportFailed = api.ErrorWithStatus(
|
|
errors.New("import failed"),
|
|
http.StatusInternalServerError,
|
|
)
|
|
ErrExportFailed = api.ErrorWithStatus(
|
|
errors.New("export failed"),
|
|
http.StatusInternalServerError,
|
|
)
|
|
)
|
|
|
|
func serializeExport(export any, ar *api.Request) (data []byte, err error) {
|
|
// Get format.
|
|
format := dsd.FormatFromAccept(ar.Header.Get("Accept"))
|
|
|
|
// Serialize and add checksum.
|
|
switch format {
|
|
case dsd.JSON:
|
|
data, err = json.Marshal(export)
|
|
if err == nil {
|
|
data, err = filesig.AddJSONChecksum(data)
|
|
}
|
|
case dsd.YAML:
|
|
data, err = yaml.Marshal(export)
|
|
if err == nil {
|
|
data, err = filesig.AddYAMLChecksum(data, filesig.TextPlacementBottom)
|
|
}
|
|
default:
|
|
return nil, dsd.ErrIncompatibleFormat
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to serialize: %w", err)
|
|
}
|
|
|
|
// Set Content-Type HTTP Header.
|
|
ar.ResponseHeader.Set("Content-Type", dsd.FormatToMimeType[format])
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func serializeProfileExport(export *ProfileExport, ar *api.Request) ([]byte, error) {
|
|
// Do a regular serialize, if we don't need parts.
|
|
switch {
|
|
case export.IconData == "":
|
|
// With no icon, do a regular export.
|
|
return serializeExport(export, ar)
|
|
case dsd.FormatFromAccept(ar.Header.Get("Accept")) != dsd.YAML:
|
|
// Only export in parts for yaml.
|
|
return serializeExport(export, ar)
|
|
}
|
|
|
|
// Step 1: Separate profile icon.
|
|
profileIconExport := &ProfileIcon{
|
|
IconData: export.IconData,
|
|
}
|
|
export.IconData = ""
|
|
|
|
// Step 2: Serialize main export.
|
|
profileData, err := yaml.Marshal(export)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to serialize profile data: %w", err)
|
|
}
|
|
|
|
// Step 3: Serialize icon only.
|
|
iconData, err := yaml.Marshal(profileIconExport)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to serialize profile icon: %w", err)
|
|
}
|
|
|
|
// Step 4: Stitch data together and add copyright notice for icon.
|
|
exportData := container.New(
|
|
profileData,
|
|
[]byte(`
|
|
# The application icon below is the property of its respective owner.
|
|
# The icon is used for identification purposes only, and does not imply any endorsement or affiliation with their respective owners.
|
|
# It is the sole responsibility of the individual or entity sharing this dataset to ensure they have the necessary permissions to do so.
|
|
`),
|
|
iconData,
|
|
).CompileData()
|
|
|
|
// Step 4: Add checksum.
|
|
exportData, err = filesig.AddYAMLChecksum(exportData, filesig.TextPlacementBottom)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to add checksum: %w", err)
|
|
}
|
|
|
|
// Set Content-Type HTTP Header.
|
|
ar.ResponseHeader.Set("Content-Type", dsd.FormatToMimeType[dsd.YAML])
|
|
|
|
return exportData, nil
|
|
}
|
|
|
|
func parseExport(request *ImportRequest, export any) error {
|
|
format, err := dsd.MimeLoad([]byte(request.RawExport), request.RawMime, export)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: failed to parse export: %w", ErrInvalidImportRequest, err)
|
|
}
|
|
|
|
// Verify checksum, if available.
|
|
switch format {
|
|
case dsd.JSON:
|
|
err = filesig.VerifyJSONChecksum([]byte(request.RawExport))
|
|
case dsd.YAML:
|
|
err = filesig.VerifyYAMLChecksum([]byte(request.RawExport))
|
|
default:
|
|
// Checksums not supported.
|
|
}
|
|
if err != nil && !errors.Is(err, filesig.ErrChecksumMissing) {
|
|
return fmt.Errorf("failed to verify checksum: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|