// Copyright (C) 2019 Nicola Murino // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // Package command provides command configuration for SFTPGo hooks package command import ( "fmt" "slices" "strings" "time" ) const ( minTimeout = 1 maxTimeout = 300 defaultTimeout = 30 ) // Supported hook names const ( HookFsActions = "fs_actions" HookProviderActions = "provider_actions" HookStartup = "startup" HookPostConnect = "post_connect" HookPostDisconnect = "post_disconnect" HookDataRetention = "data_retention" HookCheckPassword = "check_password" HookPreLogin = "pre_login" HookPostLogin = "post_login" HookExternalAuth = "external_auth" HookKeyboardInteractive = "keyboard_interactive" ) var ( config Config supportedHooks = []string{HookFsActions, HookProviderActions, HookStartup, HookPostConnect, HookPostDisconnect, HookDataRetention, HookCheckPassword, HookPreLogin, HookPostLogin, HookExternalAuth, HookKeyboardInteractive} ) // Command define the configuration for a specific commands type Command struct { // Path is the command path as defined in the hook configuration Path string `json:"path" mapstructure:"path"` // Timeout specifies a time limit, in seconds, for the command execution. // This value overrides the global timeout if set. // Do not use variables with the SFTPGO_ prefix to avoid conflicts with env // vars that SFTPGo sets Timeout int `json:"timeout" mapstructure:"timeout"` // Env defines environment variable for the command. // Each entry is of the form "key=value". // These values are added to the global environment variables if any Env []string `json:"env" mapstructure:"env"` // Args defines arguments to pass to the specified command Args []string `json:"args" mapstructure:"args"` // if not empty both command path and hook name must match Hook string `json:"hook" mapstructure:"hook"` } // Config defines the configuration for external commands such as // program based hooks type Config struct { // Timeout specifies a global time limit, in seconds, for the external commands execution Timeout int `json:"timeout" mapstructure:"timeout"` // Env defines environment variable for the commands. // Each entry is of the form "key=value". // Do not use variables with the SFTPGO_ prefix to avoid conflicts with env // vars that SFTPGo sets Env []string `json:"env" mapstructure:"env"` // Commands defines configuration for specific commands Commands []Command `json:"commands" mapstructure:"commands"` } func init() { config = Config{ Timeout: defaultTimeout, } } // Initialize configures commands func (c Config) Initialize() error { if c.Timeout < minTimeout || c.Timeout > maxTimeout { return fmt.Errorf("invalid timeout %v", c.Timeout) } for _, env := range c.Env { if len(strings.SplitN(env, "=", 2)) != 2 { return fmt.Errorf("invalid env var %q", env) } } for idx, cmd := range c.Commands { if cmd.Path == "" { return fmt.Errorf("invalid path %q", cmd.Path) } if cmd.Timeout == 0 { c.Commands[idx].Timeout = c.Timeout } else { if cmd.Timeout < minTimeout || cmd.Timeout > maxTimeout { return fmt.Errorf("invalid timeout %v for command %q", cmd.Timeout, cmd.Path) } } for _, env := range cmd.Env { if len(strings.SplitN(env, "=", 2)) != 2 { return fmt.Errorf("invalid env var %q for command %q", env, cmd.Path) } } // don't validate args, we allow to pass empty arguments if cmd.Hook != "" { if !slices.Contains(supportedHooks, cmd.Hook) { return fmt.Errorf("invalid hook name %q, supported values: %+v", cmd.Hook, supportedHooks) } } } config = c return nil } // GetConfig returns the configuration for the specified command func GetConfig(command, hook string) (time.Duration, []string, []string) { env := []string{} var args []string timeout := time.Duration(config.Timeout) * time.Second env = append(env, config.Env...) for _, cmd := range config.Commands { if cmd.Path == command { if cmd.Hook == "" || cmd.Hook == hook { timeout = time.Duration(cmd.Timeout) * time.Second env = append(env, cmd.Env...) args = cmd.Args break } } } return timeout, env, args }