portable.go 9.6 KB

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