startsubsys.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Copyright (C) 2019 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. package cmd
  15. import (
  16. "io"
  17. "os"
  18. "os/user"
  19. "path/filepath"
  20. "github.com/rs/xid"
  21. "github.com/rs/zerolog"
  22. "github.com/spf13/cobra"
  23. "github.com/spf13/viper"
  24. "github.com/drakkan/sftpgo/v2/internal/common"
  25. "github.com/drakkan/sftpgo/v2/internal/config"
  26. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  27. "github.com/drakkan/sftpgo/v2/internal/logger"
  28. "github.com/drakkan/sftpgo/v2/internal/plugin"
  29. "github.com/drakkan/sftpgo/v2/internal/sftpd"
  30. "github.com/drakkan/sftpgo/v2/internal/version"
  31. )
  32. var (
  33. logJournalD = false
  34. preserveHomeDir = false
  35. baseHomeDir = ""
  36. subsystemCmd = &cobra.Command{
  37. Use: "startsubsys",
  38. Short: "Use sftpgo as SFTP file transfer subsystem",
  39. Long: `In this mode SFTPGo speaks the server side of SFTP protocol to stdout and
  40. expects client requests from stdin.
  41. This mode is not intended to be called directly, but from sshd using the
  42. Subsystem option.
  43. For example adding a line like this one in "/etc/ssh/sshd_config":
  44. Subsystem sftp sftpgo startsubsys
  45. Command-line flags should be specified in the Subsystem declaration.
  46. `,
  47. Run: func(_ *cobra.Command, _ []string) {
  48. logSender := "startsubsys"
  49. connectionID := xid.New().String()
  50. var zeroLogLevel zerolog.Level
  51. switch logLevel {
  52. case "info":
  53. zeroLogLevel = zerolog.InfoLevel
  54. case "warn":
  55. zeroLogLevel = zerolog.WarnLevel
  56. case "error":
  57. zeroLogLevel = zerolog.ErrorLevel
  58. default:
  59. zeroLogLevel = zerolog.DebugLevel
  60. }
  61. logger.SetLogTime(logUTCTime)
  62. if logJournalD {
  63. logger.InitJournalDLogger(zeroLogLevel)
  64. } else {
  65. logger.InitStdErrLogger(zeroLogLevel)
  66. }
  67. osUser, err := user.Current()
  68. if err != nil {
  69. logger.Error(logSender, connectionID, "unable to get the current user: %v", err)
  70. os.Exit(1)
  71. }
  72. username := osUser.Username
  73. homedir := osUser.HomeDir
  74. logger.Info(logSender, connectionID, "starting SFTPGo %v as subsystem, user %q home dir %q config dir %q base home dir %q",
  75. version.Get(), username, homedir, configDir, baseHomeDir)
  76. err = config.LoadConfig(configDir, configFile)
  77. if err != nil {
  78. logger.Error(logSender, connectionID, "unable to load configuration: %v", err)
  79. os.Exit(1)
  80. }
  81. kmsConfig := config.GetKMSConfig()
  82. if err := kmsConfig.Initialize(); err != nil {
  83. logger.Error(logSender, connectionID, "unable to initialize KMS: %v", err)
  84. os.Exit(1)
  85. }
  86. mfaConfig := config.GetMFAConfig()
  87. err = mfaConfig.Initialize()
  88. if err != nil {
  89. logger.Error(logSender, "", "unable to initialize MFA: %v", err)
  90. os.Exit(1)
  91. }
  92. dataProviderConf := config.GetProviderConf()
  93. if dataProviderConf.Driver == dataprovider.SQLiteDataProviderName || dataProviderConf.Driver == dataprovider.BoltDataProviderName {
  94. logger.Debug(logSender, connectionID, "data provider %q not supported in subsystem mode, using %q provider",
  95. dataProviderConf.Driver, dataprovider.MemoryDataProviderName)
  96. dataProviderConf.Driver = dataprovider.MemoryDataProviderName
  97. dataProviderConf.Name = ""
  98. }
  99. config.SetProviderConf(dataProviderConf)
  100. err = dataprovider.Initialize(dataProviderConf, configDir, false)
  101. if err != nil {
  102. logger.Error(logSender, connectionID, "unable to initialize the data provider: %v", err)
  103. os.Exit(1)
  104. }
  105. if err := plugin.Initialize(config.GetPluginsConfig(), logLevel); err != nil {
  106. logger.Error(logSender, connectionID, "unable to initialize plugin system: %v", err)
  107. os.Exit(1)
  108. }
  109. smtpConfig := config.GetSMTPConfig()
  110. err = smtpConfig.Initialize(configDir, false)
  111. if err != nil {
  112. logger.Error(logSender, connectionID, "unable to initialize SMTP configuration: %v", err)
  113. os.Exit(1)
  114. }
  115. commonConfig := config.GetCommonConfig()
  116. // idle connection are managed externally
  117. commonConfig.IdleTimeout = 0
  118. config.SetCommonConfig(commonConfig)
  119. if err := common.Initialize(config.GetCommonConfig(), dataProviderConf.GetShared()); err != nil {
  120. logger.Error(logSender, connectionID, "%v", err)
  121. os.Exit(1)
  122. }
  123. httpConfig := config.GetHTTPConfig()
  124. if err := httpConfig.Initialize(configDir); err != nil {
  125. logger.Error(logSender, connectionID, "unable to initialize http client: %v", err)
  126. os.Exit(1)
  127. }
  128. commandConfig := config.GetCommandConfig()
  129. if err := commandConfig.Initialize(); err != nil {
  130. logger.Error(logSender, connectionID, "unable to initialize commands configuration: %v", err)
  131. os.Exit(1)
  132. }
  133. user, err := dataprovider.UserExists(username, "")
  134. if err == nil {
  135. if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {
  136. // update the user
  137. user.HomeDir = filepath.Clean(homedir)
  138. err = dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSystem, "", "")
  139. if err != nil {
  140. logger.Error(logSender, connectionID, "unable to update user %q: %v", username, err)
  141. os.Exit(1)
  142. }
  143. }
  144. } else {
  145. user.Username = username
  146. if baseHomeDir != "" && filepath.IsAbs(baseHomeDir) {
  147. user.HomeDir = filepath.Join(baseHomeDir, username)
  148. } else {
  149. user.HomeDir = filepath.Clean(homedir)
  150. }
  151. logger.Debug(logSender, connectionID, "home dir for new user %q", user.HomeDir)
  152. user.Password = connectionID
  153. user.Permissions = make(map[string][]string)
  154. user.Permissions["/"] = []string{dataprovider.PermAny}
  155. err = dataprovider.AddUser(&user, dataprovider.ActionExecutorSystem, "", "")
  156. if err != nil {
  157. logger.Error(logSender, connectionID, "unable to add user %q: %v", username, err)
  158. os.Exit(1)
  159. }
  160. }
  161. err = user.LoadAndApplyGroupSettings()
  162. if err != nil {
  163. logger.Error(logSender, connectionID, "unable to apply group settings for user %q: %v", username, err)
  164. os.Exit(1)
  165. }
  166. err = sftpd.ServeSubSystemConnection(&user, connectionID, os.Stdin, os.Stdout)
  167. if err != nil && err != io.EOF {
  168. logger.Warn(logSender, connectionID, "serving subsystem finished with error: %v", err)
  169. os.Exit(1)
  170. }
  171. logger.Info(logSender, connectionID, "serving subsystem finished")
  172. plugin.Handler.Cleanup()
  173. os.Exit(0)
  174. },
  175. }
  176. )
  177. func init() {
  178. subsystemCmd.Flags().BoolVarP(&preserveHomeDir, "preserve-home", "p", false, `If the user already exists, the existing home
  179. directory will not be changed`)
  180. subsystemCmd.Flags().StringVarP(&baseHomeDir, "base-home-dir", "d", "", `If the user does not exist specify an alternate
  181. starting directory. The home directory for a new
  182. user will be:
  183. [base-home-dir]/[username]
  184. base-home-dir must be an absolute path.`)
  185. subsystemCmd.Flags().BoolVarP(&logJournalD, "log-to-journald", "j", false, `Send logs to journald. Only available on Linux.
  186. Use:
  187. $ journalctl -o verbose -f
  188. To see full logs.
  189. If not set, the logs will be sent to the standard
  190. error`)
  191. addConfigFlags(subsystemCmd)
  192. viper.SetDefault(logLevelKey, defaultLogLevel)
  193. viper.BindEnv(logLevelKey, "SFTPGO_LOG_LEVEL") //nolint:errcheck
  194. subsystemCmd.Flags().StringVar(&logLevel, logLevelFlag, viper.GetString(logLevelKey),
  195. `Set the log level. Supported values:
  196. debug, info, warn, error.
  197. This flag can be set
  198. using SFTPGO_LOG_LEVEL env var too.
  199. `)
  200. viper.BindPFlag(logLevelKey, subsystemCmd.Flags().Lookup(logLevelFlag)) //nolint:errcheck
  201. viper.SetDefault(logUTCTimeKey, defaultLogUTCTime)
  202. viper.BindEnv(logUTCTimeKey, "SFTPGO_LOG_UTC_TIME") //nolint:errcheck
  203. subsystemCmd.Flags().BoolVar(&logUTCTime, logUTCTimeFlag, viper.GetBool(logUTCTimeKey),
  204. `Use UTC time for logging. This flag can be set
  205. using SFTPGO_LOG_UTC_TIME env var too.
  206. `)
  207. viper.BindPFlag(logUTCTimeKey, subsystemCmd.Flags().Lookup(logUTCTimeFlag)) //nolint:errcheck
  208. rootCmd.AddCommand(subsystemCmd)
  209. }