|
5 | 5 | package cmd |
6 | 6 |
|
7 | 7 | import ( |
8 | | - "bytes" |
| 8 | + "fmt" |
9 | 9 | "log" |
10 | 10 | "os" |
11 | 11 | "path/filepath" |
12 | 12 | "strings" |
13 | 13 |
|
| 14 | + tea "github.com/charmbracelet/bubbletea" |
14 | 15 | "github.com/saferwall/cli/internal/util" |
15 | 16 | "github.com/saferwall/cli/internal/webapi" |
16 | 17 | "github.com/spf13/cobra" |
17 | 18 | ) |
18 | 19 |
|
19 | | -var sha256Flag string |
20 | | -var txtFlag string |
21 | 20 | var outputFlag string |
| 21 | +var extractFlag bool |
22 | 22 |
|
23 | 23 | func init() { |
24 | 24 | ex, err := os.Executable() |
25 | 25 | if err != nil { |
26 | 26 | panic(err) |
27 | 27 | } |
28 | 28 |
|
29 | | - downloadCmd.Flags().StringVarP(&sha256Flag, "hash", "s", "", "SHA256 hash to download") |
30 | | - downloadCmd.Flags().StringVarP(&txtFlag, "txt", "t", "", "Download all hashes in a text file, separate by a line break") |
31 | 29 | downloadCmd.Flags().StringVarP(&outputFlag, "output", "o", filepath.Dir(ex), |
32 | 30 | "Destination directory where to save samples. (default=current dir)") |
| 31 | + downloadCmd.Flags().IntVarP(¶llelFlag, "parallel", "p", 4, |
| 32 | + "Number of files to download in parallel") |
| 33 | + downloadCmd.Flags().BoolVarP(&extractFlag, "extract", "x", false, |
| 34 | + "Extract samples from zip (password: infected)") |
33 | 35 | } |
34 | 36 |
|
35 | 37 | var downloadCmd = &cobra.Command{ |
36 | | - Use: "download", |
37 | | - Short: "Download a sample(s)", |
38 | | - Long: `Download a binary sample given a sha256`, |
| 38 | + Use: "download <sha256|file.txt>", |
| 39 | + Short: "Download a sample (and its artifacts)", |
| 40 | + Long: `Download a binary sample given a SHA256 hash, or a batch of samples from a text file containing one hash per line.`, |
| 41 | + Args: cobra.ExactArgs(1), |
39 | 42 | Run: func(cmd *cobra.Command, args []string) { |
| 43 | + arg := args[0] |
40 | 44 |
|
41 | | - // Login to saferwall web service |
| 45 | + // Login to saferwall web service. |
42 | 46 | webSvc := webapi.New(cfg.Credentials.URL) |
43 | 47 | token, err := webSvc.Login(cfg.Credentials.Username, cfg.Credentials.Password) |
44 | 48 | if err != nil { |
45 | 49 | log.Fatalf("failed to login to saferwall web service") |
46 | 50 | } |
47 | 51 |
|
48 | | - // download a single binary. |
49 | | - if sha256Flag != "" { |
50 | | - download(sha256Flag, token, webSvc) |
51 | | - } else if txtFlag != "" { |
52 | | - // Download a list of sha256 hashes. |
53 | | - data, err := util.ReadAll(txtFlag) |
54 | | - if err != nil { |
55 | | - log.Fatalf("failed to read to SHA256 hashes from txt file: %v", txtFlag) |
56 | | - } |
57 | | - |
58 | | - sha256list := strings.Split(string(data), "\n") |
59 | | - for _, sha256 := range sha256list { |
60 | | - if len(sha256) >= 64 { |
61 | | - err = download(sha256, token, webSvc) |
62 | | - if err != nil { |
63 | | - log.Fatalf("failed to download sample (%s): %v", sha256, err) |
64 | | - } |
65 | | - } |
66 | | - } |
| 52 | + hashes := collectHashes(arg) |
| 53 | + if len(hashes) == 0 { |
| 54 | + log.Fatalf("no valid SHA256 hashes found in %q", arg) |
67 | 55 | } |
| 56 | + |
| 57 | + downloadFiles(webSvc, token, hashes) |
68 | 58 | }, |
69 | 59 | } |
70 | 60 |
|
71 | | -func download(sha256, token string, web webapi.Service) error { |
72 | | - var err error |
73 | | - var data bytes.Buffer |
74 | | - var destPath string |
| 61 | +// collectHashes returns a list of SHA256 hashes from the argument. |
| 62 | +// If arg is a SHA256 hash, it returns a single-element slice. |
| 63 | +// Otherwise it treats arg as a file path and reads hashes from it. |
| 64 | +func collectHashes(arg string) []string { |
| 65 | + if sha256Re.MatchString(arg) { |
| 66 | + return []string{arg} |
| 67 | + } |
75 | 68 |
|
76 | | - log.Printf("downloading %s to %s", sha256, outputFlag) |
77 | | - dataContent, err := web.Download(sha256, token) |
| 69 | + data, err := util.ReadAll(arg) |
78 | 70 | if err != nil { |
79 | | - log.Fatalf("failed to download %s, err: %v", sha256, err) |
80 | | - return err |
| 71 | + log.Fatalf("failed to read SHA256 hashes from file: %s", arg) |
81 | 72 | } |
82 | | - data = *dataContent |
83 | 73 |
|
84 | | - filename := sha256 + ".zip" |
85 | | - destPath = filepath.Join(outputFlag, filename) |
86 | | - _, err = util.WriteBytesFile(destPath, &data) |
87 | | - if err != nil { |
88 | | - return err |
| 74 | + var hashes []string |
| 75 | + for _, line := range strings.Split(string(data), "\n") { |
| 76 | + line = strings.TrimSpace(line) |
| 77 | + if sha256Re.MatchString(line) { |
| 78 | + hashes = append(hashes, line) |
| 79 | + } |
89 | 80 | } |
| 81 | + return hashes |
| 82 | +} |
90 | 83 |
|
91 | | - return nil |
| 84 | +func downloadFiles(web webapi.Service, token string, hashes []string) { |
| 85 | + model := newDownloadModel(hashes, web, token, outputFlag, parallelFlag, extractFlag) |
| 86 | + p := tea.NewProgram(model) |
| 87 | + if _, err := p.Run(); err != nil { |
| 88 | + fmt.Fprintf(os.Stderr, "TUI error: %v\n", err) |
| 89 | + os.Exit(1) |
| 90 | + } |
92 | 91 | } |
0 commit comments