httpd.go 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. // Package httpd implements REST API and Web interface for SFTPGo.
  15. // The OpenAPI 3 schema for the supported API can be found inside the source tree:
  16. // https://github.com/drakkan/sftpgo/blob/main/openapi/openapi.yaml
  17. package httpd
  18. import (
  19. "crypto/sha256"
  20. "errors"
  21. "fmt"
  22. "net"
  23. "net/http"
  24. "os"
  25. "path"
  26. "path/filepath"
  27. "runtime"
  28. "strings"
  29. "time"
  30. "github.com/go-chi/chi/v5"
  31. "github.com/go-chi/jwtauth/v5"
  32. "github.com/lestrrat-go/jwx/v2/jwa"
  33. "github.com/drakkan/sftpgo/v2/internal/acme"
  34. "github.com/drakkan/sftpgo/v2/internal/common"
  35. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  36. "github.com/drakkan/sftpgo/v2/internal/ftpd"
  37. "github.com/drakkan/sftpgo/v2/internal/logger"
  38. "github.com/drakkan/sftpgo/v2/internal/mfa"
  39. "github.com/drakkan/sftpgo/v2/internal/sftpd"
  40. "github.com/drakkan/sftpgo/v2/internal/util"
  41. "github.com/drakkan/sftpgo/v2/internal/webdavd"
  42. )
  43. const (
  44. logSender = "httpd"
  45. tokenPath = "/api/v2/token"
  46. logoutPath = "/api/v2/logout"
  47. userTokenPath = "/api/v2/user/token"
  48. userLogoutPath = "/api/v2/user/logout"
  49. activeConnectionsPath = "/api/v2/connections"
  50. quotasBasePath = "/api/v2/quotas"
  51. userPath = "/api/v2/users"
  52. versionPath = "/api/v2/version"
  53. folderPath = "/api/v2/folders"
  54. groupPath = "/api/v2/groups"
  55. serverStatusPath = "/api/v2/status"
  56. dumpDataPath = "/api/v2/dumpdata"
  57. loadDataPath = "/api/v2/loaddata"
  58. defenderHosts = "/api/v2/defender/hosts"
  59. adminPath = "/api/v2/admins"
  60. adminPwdPath = "/api/v2/admin/changepwd"
  61. adminProfilePath = "/api/v2/admin/profile"
  62. userPwdPath = "/api/v2/user/changepwd"
  63. userDirsPath = "/api/v2/user/dirs"
  64. userFilesPath = "/api/v2/user/files"
  65. userFileActionsPath = "/api/v2/user/file-actions"
  66. userStreamZipPath = "/api/v2/user/streamzip"
  67. userUploadFilePath = "/api/v2/user/files/upload"
  68. userFilesDirsMetadataPath = "/api/v2/user/files/metadata"
  69. apiKeysPath = "/api/v2/apikeys"
  70. adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
  71. adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
  72. adminTOTPValidatePath = "/api/v2/admin/totp/validate"
  73. adminTOTPSavePath = "/api/v2/admin/totp/save"
  74. admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
  75. userTOTPConfigsPath = "/api/v2/user/totp/configs"
  76. userTOTPGeneratePath = "/api/v2/user/totp/generate"
  77. userTOTPValidatePath = "/api/v2/user/totp/validate"
  78. userTOTPSavePath = "/api/v2/user/totp/save"
  79. user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
  80. userProfilePath = "/api/v2/user/profile"
  81. userSharesPath = "/api/v2/user/shares"
  82. retentionBasePath = "/api/v2/retention/users"
  83. retentionChecksPath = "/api/v2/retention/users/checks"
  84. fsEventsPath = "/api/v2/events/fs"
  85. providerEventsPath = "/api/v2/events/provider"
  86. logEventsPath = "/api/v2/events/logs"
  87. sharesPath = "/api/v2/shares"
  88. eventActionsPath = "/api/v2/eventactions"
  89. eventRulesPath = "/api/v2/eventrules"
  90. rolesPath = "/api/v2/roles"
  91. ipListsPath = "/api/v2/iplists"
  92. healthzPath = "/healthz"
  93. webRootPathDefault = "/"
  94. webBasePathDefault = "/web"
  95. webBasePathAdminDefault = "/web/admin"
  96. webBasePathClientDefault = "/web/client"
  97. webAdminSetupPathDefault = "/web/admin/setup"
  98. webAdminLoginPathDefault = "/web/admin/login"
  99. webAdminOIDCLoginPathDefault = "/web/admin/oidclogin"
  100. webOIDCRedirectPathDefault = "/web/oidc/redirect"
  101. webOAuth2RedirectPathDefault = "/web/oauth2/redirect"
  102. webOAuth2TokenPathDefault = "/web/admin/oauth2/token"
  103. webAdminTwoFactorPathDefault = "/web/admin/twofactor"
  104. webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
  105. webLogoutPathDefault = "/web/admin/logout"
  106. webUsersPathDefault = "/web/admin/users"
  107. webUserPathDefault = "/web/admin/user"
  108. webConnectionsPathDefault = "/web/admin/connections"
  109. webFoldersPathDefault = "/web/admin/folders"
  110. webFolderPathDefault = "/web/admin/folder"
  111. webGroupsPathDefault = "/web/admin/groups"
  112. webGroupPathDefault = "/web/admin/group"
  113. webStatusPathDefault = "/web/admin/status"
  114. webAdminsPathDefault = "/web/admin/managers"
  115. webAdminPathDefault = "/web/admin/manager"
  116. webMaintenancePathDefault = "/web/admin/maintenance"
  117. webBackupPathDefault = "/web/admin/backup"
  118. webRestorePathDefault = "/web/admin/restore"
  119. webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
  120. webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
  121. webChangeAdminPwdPathDefault = "/web/admin/changepwd"
  122. webAdminForgotPwdPathDefault = "/web/admin/forgot-password"
  123. webAdminResetPwdPathDefault = "/web/admin/reset-password"
  124. webAdminProfilePathDefault = "/web/admin/profile"
  125. webAdminMFAPathDefault = "/web/admin/mfa"
  126. webAdminEventRulesPathDefault = "/web/admin/eventrules"
  127. webAdminEventRulePathDefault = "/web/admin/eventrule"
  128. webAdminEventActionsPathDefault = "/web/admin/eventactions"
  129. webAdminEventActionPathDefault = "/web/admin/eventaction"
  130. webAdminRolesPathDefault = "/web/admin/roles"
  131. webAdminRolePathDefault = "/web/admin/role"
  132. webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
  133. webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
  134. webAdminTOTPSavePathDefault = "/web/admin/totp/save"
  135. webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
  136. webTemplateUserDefault = "/web/admin/template/user"
  137. webTemplateFolderDefault = "/web/admin/template/folder"
  138. webDefenderPathDefault = "/web/admin/defender"
  139. webIPListsPathDefault = "/web/admin/ip-lists"
  140. webIPListPathDefault = "/web/admin/ip-list"
  141. webDefenderHostsPathDefault = "/web/admin/defender/hosts"
  142. webEventsPathDefault = "/web/admin/events"
  143. webEventsFsSearchPathDefault = "/web/admin/events/fs"
  144. webEventsProviderSearchPathDefault = "/web/admin/events/provider"
  145. webEventsLogSearchPathDefault = "/web/admin/events/logs"
  146. webConfigsPathDefault = "/web/admin/configs"
  147. webClientLoginPathDefault = "/web/client/login"
  148. webClientOIDCLoginPathDefault = "/web/client/oidclogin"
  149. webClientTwoFactorPathDefault = "/web/client/twofactor"
  150. webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
  151. webClientFilesPathDefault = "/web/client/files"
  152. webClientFilePathDefault = "/web/client/file"
  153. webClientFileActionsPathDefault = "/web/client/file-actions"
  154. webClientSharesPathDefault = "/web/client/shares"
  155. webClientSharePathDefault = "/web/client/share"
  156. webClientEditFilePathDefault = "/web/client/editfile"
  157. webClientDirsPathDefault = "/web/client/dirs"
  158. webClientDownloadZipPathDefault = "/web/client/downloadzip"
  159. webClientProfilePathDefault = "/web/client/profile"
  160. webClientPingPathDefault = "/web/client/ping"
  161. webClientMFAPathDefault = "/web/client/mfa"
  162. webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
  163. webClientTOTPValidatePathDefault = "/web/client/totp/validate"
  164. webClientTOTPSavePathDefault = "/web/client/totp/save"
  165. webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
  166. webChangeClientPwdPathDefault = "/web/client/changepwd"
  167. webClientLogoutPathDefault = "/web/client/logout"
  168. webClientPubSharesPathDefault = "/web/client/pubshares"
  169. webClientForgotPwdPathDefault = "/web/client/forgot-password"
  170. webClientResetPwdPathDefault = "/web/client/reset-password"
  171. webClientViewPDFPathDefault = "/web/client/viewpdf"
  172. webClientGetPDFPathDefault = "/web/client/getpdf"
  173. webClientExistPathDefault = "/web/client/exist"
  174. webClientTasksPathDefault = "/web/client/tasks"
  175. webStaticFilesPathDefault = "/static"
  176. webOpenAPIPathDefault = "/openapi"
  177. // MaxRestoreSize defines the max size for the loaddata input file
  178. MaxRestoreSize = 20 * 1048576 // 20 MB
  179. maxRequestSize = 1048576 // 1MB
  180. maxLoginBodySize = 262144 // 256 KB
  181. httpdMaxEditFileSize = 2 * 1048576 // 2 MB
  182. maxMultipartMem = 10 * 1048576 // 10 MB
  183. osWindows = "windows"
  184. otpHeaderCode = "X-SFTPGO-OTP"
  185. mTimeHeader = "X-SFTPGO-MTIME"
  186. acmeChallengeURI = "/.well-known/acme-challenge/"
  187. )
  188. var (
  189. certMgr *common.CertManager
  190. cleanupTicker *time.Ticker
  191. cleanupDone chan bool
  192. invalidatedJWTTokens tokenManager
  193. csrfTokenAuth *jwtauth.JWTAuth
  194. webRootPath string
  195. webBasePath string
  196. webBaseAdminPath string
  197. webBaseClientPath string
  198. webOIDCRedirectPath string
  199. webOAuth2RedirectPath string
  200. webOAuth2TokenPath string
  201. webAdminSetupPath string
  202. webAdminOIDCLoginPath string
  203. webAdminLoginPath string
  204. webAdminTwoFactorPath string
  205. webAdminTwoFactorRecoveryPath string
  206. webLogoutPath string
  207. webUsersPath string
  208. webUserPath string
  209. webConnectionsPath string
  210. webFoldersPath string
  211. webFolderPath string
  212. webGroupsPath string
  213. webGroupPath string
  214. webStatusPath string
  215. webAdminsPath string
  216. webAdminPath string
  217. webMaintenancePath string
  218. webBackupPath string
  219. webRestorePath string
  220. webScanVFolderPath string
  221. webQuotaScanPath string
  222. webAdminProfilePath string
  223. webAdminMFAPath string
  224. webAdminEventRulesPath string
  225. webAdminEventRulePath string
  226. webAdminEventActionsPath string
  227. webAdminEventActionPath string
  228. webAdminRolesPath string
  229. webAdminRolePath string
  230. webAdminTOTPGeneratePath string
  231. webAdminTOTPValidatePath string
  232. webAdminTOTPSavePath string
  233. webAdminRecoveryCodesPath string
  234. webChangeAdminPwdPath string
  235. webAdminForgotPwdPath string
  236. webAdminResetPwdPath string
  237. webTemplateUser string
  238. webTemplateFolder string
  239. webDefenderPath string
  240. webIPListPath string
  241. webIPListsPath string
  242. webEventsPath string
  243. webEventsFsSearchPath string
  244. webEventsProviderSearchPath string
  245. webEventsLogSearchPath string
  246. webConfigsPath string
  247. webDefenderHostsPath string
  248. webClientLoginPath string
  249. webClientOIDCLoginPath string
  250. webClientTwoFactorPath string
  251. webClientTwoFactorRecoveryPath string
  252. webClientFilesPath string
  253. webClientFilePath string
  254. webClientFileActionsPath string
  255. webClientSharesPath string
  256. webClientSharePath string
  257. webClientEditFilePath string
  258. webClientDirsPath string
  259. webClientDownloadZipPath string
  260. webClientProfilePath string
  261. webClientPingPath string
  262. webChangeClientPwdPath string
  263. webClientMFAPath string
  264. webClientTOTPGeneratePath string
  265. webClientTOTPValidatePath string
  266. webClientTOTPSavePath string
  267. webClientRecoveryCodesPath string
  268. webClientPubSharesPath string
  269. webClientLogoutPath string
  270. webClientForgotPwdPath string
  271. webClientResetPwdPath string
  272. webClientViewPDFPath string
  273. webClientGetPDFPath string
  274. webClientExistPath string
  275. webClientTasksPath string
  276. webStaticFilesPath string
  277. webOpenAPIPath string
  278. // max upload size for http clients, 1GB by default
  279. maxUploadFileSize = int64(1048576000)
  280. hideSupportLink bool
  281. installationCode string
  282. installationCodeHint string
  283. fnInstallationCodeResolver FnInstallationCodeResolver
  284. configurationDir string
  285. )
  286. func init() {
  287. updateWebAdminURLs("")
  288. updateWebClientURLs("")
  289. acme.SetReloadHTTPDCertsFn(ReloadCertificateMgr)
  290. }
  291. // FnInstallationCodeResolver defines a method to get the installation code.
  292. // If the installation code cannot be resolved the provided default must be returned
  293. type FnInstallationCodeResolver func(defaultInstallationCode string) string
  294. // HTTPSProxyHeader defines an HTTPS proxy header as key/value.
  295. // For example Key could be "X-Forwarded-Proto" and Value "https"
  296. type HTTPSProxyHeader struct {
  297. Key string
  298. Value string
  299. }
  300. // SecurityConf allows to add some security related headers to HTTP responses and to restrict allowed hosts
  301. type SecurityConf struct {
  302. // Set to true to enable the security configurations
  303. Enabled bool `json:"enabled" mapstructure:"enabled"`
  304. // AllowedHosts is a list of fully qualified domain names that are allowed.
  305. // Default is empty list, which allows any and all host names.
  306. AllowedHosts []string `json:"allowed_hosts" mapstructure:"allowed_hosts"`
  307. // AllowedHostsAreRegex determines if the provided allowed hosts contains valid regular expressions
  308. AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
  309. // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
  310. HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
  311. // Set to true to redirect HTTP requests to HTTPS
  312. HTTPSRedirect bool `json:"https_redirect" mapstructure:"https_redirect"`
  313. // HTTPSHost defines the host name that is used to redirect HTTP requests to HTTPS.
  314. // Default is "", which indicates to use the same host.
  315. HTTPSHost string `json:"https_host" mapstructure:"https_host"`
  316. // HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
  317. HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
  318. // STSSeconds is the max-age of the Strict-Transport-Security header.
  319. // Default is 0, which would NOT include the header.
  320. STSSeconds int64 `json:"sts_seconds" mapstructure:"sts_seconds"`
  321. // If STSIncludeSubdomains is set to true, the "includeSubdomains" will be appended to the
  322. // Strict-Transport-Security header. Default is false.
  323. STSIncludeSubdomains bool `json:"sts_include_subdomains" mapstructure:"sts_include_subdomains"`
  324. // If STSPreload is set to true, the `preload` flag will be appended to the
  325. // Strict-Transport-Security header. Default is false.
  326. STSPreload bool `json:"sts_preload" mapstructure:"sts_preload"`
  327. // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value "nosniff". Default is false.
  328. ContentTypeNosniff bool `json:"content_type_nosniff" mapstructure:"content_type_nosniff"`
  329. // ContentSecurityPolicy allows to set the Content-Security-Policy header value. Default is "".
  330. ContentSecurityPolicy string `json:"content_security_policy" mapstructure:"content_security_policy"`
  331. // PermissionsPolicy allows to set the Permissions-Policy header value. Default is "".
  332. PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"`
  333. // CrossOriginOpenerPolicy allows to set the `Cross-Origin-Opener-Policy` header value. Default is "".
  334. CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"`
  335. proxyHeaders []string
  336. }
  337. func (s *SecurityConf) updateProxyHeaders() {
  338. if !s.Enabled {
  339. s.proxyHeaders = nil
  340. return
  341. }
  342. s.proxyHeaders = s.HostsProxyHeaders
  343. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  344. s.proxyHeaders = append(s.proxyHeaders, httpsProxyHeader.Key)
  345. }
  346. }
  347. func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string {
  348. headers := make(map[string]string)
  349. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  350. headers[httpsProxyHeader.Key] = httpsProxyHeader.Value
  351. }
  352. return headers
  353. }
  354. func (s *SecurityConf) redirectHandler(next http.Handler) http.Handler {
  355. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  356. if !isTLS(r) && !strings.HasPrefix(r.RequestURI, acmeChallengeURI) {
  357. url := r.URL
  358. url.Scheme = "https"
  359. if s.HTTPSHost != "" {
  360. url.Host = s.HTTPSHost
  361. } else {
  362. host := r.Host
  363. for _, header := range s.HostsProxyHeaders {
  364. if h := r.Header.Get(header); h != "" {
  365. host = h
  366. break
  367. }
  368. }
  369. url.Host = host
  370. }
  371. http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
  372. return
  373. }
  374. next.ServeHTTP(w, r)
  375. })
  376. }
  377. // UIBranding defines the supported customizations for the web UIs
  378. type UIBranding struct {
  379. // Name defines the text to show at the login page and as HTML title
  380. Name string `json:"name" mapstructure:"name"`
  381. // ShortName defines the name to show next to the logo image
  382. ShortName string `json:"short_name" mapstructure:"short_name"`
  383. // Path to your logo relative to "static_files_path".
  384. // For example, if you create a directory named "branding" inside the static dir and
  385. // put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
  386. LogoPath string `json:"logo_path" mapstructure:"logo_path"`
  387. // Path to the image to show on the login screen relative to "static_files_path"
  388. LoginImagePath string `json:"login_image_path" mapstructure:"login_image_path"`
  389. // Path to your favicon relative to "static_files_path"
  390. FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
  391. // DisclaimerName defines the name for the link to your optional disclaimer
  392. DisclaimerName string `json:"disclaimer_name" mapstructure:"disclaimer_name"`
  393. // Path to the HTML page for your disclaimer relative to "static_files_path"
  394. // or an absolute http/https URL.
  395. DisclaimerPath string `json:"disclaimer_path" mapstructure:"disclaimer_path"`
  396. // Path to custom CSS files, relative to "static_files_path", which replaces
  397. // the default CSS files
  398. DefaultCSS []string `json:"default_css" mapstructure:"default_css"`
  399. // Additional CSS file paths, relative to "static_files_path", to include
  400. ExtraCSS []string `json:"extra_css" mapstructure:"extra_css"`
  401. }
  402. func (b *UIBranding) check() {
  403. if b.LogoPath != "" {
  404. b.LogoPath = util.CleanPath(b.LogoPath)
  405. } else {
  406. b.LogoPath = "/img/logo.png"
  407. }
  408. if b.LoginImagePath != "" {
  409. b.LoginImagePath = util.CleanPath(b.LoginImagePath)
  410. } else {
  411. b.LoginImagePath = "/img/login_image.png"
  412. }
  413. if b.FaviconPath != "" {
  414. b.FaviconPath = util.CleanPath(b.FaviconPath)
  415. } else {
  416. b.FaviconPath = "/favicon.ico"
  417. }
  418. if b.DisclaimerPath != "" {
  419. if !strings.HasPrefix(b.DisclaimerPath, "https://") && !strings.HasPrefix(b.DisclaimerPath, "http://") {
  420. b.DisclaimerPath = path.Join(webStaticFilesPath, util.CleanPath(b.DisclaimerPath))
  421. }
  422. }
  423. if len(b.DefaultCSS) > 0 {
  424. for idx := range b.DefaultCSS {
  425. b.DefaultCSS[idx] = util.CleanPath(b.DefaultCSS[idx])
  426. }
  427. } else {
  428. b.DefaultCSS = []string{
  429. "/assets/plugins/global/plugins.bundle.css",
  430. "/assets/css/style.bundle.css",
  431. }
  432. }
  433. for idx := range b.ExtraCSS {
  434. b.ExtraCSS[idx] = util.CleanPath(b.ExtraCSS[idx])
  435. }
  436. }
  437. // Branding defines the branding-related customizations supported
  438. type Branding struct {
  439. WebAdmin UIBranding `json:"web_admin" mapstructure:"web_admin"`
  440. WebClient UIBranding `json:"web_client" mapstructure:"web_client"`
  441. }
  442. // WebClientIntegration defines the configuration for an external Web Client integration
  443. type WebClientIntegration struct {
  444. // Files with these extensions can be sent to the configured URL
  445. FileExtensions []string `json:"file_extensions" mapstructure:"file_extensions"`
  446. // URL that will receive the files
  447. URL string `json:"url" mapstructure:"url"`
  448. }
  449. // Binding defines the configuration for a network listener
  450. type Binding struct {
  451. // The address to listen on. A blank value means listen on all available network interfaces.
  452. Address string `json:"address" mapstructure:"address"`
  453. // The port used for serving requests
  454. Port int `json:"port" mapstructure:"port"`
  455. // Enable the built-in admin interface.
  456. // You have to define TemplatesPath and StaticFilesPath for this to work
  457. EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
  458. // Enable the built-in client interface.
  459. // You have to define TemplatesPath and StaticFilesPath for this to work
  460. EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
  461. // Enable REST API
  462. EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"`
  463. // Defines the login methods available for the WebAdmin and WebClient UIs:
  464. //
  465. // - 0 means any configured method: username/password login form and OIDC, if enabled
  466. // - 1 means OIDC for the WebAdmin UI
  467. // - 2 means OIDC for the WebClient UI
  468. // - 4 means login form for the WebAdmin UI
  469. // - 8 means login form for the WebClient UI
  470. //
  471. // You can combine the values. For example 3 means that you can only login using OIDC on
  472. // both WebClient and WebAdmin UI.
  473. EnabledLoginMethods int `json:"enabled_login_methods" mapstructure:"enabled_login_methods"`
  474. // you also need to provide a certificate for enabling HTTPS
  475. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  476. // Certificate and matching private key for this specific binding, if empty the global
  477. // ones will be used, if any
  478. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  479. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  480. // Defines the minimum TLS version. 13 means TLS 1.3, default is TLS 1.2
  481. MinTLSVersion int `json:"min_tls_version" mapstructure:"min_tls_version"`
  482. // set to 1 to require client certificate authentication in addition to basic auth.
  483. // You need to define at least a certificate authority for this to work
  484. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  485. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  486. // If CipherSuites is nil/empty, a default list of secure cipher suites
  487. // is used, with a preference order based on hardware performance.
  488. // Note that TLS 1.3 ciphersuites are not configurable.
  489. // The supported ciphersuites names are defined here:
  490. //
  491. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53
  492. //
  493. // any invalid name will be silently ignored.
  494. // The order matters, the ciphers listed first will be the preferred ones.
  495. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  496. // HTTP protocols in preference order. Supported values: http/1.1, h2
  497. Protocols []string `json:"tls_protocols" mapstructure:"tls_protocols"`
  498. // List of IP addresses and IP ranges allowed to set client IP proxy headers and
  499. // X-Forwarded-Proto header.
  500. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  501. // Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
  502. ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
  503. // Some client IP headers such as "X-Forwarded-For" can contain multiple IP address, this setting
  504. // define the position to trust starting from the right. For example if we have:
  505. // "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"
  506. // as client IP, if depth is 1, "12.0.0.1" will be used and so on
  507. ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
  508. // If both web admin and web client are enabled each login page will show a link
  509. // to the other one. This setting allows to hide this link:
  510. // - 0 login links are displayed on both admin and client login page. This is the default
  511. // - 1 the login link to the web client login page is hidden on admin login page
  512. // - 2 the login link to the web admin login page is hidden on client login page
  513. // The flags can be combined, for example 3 will disable both login links.
  514. HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
  515. // Enable the built-in OpenAPI renderer
  516. RenderOpenAPI bool `json:"render_openapi" mapstructure:"render_openapi"`
  517. // Defining an OIDC configuration the web admin and web client UI will use OpenID to authenticate users.
  518. OIDC OIDC `json:"oidc" mapstructure:"oidc"`
  519. // Security defines security headers to add to HTTP responses and allows to restrict allowed hosts
  520. Security SecurityConf `json:"security" mapstructure:"security"`
  521. // Branding defines customizations to suit your brand
  522. Branding Branding `json:"branding" mapstructure:"branding"`
  523. allowHeadersFrom []func(net.IP) bool
  524. }
  525. func (b *Binding) checkBranding() {
  526. b.Branding.WebAdmin.check()
  527. b.Branding.WebClient.check()
  528. if b.Branding.WebAdmin.Name == "" {
  529. b.Branding.WebAdmin.Name = "SFTPGo WebAdmin"
  530. }
  531. if b.Branding.WebAdmin.ShortName == "" {
  532. b.Branding.WebAdmin.ShortName = "WebAdmin"
  533. }
  534. if b.Branding.WebClient.Name == "" {
  535. b.Branding.WebClient.Name = "SFTPGo WebClient"
  536. }
  537. if b.Branding.WebClient.ShortName == "" {
  538. b.Branding.WebClient.ShortName = "WebClient"
  539. }
  540. }
  541. func (b *Binding) parseAllowedProxy() error {
  542. if filepath.IsAbs(b.Address) && len(b.ProxyAllowed) > 0 {
  543. // unix domain socket
  544. b.allowHeadersFrom = []func(net.IP) bool{func(_ net.IP) bool { return true }}
  545. return nil
  546. }
  547. allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
  548. if err != nil {
  549. return err
  550. }
  551. b.allowHeadersFrom = allowedFuncs
  552. return nil
  553. }
  554. // GetAddress returns the binding address
  555. func (b *Binding) GetAddress() string {
  556. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  557. }
  558. // IsValid returns true if the binding is valid
  559. func (b *Binding) IsValid() bool {
  560. if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient {
  561. return false
  562. }
  563. if b.Port > 0 {
  564. return true
  565. }
  566. if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
  567. return true
  568. }
  569. return false
  570. }
  571. func (b *Binding) isWebAdminOIDCLoginDisabled() bool {
  572. if b.EnableWebAdmin {
  573. if b.EnabledLoginMethods == 0 {
  574. return false
  575. }
  576. return b.EnabledLoginMethods&1 == 0
  577. }
  578. return false
  579. }
  580. func (b *Binding) isWebClientOIDCLoginDisabled() bool {
  581. if b.EnableWebClient {
  582. if b.EnabledLoginMethods == 0 {
  583. return false
  584. }
  585. return b.EnabledLoginMethods&2 == 0
  586. }
  587. return false
  588. }
  589. func (b *Binding) isWebAdminLoginFormDisabled() bool {
  590. if b.EnableWebAdmin {
  591. if b.EnabledLoginMethods == 0 {
  592. return false
  593. }
  594. return b.EnabledLoginMethods&4 == 0
  595. }
  596. return false
  597. }
  598. func (b *Binding) isWebClientLoginFormDisabled() bool {
  599. if b.EnableWebClient {
  600. if b.EnabledLoginMethods == 0 {
  601. return false
  602. }
  603. return b.EnabledLoginMethods&8 == 0
  604. }
  605. return false
  606. }
  607. func (b *Binding) checkLoginMethods() error {
  608. if b.isWebAdminLoginFormDisabled() && b.isWebAdminOIDCLoginDisabled() {
  609. return errors.New("no login method available for WebAdmin UI")
  610. }
  611. if !b.isWebAdminOIDCLoginDisabled() {
  612. if b.isWebAdminLoginFormDisabled() && !b.OIDC.hasRoles() {
  613. return errors.New("no login method available for WebAdmin UI")
  614. }
  615. }
  616. if b.isWebClientLoginFormDisabled() && b.isWebClientOIDCLoginDisabled() {
  617. return errors.New("no login method available for WebClient UI")
  618. }
  619. if !b.isWebClientOIDCLoginDisabled() {
  620. if b.isWebClientLoginFormDisabled() && !b.OIDC.isEnabled() {
  621. return errors.New("no login method available for WebClient UI")
  622. }
  623. }
  624. return nil
  625. }
  626. func (b *Binding) showAdminLoginURL() bool {
  627. if !b.EnableWebAdmin {
  628. return false
  629. }
  630. if b.HideLoginURL&2 != 0 {
  631. return false
  632. }
  633. return true
  634. }
  635. func (b *Binding) showClientLoginURL() bool {
  636. if !b.EnableWebClient {
  637. return false
  638. }
  639. if b.HideLoginURL&1 != 0 {
  640. return false
  641. }
  642. return true
  643. }
  644. type defenderStatus struct {
  645. IsActive bool `json:"is_active"`
  646. }
  647. type allowListStatus struct {
  648. IsActive bool `json:"is_active"`
  649. }
  650. type rateLimiters struct {
  651. IsActive bool `json:"is_active"`
  652. Protocols []string `json:"protocols"`
  653. }
  654. // GetProtocolsAsString returns the enabled protocols as comma separated string
  655. func (r *rateLimiters) GetProtocolsAsString() string {
  656. return strings.Join(r.Protocols, ", ")
  657. }
  658. // ServicesStatus keep the state of the running services
  659. type ServicesStatus struct {
  660. SSH sftpd.ServiceStatus `json:"ssh"`
  661. FTP ftpd.ServiceStatus `json:"ftp"`
  662. WebDAV webdavd.ServiceStatus `json:"webdav"`
  663. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  664. Defender defenderStatus `json:"defender"`
  665. MFA mfa.ServiceStatus `json:"mfa"`
  666. AllowList allowListStatus `json:"allow_list"`
  667. RateLimiters rateLimiters `json:"rate_limiters"`
  668. }
  669. // SetupConfig defines the configuration parameters for the initial web admin setup
  670. type SetupConfig struct {
  671. // Installation code to require when creating the first admin account.
  672. // As for the other configurations, this value is read at SFTPGo startup and not at runtime
  673. // even if set using an environment variable.
  674. // This is not a license key or similar, the purpose here is to prevent anyone who can access
  675. // to the initial setup screen from creating an admin user
  676. InstallationCode string `json:"installation_code" mapstructure:"installation_code"`
  677. // Description for the installation code input field
  678. InstallationCodeHint string `json:"installation_code_hint" mapstructure:"installation_code_hint"`
  679. }
  680. // CorsConfig defines the CORS configuration
  681. type CorsConfig struct {
  682. AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
  683. AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
  684. AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
  685. ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
  686. AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
  687. Enabled bool `json:"enabled" mapstructure:"enabled"`
  688. MaxAge int `json:"max_age" mapstructure:"max_age"`
  689. OptionsPassthrough bool `json:"options_passthrough" mapstructure:"options_passthrough"`
  690. OptionsSuccessStatus int `json:"options_success_status" mapstructure:"options_success_status"`
  691. AllowPrivateNetwork bool `json:"allow_private_network" mapstructure:"allow_private_network"`
  692. }
  693. // Conf httpd daemon configuration
  694. type Conf struct {
  695. // Addresses and ports to bind to
  696. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  697. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  698. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  699. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  700. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  701. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  702. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  703. //BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  704. // Path to the directory that contains the OpenAPI schema and the default renderer.
  705. // This can be an absolute path or a path relative to the config dir
  706. OpenAPIPath string `json:"openapi_path" mapstructure:"openapi_path"`
  707. // Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
  708. // be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
  709. WebRoot string `json:"web_root" mapstructure:"web_root"`
  710. // If files containing a certificate and matching private key for the server are provided you can enable
  711. // HTTPS connections for the configured bindings.
  712. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  713. // "paramchange" request to the running service on Windows.
  714. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  715. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  716. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  717. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  718. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  719. // if a client certificate has been revoked
  720. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  721. // SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
  722. // If empty a random signing key will be generated each time SFTPGo starts. If you set a
  723. // signing passphrase you should consider rotating it periodically for added security
  724. SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
  725. SigningPassphraseFile string `json:"signing_passphrase_file" mapstructure:"signing_passphrase_file"`
  726. // TokenValidation allows to define how to validate JWT tokens, cookies and CSRF tokens.
  727. // By default all the available security checks are enabled. Set to 1 to disable the requirement
  728. // that a token must be used by the same IP for which it was issued.
  729. TokenValidation int `json:"token_validation" mapstructure:"token_validation"`
  730. // MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
  731. // 0 means no limit
  732. MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
  733. // CORS configuration
  734. Cors CorsConfig `json:"cors" mapstructure:"cors"`
  735. // Initial setup configuration
  736. Setup SetupConfig `json:"setup" mapstructure:"setup"`
  737. // If enabled, the link to the sponsors section will not appear on the setup screen page
  738. HideSupportLink bool `json:"hide_support_link" mapstructure:"hide_support_link"`
  739. acmeDomain string
  740. }
  741. type apiResponse struct {
  742. Error string `json:"error,omitempty"`
  743. Message string `json:"message"`
  744. }
  745. // ShouldBind returns true if there is at least a valid binding
  746. func (c *Conf) ShouldBind() bool {
  747. for _, binding := range c.Bindings {
  748. if binding.IsValid() {
  749. return true
  750. }
  751. }
  752. return false
  753. }
  754. func (c *Conf) isWebAdminEnabled() bool {
  755. for _, binding := range c.Bindings {
  756. if binding.EnableWebAdmin {
  757. return true
  758. }
  759. }
  760. return false
  761. }
  762. func (c *Conf) isWebClientEnabled() bool {
  763. for _, binding := range c.Bindings {
  764. if binding.EnableWebClient {
  765. return true
  766. }
  767. }
  768. return false
  769. }
  770. func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
  771. if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
  772. return fmt.Errorf("required directory is invalid, static file path: %q template path: %q",
  773. staticFilesPath, templatesPath)
  774. }
  775. return nil
  776. }
  777. func (c *Conf) getRedacted() Conf {
  778. redacted := "[redacted]"
  779. conf := *c
  780. if conf.SigningPassphrase != "" {
  781. conf.SigningPassphrase = redacted
  782. }
  783. if conf.Setup.InstallationCode != "" {
  784. conf.Setup.InstallationCode = redacted
  785. }
  786. conf.Bindings = nil
  787. for _, binding := range c.Bindings {
  788. if binding.OIDC.ClientID != "" {
  789. binding.OIDC.ClientID = redacted
  790. }
  791. if binding.OIDC.ClientSecret != "" {
  792. binding.OIDC.ClientSecret = redacted
  793. }
  794. conf.Bindings = append(conf.Bindings, binding)
  795. }
  796. return conf
  797. }
  798. func (c *Conf) getKeyPairs(configDir string) []common.TLSKeyPair {
  799. var keyPairs []common.TLSKeyPair
  800. for _, binding := range c.Bindings {
  801. certificateFile := getConfigPath(binding.CertificateFile, configDir)
  802. certificateKeyFile := getConfigPath(binding.CertificateKeyFile, configDir)
  803. if certificateFile != "" && certificateKeyFile != "" {
  804. keyPairs = append(keyPairs, common.TLSKeyPair{
  805. Cert: certificateFile,
  806. Key: certificateKeyFile,
  807. ID: binding.GetAddress(),
  808. })
  809. }
  810. }
  811. var certificateFile, certificateKeyFile string
  812. if c.acmeDomain != "" {
  813. certificateFile, certificateKeyFile = util.GetACMECertificateKeyPair(c.acmeDomain)
  814. } else {
  815. certificateFile = getConfigPath(c.CertificateFile, configDir)
  816. certificateKeyFile = getConfigPath(c.CertificateKeyFile, configDir)
  817. }
  818. if certificateFile != "" && certificateKeyFile != "" {
  819. keyPairs = append(keyPairs, common.TLSKeyPair{
  820. Cert: certificateFile,
  821. Key: certificateKeyFile,
  822. ID: common.DefaultTLSKeyPaidID,
  823. })
  824. }
  825. return keyPairs
  826. }
  827. func (c *Conf) setTokenValidationMode() {
  828. if c.TokenValidation == 1 {
  829. tokenValidationMode = tokenValidationNoIPMatch
  830. } else {
  831. tokenValidationMode = tokenValidationFull
  832. }
  833. }
  834. func (c *Conf) loadFromProvider() error {
  835. configs, err := dataprovider.GetConfigs()
  836. if err != nil {
  837. return fmt.Errorf("unable to load config from provider: %w", err)
  838. }
  839. configs.SetNilsToEmpty()
  840. if configs.ACME.Domain == "" || !configs.ACME.HasProtocol(common.ProtocolHTTP) {
  841. return nil
  842. }
  843. crt, key := util.GetACMECertificateKeyPair(configs.ACME.Domain)
  844. if crt != "" && key != "" {
  845. if _, err := os.Stat(crt); err != nil {
  846. logger.Error(logSender, "", "unable to load acme cert file %q: %v", crt, err)
  847. return nil
  848. }
  849. if _, err := os.Stat(key); err != nil {
  850. logger.Error(logSender, "", "unable to load acme key file %q: %v", key, err)
  851. return nil
  852. }
  853. for idx := range c.Bindings {
  854. if c.Bindings[idx].Security.Enabled && c.Bindings[idx].Security.HTTPSRedirect {
  855. continue
  856. }
  857. c.Bindings[idx].EnableHTTPS = true
  858. }
  859. c.acmeDomain = configs.ACME.Domain
  860. logger.Info(logSender, "", "acme domain set to %q", c.acmeDomain)
  861. return nil
  862. }
  863. return nil
  864. }
  865. func (c *Conf) loadTemplates(templatesPath string) {
  866. if c.isWebAdminEnabled() {
  867. updateWebAdminURLs(c.WebRoot)
  868. loadAdminTemplates(templatesPath)
  869. } else {
  870. logger.Info(logSender, "", "built-in web admin interface disabled")
  871. }
  872. if c.isWebClientEnabled() {
  873. updateWebClientURLs(c.WebRoot)
  874. loadClientTemplates(templatesPath)
  875. } else {
  876. logger.Info(logSender, "", "built-in web client interface disabled")
  877. }
  878. }
  879. // Initialize configures and starts the HTTP server
  880. func (c *Conf) Initialize(configDir string, isShared int) error {
  881. if err := c.loadFromProvider(); err != nil {
  882. return err
  883. }
  884. logger.Info(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
  885. configurationDir = configDir
  886. invalidatedJWTTokens = newTokenManager(isShared)
  887. resetCodesMgr = newResetCodeManager(isShared)
  888. oidcMgr = newOIDCManager(isShared)
  889. oauth2Mgr = newOAuth2Manager(isShared)
  890. webTaskMgr = newWebTaskManager(isShared)
  891. staticFilesPath := util.FindSharedDataPath(c.StaticFilesPath, configDir)
  892. templatesPath := util.FindSharedDataPath(c.TemplatesPath, configDir)
  893. openAPIPath := util.FindSharedDataPath(c.OpenAPIPath, configDir)
  894. if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
  895. return err
  896. }
  897. c.loadTemplates(templatesPath)
  898. keyPairs := c.getKeyPairs(configDir)
  899. if len(keyPairs) > 0 {
  900. mgr, err := common.NewCertManager(keyPairs, configDir, logSender)
  901. if err != nil {
  902. return err
  903. }
  904. mgr.SetCACertificates(c.CACertificates)
  905. if err := mgr.LoadRootCAs(); err != nil {
  906. return err
  907. }
  908. mgr.SetCARevocationLists(c.CARevocationLists)
  909. if err := mgr.LoadCRLs(); err != nil {
  910. return err
  911. }
  912. certMgr = mgr
  913. }
  914. if c.SigningPassphraseFile != "" {
  915. passphrase, err := util.ReadConfigFromFile(c.SigningPassphraseFile, configDir)
  916. if err != nil {
  917. return err
  918. }
  919. c.SigningPassphrase = passphrase
  920. }
  921. csrfTokenAuth = jwtauth.New(jwa.HS256.String(), getSigningKey(c.SigningPassphrase), nil)
  922. hideSupportLink = c.HideSupportLink
  923. exitChannel := make(chan error, 1)
  924. for _, binding := range c.Bindings {
  925. if !binding.IsValid() {
  926. continue
  927. }
  928. if err := binding.parseAllowedProxy(); err != nil {
  929. return err
  930. }
  931. binding.checkBranding()
  932. binding.Security.updateProxyHeaders()
  933. go func(b Binding) {
  934. if err := b.OIDC.initialize(); err != nil {
  935. exitChannel <- err
  936. return
  937. }
  938. if err := b.checkLoginMethods(); err != nil {
  939. exitChannel <- err
  940. return
  941. }
  942. server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors, openAPIPath)
  943. server.setShared(isShared)
  944. exitChannel <- server.listenAndServe()
  945. }(binding)
  946. }
  947. maxUploadFileSize = c.MaxUploadFileSize
  948. installationCode = c.Setup.InstallationCode
  949. installationCodeHint = c.Setup.InstallationCodeHint
  950. startCleanupTicker(tokenDuration / 2)
  951. c.setTokenValidationMode()
  952. return <-exitChannel
  953. }
  954. func isWebRequest(r *http.Request) bool {
  955. return strings.HasPrefix(r.RequestURI, webBasePath+"/")
  956. }
  957. func isWebClientRequest(r *http.Request) bool {
  958. return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
  959. }
  960. // ReloadCertificateMgr reloads the certificate manager
  961. func ReloadCertificateMgr() error {
  962. if certMgr != nil {
  963. return certMgr.Reload()
  964. }
  965. return nil
  966. }
  967. func getConfigPath(name, configDir string) string {
  968. if !util.IsFileInputValid(name) {
  969. return ""
  970. }
  971. if name != "" && !filepath.IsAbs(name) {
  972. return filepath.Join(configDir, name)
  973. }
  974. return name
  975. }
  976. func getServicesStatus() *ServicesStatus {
  977. rtlEnabled, rtlProtocols := common.Config.GetRateLimitersStatus()
  978. status := &ServicesStatus{
  979. SSH: sftpd.GetStatus(),
  980. FTP: ftpd.GetStatus(),
  981. WebDAV: webdavd.GetStatus(),
  982. DataProvider: dataprovider.GetProviderStatus(),
  983. Defender: defenderStatus{
  984. IsActive: common.Config.DefenderConfig.Enabled,
  985. },
  986. MFA: mfa.GetStatus(),
  987. AllowList: allowListStatus{
  988. IsActive: common.Config.IsAllowListEnabled(),
  989. },
  990. RateLimiters: rateLimiters{
  991. IsActive: rtlEnabled,
  992. Protocols: rtlProtocols,
  993. },
  994. }
  995. return status
  996. }
  997. func fileServer(r chi.Router, path string, root http.FileSystem, disableDirectoryIndex bool) {
  998. if path != "/" && path[len(path)-1] != '/' {
  999. r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
  1000. path += "/"
  1001. }
  1002. path += "*"
  1003. r.Get(path, func(w http.ResponseWriter, r *http.Request) {
  1004. rctx := chi.RouteContext(r.Context())
  1005. pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
  1006. if disableDirectoryIndex {
  1007. root = neuteredFileSystem{root}
  1008. }
  1009. fs := http.StripPrefix(pathPrefix, http.FileServer(root))
  1010. fs.ServeHTTP(w, r)
  1011. })
  1012. }
  1013. func updateWebClientURLs(baseURL string) {
  1014. if !path.IsAbs(baseURL) {
  1015. baseURL = "/"
  1016. }
  1017. webRootPath = path.Join(baseURL, webRootPathDefault)
  1018. webBasePath = path.Join(baseURL, webBasePathDefault)
  1019. webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
  1020. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  1021. webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
  1022. webClientOIDCLoginPath = path.Join(baseURL, webClientOIDCLoginPathDefault)
  1023. webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
  1024. webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
  1025. webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
  1026. webClientFilePath = path.Join(baseURL, webClientFilePathDefault)
  1027. webClientFileActionsPath = path.Join(baseURL, webClientFileActionsPathDefault)
  1028. webClientSharesPath = path.Join(baseURL, webClientSharesPathDefault)
  1029. webClientPubSharesPath = path.Join(baseURL, webClientPubSharesPathDefault)
  1030. webClientSharePath = path.Join(baseURL, webClientSharePathDefault)
  1031. webClientEditFilePath = path.Join(baseURL, webClientEditFilePathDefault)
  1032. webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
  1033. webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
  1034. webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
  1035. webClientPingPath = path.Join(baseURL, webClientPingPathDefault)
  1036. webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
  1037. webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
  1038. webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
  1039. webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
  1040. webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
  1041. webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
  1042. webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
  1043. webClientForgotPwdPath = path.Join(baseURL, webClientForgotPwdPathDefault)
  1044. webClientResetPwdPath = path.Join(baseURL, webClientResetPwdPathDefault)
  1045. webClientViewPDFPath = path.Join(baseURL, webClientViewPDFPathDefault)
  1046. webClientGetPDFPath = path.Join(baseURL, webClientGetPDFPathDefault)
  1047. webClientExistPath = path.Join(baseURL, webClientExistPathDefault)
  1048. webClientTasksPath = path.Join(baseURL, webClientTasksPathDefault)
  1049. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  1050. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  1051. }
  1052. func updateWebAdminURLs(baseURL string) {
  1053. if !path.IsAbs(baseURL) {
  1054. baseURL = "/"
  1055. }
  1056. webRootPath = path.Join(baseURL, webRootPathDefault)
  1057. webBasePath = path.Join(baseURL, webBasePathDefault)
  1058. webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
  1059. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  1060. webOAuth2RedirectPath = path.Join(baseURL, webOAuth2RedirectPathDefault)
  1061. webOAuth2TokenPath = path.Join(baseURL, webOAuth2TokenPathDefault)
  1062. webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
  1063. webAdminLoginPath = path.Join(baseURL, webAdminLoginPathDefault)
  1064. webAdminOIDCLoginPath = path.Join(baseURL, webAdminOIDCLoginPathDefault)
  1065. webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
  1066. webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
  1067. webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
  1068. webUsersPath = path.Join(baseURL, webUsersPathDefault)
  1069. webUserPath = path.Join(baseURL, webUserPathDefault)
  1070. webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
  1071. webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
  1072. webFolderPath = path.Join(baseURL, webFolderPathDefault)
  1073. webGroupsPath = path.Join(baseURL, webGroupsPathDefault)
  1074. webGroupPath = path.Join(baseURL, webGroupPathDefault)
  1075. webStatusPath = path.Join(baseURL, webStatusPathDefault)
  1076. webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
  1077. webAdminPath = path.Join(baseURL, webAdminPathDefault)
  1078. webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
  1079. webBackupPath = path.Join(baseURL, webBackupPathDefault)
  1080. webRestorePath = path.Join(baseURL, webRestorePathDefault)
  1081. webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
  1082. webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
  1083. webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
  1084. webAdminForgotPwdPath = path.Join(baseURL, webAdminForgotPwdPathDefault)
  1085. webAdminResetPwdPath = path.Join(baseURL, webAdminResetPwdPathDefault)
  1086. webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
  1087. webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
  1088. webAdminEventRulesPath = path.Join(baseURL, webAdminEventRulesPathDefault)
  1089. webAdminEventRulePath = path.Join(baseURL, webAdminEventRulePathDefault)
  1090. webAdminEventActionsPath = path.Join(baseURL, webAdminEventActionsPathDefault)
  1091. webAdminEventActionPath = path.Join(baseURL, webAdminEventActionPathDefault)
  1092. webAdminRolesPath = path.Join(baseURL, webAdminRolesPathDefault)
  1093. webAdminRolePath = path.Join(baseURL, webAdminRolePathDefault)
  1094. webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
  1095. webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
  1096. webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
  1097. webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
  1098. webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
  1099. webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
  1100. webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
  1101. webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
  1102. webIPListPath = path.Join(baseURL, webIPListPathDefault)
  1103. webIPListsPath = path.Join(baseURL, webIPListsPathDefault)
  1104. webEventsPath = path.Join(baseURL, webEventsPathDefault)
  1105. webEventsFsSearchPath = path.Join(baseURL, webEventsFsSearchPathDefault)
  1106. webEventsProviderSearchPath = path.Join(baseURL, webEventsProviderSearchPathDefault)
  1107. webEventsLogSearchPath = path.Join(baseURL, webEventsLogSearchPathDefault)
  1108. webConfigsPath = path.Join(baseURL, webConfigsPathDefault)
  1109. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  1110. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  1111. }
  1112. // GetHTTPRouter returns an HTTP handler suitable to use for test cases
  1113. func GetHTTPRouter(b Binding) http.Handler {
  1114. server := newHttpdServer(b, filepath.Join("..", "..", "static"), "", CorsConfig{}, filepath.Join("..", "..", "openapi"))
  1115. server.initializeRouter()
  1116. return server.router
  1117. }
  1118. // the ticker cannot be started/stopped from multiple goroutines
  1119. func startCleanupTicker(duration time.Duration) {
  1120. stopCleanupTicker()
  1121. cleanupTicker = time.NewTicker(duration)
  1122. cleanupDone = make(chan bool)
  1123. go func() {
  1124. counter := int64(0)
  1125. for {
  1126. select {
  1127. case <-cleanupDone:
  1128. return
  1129. case <-cleanupTicker.C:
  1130. counter++
  1131. invalidatedJWTTokens.Cleanup()
  1132. resetCodesMgr.Cleanup()
  1133. webTaskMgr.Cleanup()
  1134. if counter%2 == 0 {
  1135. oidcMgr.cleanup()
  1136. oauth2Mgr.cleanup()
  1137. }
  1138. }
  1139. }
  1140. }()
  1141. }
  1142. func stopCleanupTicker() {
  1143. if cleanupTicker != nil {
  1144. cleanupTicker.Stop()
  1145. cleanupDone <- true
  1146. cleanupTicker = nil
  1147. }
  1148. }
  1149. func getSigningKey(signingPassphrase string) []byte {
  1150. if signingPassphrase != "" {
  1151. sk := sha256.Sum256([]byte(signingPassphrase))
  1152. return sk[:]
  1153. }
  1154. return util.GenerateRandomBytes(32)
  1155. }
  1156. // SetInstallationCodeResolver sets a function to call to resolve the installation code
  1157. func SetInstallationCodeResolver(fn FnInstallationCodeResolver) {
  1158. fnInstallationCodeResolver = fn
  1159. }
  1160. func resolveInstallationCode() string {
  1161. if fnInstallationCodeResolver != nil {
  1162. return fnInstallationCodeResolver(installationCode)
  1163. }
  1164. return installationCode
  1165. }
  1166. type neuteredFileSystem struct {
  1167. fs http.FileSystem
  1168. }
  1169. func (nfs neuteredFileSystem) Open(name string) (http.File, error) {
  1170. f, err := nfs.fs.Open(name)
  1171. if err != nil {
  1172. return nil, err
  1173. }
  1174. s, err := f.Stat()
  1175. if err != nil {
  1176. return nil, err
  1177. }
  1178. if s.IsDir() {
  1179. index := path.Join(name, "index.html")
  1180. if _, err := nfs.fs.Open(index); err != nil {
  1181. defer f.Close()
  1182. return nil, err
  1183. }
  1184. }
  1185. return f, nil
  1186. }