| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217 | 
							- // Copyright (C) 2019-2023 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 httpd implements REST API and Web interface for SFTPGo.
 
- // The OpenAPI 3 schema for the supported API can be found inside the source tree:
 
- // https://github.com/drakkan/sftpgo/blob/main/openapi/openapi.yaml
 
- package httpd
 
- import (
 
- 	"crypto/sha256"
 
- 	"errors"
 
- 	"fmt"
 
- 	"net"
 
- 	"net/http"
 
- 	"os"
 
- 	"path"
 
- 	"path/filepath"
 
- 	"runtime"
 
- 	"strings"
 
- 	"sync"
 
- 	"time"
 
- 	"github.com/go-chi/chi/v5"
 
- 	"github.com/go-chi/jwtauth/v5"
 
- 	"github.com/lestrrat-go/jwx/v2/jwa"
 
- 	"github.com/drakkan/sftpgo/v2/internal/acme"
 
- 	"github.com/drakkan/sftpgo/v2/internal/common"
 
- 	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
 
- 	"github.com/drakkan/sftpgo/v2/internal/ftpd"
 
- 	"github.com/drakkan/sftpgo/v2/internal/logger"
 
- 	"github.com/drakkan/sftpgo/v2/internal/mfa"
 
- 	"github.com/drakkan/sftpgo/v2/internal/sftpd"
 
- 	"github.com/drakkan/sftpgo/v2/internal/util"
 
- 	"github.com/drakkan/sftpgo/v2/internal/webdavd"
 
- )
 
- const (
 
- 	logSender                             = "httpd"
 
- 	tokenPath                             = "/api/v2/token"
 
- 	logoutPath                            = "/api/v2/logout"
 
- 	userTokenPath                         = "/api/v2/user/token"
 
- 	userLogoutPath                        = "/api/v2/user/logout"
 
- 	activeConnectionsPath                 = "/api/v2/connections"
 
- 	quotasBasePath                        = "/api/v2/quotas"
 
- 	userPath                              = "/api/v2/users"
 
- 	versionPath                           = "/api/v2/version"
 
- 	folderPath                            = "/api/v2/folders"
 
- 	groupPath                             = "/api/v2/groups"
 
- 	serverStatusPath                      = "/api/v2/status"
 
- 	dumpDataPath                          = "/api/v2/dumpdata"
 
- 	loadDataPath                          = "/api/v2/loaddata"
 
- 	defenderHosts                         = "/api/v2/defender/hosts"
 
- 	adminPath                             = "/api/v2/admins"
 
- 	adminPwdPath                          = "/api/v2/admin/changepwd"
 
- 	adminProfilePath                      = "/api/v2/admin/profile"
 
- 	userPwdPath                           = "/api/v2/user/changepwd"
 
- 	userDirsPath                          = "/api/v2/user/dirs"
 
- 	userFilesPath                         = "/api/v2/user/files"
 
- 	userFileActionsPath                   = "/api/v2/user/file-actions"
 
- 	userStreamZipPath                     = "/api/v2/user/streamzip"
 
- 	userUploadFilePath                    = "/api/v2/user/files/upload"
 
- 	userFilesDirsMetadataPath             = "/api/v2/user/files/metadata"
 
- 	apiKeysPath                           = "/api/v2/apikeys"
 
- 	adminTOTPConfigsPath                  = "/api/v2/admin/totp/configs"
 
- 	adminTOTPGeneratePath                 = "/api/v2/admin/totp/generate"
 
- 	adminTOTPValidatePath                 = "/api/v2/admin/totp/validate"
 
- 	adminTOTPSavePath                     = "/api/v2/admin/totp/save"
 
- 	admin2FARecoveryCodesPath             = "/api/v2/admin/2fa/recoverycodes"
 
- 	userTOTPConfigsPath                   = "/api/v2/user/totp/configs"
 
- 	userTOTPGeneratePath                  = "/api/v2/user/totp/generate"
 
- 	userTOTPValidatePath                  = "/api/v2/user/totp/validate"
 
- 	userTOTPSavePath                      = "/api/v2/user/totp/save"
 
- 	user2FARecoveryCodesPath              = "/api/v2/user/2fa/recoverycodes"
 
- 	userProfilePath                       = "/api/v2/user/profile"
 
- 	userSharesPath                        = "/api/v2/user/shares"
 
- 	retentionBasePath                     = "/api/v2/retention/users"
 
- 	retentionChecksPath                   = "/api/v2/retention/users/checks"
 
- 	metadataBasePath                      = "/api/v2/metadata/users"
 
- 	metadataChecksPath                    = "/api/v2/metadata/users/checks"
 
- 	fsEventsPath                          = "/api/v2/events/fs"
 
- 	providerEventsPath                    = "/api/v2/events/provider"
 
- 	sharesPath                            = "/api/v2/shares"
 
- 	eventActionsPath                      = "/api/v2/eventactions"
 
- 	eventRulesPath                        = "/api/v2/eventrules"
 
- 	rolesPath                             = "/api/v2/roles"
 
- 	ipListsPath                           = "/api/v2/iplists"
 
- 	healthzPath                           = "/healthz"
 
- 	robotsTxtPath                         = "/robots.txt"
 
- 	webRootPathDefault                    = "/"
 
- 	webBasePathDefault                    = "/web"
 
- 	webBasePathAdminDefault               = "/web/admin"
 
- 	webBasePathClientDefault              = "/web/client"
 
- 	webAdminSetupPathDefault              = "/web/admin/setup"
 
- 	webAdminLoginPathDefault              = "/web/admin/login"
 
- 	webAdminOIDCLoginPathDefault          = "/web/admin/oidclogin"
 
- 	webOIDCRedirectPathDefault            = "/web/oidc/redirect"
 
- 	webAdminTwoFactorPathDefault          = "/web/admin/twofactor"
 
- 	webAdminTwoFactorRecoveryPathDefault  = "/web/admin/twofactor-recovery"
 
- 	webLogoutPathDefault                  = "/web/admin/logout"
 
- 	webUsersPathDefault                   = "/web/admin/users"
 
- 	webUserPathDefault                    = "/web/admin/user"
 
- 	webConnectionsPathDefault             = "/web/admin/connections"
 
- 	webFoldersPathDefault                 = "/web/admin/folders"
 
- 	webFolderPathDefault                  = "/web/admin/folder"
 
- 	webGroupsPathDefault                  = "/web/admin/groups"
 
- 	webGroupPathDefault                   = "/web/admin/group"
 
- 	webStatusPathDefault                  = "/web/admin/status"
 
- 	webAdminsPathDefault                  = "/web/admin/managers"
 
- 	webAdminPathDefault                   = "/web/admin/manager"
 
- 	webMaintenancePathDefault             = "/web/admin/maintenance"
 
- 	webBackupPathDefault                  = "/web/admin/backup"
 
- 	webRestorePathDefault                 = "/web/admin/restore"
 
- 	webScanVFolderPathDefault             = "/web/admin/quotas/scanfolder"
 
- 	webQuotaScanPathDefault               = "/web/admin/quotas/scanuser"
 
- 	webChangeAdminPwdPathDefault          = "/web/admin/changepwd"
 
- 	webAdminForgotPwdPathDefault          = "/web/admin/forgot-password"
 
- 	webAdminResetPwdPathDefault           = "/web/admin/reset-password"
 
- 	webAdminProfilePathDefault            = "/web/admin/profile"
 
- 	webAdminMFAPathDefault                = "/web/admin/mfa"
 
- 	webAdminEventRulesPathDefault         = "/web/admin/eventrules"
 
- 	webAdminEventRulePathDefault          = "/web/admin/eventrule"
 
- 	webAdminEventActionsPathDefault       = "/web/admin/eventactions"
 
- 	webAdminEventActionPathDefault        = "/web/admin/eventaction"
 
- 	webAdminRolesPathDefault              = "/web/admin/roles"
 
- 	webAdminRolePathDefault               = "/web/admin/role"
 
- 	webAdminTOTPGeneratePathDefault       = "/web/admin/totp/generate"
 
- 	webAdminTOTPValidatePathDefault       = "/web/admin/totp/validate"
 
- 	webAdminTOTPSavePathDefault           = "/web/admin/totp/save"
 
- 	webAdminRecoveryCodesPathDefault      = "/web/admin/recoverycodes"
 
- 	webTemplateUserDefault                = "/web/admin/template/user"
 
- 	webTemplateFolderDefault              = "/web/admin/template/folder"
 
- 	webDefenderPathDefault                = "/web/admin/defender"
 
- 	webIPListsPathDefault                 = "/web/admin/ip-lists"
 
- 	webIPListPathDefault                  = "/web/admin/ip-list"
 
- 	webDefenderHostsPathDefault           = "/web/admin/defender/hosts"
 
- 	webEventsPathDefault                  = "/web/admin/events"
 
- 	webEventsFsSearchPathDefault          = "/web/admin/events/fs"
 
- 	webEventsProviderSearchPathDefault    = "/web/admin/events/provider"
 
- 	webConfigsPathDefault                 = "/web/admin/configs"
 
- 	webClientLoginPathDefault             = "/web/client/login"
 
- 	webClientOIDCLoginPathDefault         = "/web/client/oidclogin"
 
- 	webClientTwoFactorPathDefault         = "/web/client/twofactor"
 
- 	webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
 
- 	webClientFilesPathDefault             = "/web/client/files"
 
- 	webClientFilePathDefault              = "/web/client/file"
 
- 	webClientFileActionsPathDefault       = "/web/client/file-actions"
 
- 	webClientSharesPathDefault            = "/web/client/shares"
 
- 	webClientSharePathDefault             = "/web/client/share"
 
- 	webClientEditFilePathDefault          = "/web/client/editfile"
 
- 	webClientDirsPathDefault              = "/web/client/dirs"
 
- 	webClientDownloadZipPathDefault       = "/web/client/downloadzip"
 
- 	webClientProfilePathDefault           = "/web/client/profile"
 
- 	webClientMFAPathDefault               = "/web/client/mfa"
 
- 	webClientTOTPGeneratePathDefault      = "/web/client/totp/generate"
 
- 	webClientTOTPValidatePathDefault      = "/web/client/totp/validate"
 
- 	webClientTOTPSavePathDefault          = "/web/client/totp/save"
 
- 	webClientRecoveryCodesPathDefault     = "/web/client/recoverycodes"
 
- 	webChangeClientPwdPathDefault         = "/web/client/changepwd"
 
- 	webClientLogoutPathDefault            = "/web/client/logout"
 
- 	webClientPubSharesPathDefault         = "/web/client/pubshares"
 
- 	webClientForgotPwdPathDefault         = "/web/client/forgot-password"
 
- 	webClientResetPwdPathDefault          = "/web/client/reset-password"
 
- 	webClientViewPDFPathDefault           = "/web/client/viewpdf"
 
- 	webClientGetPDFPathDefault            = "/web/client/getpdf"
 
- 	webStaticFilesPathDefault             = "/static"
 
- 	webOpenAPIPathDefault                 = "/openapi"
 
- 	// MaxRestoreSize defines the max size for the loaddata input file
 
- 	MaxRestoreSize       = 20 * 1048576 // 20 MB
 
- 	maxRequestSize       = 1048576      // 1MB
 
- 	maxLoginBodySize     = 262144       // 256 KB
 
- 	httpdMaxEditFileSize = 1048576      // 1 MB
 
- 	maxMultipartMem      = 10 * 1048576 // 10 MB
 
- 	osWindows            = "windows"
 
- 	otpHeaderCode        = "X-SFTPGO-OTP"
 
- 	mTimeHeader          = "X-SFTPGO-MTIME"
 
- 	acmeChallengeURI     = "/.well-known/acme-challenge/"
 
- )
 
