|
1 | 1 | package cmd
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bufio" |
| 5 | + "context" |
4 | 6 | "errors"
|
5 | 7 | "fmt"
|
6 | 8 | "github.com/LubyRuffy/gofofa"
|
7 | 9 | "github.com/LubyRuffy/gofofa/pkg/outformats"
|
8 | 10 | "github.com/sirupsen/logrus"
|
9 | 11 | "github.com/urfave/cli/v2"
|
| 12 | + "golang.org/x/time/rate" |
10 | 13 | "io"
|
| 14 | + "log" |
11 | 15 | "os"
|
| 16 | + "strconv" |
12 | 17 | "strings"
|
| 18 | + "sync" |
13 | 19 | )
|
14 | 20 |
|
15 | 21 | var (
|
16 |
| - fieldString string // fieldString |
17 |
| - size int // fetch size |
18 |
| - format string // out format |
19 |
| - outFile string // out file |
20 |
| - inFile string // in file |
21 |
| - deductMode string // deduct Mode |
22 |
| - fixUrl bool // each host fix as url, like 1.1.1.1,80 will change to http://1.1.1.1 |
23 |
| - urlPrefix string // each host fix as url, like 1.1.1.1,80 will change to http://1.1.1.1 |
24 |
| - full bool // search result for over a year |
25 |
| - batchSize int // amount of data contained in each batch, only for dump |
26 |
| - json bool // out format as json for short |
27 |
| - uniqByIP bool // group by ip |
| 22 | + fieldString string // fieldString |
| 23 | + size int // fetch size |
| 24 | + format string // out format |
| 25 | + outFile string // out file |
| 26 | + inFile string // in file |
| 27 | + deductMode string // deduct Mode |
| 28 | + fixUrl bool // each host fix as url, like 1.1.1.1,80 will change to http://1.1.1.1 |
| 29 | + urlPrefix string // each host fix as url, like 1.1.1.1,80 will change to http://1.1.1.1 |
| 30 | + full bool // search result for over a year |
| 31 | + batchSize int // amount of data contained in each batch, only for dump |
| 32 | + json bool // out format as json for short |
| 33 | + uniqByIP bool // group by ip |
| 34 | + workers int // number of workers |
| 35 | + ratePerSecond int // fofa request per second |
| 36 | + template string // template in pipeline mode |
28 | 37 | )
|
29 | 38 |
|
30 | 39 | // search subcommand
|
@@ -89,6 +98,24 @@ var searchCmd = &cli.Command{
|
89 | 98 | Usage: "uniq by ip",
|
90 | 99 | Destination: &uniqByIP,
|
91 | 100 | },
|
| 101 | + &cli.IntFlag{ |
| 102 | + Name: "workers", |
| 103 | + Value: 10, |
| 104 | + Usage: "number of workers", |
| 105 | + Destination: &workers, |
| 106 | + }, |
| 107 | + &cli.IntFlag{ |
| 108 | + Name: "rate", |
| 109 | + Value: 2, |
| 110 | + Usage: "fofa query per second", |
| 111 | + Destination: &ratePerSecond, |
| 112 | + }, |
| 113 | + &cli.StringFlag{ |
| 114 | + Name: "template", |
| 115 | + Value: "ip={}", |
| 116 | + Usage: "template in pipeline mode", |
| 117 | + Destination: &template, |
| 118 | + }, |
92 | 119 | },
|
93 | 120 | Action: SearchAction,
|
94 | 121 | }
|
@@ -126,8 +153,10 @@ func SearchAction(ctx *cli.Context) error {
|
126 | 153 |
|
127 | 154 | query := ctx.Args().First()
|
128 | 155 | if len(query) == 0 {
|
129 |
| - return errors.New("fofa query cannot be empty") |
| 156 | + //return errors.New("fofa query cannot be empty") |
| 157 | + log.Println("not set fofa query, now in pipeline mode....") |
130 | 158 | }
|
| 159 | + |
131 | 160 | fields := strings.Split(fieldString, ",")
|
132 | 161 | if len(fields) == 0 {
|
133 | 162 | return errors.New("fofa fields cannot be empty")
|
@@ -165,20 +194,65 @@ func SearchAction(ctx *cli.Context) error {
|
165 | 194 | }
|
166 | 195 | }
|
167 | 196 |
|
168 |
| - // do search |
169 |
| - res, err := fofaCli.HostSearch(query, size, fields, gofofa.SearchOptions{ |
170 |
| - FixUrl: fixUrl, |
171 |
| - UrlPrefix: urlPrefix, |
172 |
| - Full: full, |
173 |
| - UniqByIP: uniqByIP, |
174 |
| - }) |
175 |
| - if err != nil { |
176 |
| - return err |
| 197 | + writeQuery := func(query string) error { |
| 198 | + log.Println("query fofa of:", query) |
| 199 | + // do search |
| 200 | + res, err := fofaCli.HostSearch(query, size, fields, gofofa.SearchOptions{ |
| 201 | + FixUrl: fixUrl, |
| 202 | + UrlPrefix: urlPrefix, |
| 203 | + Full: full, |
| 204 | + UniqByIP: uniqByIP, |
| 205 | + }) |
| 206 | + if err != nil { |
| 207 | + return err |
| 208 | + } |
| 209 | + |
| 210 | + // output |
| 211 | + if err = writer.WriteAll(res); err != nil { |
| 212 | + return err |
| 213 | + } |
| 214 | + |
| 215 | + return nil |
177 | 216 | }
|
178 | 217 |
|
179 |
| - // output |
180 |
| - if err = writer.WriteAll(res); err != nil { |
181 |
| - return err |
| 218 | + if query != "" { |
| 219 | + return writeQuery(query) |
| 220 | + } else { |
| 221 | + // 并发模式 |
| 222 | + wg := sync.WaitGroup{} |
| 223 | + queries := make(chan string, workers) |
| 224 | + limiter := rate.NewLimiter(rate.Limit(ratePerSecond), 5) |
| 225 | + |
| 226 | + worker := func(queries <-chan string, wg *sync.WaitGroup) { |
| 227 | + for q := range queries { |
| 228 | + tmpQuery := strings.ReplaceAll(template, "{}", |
| 229 | + strconv.Quote(q)) |
| 230 | + if err := limiter.Wait(context.Background()); err != nil { |
| 231 | + fmt.Println("Error: ", err) |
| 232 | + } |
| 233 | + if err := writeQuery(tmpQuery); err != nil { |
| 234 | + log.Println(err) |
| 235 | + } |
| 236 | + wg.Done() |
| 237 | + } |
| 238 | + } |
| 239 | + for w := 0; w < workers; w++ { |
| 240 | + go worker(queries, &wg) |
| 241 | + } |
| 242 | + |
| 243 | + scanner := bufio.NewScanner(os.Stdin) |
| 244 | + for scanner.Scan() { // internally, it advances token based on sperator |
| 245 | + line := scanner.Text() |
| 246 | + wg.Add(1) |
| 247 | + queries <- line |
| 248 | + } |
| 249 | + |
| 250 | + if err := scanner.Err(); err != nil { |
| 251 | + log.Println(err) |
| 252 | + } |
| 253 | + |
| 254 | + wg.Wait() |
182 | 255 | }
|
| 256 | + |
183 | 257 | return nil
|
184 | 258 | }
|
0 commit comments