Skip to content

Commit 5cdc8e1

Browse files
committed
Add CLI
1 parent aab2d04 commit 5cdc8e1

File tree

10 files changed

+380
-185
lines changed

10 files changed

+380
-185
lines changed

CONTRIBUTING.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
Contributions are welcome and greatly appreciated. To contribute to the project, please follow these steps:
1+
# Contributing Guidelines
22

3-
1. Create an issue to discuss the change you would like to make.
4-
2. Fork the repository and create a new branch for your feature or bug fix.
5-
3. Make your changes and ensure that your code passes any existing tests.
6-
4. Submit a pull request and explain your changes. Please reference the issue number that you created in step 1.
3+
Thank you for considering contributing to this project.
4+
5+
## How to Contribute
6+
7+
1. **Fork the Repository**: Fork the repository to your GitHub account.
8+
3. **Make Changes**: Implement your changes in the codebase.
9+
4. **Commit Changes**: Commit your changes with a clear and concise commit message.
10+
5. **Push to GitHub**: Push your changes to your forked repository.
11+
6. **Create a Pull Request**: Go to the original repository on GitHub and create a pull request from your branch.

Dockerfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM golang:1.21-alpine as builder
2+
3+
RUN mkdir /app
4+
ADD . /app
5+
WORKDIR /app
6+
RUN go build -o ctlog ./cmd/...
7+
RUN chmod +x ./ctlog
8+
ENTRYPOINT ["/app/ctlog"]

README.md