- var (
 
- 	certMgr                        *common.CertManager
 
- 	cleanupTicker                  *time.Ticker
 
- 	cleanupDone                    chan bool
 
- 	invalidatedJWTTokens           sync.Map
 
- 	csrfTokenAuth                  *jwtauth.JWTAuth
 
- 	webRootPath                    string
 
- 	webBasePath                    string
 
- 	webBaseAdminPath               string
 
- 	webBaseClientPath              string
 
- 	webOIDCRedirectPath            string
 
- 	webAdminSetupPath              string
 
- 	webAdminOIDCLoginPath          string
 
- 	webAdminLoginPath              string
 
- 	webAdminTwoFactorPath          string
 
- 	webAdminTwoFactorRecoveryPath  string
 
- 	webLogoutPath                  string
 
- 	webUsersPath                   string
 
- 	webUserPath                    string
 
- 	webConnectionsPath             string
 
- 	webFoldersPath                 string
 
- 	webFolderPath                  string
 
- 	webGroupsPath                  string
 
- 	webGroupPath                   string
 
- 	webStatusPath                  string
 
- 	webAdminsPath                  string
 
- 	webAdminPath                   string
 
- 	webMaintenancePath             string
 
- 	webBackupPath                  string
 
- 	webRestorePath                 string
 
- 	webScanVFolderPath             string
 
- 	webQuotaScanPath               string
 
- 	webAdminProfilePath            string
 
- 	webAdminMFAPath                string
 
- 	webAdminEventRulesPath         string
 
- 	webAdminEventRulePath          string
 
- 	webAdminEventActionsPath       string
 
- 	webAdminEventActionPath        string
 
- 	webAdminRolesPath              string
 
- 	webAdminRolePath               string
 
- 	webAdminTOTPGeneratePath       string
 
- 	webAdminTOTPValidatePath       string
 
- 	webAdminTOTPSavePath           string
 
- 	webAdminRecoveryCodesPath      string
 
- 	webChangeAdminPwdPath          string
 
- 	webAdminForgotPwdPath          string
 
- 	webAdminResetPwdPath           string
 
- 	webTemplateUser                string
 
- 	webTemplateFolder              string
 
- 	webDefenderPath                string
 
- 	webIPListPath                  string
 
- 	webIPListsPath                 string
 
- 	webEventsPath                  string
 
- 	webEventsFsSearchPath          string
 
- 	webEventsProviderSearchPath    string
 
- 	webConfigsPath                 string
 
- 	webDefenderHostsPath           string
 
- 	webClientLoginPath             string
 
- 	webClientOIDCLoginPath         string
 
- 	webClientTwoFactorPath         string
 
- 	webClientTwoFactorRecoveryPath string
 
- 	webClientFilesPath             string
 
- 	webClientFilePath              string
 
- 	webClientFileActionsPath       string
 
- 	webClientSharesPath            string
 
- 	webClientSharePath             string
 
- 	webClientEditFilePath          string
 
- 	webClientDirsPath              string
 
- 	webClientDownloadZipPath       string
 
- 	webClientProfilePath           string
 
- 	webChangeClientPwdPath         string
 
- 	webClientMFAPath               string
 
- 	webClientTOTPGeneratePath      string
 
- 	webClientTOTPValidatePath      string
 
- 	webClientTOTPSavePath          string
 
- 	webClientRecoveryCodesPath     string
 
- 	webClientPubSharesPath         string
 
- 	webClientLogoutPath            string
 
- 	webClientForgotPwdPath         string
 
- 	webClientResetPwdPath          string
 
- 	webClientViewPDFPath           string
 
- 	webClientGetPDFPath            string
 
- 	webStaticFilesPath             string
 
- 	webOpenAPIPath                 string
 
- 	// max upload size for http clients, 1GB by default
 
- 	maxUploadFileSize          = int64(1048576000)
 
- 	hideSupportLink            bool
 
- 	installationCode           string
 
- 	installationCodeHint       string
 
- 	fnInstallationCodeResolver FnInstallationCodeResolver
 
- 	configurationDir           string
 
- )
 
