service.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Package service allows to start and stop the SFTPGo service
  2. package service
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "os"
  7. "os/signal"
  8. "path/filepath"
  9. "strings"
  10. "syscall"
  11. "time"
  12. "github.com/drakkan/sftpgo/config"
  13. "github.com/drakkan/sftpgo/dataprovider"
  14. "github.com/drakkan/sftpgo/httpd"
  15. "github.com/drakkan/sftpgo/logger"
  16. "github.com/drakkan/sftpgo/sftpd"
  17. "github.com/drakkan/sftpgo/utils"
  18. "github.com/grandcat/zeroconf"
  19. "github.com/rs/zerolog"
  20. )
  21. const (
  22. logSender = "service"
  23. )
  24. var (
  25. chars = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
  26. )
  27. // Service defines the SFTPGo service
  28. type Service struct {
  29. ConfigDir string
  30. ConfigFile string
  31. LogFilePath string
  32. LogMaxSize int
  33. LogMaxBackups int
  34. LogMaxAge int
  35. LogCompress bool
  36. LogVerbose bool
  37. PortableMode int
  38. PortableUser dataprovider.User
  39. Profiler bool
  40. Shutdown chan bool
  41. }
  42. // Start initializes the service
  43. func (s *Service) Start() error {
  44. logLevel := zerolog.DebugLevel
  45. if !s.LogVerbose {
  46. logLevel = zerolog.InfoLevel
  47. }
  48. if !filepath.IsAbs(s.LogFilePath) && utils.IsFileInputValid(s.LogFilePath) {
  49. s.LogFilePath = filepath.Join(s.ConfigDir, s.LogFilePath)
  50. }
  51. logger.InitLogger(s.LogFilePath, s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogCompress, logLevel)
  52. if s.PortableMode == 1 {
  53. logger.EnableConsoleLogger(logLevel)
  54. if len(s.LogFilePath) == 0 {
  55. logger.DisableLogger()
  56. }
  57. }
  58. version := utils.GetAppVersion()
  59. logger.Info(logSender, "", "starting SFTPGo %v, config dir: %v, config file: %v, log max size: %v log max backups: %v "+
  60. "log max age: %v log verbose: %v, log compress: %v, profile: %v", version.GetVersionAsString(), s.ConfigDir, s.ConfigFile,
  61. s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogVerbose, s.LogCompress, s.Profiler)
  62. // in portable mode we don't read configuration from file
  63. if s.PortableMode != 1 {
  64. config.LoadConfig(s.ConfigDir, s.ConfigFile)
  65. }
  66. providerConf := config.GetProviderConf()
  67. err := dataprovider.Initialize(providerConf, s.ConfigDir)
  68. if err != nil {
  69. logger.Error(logSender, "", "error initializing data provider: %v", err)
  70. logger.ErrorToConsole("error initializing data provider: %v", err)
  71. return err
  72. }
  73. httpConfig := config.GetHTTPConfig()
  74. httpConfig.Initialize(s.ConfigDir)
  75. dataProvider := dataprovider.GetProvider()
  76. sftpdConf := config.GetSFTPDConfig()
  77. httpdConf := config.GetHTTPDConfig()
  78. if s.PortableMode == 1 {
  79. // create the user for portable mode
  80. err = dataprovider.AddUser(dataProvider, s.PortableUser)
  81. if err != nil {
  82. logger.ErrorToConsole("error adding portable user: %v", err)
  83. return err
  84. }
  85. }
  86. sftpd.SetDataProvider(dataProvider)
  87. go func() {
  88. logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
  89. if err := sftpdConf.Initialize(s.ConfigDir); err != nil {
  90. logger.Error(logSender, "", "could not start SFTP server: %v", err)
  91. logger.ErrorToConsole("could not start SFTP server: %v", err)
  92. }
  93. s.Shutdown <- true
  94. }()
  95. if httpdConf.BindPort > 0 {
  96. httpd.SetDataProvider(dataProvider)
  97. go func() {
  98. if err := httpdConf.Initialize(s.ConfigDir, s.Profiler); err != nil {
  99. logger.Error(logSender, "", "could not start HTTP server: %v", err)
  100. logger.ErrorToConsole("could not start HTTP server: %v", err)
  101. }
  102. s.Shutdown <- true
  103. }()
  104. } else {
  105. logger.Debug(logSender, "", "HTTP server not started, disabled in config file")
  106. if s.PortableMode != 1 {
  107. logger.DebugToConsole("HTTP server not started, disabled in config file")
  108. }
  109. }
  110. return nil
  111. }
  112. // Wait blocks until the service exits
  113. func (s *Service) Wait() {
  114. if s.PortableMode != 1 {
  115. registerSigHup()
  116. }
  117. <-s.Shutdown
  118. }
  119. // Stop terminates the service unblocking the Wait method
  120. func (s *Service) Stop() {
  121. close(s.Shutdown)
  122. logger.Debug(logSender, "", "Service stopped")
  123. }
  124. // StartPortableMode starts the service in portable mode
  125. func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string, advertiseService, advertiseCredentials bool) error {
  126. if s.PortableMode != 1 {
  127. return fmt.Errorf("service is not configured for portable mode")
  128. }
  129. var err error
  130. rand.Seed(time.Now().UnixNano())
  131. if len(s.PortableUser.Username) == 0 {
  132. s.PortableUser.Username = "user"
  133. }
  134. if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 {
  135. var b strings.Builder
  136. for i := 0; i < 8; i++ {
  137. b.WriteRune(chars[rand.Intn(len(chars))])
  138. }
  139. s.PortableUser.Password = b.String()
  140. }
  141. dataProviderConf := config.GetProviderConf()
  142. dataProviderConf.Driver = dataprovider.MemoryDataProviderName
  143. dataProviderConf.Name = ""
  144. dataProviderConf.CredentialsPath = filepath.Join(os.TempDir(), "credentials")
  145. config.SetProviderConf(dataProviderConf)
  146. httpdConf := config.GetHTTPDConfig()
  147. httpdConf.BindPort = 0
  148. config.SetHTTPDConfig(httpdConf)
  149. sftpdConf := config.GetSFTPDConfig()
  150. sftpdConf.MaxAuthTries = 12
  151. if sftpdPort > 0 {
  152. sftpdConf.BindPort = sftpdPort
  153. } else {
  154. // dynamic ports starts from 49152
  155. sftpdConf.BindPort = 49152 + rand.Intn(15000)
  156. }
  157. if utils.IsStringInSlice("*", enabledSSHCommands) {
  158. sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
  159. } else {
  160. sftpdConf.EnabledSSHCommands = enabledSSHCommands
  161. }
  162. config.SetSFTPDConfig(sftpdConf)
  163. err = s.Start()
  164. if err != nil {
  165. return err
  166. }
  167. var mDNSService *zeroconf.Server
  168. if advertiseService {
  169. version := utils.GetAppVersion()
  170. meta := []string{
  171. fmt.Sprintf("version=%v", version.GetVersionAsString()),
  172. }
  173. if advertiseCredentials {
  174. logger.InfoToConsole("Advertising credentials via multicast DNS")
  175. meta = append(meta, fmt.Sprintf("user=%v", s.PortableUser.Username))
  176. if len(s.PortableUser.Password) > 0 {
  177. meta = append(meta, fmt.Sprintf("password=%v", s.PortableUser.Password))
  178. } else {
  179. logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key")
  180. }
  181. }
  182. mDNSService, err = zeroconf.Register(
  183. fmt.Sprintf("SFTPGo portable %v", sftpdConf.BindPort), // service instance name
  184. "_sftp-ssh._tcp", // service type and protocol
  185. "local.", // service domain
  186. sftpdConf.BindPort, // service port
  187. meta, // service metadata
  188. nil, // register on all network interfaces
  189. )
  190. if err != nil {
  191. mDNSService = nil
  192. logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err)
  193. } else {
  194. logger.InfoToConsole("SFTP service advertised via multicast DNS")
  195. }
  196. }
  197. sig := make(chan os.Signal, 1)
  198. signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
  199. go func() {
  200. <-sig
  201. if mDNSService != nil {
  202. logger.InfoToConsole("unregistering multicast DNS service")
  203. mDNSService.Shutdown()
  204. }
  205. s.Stop()
  206. }()
  207. logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+
  208. "permissions: %+v, enabled ssh commands: %v file extensions filters: %+v", sftpdConf.BindPort, s.PortableUser.Username,
  209. s.PortableUser.Password, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
  210. sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FileExtensions)
  211. return nil
  212. }
  213. func (s *Service) getPortableDirToServe() string {
  214. var dirToServe string
  215. if s.PortableUser.FsConfig.Provider == 1 {
  216. dirToServe = s.PortableUser.FsConfig.S3Config.KeyPrefix
  217. } else if s.PortableUser.FsConfig.Provider == 2 {
  218. dirToServe = s.PortableUser.FsConfig.GCSConfig.KeyPrefix
  219. } else {
  220. dirToServe = s.PortableUser.HomeDir
  221. }
  222. return dirToServe
  223. }