command.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // Copyright (C) 2019-2023 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 command provides command configuration for SFTPGo hooks
  15. package command
  16. import (
  17. "fmt"
  18. "strings"
  19. "time"
  20. "github.com/drakkan/sftpgo/v2/internal/util"
  21. )
  22. const (
  23. minTimeout = 1
  24. maxTimeout = 300
  25. defaultTimeout = 30
  26. )
  27. // Supported hook names
  28. const (
  29. HookFsActions = "fs_actions"
  30. HookProviderActions = "provider_actions"
  31. HookStartup = "startup"
  32. HookPostConnect = "post_connect"
  33. HookPostDisconnect = "post_disconnect"
  34. HookDataRetention = "data_retention"
  35. HookCheckPassword = "check_password"
  36. HookPreLogin = "pre_login"
  37. HookPostLogin = "post_login"
  38. HookExternalAuth = "external_auth"
  39. HookKeyboardInteractive = "keyboard_interactive"
  40. )
  41. var (
  42. config Config
  43. supportedHooks = []string{HookFsActions, HookProviderActions, HookStartup, HookPostConnect, HookPostDisconnect,
  44. HookDataRetention, HookCheckPassword, HookPreLogin, HookPostLogin, HookExternalAuth, HookKeyboardInteractive}
  45. )
  46. // Command define the configuration for a specific commands
  47. type Command struct {
  48. // Path is the command path as defined in the hook configuration
  49. Path string `json:"path" mapstructure:"path"`
  50. // Timeout specifies a time limit, in seconds, for the command execution.
  51. // This value overrides the global timeout if set.
  52. // Do not use variables with the SFTPGO_ prefix to avoid conflicts with env
  53. // vars that SFTPGo sets
  54. Timeout int `json:"timeout" mapstructure:"timeout"`
  55. // Env defines environment variable for the command.
  56. // Each entry is of the form "key=value".
  57. // These values are added to the global environment variables if any
  58. Env []string `json:"env" mapstructure:"env"`
  59. // Args defines arguments to pass to the specified command
  60. Args []string `json:"args" mapstructure:"args"`
  61. // if not empty both command path and hook name must match
  62. Hook string `json:"hook" mapstructure:"hook"`
  63. }
  64. // Config defines the configuration for external commands such as
  65. // program based hooks
  66. type Config struct {
  67. // Timeout specifies a global time limit, in seconds, for the external commands execution
  68. Timeout int `json:"timeout" mapstructure:"timeout"`
  69. // Env defines environment variable for the commands.
  70. // Each entry is of the form "key=value".
  71. // Do not use variables with the SFTPGO_ prefix to avoid conflicts with env
  72. // vars that SFTPGo sets
  73. Env []string `json:"env" mapstructure:"env"`
  74. // Commands defines configuration for specific commands
  75. Commands []Command `json:"commands" mapstructure:"commands"`
  76. }
  77. func init() {
  78. config = Config{
  79. Timeout: defaultTimeout,
  80. }
  81. }
  82. // Initialize configures commands
  83. func (c Config) Initialize() error {
  84. if c.Timeout < minTimeout || c.Timeout > maxTimeout {
  85. return fmt.Errorf("invalid timeout %v", c.Timeout)
  86. }
  87. for _, env := range c.Env {
  88. if len(strings.SplitN(env, "=", 2)) != 2 {
  89. return fmt.Errorf("invalid env var %q", env)
  90. }
  91. }
  92. for idx, cmd := range c.Commands {
  93. if cmd.Path == "" {
  94. return fmt.Errorf("invalid path %q", cmd.Path)
  95. }
  96. if cmd.Timeout == 0 {
  97. c.Commands[idx].Timeout = c.Timeout
  98. } else {
  99. if cmd.Timeout < minTimeout || cmd.Timeout > maxTimeout {
  100. return fmt.Errorf("invalid timeout %v for command %q", cmd.Timeout, cmd.Path)
  101. }
  102. }
  103. for _, env := range cmd.Env {
  104. if len(strings.SplitN(env, "=", 2)) != 2 {
  105. return fmt.Errorf("invalid env var %q for command %q", env, cmd.Path)
  106. }
  107. }
  108. // don't validate args, we allow to pass empty arguments
  109. if cmd.Hook != "" {
  110. if !util.Contains(supportedHooks, cmd.Hook) {
  111. return fmt.Errorf("invalid hook name %q, supported values: %+v", cmd.Hook, supportedHooks)
  112. }
  113. }
  114. }
  115. config = c
  116. return nil
  117. }
  118. // GetConfig returns the configuration for the specified command
  119. func GetConfig(command, hook string) (time.Duration, []string, []string) {
  120. env := []string{}
  121. var args []string
  122. timeout := time.Duration(config.Timeout) * time.Second
  123. env = append(env, config.Env...)
  124. for _, cmd := range config.Commands {
  125. if cmd.Path == command {
  126. if cmd.Hook == "" || cmd.Hook == hook {
  127. timeout = time.Duration(cmd.Timeout) * time.Second
  128. env = append(env, cmd.Env...)
  129. args = cmd.Args
  130. break
  131. }
  132. }
  133. }
  134. return timeout, env, args
  135. }