- func init() {
 
- 	updateWebAdminURLs("")
 
- 	updateWebClientURLs("")
 
- 	acme.SetReloadHTTPDCertsFn(ReloadCertificateMgr)
 
- }
 
- // FnInstallationCodeResolver defines a method to get the installation code.
 
- // If the installation code cannot be resolved the provided default must be returned
 
- type FnInstallationCodeResolver func(defaultInstallationCode string) string
 
- // HTTPSProxyHeader defines an HTTPS proxy header as key/value.
 
- // For example Key could be "X-Forwarded-Proto" and Value "https"
 
- type HTTPSProxyHeader struct {
 
- 	Key   string
 
- 	Value string
 
- }
 
- // SecurityConf allows to add some security related headers to HTTP responses and to restrict allowed hosts
 
- type SecurityConf struct {
 
- 	// Set to true to enable the security configurations
 
- 	Enabled bool `json:"enabled" mapstructure:"enabled"`
 
- 	// AllowedHosts is a list of fully qualified domain names that are allowed.
 
- 	// Default is empty list, which allows any and all host names.
 
- 	AllowedHosts []string `json:"allowed_hosts" mapstructure:"allowed_hosts"`
 
- 	// AllowedHostsAreRegex determines if the provided allowed hosts contains valid regular expressions
 
- 	AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
 
- 	// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
 
- 	HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
 
- 	// Set to true to redirect HTTP requests to HTTPS
 
- 	HTTPSRedirect bool `json:"https_redirect" mapstructure:"https_redirect"`
 
- 	// HTTPSHost defines the host name that is used to redirect HTTP requests to HTTPS.
 
- 	// Default is "", which indicates to use the same host.
 
- 	HTTPSHost string `json:"https_host" mapstructure:"https_host"`
 
- 	// HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
 
- 	HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
 
- 	// STSSeconds is the max-age of the Strict-Transport-Security header.
 
- 	// Default is 0, which would NOT include the header.
 
- 	STSSeconds int64 `json:"sts_seconds" mapstructure:"sts_seconds"`
 
- 	// If STSIncludeSubdomains is set to true, the "includeSubdomains" will be appended to the
 
- 	// Strict-Transport-Security header. Default is false.
 
- 	STSIncludeSubdomains bool `json:"sts_include_subdomains" mapstructure:"sts_include_subdomains"`
 
- 	// If STSPreload is set to true, the `preload` flag will be appended to the
 
- 	// Strict-Transport-Security header. Default is false.
 
- 	STSPreload bool `json:"sts_preload" mapstructure:"sts_preload"`
 
- 	// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value "nosniff". Default is false.
 
- 	ContentTypeNosniff bool `json:"content_type_nosniff" mapstructure:"content_type_nosniff"`
 
- 	// ContentSecurityPolicy allows to set the Content-Security-Policy header value. Default is "".
 
- 	ContentSecurityPolicy string `json:"content_security_policy" mapstructure:"content_security_policy"`
 
- 	// PermissionsPolicy allows to set the Permissions-Policy header value. Default is "".
 
- 	PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"`
 
- 	// CrossOriginOpenerPolicy allows to set the `Cross-Origin-Opener-Policy` header value. Default is "".
 
- 	CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"`
 
- 	// ExpectCTHeader allows to set the Expect-CT header value. Default is "".
 
- 	ExpectCTHeader string `json:"expect_ct_header" mapstructure:"expect_ct_header"`
 
- 	proxyHeaders   []string
 
- }
 
- func (s *SecurityConf) updateProxyHeaders() {
 
- 	if !s.Enabled {
 
- 		s.proxyHeaders = nil
 
- 		return
 
- 	}
 
- 	s.proxyHeaders = s.HostsProxyHeaders
 
- 	for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
 
- 		s.proxyHeaders = append(s.proxyHeaders, httpsProxyHeader.Key)
 
- 	}
 
- }
 
- func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string {
 
- 	headers := make(map[string]string)
 
- 	for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
 
- 		headers[httpsProxyHeader.Key] = httpsProxyHeader.Value
 
- 	}
 
- 	return headers
 
- }
 
- func (s *SecurityConf) redirectHandler(next http.Handler) http.Handler {
 
- 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 
- 		if !isTLS(r) && !strings.HasPrefix(r.RequestURI, acmeChallengeURI) {
 
- 			url := r.URL
 
- 			url.Scheme = "https"
 
- 			if s.HTTPSHost != "" {
 
- 				url.Host = s.HTTPSHost
 
- 			} else {
 
- 				host := r.Host
 
- 				for _, header := range s.HostsProxyHeaders {
 
- 					if h := r.Header.Get(header); h != "" {
 
- 						host = h
 
- 						break
 
- 					}
 
- 				}
 
- 				url.Host = host
 
- 			}
 
- 			http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
 
- 			return
 
- 		}
 
- 		next.ServeHTTP(w, r)
 
- 	})
 
- }
 
- // UIBranding defines the supported customizations for the web UIs
 
