Skip to content

Commit ebc18cb

Browse files
committed
Update Dockerfile, main.go, and tests to enhance functionality and validation. Added output file support with -o flag, DNS retry mechanism with -retries flag, and graceful shutdown on SIGINT/SIGTERM. Implemented domain and DNS server validation. Updated tests for new features and improved error handling.
1 parent 1a65fe2 commit ebc18cb

File tree

10 files changed

+312
-132
lines changed

10 files changed

+312
-132
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ CMD ["-version"]
3535

3636
LABEL org.opencontainers.image.title="subenum"
3737
LABEL org.opencontainers.image.description="A Go-based CLI tool for subdomain enumeration"
38-
LABEL org.opencontainers.image.source="https://github.com/yourusername/subenum"
38+
LABEL org.opencontainers.image.source="https://github.com/TMHSDigital/subenum"
3939
LABEL org.opencontainers.image.licenses="MIT"
40-
LABEL org.opencontainers.image.documentation="https://github.com/yourusername/subenum/blob/main/README.md"
40+
LABEL org.opencontainers.image.documentation="https://github.com/TMHSDigital/subenum/blob/main/README.md"
4141
LABEL org.opencontainers.image.vendor="Educational Use Only"
4242
LABEL org.opencontainers.image.usage="For educational and legitimate security testing purposes only"

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ go build
9696
- `-dns-server <ip:port>`: DNS server to use for lookups (default: 8.8.8.8:53).
9797
- `-v`: Enable verbose output with detailed information about each lookup.
9898
- `-progress`: Show scan progress (default: true, use `-progress=false` to disable).
99+
- `-o <file>`: Write discovered subdomains to file (in addition to stdout).
100+
- `-retries <number>`: Number of DNS retry attempts per subdomain (default: 1).
99101
- `-version`: Show version information and exit.
100102

