2019-10-07 16:19:01 +00:00
|
|
|
// Package httpd implements REST API and Web interface for SFTPGo.
|
|
|
|
// REST API allows to manage users and quota and to get real time reports for the active connections
|
|
|
|
// with possibility of forcibly closing a connection.
|
|
|
|
// The OpenAPI 3 schema for the exposed API can be found inside the source tree:
|
2020-12-23 15:12:30 +00:00
|
|
|
// https://github.com/drakkan/sftpgo/blob/master/httpd/schema/openapi.yaml
|
2019-10-07 16:19:01 +00:00
|
|
|
// A basic Web interface to manage users and connections is provided too
|
|
|
|
package httpd
|
|
|
|
|
|
|
|
import (
|
2020-02-04 22:21:33 +00:00
|
|
|
"crypto/tls"
|
2019-10-07 16:19:01 +00:00
|
|
|
"fmt"
|
2021-01-03 09:38:28 +00:00
|
|
|
"log"
|
2019-10-07 16:19:01 +00:00
|
|
|
"net/http"
|
|
|
|
"path/filepath"
|
2020-12-29 18:02:56 +00:00
|
|
|
"runtime"
|
2019-10-07 16:19:01 +00:00
|
|
|
"time"
|
|
|
|
|
2020-05-06 17:36:34 +00:00
|
|
|
"github.com/go-chi/chi"
|
|
|
|
|
2020-07-24 21:39:38 +00:00
|
|
|
"github.com/drakkan/sftpgo/common"
|
2020-12-08 10:18:34 +00:00
|
|
|
"github.com/drakkan/sftpgo/dataprovider"
|
|
|
|
"github.com/drakkan/sftpgo/ftpd"
|
2019-10-07 16:19:01 +00:00
|
|
|
"github.com/drakkan/sftpgo/logger"
|
2020-12-08 10:18:34 +00:00
|
|
|
"github.com/drakkan/sftpgo/sftpd"
|
2020-03-03 08:09:58 +00:00
|
|
|
"github.com/drakkan/sftpgo/utils"
|
2020-12-08 10:18:34 +00:00
|
|
|
"github.com/drakkan/sftpgo/webdavd"
|
2019-10-07 16:19:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-06-20 10:38:04 +00:00
|
|
|
logSender = "httpd"
|
|
|
|
apiPrefix = "/api/v1"
|
|
|
|
activeConnectionsPath = "/api/v1/connection"
|
|
|
|
quotaScanPath = "/api/v1/quota_scan"
|
|
|
|
quotaScanVFolderPath = "/api/v1/folder_quota_scan"
|
|
|
|
userPath = "/api/v1/user"
|
|
|
|
versionPath = "/api/v1/version"
|
|
|
|
folderPath = "/api/v1/folder"
|
2020-12-08 10:18:34 +00:00
|
|
|
serverStatusPath = "/api/v1/status"
|
2020-06-20 10:38:04 +00:00
|
|
|
dumpDataPath = "/api/v1/dumpdata"
|
|
|
|
loadDataPath = "/api/v1/loaddata"
|
|
|
|
updateUsedQuotaPath = "/api/v1/quota_update"
|
|
|
|
updateFolderUsedQuotaPath = "/api/v1/folder_quota_update"
|
2021-01-02 18:33:24 +00:00
|
|
|
defenderBanTime = "/api/v1/defender/ban_time"
|
|
|
|
defenderUnban = "/api/v1/defender/unban"
|
|
|
|
defenderScore = "/api/v1/defender/score"
|
2020-06-20 10:38:04 +00:00
|
|
|
metricsPath = "/metrics"
|
|
|
|
webBasePath = "/web"
|
|
|
|
webUsersPath = "/web/users"
|
|
|
|
webUserPath = "/web/user"
|
|
|
|
webConnectionsPath = "/web/connections"
|
|
|
|
webFoldersPath = "/web/folders"
|
|
|
|
webFolderPath = "/web/folder"
|
2020-12-08 10:18:34 +00:00
|
|
|
webStatusPath = "/web/status"
|
2020-06-20 10:38:04 +00:00
|
|
|
webStaticFilesPath = "/static"
|
2020-10-20 16:42:37 +00:00
|
|
|
// MaxRestoreSize defines the max size for the loaddata input file
|
|
|
|
MaxRestoreSize = 10485760 // 10 MB
|
|
|
|
maxRequestSize = 1048576 // 1MB
|
2020-12-29 18:02:56 +00:00
|
|
|
osWindows = "windows"
|
2019-10-07 16:19:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-07-08 17:59:31 +00:00
|
|
|
router *chi.Mux
|
|
|
|
backupsPath string
|
2020-12-18 15:04:42 +00:00
|
|
|
httpAuth common.HTTPAuthProvider
|
2020-07-24 21:39:38 +00:00
|
|
|
certMgr *common.CertManager
|
2019-10-07 16:19:01 +00:00
|
|
|
)
|
|
|
|
|
2021-01-02 18:33:24 +00:00
|
|
|
type defenderStatus struct {
|
|
|
|
IsActive bool `json:"is_active"`
|
|
|
|
}
|
|
|
|
|
2020-12-08 10:18:34 +00:00
|
|
|
// ServicesStatus keep the state of the running services
|
|
|
|
type ServicesStatus struct {
|
|
|
|
SSH sftpd.ServiceStatus `json:"ssh"`
|
|
|
|
FTP ftpd.ServiceStatus `json:"ftp"`
|
|
|
|
WebDAV webdavd.ServiceStatus `json:"webdav"`
|
|
|
|
DataProvider dataprovider.ProviderStatus `json:"data_provider"`
|
2021-01-02 18:33:24 +00:00
|
|
|
Defender defenderStatus `json:"defender"`
|
2020-12-08 10:18:34 +00:00
|
|
|
}
|
|
|
|
|
2019-10-07 16:19:01 +00:00
|
|
|
// Conf httpd daemon configuration
|
|
|
|
type Conf struct {
|
|
|
|
// The port used for serving HTTP requests. 0 disable the HTTP server. Default: 8080
|
|
|
|
BindPort int `json:"bind_port" mapstructure:"bind_port"`
|
|
|
|
// The address to listen on. A blank value means listen on all available network interfaces. Default: "127.0.0.1"
|
|
|
|
BindAddress string `json:"bind_address" mapstructure:"bind_address"`
|
|
|
|
// Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
|
|
|
TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
|
2020-06-18 21:53:38 +00:00
|
|
|
// Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
|
|
|
|
// If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
|
2019-10-07 16:19:01 +00:00
|
|
|
StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
|
2019-12-27 22:12:44 +00:00
|
|
|
// Path to the backup directory. This can be an absolute path or a path relative to the config dir
|
|
|
|
BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
|
2020-02-03 23:08:00 +00:00
|
|
|
// Path to a file used to store usernames and password for basic authentication.
|
|
|
|
// This can be an absolute path or a path relative to the config dir.
|
|
|
|
// We support HTTP basic authentication and the file format must conform to the one generated using the Apache
|
|
|
|
// htpasswd tool. The supported password formats are bcrypt ($2y$ prefix) and md5 crypt ($apr1$ prefix).
|
|
|
|
// If empty HTTP authentication is disabled
|
|
|
|
AuthUserFile string `json:"auth_user_file" mapstructure:"auth_user_file"`
|
|
|
|
// If files containing a certificate and matching private key for the server are provided the server will expect
|
2020-02-04 22:21:33 +00:00
|
|
|
// HTTPS connections.
|
|
|
|
// Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
|
|
|
|
// "paramchange" request to the running service on Windows.
|
2020-02-03 23:08:00 +00:00
|
|
|
CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
|
|
|
|
CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
|
2019-12-27 22:12:44 +00:00
|
|
|
}
|
|
|
|
|
2019-10-07 16:19:01 +00:00
|
|
|
type apiResponse struct {
|
2020-09-08 07:45:21 +00:00
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
Message string `json:"message"`
|
2019-10-07 16:19:01 +00:00
|
|
|
}
|
|
|
|
|
2020-12-29 18:02:56 +00:00
|
|
|
// ShouldBind returns true if there service must be started
|
|
|
|
func (c Conf) ShouldBind() bool {
|
|
|
|
if c.BindPort > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if filepath.IsAbs(c.BindAddress) && runtime.GOOS != osWindows {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-26 21:29:09 +00:00
|
|
|
// Initialize configures and starts the HTTP server
|
2020-12-18 08:47:22 +00:00
|
|
|
func (c Conf) Initialize(configDir string) error {
|
2020-02-03 23:08:00 +00:00
|
|
|
var err error
|
2019-10-07 16:19:01 +00:00
|
|
|
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
2020-02-03 23:08:00 +00:00
|
|
|
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
|
|
|
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
|
|
|
templatesPath := getConfigPath(c.TemplatesPath, configDir)
|
2020-06-18 21:53:38 +00:00
|
|
|
enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
|
|
|
|
if len(backupsPath) == 0 {
|
|
|
|
return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
|
|
|
|
}
|
|
|
|
if enableWebAdmin && (len(staticFilesPath) == 0 || len(templatesPath) == 0) {
|
|
|
|
return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
|
|
|
|
staticFilesPath, templatesPath)
|
2020-03-03 08:09:58 +00:00
|
|
|
}
|
2020-02-03 23:08:00 +00:00
|
|
|
authUserFile := getConfigPath(c.AuthUserFile, configDir)
|
2020-12-18 15:04:42 +00:00
|
|
|
httpAuth, err = common.NewBasicAuthProvider(authUserFile)
|
2020-02-03 23:08:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-10-07 16:19:01 +00:00
|
|
|
}
|
2020-02-03 23:08:00 +00:00
|
|
|
certificateFile := getConfigPath(c.CertificateFile, configDir)
|
|
|
|
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
|
2020-06-18 21:53:38 +00:00
|
|
|
if enableWebAdmin {
|
|
|
|
loadTemplates(templatesPath)
|
|
|
|
} else {
|
|
|
|
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
|
|
|
|
}
|
2020-12-18 08:47:22 +00:00
|
|
|
initializeRouter(staticFilesPath, enableWebAdmin)
|
2019-10-07 16:19:01 +00:00
|
|
|
httpServer := &http.Server{
|
|
|
|
Handler: router,
|
2020-02-03 23:08:00 +00:00
|
|
|
ReadTimeout: 60 * time.Second,
|
|
|
|
WriteTimeout: 60 * time.Second,
|
2020-02-23 10:30:26 +00:00
|
|
|
IdleTimeout: 120 * time.Second,
|
2019-11-14 17:48:01 +00:00
|
|
|
MaxHeaderBytes: 1 << 16, // 64KB
|
2021-01-03 09:38:28 +00:00
|
|
|
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
2019-10-07 16:19:01 +00:00
|
|
|
}
|
2020-12-18 15:04:42 +00:00
|
|
|
if certificateFile != "" && certificateKeyFile != "" {
|
2021-01-03 16:03:04 +00:00
|
|
|
certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
2020-02-04 22:21:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
config := &tls.Config{
|
|
|
|
GetCertificate: certMgr.GetCertificateFunc(),
|
2020-08-15 11:02:25 +00:00
|
|
|
MinVersion: tls.VersionTLS12,
|
2020-02-04 22:21:33 +00:00
|
|
|
}
|
|
|
|
httpServer.TLSConfig = config
|
2021-01-03 08:51:54 +00:00
|
|
|
return utils.HTTPListenAndServe(httpServer, c.BindAddress, c.BindPort, true, logSender)
|
2020-02-03 23:08:00 +00:00
|
|
|
}
|
2021-01-03 08:51:54 +00:00
|
|
|
return utils.HTTPListenAndServe(httpServer, c.BindAddress, c.BindPort, false, logSender)
|
2019-10-07 16:19:01 +00:00
|
|
|
}
|
2020-02-03 23:08:00 +00:00
|
|
|
|
2021-01-03 16:03:04 +00:00
|
|
|
// ReloadCertificateMgr reloads the certificate manager
|
|
|
|
func ReloadCertificateMgr() error {
|
2020-02-04 22:21:33 +00:00
|
|
|
if certMgr != nil {
|
2021-01-03 16:03:04 +00:00
|
|
|
return certMgr.Reload()
|
2020-02-04 22:21:33 +00:00
|
|
|
}
|
2020-04-30 12:23:55 +00:00
|
|
|
return nil
|
2020-02-04 22:21:33 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 23:08:00 +00:00
|
|
|
func getConfigPath(name, configDir string) string {
|
2020-03-03 08:09:58 +00:00
|
|
|
if !utils.IsFileInputValid(name) {
|
|
|
|
return ""
|
|
|
|
}
|
2020-12-18 15:04:42 +00:00
|
|
|
if name != "" && !filepath.IsAbs(name) {
|
2020-02-03 23:08:00 +00:00
|
|
|
return filepath.Join(configDir, name)
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
2020-12-08 10:18:34 +00:00
|
|
|
|
|
|
|
func getServicesStatus() ServicesStatus {
|
|
|
|
status := ServicesStatus{
|
|
|
|
SSH: sftpd.GetStatus(),
|
|
|
|
FTP: ftpd.GetStatus(),
|
|
|
|
WebDAV: webdavd.GetStatus(),
|
|
|
|
DataProvider: dataprovider.GetProviderStatus(),
|
2021-01-02 18:33:24 +00:00
|
|
|
Defender: defenderStatus{
|
|
|
|
IsActive: common.Config.DefenderConfig.Enabled,
|
|
|
|
},
|
2020-12-08 10:18:34 +00:00
|
|
|
}
|
|
|
|
return status
|
|
|
|
}
|