- type UIBranding struct {
 
- 	// Name defines the text to show at the login page and as HTML title
 
- 	Name string `json:"name" mapstructure:"name"`
 
- 	// ShortName defines the name to show next to the logo image
 
- 	ShortName string `json:"short_name" mapstructure:"short_name"`
 
- 	// Path to your logo relative to "static_files_path".
 
- 	// For example, if you create a directory named "branding" inside the static dir and
 
- 	// put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
 
- 	LogoPath string `json:"logo_path" mapstructure:"logo_path"`
 
- 	// Path to the image to show on the login screen relative to "static_files_path"
 
- 	LoginImagePath string `json:"login_image_path" mapstructure:"login_image_path"`
 
- 	// Path to your favicon relative to "static_files_path"
 
- 	FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
 
- 	// DisclaimerName defines the name for the link to your optional disclaimer
 
- 	DisclaimerName string `json:"disclaimer_name" mapstructure:"disclaimer_name"`
 
- 	// Path to the HTML page for your disclaimer relative to "static_files_path".
 
- 	DisclaimerPath string `json:"disclaimer_path" mapstructure:"disclaimer_path"`
 
- 	// Path to a custom CSS file, relative to "static_files_path", which replaces
 
- 	// the SB Admin2 default CSS. This is useful, for example, if you rebuild
 
- 	// SB Admin2 CSS to use custom colors
 
- 	DefaultCSS string `json:"default_css" mapstructure:"default_css"`
 
- 	// Additional CSS file paths, relative to "static_files_path", to include
 
- 	ExtraCSS []string `json:"extra_css" mapstructure:"extra_css"`
 
- }
 
- func (b *UIBranding) check() {
 
- 	if b.LogoPath != "" {
 
- 		b.LogoPath = util.CleanPath(b.LogoPath)
 
- 	} else {
 
- 		b.LogoPath = "/img/logo.png"
 
- 	}
 
- 	if b.LoginImagePath != "" {
 
- 		b.LoginImagePath = util.CleanPath(b.LoginImagePath)
 
- 	} else {
 
- 		b.LoginImagePath = "/img/login_image.png"
 
- 	}
 
- 	if b.FaviconPath != "" {
 
- 		b.FaviconPath = util.CleanPath(b.FaviconPath)
 
- 	} else {
 
- 		b.FaviconPath = "/favicon.ico"
 
- 	}
 
- 	if b.DisclaimerPath != "" {
 
- 		b.DisclaimerPath = util.CleanPath(b.DisclaimerPath)
 
- 	}
 
- 	if b.DefaultCSS != "" {
 
- 		b.DefaultCSS = util.CleanPath(b.DefaultCSS)
 
- 	} else {
 
- 		b.DefaultCSS = "/css/sb-admin-2.min.css"
 
- 	}
 
- 	for idx := range b.ExtraCSS {
 
- 		b.ExtraCSS[idx] = util.CleanPath(b.ExtraCSS[idx])
 
- 	}
 
- }
 
- // Branding defines the branding-related customizations supported
 
- type Branding struct {
 
- 	WebAdmin  UIBranding `json:"web_admin" mapstructure:"web_admin"`
 
- 	WebClient UIBranding `json:"web_client" mapstructure:"web_client"`
 
- }
 
- // WebClientIntegration defines the configuration for an external Web Client integration
 
- type WebClientIntegration struct {
 
- 	// Files with these extensions can be sent to the configured URL
 
- 	FileExtensions []string `json:"file_extensions" mapstructure:"file_extensions"`
 
- 	// URL that will receive the files
 
- 	URL string `json:"url" mapstructure:"url"`
 
- }
 
- // Binding defines the configuration for a network listener
 
- type Binding struct {
 
- 	// The address to listen on. A blank value means listen on all available network interfaces.
 
- 	Address string `json:"address" mapstructure:"address"`
 
- 	// The port used for serving requests
 
- 	Port int `json:"port" mapstructure:"port"`
 
- 	// Enable the built-in admin interface.
 
- 	// You have to define TemplatesPath and StaticFilesPath for this to work
 
- 	EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
 
- 	// Enable the built-in client interface.
 
- 	// You have to define TemplatesPath and StaticFilesPath for this to work
 
- 	EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
 
- 	// Enable REST API
 
- 	EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"`
 
- 	// 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.
 
- 	EnabledLoginMethods int `json:"enabled_login_methods" mapstructure:"enabled_login_methods"`
 
- 	// you also need to provide a certificate for enabling HTTPS
 
- 	EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
 
- 	// Certificate and matching private key for this specific binding, if empty the global
 
- 	// ones will be used, if any
 
- 	CertificateFile    string `json:"certificate_file" mapstructure:"certificate_file"`
 
- 	CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
 
- 	// Defines the minimum TLS version. 13 means TLS 1.3, default is TLS 1.2
 
- 	MinTLSVersion int `json:"min_tls_version" mapstructure:"min_tls_version"`
 
- 	// set to 1 to require client certificate authentication in addition to basic auth.
 
- 	// You need to define at least a certificate authority for this to work
 
- 	ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
 
- 	// TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
 
- 	// If CipherSuites is nil/empty, a default list of secure cipher suites
 
- 	// is used, with a preference order based on hardware performance.
 
- 	// Note that TLS 1.3 ciphersuites are not configurable.
 
- 	// The supported ciphersuites names are defined here:
 
- 	//
 
- 	// https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52
 
- 	//
 
- 	// any invalid name will be silently ignored.
 
- 	// The order matters, the ciphers listed first will be the preferred ones.
 
- 	TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
 
- 	// List of IP addresses and IP ranges allowed to set client IP proxy headers and
 
- 	// X-Forwarded-Proto header.
 
- 	ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
 
- 	// Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
 
- 	ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
 
- 	// Some client IP headers such as "X-Forwarded-For" can contain multiple IP address, this setting
 
- 	// define the position to trust starting from the right. For example if we have:
 
- 	// "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" and the depth is 0, SFTPGo will use "13.0.0.1"
 
- 	// as client IP, if depth is 1, "12.0.0.1" will be used and so on
 
- 	ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
 
- 	// If both web admin and web client are enabled each login page will show a link
 
- 	// to the other one. This setting allows to hide this link:
 
- 	// - 0 login links are displayed on both admin and client login page. This is the default
 
- 	// - 1 the login link to the web client login page is hidden on admin login page
 
- 	// - 2 the login link to the web admin login page is hidden on client login page
 
- 	// The flags can be combined, for example 3 will disable both login links.
 
- 	HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
 
- 	// Enable the built-in OpenAPI renderer
 
- 	RenderOpenAPI bool `json:"render_openapi" mapstructure:"render_openapi"`
 
- 	// Enabling web client integrations you can render or modify the files with the specified
 
- 	// extensions using an external tool.
 
- 	WebClientIntegrations []WebClientIntegration `json:"web_client_integrations" mapstructure:"web_client_integrations"`
 
- 	// Defining an OIDC configuration the web admin and web client UI will use OpenID to authenticate users.
 
- 	OIDC OIDC `json:"oidc" mapstructure:"oidc"`
 
- 	// Security defines security headers to add to HTTP responses and allows to restrict allowed hosts
 
- 	Security SecurityConf `json:"security" mapstructure:"security"`
 
- 	// Branding defines customizations to suit your brand
 
- 	Branding         Branding `json:"branding" mapstructure:"branding"`
 
- 	allowHeadersFrom []func(net.IP) bool
 
- }
 
- func (b *Binding) checkWebClientIntegrations() {
 
- 	var integrations []WebClientIntegration
 
- 	for _, integration := range b.WebClientIntegrations {
 
- 		if integration.URL != "" && len(integration.FileExtensions) > 0 {
 
- 			integrations = append(integrations, integration)
 
- 		}
 
- 	}
 
- 	b.WebClientIntegrations = integrations
 
- }
 