101103
### Output:
@@ -168,6 +170,16 @@ Disabling progress reporting (useful for scripting):
168170
./subenum -w words.txt -progress=false example.com
169171
```
170172

173+
Saving results to a file:
174+
```bash
175+
./subenum -w words.txt -o results.txt example.com
176+
```
177+
178+
Using retries for unreliable networks:
179+
```bash
180+
./subenum -w words.txt -retries 3 example.com
181+
```
182+
171183
### Simulation Mode for Safe Testing
172184

173185
The tool includes a simulation mode for safely testing functionality without performing actual DNS queries:

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
If you discover a security vulnerability in `subenum`, please follow these steps to report it responsibly:
66

77
1. **Do NOT** disclose the vulnerability publicly until it has been addressed.
8-
2. Send details of the vulnerability to [your-email@example.com] or create a private report through GitHub's Security tab.
8+
2. Send details of the vulnerability to the repository maintainers via GitHub Issues or create a private report through GitHub's Security tab.
99
3. Include the following information:
1010
- A description of the vulnerability
1111
- Steps to reproduce the issue

docs/ARCHITECTURE.md

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ This architecture is designed to be efficient by performing multiple DNS lookups
1616

1717
## 2. Key Components / Modules
1818

19-
*(Details to be added regarding components, data flow, concurrency, etc.)*
20-
2119
### 2.1. Argument Parsing
2220

2321
* **Purpose**: This component is responsible for processing the command-line arguments provided by the user when `subenum` is executed. It extracts the target domain, the path to the wordlist file, the desired number of concurrent workers, and the DNS lookup timeout.
@@ -29,9 +27,11 @@ This architecture is designed to be efficient by performing multiple DNS lookups
2927
* `flag.Bool("v", false, "Enable verbose output")`: Defines the verbose flag.
3028
* `flag.Bool("progress", true, "Show progress during scanning")`: Defines the progress reporting flag.
3129
* `flag.Bool("version", false, "Show version information")`: Defines the version flag.
30+
* `flag.String("o", "", "Write results to file")`: Defines the output file flag.
31+
* `flag.Int("retries", 1, "Number of DNS retry attempts")`: Defines the retry count flag.
3232
* `flag.Parse()`: Parses the provided arguments.
3333
* `flag.Arg(0)`: Retrieves the positional argument (the target domain).
34-
* **Interactions**: The parsed values are used to configure the subsequent components, such as the Wordlist Processing and DNS Resolution Engine. Input validation is also performed to ensure valid values for critical parameters like concurrency and timeout.
34+
* **Interactions**: The parsed values are used to configure the subsequent components, such as the Wordlist Processing and DNS Resolution Engine. Input validation is performed to ensure valid values for critical parameters like concurrency, timeout, DNS server format (validated via `validateDNSServer`), and domain syntax (validated via `validateDomain`).
3535

3636
### 2.2. Wordlist Processing
3737

@@ -47,15 +47,16 @@ This architecture is designed to be efficient by performing multiple DNS lookups
4747

4848
* **Purpose**: This is the core component responsible for performing the actual DNS lookup for each constructed subdomain (e.g., `prefix.targetdomain.com`). It determines if a subdomain has a valid DNS record (typically A or CNAME, though the current implementation checks for any successful resolution).
4949
* **Implementation**:
50-
* Function: `resolveDomain(domain string, timeout time.Duration) bool`
50+
* Function: `resolveDomain(domain string, timeout time.Duration, dnsServer string, verbose bool) bool`
51+
* Function: `resolveDomainWithRetry(domain string, timeout time.Duration, dnsServer string, verbose bool, maxRetries int) bool` — wraps `resolveDomain` with configurable retry logic and exponential backoff between attempts.
5152
* `net.Resolver{}`: A custom DNS resolver is configured.
5253
* `PreferGo: true`: Instructs the resolver to use the pure Go DNS client.
53-
* `Dial func(ctx context.Context, network, address string) (net.Conn, error)`: A custom dial function is provided to control the connection to the DNS server. This allows for setting a specific timeout for the dial operation and for specifying the DNS server to use (currently hardcoded to Google's public DNS `8.8.8.8:53`).
54+
* `Dial func(ctx context.Context, network, address string) (net.Conn, error)`: A custom dial function is provided to control the connection to the DNS server, using the user-specified `dnsServer` address.
5455
* `net.Dialer{Timeout: timeout}`: A `Dialer` is created with the user-specified timeout.
55-
* `d.DialContext(ctx, "udp", "8.8.8.8:53")`: Establishes a UDP connection to the DNS server.
56+
* `d.DialContext(ctx, "udp", dnsServer)`: Establishes a UDP connection to the configured DNS server.
5657
* `resolver.LookupHost(context.Background(), domain)`: Performs the DNS lookup for the given domain. It attempts to find A or AAAA records for the host.
5758
* The function returns `true` if `LookupHost` returns no error (i.e., the domain resolved), and `false` otherwise.
58-
* **Interactions**: This function is called by each worker goroutine. It takes a fully qualified domain name and the timeout duration as input. It outputs a boolean indicating whether the domain resolved successfully. The result is used to decide if the domain should be printed to the console.
59+
* **Interactions**: Workers call `resolveDomainWithRetry`, which delegates to `resolveDomain` with retry logic. It takes a fully qualified domain name, timeout duration, DNS server address, verbose flag, and retry count as input. It outputs a boolean indicating whether the domain resolved successfully. The result is used to decide if the domain should be printed to the console and/or written to the output file.
5960

6061
### 2.4. Concurrency Management (Worker Pool)
6162

@@ -144,8 +145,13 @@ Visually, this can be seen as:
144145

145146
### 4.1. User Input Errors
146147

147-
* **Missing Required Arguments**: When the user doesn't provide a wordlist file (`-w` flag) or a target domain, the tool prints a usage message (`fmt.Println("Usage: subenum -w <wordlist_file> <domain>")`) followed by the description of all flags, and then exits with a non-zero status code (`os.Exit(1)`).
148-
* **Validation**: Currently, the tool performs minimal validation of the input arguments. The domain and wordlist file must be provided, but there is no validation of flag values (e.g., checking if the concurrency level or timeout is a positive number).
148+
* **Missing Required Arguments**: When the user doesn't provide a wordlist file (`-w` flag) or a target domain, the tool prints a usage message followed by the description of all flags, and then exits with a non-zero status code (`os.Exit(1)`).
149+
* **Validation**: The tool validates:
150+
* Concurrency level and timeout must be positive integers.
151+
* DNS server must be a valid `ip:port` format with proper IP address and port range (1-65535), validated by `validateDNSServer`.
152+
* Target domain must conform to DNS naming rules, validated by `validateDomain`.
153+
* Hit rate (simulation mode) must be 1-100.
154+
* Retry count must be at least 1.
149155

150156
### 4.2. File Operation Errors
151157

@@ -162,12 +168,14 @@ Visually, this can be seen as:
162168
* **Channel Operations**: The tool uses a channel (`subdomains`) to pass work between the wordlist reading goroutine and the worker goroutines. No explicit error handling is implemented for channel operations, as Go's channel semantics ensure that operations like closing an already closed channel would panic. This is avoided by design in the current implementation.
163169
* **Worker Goroutine Errors**: Each worker goroutine processes DNS lookups independently. If an error occurs within a worker (outside of the expected DNS resolution failures), it can cause the entire goroutine to terminate. The current implementation doesn't have specific handling for such scenarios.
164170

165-
### 4.5. Potential Improvements
171+
### 4.5. Graceful Shutdown
172+
173+
The tool listens for `SIGINT` and `SIGTERM` signals. Upon receiving an interrupt, it cancels the work context, drains in-flight workers, and exits cleanly with a summary of results processed so far.
174+
175+
### 4.6. Output File Support
176+
177+
When the `-o` flag is provided, resolved subdomains are written to the specified file (one per line) in addition to stdout. A mutex protects concurrent writes to both stdout and the output file.
178+
179+
### 4.7. Retry Mechanism
166180

167-
* **Input Validation**: Add more thorough validation of command-line arguments, including:
168-
* Checking that the concurrency level is positive.
169-
* Validating that the timeout value is reasonable.
170-
* Verifying that the domain adheres to DNS naming rules.
171-
* **Verbose Mode**: Implement a verbose mode (e.g., `-v` flag) that would print more information, including errors during DNS lookups, to help with debugging.
172-
* **Graceful Handling of DNS Server Issues**: Add better handling of DNS server problems, possibly including retry mechanisms or fall-back to alternative DNS servers.
173-
* **Progress Reporting**: Provide information about the progress of the enumeration to give the user feedback on long-running scans.
181+
The `-retries` flag (default: 1) controls how many times each subdomain is attempted before being marked as unresolved. A short backoff delay is applied between retries to handle transient DNS failures.

docs/CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ We welcome contributions! Please read this guide to understand how you can contr
1818
1. **Fork the repository** on GitHub
1919
2. **Clone your fork**:
2020
```bash
21-
git clone https://github.com/your-username/subenum.git
21+
git clone https://github.com/TMHSDigital/subenum.git
2222
cd subenum
2323
```
2424
3. **Set up the upstream remote**:
2525
```bash
26-
git remote add upstream https://github.com/original-owner/subenum.git
26+
git remote add upstream https://github.com/TMHSDigital/subenum.git
2727
```
2828

2929
## Development Workflow

docs/DEVELOPER_GUIDE.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This guide provides information for developers looking to contribute to or build
88

99
To work with `subenum`, you'll need:
1010

11-
* **Go Programming Language**: [Go 1.16+](https://golang.org/dl/) is recommended. The tool uses features from recent Go versions.
11+
* **Go Programming Language**: [Go 1.22+](https://golang.org/dl/) is required.
1212
* **Git**: For version control.
1313
* **Text Editor or IDE**: VS Code, GoLand, or any editor with Go support is recommended.
1414

@@ -17,7 +17,7 @@ To work with `subenum`, you'll need:
1717
1. **Clone the Repository**
1818

1919
```bash
20-
git clone https://github.com/yourusername/subenum.git
20+
git clone https://github.com/TMHSDigital/subenum.git
2121
cd subenum
2222
```
2323

@@ -79,17 +79,14 @@ go test ./...
7979
When adding new features or modifying existing ones, please ensure you add appropriate tests. Here's a basic structure for tests:
8080

8181
```go
82-
// In a file like main_test.go
8382
package main
8483