+45-66
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,62 @@
1-
![Go version](https://img.shields.io/badge/Go-v1.19-blue.svg) [![Contribute](https://img.shields.io/badge/Contribute-Welcome-green.svg)](CONTRIBUTING.md)
1+
![Go version](https://img.shields.io/badge/Go-v1.21-blue.svg) [![Contribute](https://img.shields.io/badge/Contribute-Welcome-green.svg)](CONTRIBUTING.md)
22

33
# ctlog
4-
A package used to obtain domains from transparancy logs, either by domain or organization name.
54

6-
```
5+
A tool used to grab domains from certificate transparency logs (crt.sh).
6+
7+
## Why another crt.sh tool?
8+
9+
Unlike other tools that often make a single request to crt.sh, this tool is designed to handle the inherent slowness and unreliability of crt.sh, especially when dealing with large responses. It includes retry logic to detect and recover from failed requests. It offers a simple API that can also be used to run tasks asynchronously.
10+
11+
## Installation
12+
13+
```bash
714
go get github.com/root4loot/ctlog@latest
815
```
916

10-
See [Examples](https://github.com/root4loot/ctlog/tree/master/examples)
17+
## Docker
1118

12-
```go
13-
package main
19+
```bash
20+
git clone https://github.com/root4loot/ctlog
21+
cd ctlog
22+
docker run --rm -it $(docker build -q .) example.com
23+
```
24+
25+
## Usage
1426

15-
import (
16-
"fmt"
27+
```bash
28+
Usage: ctlog [options] <domain | orgname>
29+
-f, --file <file> Specify input file containing targets, one per line.
30+
-t, --timeout <seconds> Set the timeout for each request (default: 90).
31+
-c, --concurrency <number> Set the number of concurrent requests (default: 3).
32+
--version Display the version information.
33+
--help Display this help message.
1734

18-
"github.com/root4loot/ctlog"
19-
)
35+
Search Query Identity:
36+
- Domain Name
37+
- Organization Name
2038

21-
func main() {
22-
// run ctlog against targets
23-
results := ctlog.Multiple([]string{"example.com", "Hackerone Inc"})
24-
for _, result := range results {
25-
for _, res := range result {
26-
if res.Domain() != "" {
27-
fmt.Println(res.Domain())
28-
}
29-
}
30-
}
31-
}
39+
Examples:
40+
ctlog example.com
41+
ctlog "Hackerone Inc"
42+
ctlog --file domains.txt
3243
```
3344

34-
### Output
45+
## Example Running
3546

47+
```bash
48+
$ ctlog example.com
49+
[ctlog] (INF) Querying example.com
50+
[ctlog] (RES) www.example.org
51+
[ctlog] (RES) hosted.jivesoftware.com
52+
[ctlog] (RES) uat3.hosted.jivesoftware.com
53+
[ctlog] (RES) www.example.com
54+
[ctlog] (RES) example.com
3655
```
37-
hackerone.com
38-
enorekcah.com
39-
errors.hackerone.net
40-
gitaly.code-pdx1.inverselink.com
41-
www.testserver.inverselink.com
42-
www.enorekcah.com
43-
www.hackerone.com
44-
events.hackerone.com
45-
go.inverselink.com
46-
support-app.inverselink.com
47-
staging.inverselink.com
48-
testserver.inverselink.com
49-
attjira.inverselink.com
50-
signatures.hacker.one
51-
looker.inverselink.com
52-
links.hackerone.com
53-
support.hackerone.com
54-
phabricator.inverselink.com
55-
ci.inverselink.com
56-
info.hackerone.com
57-
hackerone-user-content.com
58-
hackerone-ext-content.com
59-
ci-production.inverselink.com
60-
storybook.inverselink.com
61-
go.hacker.one
62-
sentry.inverselink.com
63-
ma.hacker.one
64-
payments-production.inverselink.com
65-
hacker.one
66-
ui-docs.inverselink.com
67-
proteus.inverselink.com
68-
info.hacker.one
69-
logstash.inverselink.com
70-
kibana.inverselink.com
71-
withinsecurity.com
72-
bd1.inverselink.com
73-
bd2.inverselink.com
74-
bd3.inverselink.com
75-
www.example.org
76-
hosted.jivesoftware.com
77-
uat3.hosted.jivesoftware.com
78-
www.example.com
79-
example.com
80-
```
56+
57+
## As a Library
58+
59+
See the `examples` folder for usage examples.
8160

8261
## Contributing
8362

cmd/main.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"strings"
9+
10+
"github.com/root4loot/ctlog"
11+
"github.com/root4loot/goutils/domainutil"
12+
"github.com/root4loot/goutils/fileutil"
13+
"github.com/root4loot/goutils/log"
14+
)
15+
16+
const (
17+
AppName = "ctlog"
18+
Version = "0.1.0"
19+
)
20+
21+
func init() {
22+
log.Init(AppName)
23+
}
24+
25+
const usage = `
26+
Usage: ctlog [options] <domain | orgname>
27+
-f, --file <file> Specify input file containing targets, one per line.
28+
-t, --timeout <seconds> Set the timeout for each request (default: 90).
29+
-c, --concurrency <number> Set the number of concurrent requests (default: 3).
30+
--version Display the version information.
31+
--help Display this help message.
32+
33+
Search Query Identity:
34+
- Domain Name
35+
- Organization Name
36+
37+
Examples:
38+
ctlog example.com
39+
ctlog "Hackerone Inc"
40+
ctlog --file domains.txt
41+
`
42+
43+
func main() {
44+
inputList, opts, err := parseCLI()
45+
runner := ctlog.NewRunnerWithOptions(opts)
46+
47+
if err != nil {
48+
if err.Error() == "version" {
49+
fmt.Println("version:", Version)
50+
os.Exit(0)
51+
}
52+
53+
log.Error(err)
54+
os.Exit(0)
55+
}
56+
57+
if hasStdin() {
58+
log.Debug("Reading from stdin")
59+
60+
scanner := bufio.NewScanner(os.Stdin)
61+
for scanner.Scan() {
62+
inputList = append(inputList, strings.TrimSpace(scanner.Text()))
63+
}
64+
}
65+
66+
if inputList != nil {
67+
processResults(runner, inputList...)
68+
}
69+
70+
}
71+
72+
func processResults(runner *ctlog.Runner, target ...string) {
73+
go runner.RunMultipleAsync(target)
74+
75+
printed := make(map[string]bool)
76+
for res := range runner.Results {
77+
res.CommonName = strings.ToLower(res.CommonName)
78+
res.CommonName = strings.TrimPrefix(res.CommonName, "*.")
79+
res.IssuerName = strings.ToLower(res.IssuerName)
80+
res.IssuerName = strings.TrimPrefix(res.IssuerName, "*.")
81+
82+
if !printed[res.CommonName] {
83+
if domainutil.IsDomainName(res.CommonName) {
84+
log.Result(res.CommonName)
85+
printed[res.CommonName] = true
86+
}
87+
88+
if domainutil.IsDomainName(res.IssuerName) {
89+
log.Result(res.IssuerName)
90+
printed[res.IssuerName] = true
91+
}
92+
}
93+
}
94+
}
95+
96+
func parseCLI() ([]string, *ctlog.Options, error) {
97+
var version, help bool
98+
var inputFilePath string
99+
var inputList []string
100+
101+
opts := *ctlog.DefaultOptions()
102+
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)
113+
}
114+
flag.Parse()
115+
116+
args := flag.Args()
117+
if len(args) > 0 {
118+
inputList = args
119+
}
120+
121+
if inputFilePath != "" {
122+
lines, err := fileutil.ReadFile(inputFilePath)
123+
if err != nil {
124+
return nil, nil, err
125+
}
126+
127+
inputList = append(inputList, lines...)
128+
}
129+
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+
}
139+
}
140+
141+
if log.IsOutputPiped() {
142+
log.Notify(log.PipedOutputNotification)
143+
}
144+
145+
if hasStdin() {
146+
inputList = append(inputList, processStdin()...)
147+
}
148+
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...)
156+
}
157+
158+
return inputList, &opts, nil
159+
}
160+
161+
func hasStdin() bool {
162+
stat, err := os.Stdin.Stat()
163+
if err != nil {
164+
return false
165+
}
166+
167+
mode := stat.Mode()
168+
isPipedFromChrDev := (mode & os.ModeCharDevice) == 0
169+
isPipedFromFIFO := (mode & os.ModeNamedPipe) != 0
170+
171+
return isPipedFromChrDev || isPipedFromFIFO
172+
}
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

Comments
 (0)