- func (b *Binding) checkBranding() {
 
- 	b.Branding.WebAdmin.check()
 
- 	b.Branding.WebClient.check()
 
- 	if b.Branding.WebAdmin.Name == "" {
 
- 		b.Branding.WebAdmin.Name = "SFTPGo WebAdmin"
 
- 	}
 
- 	if b.Branding.WebAdmin.ShortName == "" {
 
- 		b.Branding.WebAdmin.ShortName = "WebAdmin"
 
- 	}
 
- 	if b.Branding.WebClient.Name == "" {
 
- 		b.Branding.WebClient.Name = "SFTPGo WebClient"
 
- 	}
 
- 	if b.Branding.WebClient.ShortName == "" {
 
- 		b.Branding.WebClient.ShortName = "WebClient"
 
- 	}
 
- }
 
- func (b *Binding) parseAllowedProxy() error {
 
- 	if filepath.IsAbs(b.Address) && len(b.ProxyAllowed) > 0 {
 
- 		// unix domain socket
 
- 		b.allowHeadersFrom = []func(net.IP) bool{func(ip net.IP) bool { return true }}
 
- 		return nil
 
- 	}
 
- 	allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
 
- 	if err != nil {
 
- 		return err
 
- 	}
 
- 	b.allowHeadersFrom = allowedFuncs
 
- 	return nil
 
- }
 
- // GetAddress returns the binding address
 
- func (b *Binding) GetAddress() string {
 
- 	return fmt.Sprintf("%s:%d", b.Address, b.Port)
 
- }
 
- // IsValid returns true if the binding is valid
 
- func (b *Binding) IsValid() bool {
 
- 	if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient {
 
- 		return false
 
- 	}
 
- 	if b.Port > 0 {
 
- 		return true
 
- 	}
 
- 	if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
 
- 		return true
 
- 	}
 
- 	return false
 
- }
 
- func (b *Binding) isWebAdminOIDCLoginDisabled() bool {
 
- 	if b.EnableWebAdmin {
 
- 		if b.EnabledLoginMethods == 0 {
 
- 			return false
 
- 		}
 
- 		return b.EnabledLoginMethods&1 == 0
 
- 	}
 
- 	return false
 
- }
 
- func (b *Binding) isWebClientOIDCLoginDisabled() bool {
 
- 	if b.EnableWebClient {
 
- 		if b.EnabledLoginMethods == 0 {
 
- 			return false
 
- 		}
 
- 		return b.EnabledLoginMethods&2 == 0
 
- 	}
 
- 	return false
 
- }
 
- func (b *Binding) isWebAdminLoginFormDisabled() bool {
 
- 	if b.EnableWebAdmin {
 
- 		if b.EnabledLoginMethods == 0 {
 
- 			return false
 
- 		}
 
- 		return b.EnabledLoginMethods&4 == 0
 
- 	}
 
- 	return false
 
- }
 
- func (b *Binding) isWebClientLoginFormDisabled() bool {
 
- 	if b.EnableWebClient {
 
- 		if b.EnabledLoginMethods == 0 {
 
- 			return false
 
- 		}
 
- 		return b.EnabledLoginMethods&8 == 0
 
- 	}
 
- 	return false
 
- }
 
- func (b *Binding) checkLoginMethods() error {
 
- 	if b.isWebAdminLoginFormDisabled() && b.isWebAdminOIDCLoginDisabled() {
 
- 		return errors.New("no login method available for WebAdmin UI")
 
- 	}
 
- 	if !b.isWebAdminOIDCLoginDisabled() {
 
- 		if b.isWebAdminLoginFormDisabled() && !b.OIDC.hasRoles() {
 
- 			return errors.New("no login method available for WebAdmin UI")
 
- 		}
 
- 	}
 
- 	if b.isWebClientLoginFormDisabled() && b.isWebClientOIDCLoginDisabled() {
 
- 		return errors.New("no login method available for WebClient UI")
 
- 	}
 
- 	if !b.isWebClientOIDCLoginDisabled() {
 
- 		if b.isWebClientLoginFormDisabled() && !b.OIDC.isEnabled() {
 
- 			return errors.New("no login method available for WebClient UI")
 
- 		}
 
- 	}
 
- 	return nil
 
- }
 
- func (b *Binding) showAdminLoginURL() bool {
 
- 	if !b.EnableWebAdmin {
 
- 		return false
 
- 	}
 
- 	if b.HideLoginURL&2 != 0 {
 
- 		return false
 
- 	}
 
- 	return true
 
- }
 
- func (b *Binding) showClientLoginURL() bool {
 
- 	if !b.EnableWebClient {
 
- 		return false
 
- 	}
 
- 	if b.HideLoginURL&1 != 0 {
 
- 		return false
 
- 	}
 
- 	return true
 
- }
 
- type defenderStatus struct {
 
- 	IsActive bool `json:"is_active"`
 
- }
 
- type allowListStatus struct {
 
- 	IsActive bool `json:"is_active"`
 
- }
 
- type rateLimiters struct {
 
- 	IsActive  bool     `json:"is_active"`
 
- 	Protocols []string `json:"protocols"`
 
- }
 
- // GetProtocolsAsString returns the enabled protocols as comma separated string
 
- func (r *rateLimiters) GetProtocolsAsString() string {
 
- 	return strings.Join(r.Protocols, ", ")
 
- }
 
- // ServicesStatus keep the state of the running services
 
- type ServicesStatus struct {
 
- 	SSH          sftpd.ServiceStatus         `json:"ssh"`
 
- 	FTP          ftpd.ServiceStatus          `json:"ftp"`
 
- 	WebDAV       webdavd.ServiceStatus       `json:"webdav"`
 
- 	DataProvider dataprovider.ProviderStatus `json:"data_provider"`
 
- 	Defender     defenderStatus              `json:"defender"`
 
- 	MFA          mfa.ServiceStatus           `json:"mfa"`
 
- 	AllowList    allowListStatus             `json:"allow_list"`
 
- 	RateLimiters rateLimiters                `json:"rate_limiters"`
 
- }
 
- // SetupConfig defines the configuration parameters for the initial web admin setup
 
- type SetupConfig struct {
 
- 	// Installation code to require when creating the first admin account.
 
- 	// As for the other configurations, this value is read at SFTPGo startup and not at runtime
 
- 	// even if set using an environment variable.
 
- 	// 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
 
- 	InstallationCode string `json:"installation_code" mapstructure:"installation_code"`
 
- 	// Description for the installation code input field
 
- 	InstallationCodeHint string `json:"installation_code_hint" mapstructure:"installation_code_hint"`
 
- }
 
- // CorsConfig defines the CORS configuration
 
