portable.go 9.5 KB

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