8584
import (
86-
"context"
8785
"testing"
8886
"time"
8987
)
9088

9189
func TestResolveDomain(t *testing.T) {
92-
// Test cases
9390
testCases := []struct {
9491
name string
9592
domain string
@@ -110,10 +107,9 @@ func TestResolveDomain(t *testing.T) {
110107
},
111108
}
112109

113-
// Run test cases
114110
for _, tc := range testCases {
115111
t.Run(tc.name, func(t *testing.T) {
116-
result := resolveDomain(tc.domain, tc.timeout)
112+
result := resolveDomain(tc.domain, tc.timeout, DefaultDNSServer, false)
117113
if result != tc.expected {
118114
t.Errorf("Expected %v for domain %s, got %v", tc.expected, tc.domain, result)
119115
}
@@ -188,10 +184,10 @@ Please follow these style guidelines when contributing:
188184
189185
Areas for potential enhancement include:
190186
191-
* **Custom DNS Server**: Adding a flag to specify a DNS server to use for lookups.
192-
* **Output Formats**: Supporting different output formats (JSON, CSV).
193-
* **Verbose Mode**: Adding a verbose flag for more detailed output, including errors and progress.
187+
* **Output Formats**: Supporting different output formats (JSON, CSV) in addition to the current plain text output file (`-o`).
194188
* **Result Filtering**: Allowing users to filter results based on DNS record types.
195189
* **Recursive Enumeration**: Adding support for recursive subdomain enumeration (e.g., finding subdomains of discovered subdomains).
190+
* **Wildcard Detection**: Detecting wildcard DNS records that resolve all subdomains.
191+
* **Rate Limiting**: Adding configurable rate limiting for DNS queries to avoid triggering abuse detection.
196192
197193
When working on new features, please update the documentation accordingly and add tests to cover the new functionality.

docs/index.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ A Go-based CLI tool for subdomain enumeration designed for educational purposes
1111

1212
- Fast, concurrent DNS lookup of subdomains using customizable wordlists
1313
- Configurable concurrency level and timeout settings
14-
- Support for custom DNS servers
14+
- Support for custom DNS servers with proper validation
1515
- Verbose mode for detailed output
1616
- Real-time progress tracking
17+
- Output to file with `-o` flag
18+
- DNS retry mechanism for transient failure resilience
19+
- Graceful shutdown on Ctrl+C (SIGINT/SIGTERM)
20+
- Simulation mode for safe testing without network access
21+
- Domain and DNS server input validation
1722
- Docker support for containerized usage
1823
- Extensive documentation and examples
1924

@@ -23,7 +28,7 @@ A Go-based CLI tool for subdomain enumeration designed for educational purposes
2328

2429
```bash
2530
# Clone the repository
26-
git clone https://github.com/yourusername/subenum.git
31+
git clone https://github.com/TMHSDigital/subenum.git
2732
cd subenum
2833

2934
# Build the tool
@@ -34,7 +39,7 @@ go build
3439

3540
```bash
3641
# Clone the repository
37-
git clone https://github.com/yourusername/subenum.git
42+
git clone https://github.com/TMHSDigital/subenum.git
3843
cd subenum
3944

4045
# Build the Docker image
@@ -74,16 +79,16 @@ make docker-build docker-run
7479

7580
## Documentation
7681

77-
- [Usage Guide](https://github.com/yourusername/subenum#usage)
78-
- [Advanced Usage Examples](https://github.com/yourusername/subenum/blob/main/examples/advanced_usage.md)
82+
- [Usage Guide](https://github.com/TMHSDigital/subenum#usage)
83+
- [Advanced Usage Examples](https://github.com/TMHSDigital/subenum/blob/main/examples/advanced_usage.md)
7984
- [Docker Usage](#using-docker)
8085
- [Architecture](ARCHITECTURE.html)
8186
- [Developer Guide](DEVELOPER_GUIDE.html)
8287
- [Contributing](CONTRIBUTING.html)
8388

8489
## Ethical Use
8590

86-
This tool is provided for **educational and legitimate security testing purposes only**. Users must ensure they have proper authorization before conducting subdomain enumeration on any domains. See the [LICENSE](https://github.com/yourusername/subenum/blob/main/LICENSE) file for detailed terms of use.
91+
This tool is provided for **educational and legitimate security testing purposes only**. Users must ensure they have proper authorization before conducting subdomain enumeration on any domains. See the [LICENSE](https://github.com/TMHSDigital/subenum/blob/main/LICENSE) file for detailed terms of use.
8792

8893
## License
8994

logs/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.0] - 2026-02-22
11+
12+
### Added
13+
- Output file support with the `-o` flag to save results to a file
14+
- DNS retry mechanism with configurable `-retries` flag for transient failure resilience
15+
- Graceful shutdown on SIGINT/SIGTERM — drains in-flight workers and prints partial results
16+
- Proper DNS server validation (IP format and port range 1-65535)
17+
- Domain format validation against DNS naming rules
18+
- Tests for `validateDNSServer`, `validateDomain`, `resolveDomainWithRetry`, and `simulateResolution`
19+
20+
### Changed
21+
- Removed deprecated `rand.Seed` call (auto-seeded since Go 1.20)
22+
- Tests now use `t.Errorf` for real assertions instead of `t.Logf` warnings
23+
- Fixed test compilation — `resolveDomain` calls now pass all 4 required parameters
24+
- Updated all placeholder URLs/emails in documentation to actual repo values
25+
26+
### Fixed
27+
- Progress goroutine `done` channel is now buffered to prevent potential deadlock
28+
- Mutex-protected stdout/file output to prevent interleaved writes from concurrent workers
29+
1030
## [0.2.0] - 2025-05-08
1131

1232
### Added

0 commit comments

Comments
 (0)