- type CorsConfig struct {
 
- 	AllowedOrigins       []string `json:"allowed_origins" mapstructure:"allowed_origins"`
 
- 	AllowedMethods       []string `json:"allowed_methods" mapstructure:"allowed_methods"`
 
- 	AllowedHeaders       []string `json:"allowed_headers" mapstructure:"allowed_headers"`
 
- 	ExposedHeaders       []string `json:"exposed_headers" mapstructure:"exposed_headers"`
 
- 	AllowCredentials     bool     `json:"allow_credentials" mapstructure:"allow_credentials"`
 
- 	Enabled              bool     `json:"enabled" mapstructure:"enabled"`
 
- 	MaxAge               int      `json:"max_age" mapstructure:"max_age"`
 
- 	OptionsPassthrough   bool     `json:"options_passthrough" mapstructure:"options_passthrough"`
 
- 	OptionsSuccessStatus int      `json:"options_success_status" mapstructure:"options_success_status"`
 
- 	AllowPrivateNetwork  bool     `json:"allow_private_network" mapstructure:"allow_private_network"`
 
- }
 
- // Conf httpd daemon configuration
 
- type Conf struct {
 
- 	// Addresses and ports to bind to
 
- 	Bindings []Binding `json:"bindings" mapstructure:"bindings"`
 
- 	// Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
 
- 	TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
 
- 	// Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
 
- 	// If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
 
- 	StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
 
- 	// Path to the backup directory. This can be an absolute path or a path relative to the config dir
 
- 	//BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
 
- 	// Path to the directory that contains the OpenAPI schema and the default renderer.
 
- 	// This can be an absolute path or a path relative to the config dir
 
- 	OpenAPIPath string `json:"openapi_path" mapstructure:"openapi_path"`
 
- 	// Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
 
- 	// be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
 
- 	WebRoot string `json:"web_root" mapstructure:"web_root"`
 
- 	// If files containing a certificate and matching private key for the server are provided you can enable
 
- 	// HTTPS connections for the configured bindings.
 
- 	// Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
 
- 	// "paramchange" request to the running service on Windows.
 
- 	CertificateFile    string `json:"certificate_file" mapstructure:"certificate_file"`
 
- 	CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
 
- 	// CACertificates defines the set of root certificate authorities to be used to verify client certificates.
 
- 	CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
 
- 	// CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
 
- 	// if a client certificate has been revoked
 
- 	CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
 
- 	// SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
 
- 	// If empty a random signing key will be generated each time SFTPGo starts. If you set a
 
- 	// signing passphrase you should consider rotating it periodically for added security
 
- 	SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
 
- 	// TokenValidation allows to define how to validate JWT tokens, cookies and CSRF tokens.
 
- 	// By default all the available security checks are enabled. Set to 1 to disable the requirement
 
- 	// that a token must be used by the same IP for which it was issued.
 
- 	TokenValidation int `json:"token_validation" mapstructure:"token_validation"`
 
- 	// MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
 
- 	// 0 means no limit
 
- 	MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
 
- 	// CORS configuration
 
- 	Cors CorsConfig `json:"cors" mapstructure:"cors"`
 
- 	// Initial setup configuration
 
- 	Setup SetupConfig `json:"setup" mapstructure:"setup"`
 
- 	// If enabled, the link to the sponsors section will not appear on the setup screen page
 
- 	HideSupportLink bool `json:"hide_support_link" mapstructure:"hide_support_link"`
 
- 	acmeDomain      string
 
- }
 
- type apiResponse struct {
 
- 	Error   string `json:"error,omitempty"`
 
- 	Message string `json:"message"`
 
- }
 
- // ShouldBind returns true if there is at least a valid binding
 
- func (c *Conf) ShouldBind() bool {
 
- 	for _, binding := range c.Bindings {
 
- 		if binding.IsValid() {
 
- 			return true
 
- 		}
 
- 	}
 
- 	return false
 
- }
 
- func (c *Conf) isWebAdminEnabled() bool {
 
- 	for _, binding := range c.Bindings {
 
- 		if binding.EnableWebAdmin {
 
- 			return true
 
- 		}
 
- 	}
 
- 	return false
 
- }
 
- func (c *Conf) isWebClientEnabled() bool {
 
- 	for _, binding := range c.Bindings {
 
- 		if binding.EnableWebClient {
 
- 			return true
 
- 		}
 
- 	}
 
- 	return false
 
- }
 
- func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
 
- 	if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
 
- 		return fmt.Errorf("required directory is invalid, static file path: %q template path: %q",
 
- 			staticFilesPath, templatesPath)
 
- 	}
 
- 	return nil
 
- }
 
- func (c *Conf) getRedacted() Conf {
 
- 	redacted := "[redacted]"
 
- 	conf := *c
 
- 	if conf.SigningPassphrase != "" {
 
- 		conf.SigningPassphrase = redacted
 
- 	}
 
- 	if conf.Setup.InstallationCode != "" {
 
- 		conf.Setup.InstallationCode = redacted
 
- 	}
 
- 	conf.Bindings = nil
 
- 	for _, binding := range c.Bindings {
 
- 		if binding.OIDC.ClientID != "" {
 
- 			binding.OIDC.ClientID = redacted
 
- 		}
 
- 		if binding.OIDC.ClientSecret != "" {
 
- 			binding.OIDC.ClientSecret = redacted
 
- 		}
 
- 		conf.Bindings = append(conf.Bindings, binding)
 
- 	}
 
- 	return conf
 
- }
 
- func (c *Conf) getKeyPairs(configDir string) []common.TLSKeyPair {
 
- 	var keyPairs []common.TLSKeyPair
 
- 	for _, binding := range c.Bindings {
 
- 		certificateFile := getConfigPath(binding.CertificateFile, configDir)
 
- 		certificateKeyFile := getConfigPath(binding.CertificateKeyFile, configDir)
 
- 		if certificateFile != "" && certificateKeyFile != "" {
 
- 			keyPairs = append(keyPairs, common.TLSKeyPair{
 
- 				Cert: certificateFile,
 
- 				Key:  certificateKeyFile,
 
- 				ID:   binding.GetAddress(),
 
- 			})
 
- 		}
 
- 	}
 
- 	var certificateFile, certificateKeyFile string
 
- 	if c.acmeDomain != "" {
 
- 		certificateFile, certificateKeyFile = util.GetACMECertificateKeyPair(c.acmeDomain)
 
- 	} else {
 
- 		certificateFile = getConfigPath(c.CertificateFile, configDir)
 
- 		certificateKeyFile = getConfigPath(c.CertificateKeyFile, configDir)
 
- 	}
 
- 	if certificateFile != "" && certificateKeyFile != "" {
 
- 		keyPairs = append(keyPairs, common.TLSKeyPair{
 
- 			Cert: certificateFile,
 
- 			Key:  certificateKeyFile,
 
- 			ID:   common.DefaultTLSKeyPaidID,
 
- 		})
 
- 	}
 
- 	return keyPairs
 
- }
 
- func (c *Conf) setTokenValidationMode() {
 
- 	if c.TokenValidation == 1 {
 
- 		tokenValidationMode = tokenValidationNoIPMatch
 
- 	} else {
 
- 		tokenValidationMode = tokenValidationFull
 
- 	}
 
- }
 
