portable.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package cmd
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "strings"
  10. "github.com/drakkan/sftpgo/dataprovider"
  11. "github.com/drakkan/sftpgo/service"
  12. "github.com/drakkan/sftpgo/sftpd"
  13. "github.com/drakkan/sftpgo/vfs"
  14. "github.com/spf13/cobra"
  15. )
  16. var (
  17. directoryToServe string
  18. portableSFTPDPort int
  19. portableAdvertiseService bool
  20. portableAdvertiseCredentials bool
  21. portableUsername string
  22. portablePassword string
  23. portableLogFile string
  24. portablePublicKeys []string
  25. portablePermissions []string
  26. portableSSHCommands []string
  27. portableAllowedExtensions []string
  28. portableDeniedExtensions []string
  29. portableFsProvider int
  30. portableS3Bucket string
  31. portableS3Region string
  32. portableS3AccessKey string
  33. portableS3AccessSecret string
  34. portableS3Endpoint string
  35. portableS3StorageClass string
  36. portableS3KeyPrefix string
  37. portableGCSBucket string
  38. portableGCSCredentialsFile string
  39. portableGCSAutoCredentials int
  40. portableGCSStorageClass string
  41. portableGCSKeyPrefix string
  42. portableCmd = &cobra.Command{
  43. Use: "portable",
  44. Short: "Serve a single directory",
  45. Long: `To serve the current working directory with auto generated credentials simply use:
  46. sftpgo portable
  47. Please take a look at the usage below to customize the serving parameters`,
  48. Run: func(cmd *cobra.Command, args []string) {
  49. portableDir := directoryToServe
  50. if !filepath.IsAbs(portableDir) {
  51. if portableFsProvider == 0 {
  52. portableDir, _ = filepath.Abs(portableDir)
  53. } else {
  54. portableDir = os.TempDir()
  55. }
  56. }
  57. permissions := make(map[string][]string)
  58. permissions["/"] = portablePermissions
  59. portableGCSCredentials := ""
  60. if portableFsProvider == 2 && len(portableGCSCredentialsFile) > 0 {
  61. fi, err := os.Stat(portableGCSCredentialsFile)
  62. if err != nil {
  63. fmt.Printf("Invalid GCS credentials file: %v\n", err)
  64. return
  65. }
  66. if fi.Size() > 1048576 {
  67. fmt.Printf("Invalid GCS credentials file: %#v is too big %v/1048576 bytes\n", portableGCSCredentialsFile,
  68. fi.Size())
  69. return
  70. }
  71. creds, err := ioutil.ReadFile(portableGCSCredentialsFile)
  72. if err != nil {
  73. fmt.Printf("Unable to read credentials file: %v\n", err)
  74. }
  75. portableGCSCredentials = base64.StdEncoding.EncodeToString(creds)
  76. portableGCSAutoCredentials = 0
  77. }
  78. service := service.Service{
  79. ConfigDir: filepath.Clean(defaultConfigDir),
  80. ConfigFile: defaultConfigName,
  81. LogFilePath: portableLogFile,
  82. LogMaxSize: defaultLogMaxSize,
  83. LogMaxBackups: defaultLogMaxBackup,
  84. LogMaxAge: defaultLogMaxAge,
  85. LogCompress: defaultLogCompress,
  86. LogVerbose: defaultLogVerbose,
  87. Shutdown: make(chan bool),
  88. PortableMode: 1,
  89. PortableUser: dataprovider.User{
  90. Username: portableUsername,
  91. Password: portablePassword,
  92. PublicKeys: portablePublicKeys,
  93. Permissions: permissions,
  94. HomeDir: portableDir,
  95. Status: 1,
  96. FsConfig: dataprovider.Filesystem{
  97. Provider: portableFsProvider,
  98. S3Config: vfs.S3FsConfig{
  99. Bucket: portableS3Bucket,
  100. Region: portableS3Region,
  101. AccessKey: portableS3AccessKey,
  102. AccessSecret: portableS3AccessSecret,
  103. Endpoint: portableS3Endpoint,
  104. StorageClass: portableS3StorageClass,
  105. KeyPrefix: portableS3KeyPrefix,
  106. },
  107. GCSConfig: vfs.GCSFsConfig{
  108. Bucket: portableGCSBucket,
  109. Credentials: portableGCSCredentials,
  110. AutomaticCredentials: portableGCSAutoCredentials,
  111. StorageClass: portableGCSStorageClass,
  112. KeyPrefix: portableGCSKeyPrefix,
  113. },
  114. },
  115. Filters: dataprovider.UserFilters{
  116. FileExtensions: parseFileExtensionsFilters(),
  117. },
  118. },
  119. }
  120. if err := service.StartPortableMode(portableSFTPDPort, portableSSHCommands, portableAdvertiseService,
  121. portableAdvertiseCredentials); err == nil {
  122. service.Wait()
  123. }
  124. },
  125. }
  126. )
  127. func init() {
  128. portableCmd.Flags().StringVarP(&directoryToServe, "directory", "d", ".",
  129. "Path to the directory to serve. This can be an absolute path or a path relative to the current directory")
  130. portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, "0 means a random non privileged port")
  131. portableCmd.Flags().StringSliceVarP(&portableSSHCommands, "ssh-commands", "c", sftpd.GetDefaultSSHCommands(),
  132. "SSH commands to enable. \"*\" means any supported SSH command including scp")
  133. portableCmd.Flags().StringVarP(&portableUsername, "username", "u", "", "Leave empty to use an auto generated value")
  134. portableCmd.Flags().StringVarP(&portablePassword, "password", "p", "", "Leave empty to use an auto generated value")
  135. portableCmd.Flags().StringVarP(&portableLogFile, logFilePathFlag, "l", "", "Leave empty to disable logging")
  136. portableCmd.Flags().StringSliceVarP(&portablePublicKeys, "public-key", "k", []string{}, "")
  137. portableCmd.Flags().StringSliceVarP(&portablePermissions, "permissions", "g", []string{"list", "download"},
  138. "User's permissions. \"*\" means any permission")
  139. portableCmd.Flags().StringArrayVar(&portableAllowedExtensions, "allowed-extensions", []string{},
  140. "Allowed file extensions case insensitive. The format is /dir::ext1,ext2. For example: \"/somedir::.jpg,.png\"")
  141. portableCmd.Flags().StringArrayVar(&portableDeniedExtensions, "denied-extensions", []string{},
  142. "Denied file extensions case insensitive. The format is /dir::ext1,ext2. For example: \"/somedir::.jpg,.png\"")
  143. portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", true,
  144. "Advertise SFTP service using multicast DNS")
  145. portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false,
  146. "If the SFTP service is advertised via multicast DNS, this flag allows to put username/password inside the advertised TXT record")
  147. portableCmd.Flags().IntVarP(&portableFsProvider, "fs-provider", "f", 0, "0 means local filesystem, 1 Amazon S3 compatible, "+
  148. "2 Google Cloud Storage")
  149. portableCmd.Flags().StringVar(&portableS3Bucket, "s3-bucket", "", "")
  150. portableCmd.Flags().StringVar(&portableS3Region, "s3-region", "", "")
  151. portableCmd.Flags().StringVar(&portableS3AccessKey, "s3-access-key", "", "")
  152. portableCmd.Flags().StringVar(&portableS3AccessSecret, "s3-access-secret", "", "")
  153. portableCmd.Flags().StringVar(&portableS3Endpoint, "s3-endpoint", "", "")
  154. portableCmd.Flags().StringVar(&portableS3StorageClass, "s3-storage-class", "", "")
  155. portableCmd.Flags().StringVar(&portableS3KeyPrefix, "s3-key-prefix", "", "Allows to restrict access to the virtual folder "+
  156. "identified by this prefix and its contents")
  157. portableCmd.Flags().StringVar(&portableGCSBucket, "gcs-bucket", "", "")
  158. portableCmd.Flags().StringVar(&portableGCSStorageClass, "gcs-storage-class", "", "")
  159. portableCmd.Flags().StringVar(&portableGCSKeyPrefix, "gcs-key-prefix", "", "Allows to restrict access to the virtual folder "+
  160. "identified by this prefix and its contents")
  161. portableCmd.Flags().StringVar(&portableGCSCredentialsFile, "gcs-credentials-file", "", "Google Cloud Storage JSON credentials file")
  162. portableCmd.Flags().IntVar(&portableGCSAutoCredentials, "gcs-automatic-credentials", 1, "0 means explicit credentials using a JSON "+
  163. "credentials file, 1 automatic")
  164. rootCmd.AddCommand(portableCmd)
  165. }
  166. func parseFileExtensionsFilters() []dataprovider.ExtensionsFilter {
  167. var extensions []dataprovider.ExtensionsFilter
  168. for _, val := range portableAllowedExtensions {
  169. p, exts := getExtensionsFilterValues(strings.TrimSpace(val))
  170. if len(p) > 0 {
  171. extensions = append(extensions, dataprovider.ExtensionsFilter{
  172. Path: path.Clean(p),
  173. AllowedExtensions: exts,
  174. DeniedExtensions: []string{},
  175. })
  176. }
  177. }
  178. for _, val := range portableDeniedExtensions {
  179. p, exts := getExtensionsFilterValues(strings.TrimSpace(val))
  180. if len(p) > 0 {
  181. found := false
  182. for index, e := range extensions {
  183. if path.Clean(e.Path) == path.Clean(p) {
  184. extensions[index].DeniedExtensions = append(extensions[index].DeniedExtensions, exts...)
  185. found = true
  186. break
  187. }
  188. }
  189. if !found {
  190. extensions = append(extensions, dataprovider.ExtensionsFilter{
  191. Path: path.Clean(p),
  192. AllowedExtensions: []string{},
  193. DeniedExtensions: exts,
  194. })
  195. }
  196. }
  197. }
  198. return extensions
  199. }
  200. func getExtensionsFilterValues(value string) (string, []string) {
  201. if strings.Contains(value, "::") {
  202. dirExts := strings.Split(value, "::")
  203. if len(dirExts) > 1 {
  204. dir := strings.TrimSpace(dirExts[0])
  205. exts := []string{}
  206. for _, e := range strings.Split(dirExts[1], ",") {
  207. cleanedExt := strings.TrimSpace(e)
  208. if len(cleanedExt) > 0 {
  209. exts = append(exts, cleanedExt)
  210. }
  211. }
  212. if len(dir) > 0 && len(exts) > 0 {
  213. return dir, exts
  214. }
  215. }
  216. }
  217. return "", nil
  218. }