service_portable.go 9.8 KB

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