portable.go 9.3 KB

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