service.go 7.2 KB

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