service_portable.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // Copyright (C) 2019-2022 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. //go:build !noportable
  15. // +build !noportable
  16. package service
  17. import (
  18. "fmt"
  19. "math/rand"
  20. "os"
  21. "os/signal"
  22. "strings"
  23. "syscall"
  24. "time"
  25. "github.com/grandcat/zeroconf"
  26. "github.com/sftpgo/sdk"
  27. "github.com/drakkan/sftpgo/v2/internal/config"
  28. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  29. "github.com/drakkan/sftpgo/v2/internal/ftpd"
  30. "github.com/drakkan/sftpgo/v2/internal/kms"
  31. "github.com/drakkan/sftpgo/v2/internal/logger"
  32. "github.com/drakkan/sftpgo/v2/internal/sftpd"
  33. "github.com/drakkan/sftpgo/v2/internal/util"
  34. "github.com/drakkan/sftpgo/v2/internal/version"
  35. "github.com/drakkan/sftpgo/v2/internal/webdavd"
  36. )
  37. // StartPortableMode starts the service in portable mode
  38. func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledSSHCommands []string, advertiseService,
  39. advertiseCredentials bool, ftpsCert, ftpsKey, webDavCert, webDavKey string) error {
  40. if s.PortableMode != 1 {
  41. return fmt.Errorf("service is not configured for portable mode")
  42. }
  43. rand.Seed(time.Now().UnixNano())
  44. err := config.LoadConfig(s.ConfigDir, s.ConfigFile)
  45. if err != nil {
  46. fmt.Printf("error loading configuration file: %v using defaults\n", err)
  47. }
  48. kmsConfig := config.GetKMSConfig()
  49. err = kmsConfig.Initialize()
  50. if err != nil {
  51. return err
  52. }
  53. printablePassword := s.configurePortableUser()
  54. dataProviderConf := config.GetProviderConf()
  55. dataProviderConf.Driver = dataprovider.MemoryDataProviderName
  56. dataProviderConf.Name = ""
  57. config.SetProviderConf(dataProviderConf)
  58. httpdConf := config.GetHTTPDConfig()
  59. httpdConf.Bindings = nil
  60. config.SetHTTPDConfig(httpdConf)
  61. telemetryConf := config.GetTelemetryConfig()
  62. telemetryConf.BindPort = 0
  63. config.SetTelemetryConfig(telemetryConf)
  64. sftpdConf := config.GetSFTPDConfig()
  65. sftpdConf.MaxAuthTries = 12
  66. sftpdConf.Bindings = []sftpd.Binding{
  67. {
  68. Port: sftpdPort,
  69. },
  70. }
  71. if sftpdPort >= 0 {
  72. if sftpdPort > 0 {
  73. sftpdConf.Bindings[0].Port = sftpdPort
  74. } else {
  75. // dynamic ports starts from 49152
  76. sftpdConf.Bindings[0].Port = 49152 + rand.Intn(15000)
  77. }
  78. if util.Contains(enabledSSHCommands, "*") {
  79. sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
  80. } else {
  81. sftpdConf.EnabledSSHCommands = enabledSSHCommands
  82. }
  83. }
  84. config.SetSFTPDConfig(sftpdConf)
  85. if ftpPort >= 0 {
  86. ftpConf := config.GetFTPDConfig()
  87. binding := ftpd.Binding{}
  88. if ftpPort > 0 {
  89. binding.Port = ftpPort
  90. } else {
  91. binding.Port = 49152 + rand.Intn(15000)
  92. }
  93. ftpConf.Bindings = []ftpd.Binding{binding}
  94. ftpConf.Banner = fmt.Sprintf("SFTPGo portable %v ready", version.Get().Version)
  95. ftpConf.CertificateFile = ftpsCert
  96. ftpConf.CertificateKeyFile = ftpsKey
  97. config.SetFTPDConfig(ftpConf)
  98. }
  99. if webdavPort >= 0 {
  100. webDavConf := config.GetWebDAVDConfig()
  101. binding := webdavd.Binding{}
  102. if webdavPort > 0 {
  103. binding.Port = webdavPort
  104. } else {
  105. binding.Port = 49152 + rand.Intn(15000)
  106. }
  107. webDavConf.Bindings = []webdavd.Binding{binding}
  108. webDavConf.CertificateFile = webDavCert
  109. webDavConf.CertificateKeyFile = webDavKey
  110. config.SetWebDAVDConfig(webDavConf)
  111. }
  112. err = s.Start(true)
  113. if err != nil {
  114. return err
  115. }
  116. s.advertiseServices(advertiseService, advertiseCredentials)
  117. logger.InfoToConsole("Portable mode ready, user: %#v, password: %#v, public keys: %v, directory: %#v, "+
  118. "permissions: %+v, enabled ssh commands: %v file patterns filters: %+v %v", s.PortableUser.Username,
  119. printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
  120. sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FilePatterns, s.getServiceOptionalInfoString())
  121. return nil
  122. }
  123. func (s *Service) getServiceOptionalInfoString() string {
  124. var info strings.Builder
  125. if config.GetSFTPDConfig().Bindings[0].IsValid() {
  126. info.WriteString(fmt.Sprintf("SFTP port: %v ", config.GetSFTPDConfig().Bindings[0].Port))
  127. }
  128. if config.GetFTPDConfig().Bindings[0].IsValid() {
  129. info.WriteString(fmt.Sprintf("FTP port: %v ", config.GetFTPDConfig().Bindings[0].Port))
  130. }
  131. if config.GetWebDAVDConfig().Bindings[0].IsValid() {
  132. scheme := "http"
  133. if config.GetWebDAVDConfig().CertificateFile != "" && config.GetWebDAVDConfig().CertificateKeyFile != "" {
  134. scheme = "https"
  135. }
  136. info.WriteString(fmt.Sprintf("WebDAV URL: %v://<your IP>:%v/", scheme, config.GetWebDAVDConfig().Bindings[0].Port))
  137. }
  138. return info.String()
  139. }
  140. func (s *Service) advertiseServices(advertiseService, advertiseCredentials bool) {
  141. var mDNSServiceSFTP *zeroconf.Server
  142. var mDNSServiceFTP *zeroconf.Server
  143. var mDNSServiceDAV *zeroconf.Server
  144. var err error
  145. if advertiseService {
  146. meta := []string{
  147. fmt.Sprintf("version=%v", version.Get().Version),
  148. }
  149. if advertiseCredentials {
  150. logger.InfoToConsole("Advertising credentials via multicast DNS")
  151. meta = append(meta, fmt.Sprintf("user=%v", s.PortableUser.Username))
  152. if len(s.PortableUser.Password) > 0 {
  153. meta = append(meta, fmt.Sprintf("password=%v", s.PortableUser.Password))
  154. } else {
  155. logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key")
  156. }
  157. }
  158. sftpdConf := config.GetSFTPDConfig()
  159. if sftpdConf.Bindings[0].IsValid() {
  160. mDNSServiceSFTP, err = zeroconf.Register(
  161. fmt.Sprintf("SFTPGo portable %v", sftpdConf.Bindings[0].Port), // service instance name
  162. "_sftp-ssh._tcp", // service type and protocol
  163. "local.", // service domain
  164. sftpdConf.Bindings[0].Port, // service port
  165. meta, // service metadata
  166. nil, // register on all network interfaces
  167. )
  168. if err != nil {
  169. mDNSServiceSFTP = nil
  170. logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err)
  171. } else {
  172. logger.InfoToConsole("SFTP service advertised via multicast DNS")
  173. }
  174. }
  175. ftpdConf := config.GetFTPDConfig()
  176. if ftpdConf.Bindings[0].IsValid() {
  177. port := ftpdConf.Bindings[0].Port
  178. mDNSServiceFTP, err = zeroconf.Register(
  179. fmt.Sprintf("SFTPGo portable %v", port),
  180. "_ftp._tcp",
  181. "local.",
  182. port,
  183. meta,
  184. nil,
  185. )
  186. if err != nil {
  187. mDNSServiceFTP = nil
  188. logger.WarnToConsole("Unable to advertise FTP service via multicast DNS: %v", err)
  189. } else {
  190. logger.InfoToConsole("FTP service advertised via multicast DNS")
  191. }
  192. }
  193. webdavConf := config.GetWebDAVDConfig()
  194. if webdavConf.Bindings[0].IsValid() {
  195. mDNSServiceDAV, err = zeroconf.Register(
  196. fmt.Sprintf("SFTPGo portable %v", webdavConf.Bindings[0].Port),
  197. "_http._tcp",
  198. "local.",
  199. webdavConf.Bindings[0].Port,
  200. meta,
  201. nil,
  202. )
  203. if err != nil {
  204. mDNSServiceDAV = nil
  205. logger.WarnToConsole("Unable to advertise WebDAV service via multicast DNS: %v", err)
  206. } else {
  207. logger.InfoToConsole("WebDAV service advertised via multicast DNS")
  208. }
  209. }
  210. }
  211. sig := make(chan os.Signal, 1)
  212. signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
  213. go func() {
  214. <-sig
  215. if mDNSServiceSFTP != nil {
  216. logger.InfoToConsole("unregistering multicast DNS SFTP service")
  217. mDNSServiceSFTP.Shutdown()
  218. }
  219. if mDNSServiceFTP != nil {
  220. logger.InfoToConsole("unregistering multicast DNS FTP service")
  221. mDNSServiceFTP.Shutdown()
  222. }
  223. if mDNSServiceDAV != nil {
  224. logger.InfoToConsole("unregistering multicast DNS WebDAV service")
  225. mDNSServiceDAV.Shutdown()
  226. }
  227. s.Stop()
  228. }()
  229. }
  230. func (s *Service) getPortableDirToServe() string {
  231. switch s.PortableUser.FsConfig.Provider {
  232. case sdk.S3FilesystemProvider:
  233. return s.PortableUser.FsConfig.S3Config.KeyPrefix
  234. case sdk.GCSFilesystemProvider:
  235. return s.PortableUser.FsConfig.GCSConfig.KeyPrefix
  236. case sdk.AzureBlobFilesystemProvider:
  237. return s.PortableUser.FsConfig.AzBlobConfig.KeyPrefix
  238. case sdk.SFTPFilesystemProvider:
  239. return s.PortableUser.FsConfig.SFTPConfig.Prefix
  240. case sdk.HTTPFilesystemProvider:
  241. return "/"
  242. default:
  243. return s.PortableUser.HomeDir
  244. }
  245. }
  246. // configures the portable user and return the printable password if any
  247. func (s *Service) configurePortableUser() string {
  248. if s.PortableUser.Username == "" {
  249. s.PortableUser.Username = "user"
  250. }
  251. printablePassword := ""
  252. if s.PortableUser.Password != "" {
  253. printablePassword = "[redacted]"
  254. }
  255. if len(s.PortableUser.PublicKeys) == 0 && s.PortableUser.Password == "" {
  256. var b strings.Builder
  257. for i := 0; i < 8; i++ {
  258. b.WriteRune(chars[rand.Intn(len(chars))])
  259. }
  260. s.PortableUser.Password = b.String()
  261. printablePassword = s.PortableUser.Password
  262. }
  263. s.configurePortableSecrets()
  264. return printablePassword
  265. }
  266. func (s *Service) configurePortableSecrets() {
  267. // we created the user before to initialize the KMS so we need to create the secret here
  268. switch s.PortableUser.FsConfig.Provider {
  269. case sdk.S3FilesystemProvider:
  270. payload := s.PortableUser.FsConfig.S3Config.AccessSecret.GetPayload()
  271. s.PortableUser.FsConfig.S3Config.AccessSecret = getSecretFromString(payload)
  272. case sdk.GCSFilesystemProvider:
  273. payload := s.PortableUser.FsConfig.GCSConfig.Credentials.GetPayload()
  274. s.PortableUser.FsConfig.GCSConfig.Credentials = getSecretFromString(payload)
  275. case sdk.AzureBlobFilesystemProvider:
  276. payload := s.PortableUser.FsConfig.AzBlobConfig.AccountKey.GetPayload()
  277. s.PortableUser.FsConfig.AzBlobConfig.AccountKey = getSecretFromString(payload)
  278. payload = s.PortableUser.FsConfig.AzBlobConfig.SASURL.GetPayload()
  279. s.PortableUser.FsConfig.AzBlobConfig.SASURL = getSecretFromString(payload)
  280. case sdk.CryptedFilesystemProvider:
  281. payload := s.PortableUser.FsConfig.CryptConfig.Passphrase.GetPayload()
  282. s.PortableUser.FsConfig.CryptConfig.Passphrase = getSecretFromString(payload)
  283. case sdk.SFTPFilesystemProvider:
  284. payload := s.PortableUser.FsConfig.SFTPConfig.Password.GetPayload()
  285. s.PortableUser.FsConfig.SFTPConfig.Password = getSecretFromString(payload)
  286. payload = s.PortableUser.FsConfig.SFTPConfig.PrivateKey.GetPayload()
  287. s.PortableUser.FsConfig.SFTPConfig.PrivateKey = getSecretFromString(payload)
  288. payload = s.PortableUser.FsConfig.SFTPConfig.KeyPassphrase.GetPayload()
  289. s.PortableUser.FsConfig.SFTPConfig.KeyPassphrase = getSecretFromString(payload)
  290. case sdk.HTTPFilesystemProvider:
  291. payload := s.PortableUser.FsConfig.HTTPConfig.Password.GetPayload()
  292. s.PortableUser.FsConfig.HTTPConfig.Password = getSecretFromString(payload)
  293. payload = s.PortableUser.FsConfig.HTTPConfig.APIKey.GetPayload()
  294. s.PortableUser.FsConfig.HTTPConfig.APIKey = getSecretFromString(payload)
  295. }
  296. }
  297. func getSecretFromString(payload string) *kms.Secret {
  298. if payload != "" {
  299. return kms.NewPlainSecret(payload)
  300. }
  301. return kms.NewEmptySecret()
  302. }