@@ -4,8 +4,10 @@ import (
4
4
"bufio"
5
5
"flag"
6
6
"fmt"
7
+ "io"
7
8
"os"
8
9
"strings"
10
+ "sync"
9
11
10
12
"github.com/root4loot/crtsher"
11
13
"github.com/root4loot/goutils/domainutil"
@@ -22,11 +24,28 @@ func init() {
22
24
log .Init (AppName )
23
25
}
24
26
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
25
43
const usage = `
26
44
Usage: crtsher [options] <domain | orgname>
27
45
-f, --file <file> Specify input file containing targets, one per line.
28
46
-t, --timeout <seconds> Set the timeout for each request (default: 90).
29
47
-c, --concurrency <number> Set the number of concurrent requests (default: 3).
48
+ --debug Enable debug mode.
30
49
--version Display the version information.
31
50
--help Display this help message.
32
51
@@ -41,39 +60,103 @@ Examples:
41
60
`
42
61
43
62
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 ()
52
66
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 )
54
70
os .Exit (0 )
55
71
}
56
72
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
+
57
89
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
+ }
59
101
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
63
107
}
64
108
}
65
109
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 )
68
130
}
69
131
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
+ }
70
145
}
71
146
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
+ }
74
156
157
+ func (cli * CLI ) processResults (results []crtsher.Result ) {
75
158
printed := make (map [string ]bool )
76
- for res := range runner . Results {
159
+ for _ , res := range results {
77
160
res .CommonName = strings .ToLower (res .CommonName )
78
161
res .CommonName = strings .TrimPrefix (res .CommonName , "*." )
79
162
res .IssuerName = strings .ToLower (res .IssuerName )
@@ -93,69 +176,47 @@ func processResults(runner *crtsher.Runner, target ...string) {
93
176
}
94
177
}
95
178
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
100
181
101
182
opts := * crtsher .DefaultOptions ()
102
183
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 )
113
187
}
114
- flag .Parse ()
115
188
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 )
119
192
}
120
193
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 , "" )
126
197
127
- inputList = append (inputList , lines ... )
128
- }
198
+ flag .BoolVar (& debug , "debug" , false , "" )
199
+ flag .BoolVar (& ver , "version" , false , "" )
200
+ flag .BoolVar (& help , "help" , false , "" )
129
201
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 )
139
204
}
205
+ flag .Parse ()
140
206
141
- if log . IsOutputPiped () {
142
- log .Notify (log .PipedOutputNotification )
207
+ if debug {
208
+ log .SetLevel (log .DebugLevel )
143
209
}
144
210
145
- if hasStdin () {
146
- inputList = append (inputList , processStdin ()... )
211
+ if help {
212
+ fmt .Print (usage )
213
+ os .Exit (0 )
147
214
}
148
215
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 )
156
219
}
157
-
158
- return inputList , & opts , nil
159
220
}
160
221
161
222
func hasStdin () bool {
@@ -165,22 +226,9 @@ func hasStdin() bool {
165
226
}
166
227
167
228
mode := stat .Mode ()
229
+
168
230
isPipedFromChrDev := (mode & os .ModeCharDevice ) == 0
169
231
isPipedFromFIFO := (mode & os .ModeNamedPipe ) != 0
170
232
171
233
return isPipedFromChrDev || isPipedFromFIFO
172
234
}
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
- }
0 commit comments