- func (c *Conf) loadFromProvider() error {
 
- 	configs, err := dataprovider.GetConfigs()
 
- 	if err != nil {
 
- 		return fmt.Errorf("unable to load config from provider: %w", err)
 
- 	}
 
- 	configs.SetNilsToEmpty()
 
- 	if configs.ACME.Domain == "" || !configs.ACME.HasProtocol(common.ProtocolHTTP) {
 
- 		return nil
 
- 	}
 
- 	crt, key := util.GetACMECertificateKeyPair(configs.ACME.Domain)
 
- 	if crt != "" && key != "" {
 
- 		if _, err := os.Stat(crt); err != nil {
 
- 			logger.Error(logSender, "", "unable to load acme cert file %q: %v", crt, err)
 
- 			return nil
 
- 		}
 
- 		if _, err := os.Stat(key); err != nil {
 
- 			logger.Error(logSender, "", "unable to load acme key file %q: %v", key, err)
 
- 			return nil
 
- 		}
 
- 		for idx := range c.Bindings {
 
- 			if c.Bindings[idx].Security.Enabled && c.Bindings[idx].Security.HTTPSRedirect {
 
- 				continue
 
- 			}
 
- 			c.Bindings[idx].EnableHTTPS = true
 
- 		}
 
- 		c.acmeDomain = configs.ACME.Domain
 
- 		logger.Info(logSender, "", "acme domain set to %q", c.acmeDomain)
 
- 		return nil
 
- 	}
 
- 	return nil
 
- }
 
- // Initialize configures and starts the HTTP server
 
- func (c *Conf) Initialize(configDir string, isShared int) error {
 
- 	if err := c.loadFromProvider(); err != nil {
 
- 		return err
 
- 	}
 
- 	logger.Info(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
 
- 	configurationDir = configDir
 
- 	resetCodesMgr = newResetCodeManager(isShared)
 
- 	oidcMgr = newOIDCManager(isShared)
 
- 	staticFilesPath := util.FindSharedDataPath(c.StaticFilesPath, configDir)
 
- 	templatesPath := util.FindSharedDataPath(c.TemplatesPath, configDir)
 
- 	openAPIPath := util.FindSharedDataPath(c.OpenAPIPath, configDir)
 
- 	if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
 
- 		return err
 
- 	}
 
- 	if c.isWebAdminEnabled() {
 
- 		updateWebAdminURLs(c.WebRoot)
 
- 		loadAdminTemplates(templatesPath)
 
- 	} else {
 
- 		logger.Info(logSender, "", "built-in web admin interface disabled")
 
- 	}
 
- 	if c.isWebClientEnabled() {
 
- 		updateWebClientURLs(c.WebRoot)
 
- 		loadClientTemplates(templatesPath)
 
- 	} else {
 
- 		logger.Info(logSender, "", "built-in web client interface disabled")
 
- 	}
 
- 	keyPairs := c.getKeyPairs(configDir)
 
- 	if len(keyPairs) > 0 {
 
- 		mgr, err := common.NewCertManager(keyPairs, configDir, logSender)
 
- 		if err != nil {
 
- 			return err
 
- 		}
 
- 		mgr.SetCACertificates(c.CACertificates)
 
- 		if err := mgr.LoadRootCAs(); err != nil {
 
- 			return err
 
- 		}
 
- 		mgr.SetCARevocationLists(c.CARevocationLists)
 
- 		if err := mgr.LoadCRLs(); err != nil {
 
- 			return err
 
- 		}
 
- 		certMgr = mgr
 
- 	}
 
- 	csrfTokenAuth = jwtauth.New(jwa.HS256.String(), getSigningKey(c.SigningPassphrase), nil)
 
- 	hideSupportLink = c.HideSupportLink
 
- 	exitChannel := make(chan error, 1)
 
- 	for _, binding := range c.Bindings {
 
- 		if !binding.IsValid() {
 
- 			continue
 
- 		}
 
- 		if err := binding.parseAllowedProxy(); err != nil {
 
- 			return err
 
- 		}
 
- 		binding.checkWebClientIntegrations()
 
- 		binding.checkBranding()
 
- 		binding.Security.updateProxyHeaders()
 
- 		go func(b Binding) {
 
- 			if err := b.OIDC.initialize(); err != nil {
 
- 				exitChannel <- err
 
- 				return
 
- 			}
 
- 			if err := b.checkLoginMethods(); err != nil {
 
- 				exitChannel <- err
 
- 				return
 
- 			}
 
- 			server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors, openAPIPath)
 
- 			server.setShared(isShared)
 
- 			exitChannel <- server.listenAndServe()
 
- 		}(binding)
 
- 	}
 
- 	maxUploadFileSize = c.MaxUploadFileSize
 
- 	installationCode = c.Setup.InstallationCode
 
- 	installationCodeHint = c.Setup.InstallationCodeHint
 
- 	startCleanupTicker(tokenDuration / 2)
 
- 	c.setTokenValidationMode()
 
- 	return <-exitChannel
 
- }
 
- func isWebRequest(r *http.Request) bool {
 
- 	return strings.HasPrefix(r.RequestURI, webBasePath+"/")
 
- }
 
- func isWebClientRequest(r *http.Request) bool {
 
- 	return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
 
- }
 
- // ReloadCertificateMgr reloads the certificate manager
 
- func ReloadCertificateMgr() error {
 
- 	if certMgr != nil {
 
- 		return certMgr.Reload()
 
- 	}
 
- 	return nil
 
- }
 
- func getConfigPath(name, configDir string) string {
 
- 	if !util.IsFileInputValid(name) {
 
- 		return ""
 
- 	}
 
- 	if name != "" && !filepath.IsAbs(name) {
 
- 		return filepath.Join(configDir, name)
 
- 	}
 
- 	return name
 
- }
 
- func getServicesStatus() *ServicesStatus {
 
- 	rtlEnabled, rtlProtocols := common.Config.GetRateLimitersStatus()
 
- 	status := &ServicesStatus{
 
- 		SSH:          sftpd.GetStatus(),
 
- 		FTP:          ftpd.GetStatus(),
 
- 		WebDAV:       webdavd.GetStatus(),
 
- 		DataProvider: dataprovider.GetProviderStatus(),
 
- 		Defender: defenderStatus{
 
- 			IsActive: common.Config.DefenderConfig.Enabled,
 
- 		},
 
- 		MFA: mfa.GetStatus(),
 
- 		AllowList: allowListStatus{
 
- 			IsActive: common.Config.IsAllowListEnabled(),
 
- 		},
 
- 		RateLimiters: rateLimiters{
 
- 			IsActive:  rtlEnabled,
 
- 			Protocols: rtlProtocols,
 
- 		},
 
- 	}
 
- 	return status
 
- }
 
- func fileServer(r chi.Router, path string, root http.FileSystem) {
 
- 	if path != "/" && path[len(path)-1] != '/' {
 
- 		r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
 
- 		path += "/"
 
- 	}
 
- 	path += "*"
 
- 	r.Get(path, func(w http.ResponseWriter, r *http.Request) {
 
- 		rctx := chi.RouteContext(r.Context())
 
- 		pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
 
- 		fs := http.StripPrefix(pathPrefix, http.FileServer(root))
 
- 		fs.ServeHTTP(w, r)
 
- 	})
 
- }
 
