portable.go 9.6 KB

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