diff --git a/cmd/stbak/cmd/serve_ftp.go b/cmd/stbak/cmd/serve_ftp.go new file mode 100644 index 0000000..2fa9a38 --- /dev/null +++ b/cmd/stbak/cmd/serve_ftp.go @@ -0,0 +1,153 @@ +package cmd + +import ( + "context" + "log" + "time" + + ftpserver "github.com/fclairamb/ftpserverlib" + sfs "github.com/pojntfx/stfs/internal/fs" + "github.com/pojntfx/stfs/internal/ftp" + "github.com/pojntfx/stfs/internal/keys" + "github.com/pojntfx/stfs/internal/logging" + "github.com/pojntfx/stfs/internal/persisters" + "github.com/pojntfx/stfs/pkg/config" + "github.com/pojntfx/stfs/pkg/operations" + "github.com/pojntfx/stfs/pkg/tape" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var serveFTPCmd = &cobra.Command{ + Use: "ftp", + Aliases: []string{"f"}, + Short: "Serve tape or tar file and the index over FTP (read-write)", + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if err := keys.CheckKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)); err != nil { + return err + } + + return keys.CheckKeyAccessible(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + }, + RunE: func(cmd *cobra.Command, args []string) error { + pubkey, err := keys.ReadKey(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + if err != nil { + return err + } + + recipient, err := keys.ParseSignerRecipient(viper.GetString(signatureFlag), pubkey) + if err != nil { + return err + } + + privkey, err := keys.ReadKey(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) + if err != nil { + return err + } + + identity, err := keys.ParseIdentity(viper.GetString(encryptionFlag), privkey, viper.GetString(passwordFlag)) + if err != nil { + return err + } + + tm := tape.NewTapeManager( + viper.GetString(driveFlag), + viper.GetInt(recordSizeFlag), + false, + ) + + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) + if err := metadataPersister.Open(); err != nil { + return err + } + + root, err := metadataPersister.GetRootPath(context.Background()) + if err != nil { + return err + } + + logger := logging.NewLogger() + + ops := operations.NewOperations( + config.BackendConfig{ + GetWriter: tm.GetWriter, + CloseWriter: tm.Close, + + GetReader: tm.GetReader, + CloseReader: tm.Close, + + GetDrive: tm.GetDrive, + CloseDrive: tm.Close, + }, + config.MetadataConfig{ + Metadata: metadataPersister, + }, + + config.PipeConfig{ + Compression: viper.GetString(compressionFlag), + Encryption: viper.GetString(encryptionFlag), + Signature: viper.GetString(signatureFlag), + RecordSize: viper.GetInt(recordSizeFlag), + }, + config.CryptoConfig{ + Recipient: recipient, + Identity: identity, + Password: viper.GetString(passwordFlag), + }, + + logger.PrintHeaderEvent, + ) + + stfs := sfs.NewFileSystem( + ops, + + config.MetadataConfig{ + Metadata: metadataPersister, + }, + + logger.PrintHeader, + ) + + var fs afero.Fs + if viper.GetBool(cacheFlag) { + fs = afero.NewCacheOnReadFs(afero.NewBasePathFs(stfs, root), afero.NewMemMapFs(), time.Hour) + } else { + fs = afero.NewBasePathFs(stfs, root) + } + + srv := ftpserver.NewFtpServer( + &ftp.FTPServer{ + Settings: &ftpserver.Settings{ + ListenAddr: viper.GetString(laddrFlag), + }, + FileSystem: fs, + }, + ) + + if viper.GetBool(verboseFlag) { + srv.Logger = &ftp.Logger{} + } + + log.Println("Listening on", viper.GetString(laddrFlag)) + + return srv.ListenAndServe() + }, +} + +func init() { + serveFTPCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") + serveFTPCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") + serveFTPCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + serveFTPCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to the public key to verify with") + serveFTPCmd.PersistentFlags().StringP(laddrFlag, "a", "localhost:1337", "Listen address") + serveFTPCmd.PersistentFlags().BoolP(cacheFlag, "n", true, "Enable in-memory caching") + + viper.AutomaticEnv() + + serveCmd.AddCommand(serveFTPCmd) +} diff --git a/cmd/stbak/cmd/serve_http.go b/cmd/stbak/cmd/serve_http.go index 30946ca..e0f5930 100644 --- a/cmd/stbak/cmd/serve_http.go +++ b/cmd/stbak/cmd/serve_http.go @@ -135,7 +135,6 @@ var serveHTTPCmd = &cobra.Command{ ), ), ) - }, } diff --git a/cmd/stftp/main.go b/cmd/stftp/main.go deleted file mode 100644 index d8d2e7d..0000000 --- a/cmd/stftp/main.go +++ /dev/null @@ -1,184 +0,0 @@ -package main - -import ( - "crypto/tls" - "errors" - "flag" - "log" - "os" - "path/filepath" - "sync" - "time" - - golog "github.com/fclairamb/go-log" - - ftpserver "github.com/fclairamb/ftpserverlib" - "github.com/pojntfx/stfs/internal/fs" - "github.com/pojntfx/stfs/internal/logging" - "github.com/pojntfx/stfs/internal/persisters" - "github.com/pojntfx/stfs/pkg/config" - "github.com/pojntfx/stfs/pkg/operations" - "github.com/pojntfx/stfs/pkg/tape" - "github.com/spf13/afero" -) - -var ( - errNoTLS = errors.New("no TLS supported") -) - -func main() { - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - laddr := flag.String("laddr", "localhost:1337", "Listen address") - dir := flag.String("dir", "/", "Directory to use as the root directory") - drive := flag.String("drive", "/dev/nst0", "Tape or tar file to use") - metadata := flag.String("metadata", filepath.Join(home, ".local", "share", "stbak", "var", "lib", "stbak", "metadata.sqlite"), "Metadata database to use") - recordSize := flag.Int("recordSize", 20, "Amount of 512-bit blocks per record") - enableCache := flag.Bool("cache", true, "Enable in-memory caching") - - flag.Parse() - - tm := tape.NewTapeManager( - *drive, - *recordSize, - false, - ) - - metadataPersister := persisters.NewMetadataPersister(*metadata) - if err := metadataPersister.Open(); err != nil { - panic(err) - } - - logger := logging.NewLogger() - - ops := operations.NewOperations( - config.BackendConfig{ - GetWriter: tm.GetWriter, - CloseWriter: tm.Close, - - GetReader: tm.GetReader, - CloseReader: tm.Close, - - GetDrive: tm.GetDrive, - CloseDrive: tm.Close, - }, - config.MetadataConfig{ - Metadata: metadataPersister, - }, - - config.PipeConfig{ - Compression: config.NoneKey, - Encryption: config.NoneKey, - Signature: config.NoneKey, - RecordSize: *recordSize, - }, - config.CryptoConfig{ - Recipient: []byte{}, - Identity: []byte{}, - Password: "", - }, - - logger.PrintHeaderEvent, - ) - - stfs := fs.NewFileSystem( - ops, - - config.MetadataConfig{ - Metadata: metadataPersister, - }, - - logger.PrintHeader, - ) - - var fs afero.Fs - if *enableCache { - fs = afero.NewCacheOnReadFs(afero.NewBasePathFs(stfs, *dir), afero.NewMemMapFs(), time.Hour) - } else { - fs = afero.NewBasePathFs(stfs, *dir) - } - - srv := ftpserver.NewFtpServer( - &FTPServer{ - Settings: &ftpserver.Settings{ - ListenAddr: *laddr, - }, - FileSystem: fs, - }, - ) - srv.Logger = &Logger{} - - log.Println("Listening on", *laddr) - - panic(srv.ListenAndServe()) -} - -type FTPServer struct { - Settings *ftpserver.Settings - FileSystem afero.Fs - - clientsLock sync.Mutex - clients []ftpserver.ClientContext -} - -func (driver *FTPServer) GetSettings() (*ftpserver.Settings, error) { - return driver.Settings, nil -} - -func (driver *FTPServer) GetTLSConfig() (*tls.Config, error) { - return nil, errNoTLS -} - -func (driver *FTPServer) ClientConnected(cc ftpserver.ClientContext) (string, error) { - driver.clientsLock.Lock() - defer driver.clientsLock.Unlock() - - driver.clients = append(driver.clients, cc) - - return "", nil -} - -func (driver *FTPServer) ClientDisconnected(cc ftpserver.ClientContext) { - driver.clientsLock.Lock() - defer driver.clientsLock.Unlock() - - for idx, client := range driver.clients { - if client.ID() == cc.ID() { - lastIdx := len(driver.clients) - 1 - driver.clients[idx] = driver.clients[lastIdx] - driver.clients[lastIdx] = nil - driver.clients = driver.clients[:lastIdx] - - return - } - } -} - -func (driver *FTPServer) AuthUser(_ ftpserver.ClientContext, user, pass string) (ftpserver.ClientDriver, error) { - return driver.FileSystem, nil -} - -type Logger struct{} - -func (l Logger) Debug(event string, keyvals ...interface{}) { - log.Println(event, keyvals) -} - -func (l Logger) Info(event string, keyvals ...interface{}) { - log.Println(event, keyvals) -} - -func (l Logger) Warn(event string, keyvals ...interface{}) { - log.Println(event, keyvals) -} - -func (l Logger) Error(event string, keyvals ...interface{}) { - log.Println(event, keyvals) -} - -func (l Logger) With(keyvals ...interface{}) golog.Logger { - return l -} diff --git a/internal/ftp/logger.go b/internal/ftp/logger.go new file mode 100644 index 0000000..afc8887 --- /dev/null +++ b/internal/ftp/logger.go @@ -0,0 +1,29 @@ +package ftp + +import ( + "log" + + golog "github.com/fclairamb/go-log" +) + +type Logger struct{} + +func (l Logger) Debug(event string, keyvals ...interface{}) { + log.Println(event, keyvals) +} + +func (l Logger) Info(event string, keyvals ...interface{}) { + log.Println(event, keyvals) +} + +func (l Logger) Warn(event string, keyvals ...interface{}) { + log.Println(event, keyvals) +} + +func (l Logger) Error(event string, keyvals ...interface{}) { + log.Println(event, keyvals) +} + +func (l Logger) With(keyvals ...interface{}) golog.Logger { + return l +} diff --git a/internal/ftp/server.go b/internal/ftp/server.go new file mode 100644 index 0000000..d973667 --- /dev/null +++ b/internal/ftp/server.go @@ -0,0 +1,59 @@ +package ftp + +import ( + "crypto/tls" + "errors" + "sync" + + ftpserver "github.com/fclairamb/ftpserverlib" + "github.com/spf13/afero" +) + +var ( + ErrNoTLS = errors.New("no TLS supported") +) + +type FTPServer struct { + Settings *ftpserver.Settings + FileSystem afero.Fs + + clientsLock sync.Mutex + clients []ftpserver.ClientContext +} + +func (driver *FTPServer) GetSettings() (*ftpserver.Settings, error) { + return driver.Settings, nil +} + +func (driver *FTPServer) GetTLSConfig() (*tls.Config, error) { + return nil, ErrNoTLS +} + +func (driver *FTPServer) ClientConnected(cc ftpserver.ClientContext) (string, error) { + driver.clientsLock.Lock() + defer driver.clientsLock.Unlock() + + driver.clients = append(driver.clients, cc) + + return "", nil +} + +func (driver *FTPServer) ClientDisconnected(cc ftpserver.ClientContext) { + driver.clientsLock.Lock() + defer driver.clientsLock.Unlock() + + for idx, client := range driver.clients { + if client.ID() == cc.ID() { + lastIdx := len(driver.clients) - 1 + driver.clients[idx] = driver.clients[lastIdx] + driver.clients[lastIdx] = nil + driver.clients = driver.clients[:lastIdx] + + return + } + } +} + +func (driver *FTPServer) AuthUser(_ ftpserver.ClientContext, user, pass string) (ftpserver.ClientDriver, error) { + return driver.FileSystem, nil +}