- func updateWebClientURLs(baseURL string) {
 
- 	if !path.IsAbs(baseURL) {
 
- 		baseURL = "/"
 
- 	}
 
- 	webRootPath = path.Join(baseURL, webRootPathDefault)
 
- 	webBasePath = path.Join(baseURL, webBasePathDefault)
 
- 	webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
 
- 	webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
 
- 	webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
 
- 	webClientOIDCLoginPath = path.Join(baseURL, webClientOIDCLoginPathDefault)
 
- 	webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
 
- 	webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
 
- 	webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
 
- 	webClientFilePath = path.Join(baseURL, webClientFilePathDefault)
 
- 	webClientFileActionsPath = path.Join(baseURL, webClientFileActionsPathDefault)
 
- 	webClientSharesPath = path.Join(baseURL, webClientSharesPathDefault)
 
- 	webClientPubSharesPath = path.Join(baseURL, webClientPubSharesPathDefault)
 
- 	webClientSharePath = path.Join(baseURL, webClientSharePathDefault)
 
- 	webClientEditFilePath = path.Join(baseURL, webClientEditFilePathDefault)
 
- 	webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
 
- 	webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
 
- 	webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
 
- 	webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
 
- 	webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
 
- 	webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
 
- 	webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
 
- 	webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
 
- 	webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
 
- 	webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
 
- 	webClientForgotPwdPath = path.Join(baseURL, webClientForgotPwdPathDefault)
 
- 	webClientResetPwdPath = path.Join(baseURL, webClientResetPwdPathDefault)
 
- 	webClientViewPDFPath = path.Join(baseURL, webClientViewPDFPathDefault)
 
- 	webClientGetPDFPath = path.Join(baseURL, webClientGetPDFPathDefault)
 
- }
 
- func updateWebAdminURLs(baseURL string) {
 
- 	if !path.IsAbs(baseURL) {
 
- 		baseURL = "/"
 
- 	}
 
- 	webRootPath = path.Join(baseURL, webRootPathDefault)
 
- 	webBasePath = path.Join(baseURL, webBasePathDefault)
 
- 	webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
 
- 	webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
 
- 	webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
 
- 	webAdminLoginPath = path.Join(baseURL, webAdminLoginPathDefault)
 
- 	webAdminOIDCLoginPath = path.Join(baseURL, webAdminOIDCLoginPathDefault)
 
- 	webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
 
- 	webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
 
- 	webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
 
- 	webUsersPath = path.Join(baseURL, webUsersPathDefault)
 
- 	webUserPath = path.Join(baseURL, webUserPathDefault)
 
- 	webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
 
- 	webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
 
- 	webFolderPath = path.Join(baseURL, webFolderPathDefault)
 
- 	webGroupsPath = path.Join(baseURL, webGroupsPathDefault)
 
- 	webGroupPath = path.Join(baseURL, webGroupPathDefault)
 
- 	webStatusPath = path.Join(baseURL, webStatusPathDefault)
 
- 	webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
 
- 	webAdminPath = path.Join(baseURL, webAdminPathDefault)
 
- 	webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
 
- 	webBackupPath = path.Join(baseURL, webBackupPathDefault)
 
- 	webRestorePath = path.Join(baseURL, webRestorePathDefault)
 
- 	webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
 
- 	webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
 
- 	webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
 
- 	webAdminForgotPwdPath = path.Join(baseURL, webAdminForgotPwdPathDefault)
 
- 	webAdminResetPwdPath = path.Join(baseURL, webAdminResetPwdPathDefault)
 
- 	webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
 
- 	webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
 
- 	webAdminEventRulesPath = path.Join(baseURL, webAdminEventRulesPathDefault)
 
- 	webAdminEventRulePath = path.Join(baseURL, webAdminEventRulePathDefault)
 
- 	webAdminEventActionsPath = path.Join(baseURL, webAdminEventActionsPathDefault)
 
- 	webAdminEventActionPath = path.Join(baseURL, webAdminEventActionPathDefault)
 
- 	webAdminRolesPath = path.Join(baseURL, webAdminRolesPathDefault)
 
- 	webAdminRolePath = path.Join(baseURL, webAdminRolePathDefault)
 
- 	webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
 
- 	webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
 
- 	webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
 
- 	webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
 
- 	webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
 
- 	webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
 
- 	webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
 
- 	webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
 
- 	webIPListPath = path.Join(baseURL, webIPListPathDefault)
 
- 	webIPListsPath = path.Join(baseURL, webIPListsPathDefault)
 
- 	webEventsPath = path.Join(baseURL, webEventsPathDefault)
 
- 	webEventsFsSearchPath = path.Join(baseURL, webEventsFsSearchPathDefault)
 
- 	webEventsProviderSearchPath = path.Join(baseURL, webEventsProviderSearchPathDefault)
 
- 	webConfigsPath = path.Join(baseURL, webConfigsPathDefault)
 
- 	webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
 
- 	webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
 
- }
 
- // GetHTTPRouter returns an HTTP handler suitable to use for test cases
 
- func GetHTTPRouter(b Binding) http.Handler {
 
- 	server := newHttpdServer(b, filepath.Join("..", "..", "static"), "", CorsConfig{}, filepath.Join("..", "..", "openapi"))
 
- 	server.initializeRouter()
 
- 	return server.router
 
- }
 
- // the ticker cannot be started/stopped from multiple goroutines
 
- func startCleanupTicker(duration time.Duration) {
 
- 	stopCleanupTicker()
 
- 	cleanupTicker = time.NewTicker(duration)
 
- 	cleanupDone = make(chan bool)
 
- 	go func() {
 
- 		counter := int64(0)
 
- 		for {
 
- 			select {
 
- 			case <-cleanupDone:
 
- 				return
 
- 			case <-cleanupTicker.C:
 
- 				counter++
 
- 				cleanupExpiredJWTTokens()
 
- 				resetCodesMgr.Cleanup()
 
- 				if counter%2 == 0 {
 
- 					oidcMgr.cleanup()
 
- 				}
 
- 			}
 
- 		}
 
- 	}()
 
- }
 
- func stopCleanupTicker() {
 
- 	if cleanupTicker != nil {
 
- 		cleanupTicker.Stop()
 
- 		cleanupDone <- true
 
- 		cleanupTicker = nil
 
- 	}
 
- }
 
- func cleanupExpiredJWTTokens() {
 
- 	invalidatedJWTTokens.Range(func(key, value any) bool {
 
- 		exp, ok := value.(time.Time)
 
- 		if !ok || exp.Before(time.Now().UTC()) {
 
- 			invalidatedJWTTokens.Delete(key)
 
- 		}
 
- 		return true
 
- 	})
 
- }
 
- func getSigningKey(signingPassphrase string) []byte {
 
- 	if signingPassphrase != "" {
 
- 		sk := sha256.Sum256([]byte(signingPassphrase))
 
- 		return sk[:]
 
- 	}
 
- 	return util.GenerateRandomBytes(32)
 
- }
 
- // SetInstallationCodeResolver sets a function to call to resolve the installation code
 
- func SetInstallationCodeResolver(fn FnInstallationCodeResolver) {
 
- 	fnInstallationCodeResolver = fn
 
- }
 
- func resolveInstallationCode() string {
 
- 	if fnInstallationCodeResolver != nil {
 
- 		return fnInstallationCodeResolver(installationCode)
 
- 	}
 
- 	return installationCode
 
- }
 
 
  |