startsubsys.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package cmd
  2. import (
  3. "io"
  4. "os"
  5. "os/user"
  6. "path/filepath"
  7. "github.com/rs/xid"
  8. "github.com/rs/zerolog"
  9. "github.com/spf13/cobra"
  10. "github.com/spf13/viper"
  11. "github.com/drakkan/sftpgo/v2/common"
  12. "github.com/drakkan/sftpgo/v2/config"
  13. "github.com/drakkan/sftpgo/v2/dataprovider"
  14. "github.com/drakkan/sftpgo/v2/logger"
  15. "github.com/drakkan/sftpgo/v2/sdk/plugin"
  16. "github.com/drakkan/sftpgo/v2/sftpd"
  17. "github.com/drakkan/sftpgo/v2/version"
  18. )
  19. var (
  20. logJournalD = false
  21. preserveHomeDir = false
  22. baseHomeDir = ""
  23. subsystemCmd = &cobra.Command{
  24. Use: "startsubsys",
  25. Short: "Use SFTPGo as SFTP file transfer subsystem",
  26. Long: `In this mode SFTPGo speaks the server side of SFTP protocol to stdout and
  27. expects client requests from stdin.
  28. This mode is not intended to be called directly, but from sshd using the
  29. Subsystem option.
  30. For example adding a line like this one in "/etc/ssh/sshd_config":
  31. Subsystem sftp sftpgo startsubsys
  32. Command-line flags should be specified in the Subsystem declaration.
  33. `,
  34. Run: func(cmd *cobra.Command, args []string) {
  35. logSender := "startsubsys"
  36. connectionID := xid.New().String()
  37. logLevel := zerolog.DebugLevel
  38. if !logVerbose {
  39. logLevel = zerolog.InfoLevel
  40. }
  41. if logJournalD {
  42. logger.InitJournalDLogger(logLevel)
  43. } else {
  44. logger.InitStdErrLogger(logLevel)
  45. }
  46. osUser, err := user.Current()
  47. if err != nil {
  48. logger.Error(logSender, connectionID, "unable to get the current user: %v", err)
  49. os.Exit(1)
  50. }
  51. username := osUser.Username
  52. homedir := osUser.HomeDir
  53. logger.Info(logSender, connectionID, "starting SFTPGo %v as subsystem, user %#v home dir %#v config dir %#v base home dir %#v",
  54. version.Get(), username, homedir, configDir, baseHomeDir)
  55. err = config.LoadConfig(configDir, configFile)
  56. if err != nil {
  57. logger.Error(logSender, connectionID, "unable to load configuration: %v", err)
  58. os.Exit(1)
  59. }
  60. commonConfig := config.GetCommonConfig()
  61. // idle connection are managed externally
  62. commonConfig.IdleTimeout = 0
  63. config.SetCommonConfig(commonConfig)
  64. if err := common.Initialize(config.GetCommonConfig()); err != nil {
  65. logger.Error(logSender, connectionID, "%v", err)
  66. os.Exit(1)
  67. }
  68. kmsConfig := config.GetKMSConfig()
  69. if err := kmsConfig.Initialize(); err != nil {
  70. logger.Error(logSender, connectionID, "unable to initialize KMS: %v", err)
  71. os.Exit(1)
  72. }
  73. mfaConfig := config.GetMFAConfig()
  74. err = mfaConfig.Initialize()
  75. if err != nil {
  76. logger.Error(logSender, "", "unable to initialize MFA: %v", err)
  77. os.Exit(1)
  78. }
  79. if err := plugin.Initialize(config.GetPluginsConfig(), logVerbose); err != nil {
  80. logger.Error(logSender, connectionID, "unable to initialize plugin system: %v", err)
  81. os.Exit(1)
  82. }
  83. smtpConfig := config.GetSMTPConfig()
  84. err = smtpConfig.Initialize(configDir)
  85. if err != nil {
  86. logger.Error(logSender, connectionID, "unable to initialize SMTP configuration: %v", err)
  87. os.Exit(1)
  88. }
  89. dataProviderConf := config.GetProviderConf()
  90. if dataProviderConf.Driver == dataprovider.SQLiteDataProviderName || dataProviderConf.Driver == dataprovider.BoltDataProviderName {
  91. logger.Debug(logSender, connectionID, "data provider %#v not supported in subsystem mode, using %#v provider",
  92. dataProviderConf.Driver, dataprovider.MemoryDataProviderName)
  93. dataProviderConf.Driver = dataprovider.MemoryDataProviderName
  94. dataProviderConf.Name = ""
  95. dataProviderConf.PreferDatabaseCredentials = true
  96. }
  97. config.SetProviderConf(dataProviderConf)
  98. err = dataprovider.Initialize(dataProviderConf, configDir, false)
  99. if err != nil {
  100. logger.Error(logSender, connectionID, "unable to initialize the data provider: %v", err)
  101. os.Exit(1)
  102. }
  103. httpConfig := config.GetHTTPConfig()
  104. if err := httpConfig.Initialize(configDir); err != nil {
  105. logger.Error(logSender, connectionID, "unable to initialize http client: %v", err)
  106. os.Exit(1)
  107. }
  108. user, err := dataprovider.UserExists(username)
  109. if err == nil {
  110. if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {
  111. // update the user
  112. user.HomeDir = filepath.Clean(homedir)
  113. err = dataprovider.UpdateUser(&user)
  114. if err != nil {
  115. logger.Error(logSender, connectionID, "unable to update user %#v: %v", username, err)
  116. os.Exit(1)
  117. }
  118. }
  119. } else {
  120. user.Username = username
  121. if baseHomeDir != "" && filepath.IsAbs(baseHomeDir) {
  122. user.HomeDir = filepath.Join(baseHomeDir, username)
  123. } else {
  124. user.HomeDir = filepath.Clean(homedir)
  125. }
  126. logger.Debug(logSender, connectionID, "home dir for new user %#v", user.HomeDir)
  127. user.Password = connectionID
  128. user.Permissions = make(map[string][]string)
  129. user.Permissions["/"] = []string{dataprovider.PermAny}
  130. err = dataprovider.AddUser(&user)
  131. if err != nil {
  132. logger.Error(logSender, connectionID, "unable to add user %#v: %v", username, err)
  133. os.Exit(1)
  134. }
  135. }
  136. err = sftpd.ServeSubSystemConnection(&user, connectionID, os.Stdin, os.Stdout)
  137. if err != nil && err != io.EOF {
  138. logger.Warn(logSender, connectionID, "serving subsystem finished with error: %v", err)
  139. os.Exit(1)
  140. }
  141. logger.Info(logSender, connectionID, "serving subsystem finished")
  142. plugin.Handler.Cleanup()
  143. os.Exit(0)
  144. },
  145. }
  146. )
  147. func init() {
  148. subsystemCmd.Flags().BoolVarP(&preserveHomeDir, "preserve-home", "p", false, `If the user already exists, the existing home
  149. directory will not be changed`)
  150. subsystemCmd.Flags().StringVarP(&baseHomeDir, "base-home-dir", "d", "", `If the user does not exist specify an alternate
  151. starting directory. The home directory for a new
  152. user will be:
  153. [base-home-dir]/[username]
  154. base-home-dir must be an absolute path.`)
  155. subsystemCmd.Flags().BoolVarP(&logJournalD, "log-to-journald", "j", false, `Send logs to journald. Only available on Linux.
  156. Use:
  157. $ journalctl -o verbose -f
  158. To see full logs.
  159. If not set, the logs will be sent to the standard
  160. error`)
  161. addConfigFlags(subsystemCmd)
  162. viper.SetDefault(logVerboseKey, defaultLogVerbose)
  163. viper.BindEnv(logVerboseKey, "SFTPGO_LOG_VERBOSE") //nolint:errcheck
  164. subsystemCmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey),
  165. `Enable verbose logs. This flag can be set
  166. using SFTPGO_LOG_VERBOSE env var too.
  167. `)
  168. viper.BindPFlag(logVerboseKey, subsystemCmd.Flags().Lookup(logVerboseFlag)) //nolint:errcheck
  169. rootCmd.AddCommand(subsystemCmd)
  170. }