Skip to content

Commit 98bc255

Browse files
committed
feat: add debug flag and refactor CLI for improved target processing
1 parent 77b9345 commit 98bc255

File tree

6 files changed

+197
-160
lines changed

6 files changed

+197
-160
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ docker run --rm -it $(docker build -q .) example.com
2727
## Usage
2828

2929
```bash
30-
Usage: crtsher [options] <domain | orgname>
30+
Usage: crtsher [options] <domain | orgname>
3131
-f, --file <file> Specify input file containing targets, one per line.
3232
-t, --timeout <seconds> Set the timeout for each request (default: 90).
3333
-c, --concurrency <number> Set the number of concurrent requests (default: 3).
34+
--debug Enable debug mode.
3435
--version Display the version information.
3536
--help Display this help message.
3637

cmd/crtsher/main.go

+127-79
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"bufio"
55
"flag"
66
"fmt"
7+
"io"
78
"os"
89
"strings"
10+
"sync"
911

1012
"github.com/root4loot/crtsher"
1113
"github.com/root4loot/goutils/domainutil"
@@ -22,11 +24,28 @@ func init() {
2224
log.Init(AppName)
2325
}
2426

27+
type CLI struct {
28+
Target string
29+
Concurrency int
30+
Infile string
31+
Timeout int
32+
Debug bool
33+
}
34+
35+
func NewCLI() *CLI {
36+
return &CLI{
37+
Concurrency: 3,
38+
Timeout: 90,
39+
}
40+
}
41+
42+
// TODO: implement debug flag
2543
const usage = `
2644
Usage: crtsher [options] <domain | orgname>
2745
-f, --file <file> Specify input file containing targets, one per line.
2846
-t, --timeout <seconds> Set the timeout for each request (default: 90).
2947
-c, --concurrency <number> Set the number of concurrent requests (default: 3).
48+
--debug Enable debug mode.
3049
--version Display the version information.
3150
--help Display this help message.
3251
@@ -41,39 +60,103 @@ Examples:
4160
`
4261

4362
func main() {
44-
inputList, opts, err := parseCLI()
45-
runner := crtsher.NewRunnerWithOptions(opts)
46-
47-
if err != nil {
48-
if err.Error() == "version" {
49-
fmt.Println("version:", Version)
50-
os.Exit(0)
51-
}
63+
cli := NewCLI()
64+
cli.parseFlags()
65+
flag.Parse()
5266

53-
log.Error(err)
67+
if len(flag.Args()) == 0 && !hasStdin() && cli.Infile == "" {
68+
log.Error("No targets specified. Please provide a domain or organization name.")
69+
fmt.Print(usage)
5470
os.Exit(0)
5571
}
5672

73+
targetChannel := make(chan string)
74+
75+
go cli.processTargets(targetChannel)
76+
cli.processTarget(targetChannel) // Directly call without a done channel
77+
}
78+
79+
func (cli *CLI) processTargets(targetChannel chan<- string) {
80+
args := flag.Args()
81+
82+
if len(args) > 0 {
83+
log.Debug("Processing command line arguments")
84+
for _, target := range args {
85+
targetChannel <- target
86+
}
87+
}
88+
5789
if hasStdin() {
58-
log.Debug("Reading from stdin")
90+
log.Debug("Reading targets from standard input")
91+
cli.processInput(os.Stdin, targetChannel)
92+
}
93+
94+
if cli.Infile != "" {
95+
log.Debugf("Reading targets from file: %s", cli.Infile)
96+
cli.processFileTargets(cli.Infile, targetChannel)
97+
}
98+
99+
close(targetChannel)
100+
}
59101

60-
scanner := bufio.NewScanner(os.Stdin)
61-
for scanner.Scan() {
62-
inputList = append(inputList, strings.TrimSpace(scanner.Text()))
102+
func (cli *CLI) processInput(input io.Reader, targetChannel chan<- string) {
103+
scanner := bufio.NewScanner(input)
104+
for scanner.Scan() {
105+
for _, target := range strings.Fields(scanner.Text()) {
106+
targetChannel <- target
63107
}
64108
}
65109

66-
if inputList != nil {
67-
processResults(runner, inputList...)
110+
if err := scanner.Err(); err != nil {
111+
log.Error("Error reading input:", err)
112+
os.Exit(1)
113+
}
114+
}
115+
116+
func (cli *CLI) processTarget(targetChannel <-chan string) {
117+
sem := make(chan struct{}, cli.Concurrency)
118+
var wg sync.WaitGroup
119+
120+
for target := range targetChannel {
121+
log.Debug("Processing CLI target:", target)
122+
sem <- struct{}{}
123+
wg.Add(1)
124+
go func(t string) {
125+
defer func() { <-sem }()
126+
defer wg.Done()
127+
results := cli.worker(t)
128+
cli.processResults(results)
129+
}(target)
68130
}
69131

132+
wg.Wait()
133+
}
134+
135+
func (cli *CLI) processFileTargets(infile string, targetChannel chan<- string) {
136+
fileTargets, err := fileutil.ReadFile(infile)
137+
if err != nil {
138+
log.Error("Error reading file:", err)
139+
close(targetChannel)
140+
os.Exit(1)
141+
}
142+
for _, target := range fileTargets {
143+
targetChannel <- target
144+
}
70145
}
71146

72-
func processResults(runner *crtsher.Runner, target ...string) {
73-
go runner.RunMultipleAsync(target)
147+
func (cli *CLI) worker(target string) []crtsher.Result {
148+
crtsherRunner := crtsher.NewRunner()
149+
crtsherRunner.Options.Concurrency = cli.Concurrency
150+
crtsherRunner.Options.Timeout = cli.Timeout
151+
log.Debugf("Starting query for target: %s", target)
152+
results := crtsherRunner.Query(target)
153+
log.Debugf("Finished query for target: %s", target)
154+
return results
155+
}
74156

157+
func (cli *CLI) processResults(results []crtsher.Result) {
75158
printed := make(map[string]bool)
76-
for res := range runner.Results {
159+
for _, res := range results {
77160
res.CommonName = strings.ToLower(res.CommonName)
78161
res.CommonName = strings.TrimPrefix(res.CommonName, "*.")
79162
res.IssuerName = strings.ToLower(res.IssuerName)
@@ -93,69 +176,47 @@ func processResults(runner *crtsher.Runner, target ...string) {
93176
}
94177
}
95178

96-
func parseCLI() ([]string, *crtsher.Options, error) {
97-
var version, help bool
98-
var inputFilePath string
99-
var inputList []string
179+
func (cli *CLI) parseFlags() {
180+
var help, ver, debug bool
100181

101182
opts := *crtsher.DefaultOptions()
102183

103-
flag.StringVar(&inputFilePath, "f", "", "")
104-
flag.StringVar(&inputFilePath, "file", "", "")
105-
flag.IntVar(&opts.Timeout, "timeout", opts.Timeout, "")
106-
flag.IntVar(&opts.Timeout, "t", opts.Timeout, "")
107-
flag.IntVar(&opts.Concurrency, "concurrency", opts.Concurrency, "")
108-
flag.IntVar(&opts.Concurrency, "c", opts.Concurrency, "")
109-
flag.BoolVar(&version, "version", false, "")
110-
flag.BoolVar(&help, "help", false, "")
111-
flag.Usage = func() {
112-
fmt.Fprint(os.Stdout, usage)
184+
setStringFlag := func(name, shorthand, value, usage string) {
185+
flag.StringVar(&cli.Infile, name, value, usage)
186+
flag.StringVar(&cli.Infile, shorthand, value, usage)
113187
}
114-
flag.Parse()
115188

116-
args := flag.Args()
117-
if len(args) > 0 {
118-
inputList = args
189+
setIntFlag := func(name, shorthand string, value int, usage string) {
190+
flag.IntVar(&cli.Timeout, name, value, usage)
191+
flag.IntVar(&cli.Timeout, shorthand, value, usage)
119192
}
120193

121-
if inputFilePath != "" {
122-
lines, err := fileutil.ReadFile(inputFilePath)
123-
if err != nil {
124-
return nil, nil, err
125-
}
194+
setStringFlag("file", "f", "", "")
195+
setIntFlag("timeout", "t", opts.Timeout, "")
196+
setIntFlag("concurrency", "c", opts.Concurrency, "")
126197

127-
inputList = append(inputList, lines...)
128-
}
198+
flag.BoolVar(&debug, "debug", false, "")
199+
flag.BoolVar(&ver, "version", false, "")
200+
flag.BoolVar(&help, "help", false, "")
129201

130-
if help || version || (len(flag.Args()) == 0 && len(inputList) == 0 && !hasStdin()) {
131-
if help {
132-
fmt.Fprint(os.Stdout, usage)
133-
return nil, nil, nil
134-
} else if version {
135-
log.Info("Version:", Version)
136-
} else {
137-
return nil, nil, fmt.Errorf("No input provided. See -h for usage")
138-
}
202+
flag.Usage = func() {
203+
fmt.Fprint(os.Stdout, usage)
139204
}
205+
flag.Parse()
140206

141-
if log.IsOutputPiped() {
142-
log.Notify(log.PipedOutputNotification)
207+
if debug {
208+
log.SetLevel(log.DebugLevel)
143209
}
144210

145-
if hasStdin() {
146-
inputList = append(inputList, processStdin()...)
211+
if help {
212+
fmt.Print(usage)
213+
os.Exit(0)
147214
}
148215

149-
if inputFilePath != "" {
150-
lines, err := fileutil.ReadFile(inputFilePath)
151-
if err != nil {
152-
return nil, nil, err
153-
}
154-
155-
inputList = append(inputList, lines...)
216+
if ver {
217+
fmt.Println(AppName, Version)
218+
os.Exit(0)
156219
}
157-
158-
return inputList, &opts, nil
159220
}
160221

161222
func hasStdin() bool {
@@ -165,22 +226,9 @@ func hasStdin() bool {
165226
}
166227

167228
mode := stat.Mode()
229+
168230
isPipedFromChrDev := (mode & os.ModeCharDevice) == 0
169231
isPipedFromFIFO := (mode & os.ModeNamedPipe) != 0
170232

171233
return isPipedFromChrDev || isPipedFromFIFO
172234
}
173-
174-
func processStdin() []string {
175-
var targets []string
176-
if hasStdin() {
177-
scanner := bufio.NewScanner(os.Stdin)
178-
for scanner.Scan() {
179-
line := strings.TrimSpace(scanner.Text())
180-
if len(line) > 0 {
181-
targets = append(targets, strings.Fields(line)...)
182-
}
183-
}
184-
}
185-
return targets
186-
}

example/example.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ import (
99
func main() {
1010
runner := crtsher.NewRunner()
1111

12-
results := runner.Run("example.com")
12+
results := runner.Query("example.com")
1313
for _, result := range results {
14-
if result.GetCommonName() != "" {
15-
fmt.Println(result.GetCommonName())
16-
}
14+
fmt.Println(result.GetCommonName())
1715
}
1816
}

0 commit comments

Comments
 (0)