opensquat 2.2.2__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. opensquat-2.3.0/PKG-INFO +413 -0
  2. opensquat-2.3.0/README.md +382 -0
  3. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/__init__.py +1 -1
  4. opensquat-2.3.0/opensquat/api_client.py +255 -0
  5. opensquat-2.3.0/opensquat/app.py +486 -0
  6. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/arg_parser.py +85 -0
  7. opensquat-2.3.0/opensquat/auth.py +89 -0
  8. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/check_update.py +12 -2
  9. opensquat-2.3.0/opensquat/cli.py +365 -0
  10. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/ct.py +1 -1
  11. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/feed_manager.py +52 -11
  12. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/file_input.py +4 -8
  13. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/output.py +17 -13
  14. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/phishing.py +55 -50
  15. opensquat-2.3.0/opensquat/vt.py +169 -0
  16. opensquat-2.3.0/opensquat.egg-info/PKG-INFO +413 -0
  17. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat.egg-info/SOURCES.txt +6 -0
  18. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat.egg-info/requires.txt +3 -2
  19. {opensquat-2.2.2 → opensquat-2.3.0}/pyproject.toml +5 -6
  20. opensquat-2.3.0/tests/test_api_client.py +361 -0
  21. opensquat-2.3.0/tests/test_arg_parser.py +142 -0
  22. opensquat-2.3.0/tests/test_auth.py +139 -0
  23. opensquat-2.3.0/tests/test_feed_manager.py +106 -0
  24. opensquat-2.2.2/PKG-INFO +0 -249
  25. opensquat-2.2.2/README.md +0 -217
  26. opensquat-2.2.2/opensquat/app.py +0 -160
  27. opensquat-2.2.2/opensquat/cli.py +0 -171
  28. opensquat-2.2.2/opensquat/vt.py +0 -106
  29. opensquat-2.2.2/opensquat.egg-info/PKG-INFO +0 -249
  30. {opensquat-2.2.2 → opensquat-2.3.0}/LICENSE +0 -0
  31. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/dns_resolvers.py +0 -0
  32. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/dns_validator.py +0 -0
  33. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/homograph.py +0 -0
  34. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/port_check.py +0 -0
  35. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/squatting_detector.py +0 -0
  36. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat/validations.py +0 -0
  37. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat.egg-info/dependency_links.txt +0 -0
  38. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat.egg-info/entry_points.txt +0 -0
  39. {opensquat-2.2.2 → opensquat-2.3.0}/opensquat.egg-info/top_level.txt +0 -0
  40. {opensquat-2.2.2 → opensquat-2.3.0}/setup.cfg +0 -0
  41. {opensquat-2.2.2 → opensquat-2.3.0}/tests/test_ct.py +0 -0
  42. {opensquat-2.2.2 → opensquat-2.3.0}/tests/test_validations.py +0 -0
@@ -0,0 +1,413 @@
1
+ Metadata-Version: 2.4
2
+ Name: opensquat
3
+ Version: 2.3.0
4
+ Summary: openSquat - Detection of domain squatting, typosquatting, IDN homograph attacks, and phishing threats
5
+ Author-email: Andre Tenreiro <andre@opensquat.com>
6
+ License-Expression: GPL-3.0-only
7
+ Project-URL: Homepage, https://opensquat.com
8
+ Project-URL: Repository, https://github.com/atenreiro/opensquat
9
+ Project-URL: Issues, https://github.com/atenreiro/opensquat/issues
10
+ Keywords: cybersecurity,phishing,typosquatting,domain-squatting,osint,homograph,brand-protection
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Security
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: confusable_homoglyphs>=2.0.0
24
+ Requires-Dist: homoglyphs>=2.0.0
25
+ Requires-Dist: colorama>=0.4.1
26
+ Requires-Dist: requests>=2.33.0
27
+ Requires-Dist: beautifulsoup4>=4.4.0
28
+ Requires-Dist: dnspython>=2.6.1
29
+ Requires-Dist: urllib3>=2.6.3
30
+ Dynamic: license-file
31
+
32
+ <p align="center">
33
+ <img src="https://raw.githubusercontent.com/atenreiro/opensquat/master/screenshots/openSquat_logo.png" alt="openSquat Logo" width="550"/>
34
+ </p>
35
+
36
+ <h1 align="center">openSquat Core</h1>
37
+
38
+ <p align="center">
39
+ <a href="#"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+"></a>
40
+ <a href="https://github.com/atenreiro/opensquat/blob/master/LICENSE"><img src="https://img.shields.io/badge/License-GPLv3-blue.svg" alt="License: GPL v3"></a>
41
+ <a href="https://github.com/atenreiro/opensquat/issues"><img src="https://img.shields.io/github/issues/atenreiro/opensquat" alt="GitHub issues"></a>
42
+ <a href="https://github.com/atenreiro/opensquat/stargazers"><img src="https://img.shields.io/github/stars/atenreiro/opensquat" alt="GitHub stars"></a>
43
+ </p>
44
+
45
+ ---
46
+
47
+ ## 📑 Table of Contents
48
+
49
+ - [What is openSquat?](#-what-is-opensquat)
50
+ - [Featured In](#-featured-in)
51
+ - [Open-Core Model](#-open-core-model)
52
+ - [Key Features](#-key-features)
53
+ - [Quick Start](#-quick-start)
54
+ - [Requirements](#-requirements)
55
+ - [Usage](#-usage)
56
+ - [Premium and API Modes](#-premium-and-api-modes)
57
+ - [Configuration](#%EF%B8%8F-configuration)
58
+ - [Automation](#-automation)
59
+ - [CLI Reference](#-cli-reference)
60
+ - [Contributing](#-contributing)
61
+ - [Author](#-author)
62
+ - [License](#-license)
63
+
64
+ ---
65
+
66
+ ## 🎯 What is openSquat?
67
+
68
+ openSquat is an **Open Source Intelligence (OSINT)** security tool that identifies cyber squatting threats targeting your brand or domains:
69
+
70
+ | Threat Type | Description |
71
+ |-------------|-------------|
72
+ | 🎣 **Phishing** | Fraudulent domains mimicking your brand |
73
+ | 🔤 **Typosquatting** | Domains with common typos (e.g., `gooogle.com`) |
74
+ | 🌐 **IDN Homograph** | Look-alike characters from other alphabets |
75
+ | 👥 **Doppelgänger** | Domains containing your brand name |
76
+ | 🔀 **Bitsquatting** | Single-bit errors in domain names |
77
+
78
+ ---
79
+
80
+ ## 🌟 Featured In
81
+
82
+ > **"A powerful swiss army knife for brand protection"**
83
+ > — [WhoisXML API Blog](https://www.whoisxmlapi.com/blog/orchestrating-open-source-software-and-whois-newly-registered-domain-data-feeds-to-fight-the-typosquatting-plague), August 2022
84
+
85
+ > **"A tool with insane power to fight typosquatting and all related types of cyber mischief."**
86
+ > — [WhoisXML API Blog](https://www.whoisxmlapi.com/blog/orchestrating-open-source-software-and-whois-newly-registered-domain-data-feeds-to-fight-the-typosquatting-plague), August 2022
87
+
88
+ > **"A handy tool for collecting information on newly registered domains."** — ranked Top 5 phishing detection tool
89
+ > — [SOCRadar Blog](https://socradar.io/blog/top-5-tools-for-phishing-domain-detection/), July 2022
90
+
91
+ > **"openSquat provides essential protection against domain squatting and phishing attacks through automated monitoring and detection."**
92
+ > — [Prince Yadav, TutorialsPoint](https://www.tutorialspoint.com/article/opensquat-ndash-domain-squatting-and-phishing-watchdog), March 2026
93
+
94
+ ### Academic Citation
95
+
96
+ > **"OpenSquat identified 103 squatting domains, 960 active phishing websites, and 53 domains with suspicious certificates."**
97
+ > — Sharma et al., [Journal of Information Security and Cybercrimes Research (JISCR)](https://journals.nauss.edu.sa/index.php/JISCR/article/download/2805/1349), Vol. 7, Issue 1, June 2024
98
+
99
+ ---
100
+
101
+ ## 🔓 Open-Core Model
102
+
103
+ openSquat follows an **open-core model**:
104
+
105
+ - **Core detection engine** — Open source and community-driven
106
+ - **Advanced capabilities** — Delivered through commercial intelligence services
107
+
108
+ This model enables transparency and community collaboration while supporting the scale, reliability, and operational requirements of enterprise use.
109
+
110
+ ---
111
+
112
+ ## ✨ Key Features
113
+
114
+ - 📅 **Daily NRD feeds** — Automatic newly registered domain updates
115
+ - 🔍 **Similarity detection** — Levenshtein distance algorithm
116
+ - 🔓 **Three operating modes** — **Community** (free feed), **Premium Feed** (paid feed, same local pipeline), or **Premium API** (hosted lookalike service). The two Premium modes share a single openSquat API key — see [Premium and API Modes](#-premium-and-api-modes).
117
+ - 🛡️ **VirusTotal integration** — Check domain reputation
118
+ - 🌐 **Quad9 DNS validation** — Identify malicious domains
119
+ - 📜 **Certificate Transparency** — Monitor SSL/TLS certificates
120
+ - 📊 **Multiple output formats** — TXT, JSON, CSV
121
+
122
+ ---
123
+
124
+ ## 🚀 Quick Start
125
+
126
+ ### Install via pip (recommended)
127
+
128
+ ```bash
129
+ pip install opensquat
130
+ opensquat -k keywords.txt
131
+ ```
132
+
133
+ ### Or clone the repository
134
+
135
+ ```bash
136
+ git clone https://github.com/atenreiro/opensquat
137
+ cd opensquat
138
+ pip install -r requirements.txt
139
+ python3 opensquat.py -k keywords.txt
140
+ ```
141
+
142
+ > **Repo users:** in all the examples below, replace `opensquat` with `python3 opensquat.py` to run from a cloned checkout.
143
+
144
+ ---
145
+
146
+ ## 📦 Requirements
147
+
148
+ - **Python 3.10+**
149
+ - Dependencies: `confusable_homoglyphs`, `homoglyphs`, `colorama`, `requests`, `dnspython`, `beautifulsoup4`
150
+
151
+ ---
152
+
153
+ ## 📖 Usage
154
+
155
+ ### Basic Commands
156
+
157
+ ```bash
158
+ # Default run
159
+ opensquat
160
+
161
+ # Show all options
162
+ opensquat -h
163
+
164
+ # Use custom keywords file
165
+ opensquat -k my_keywords.txt
166
+ ```
167
+
168
+ ### Validation Options
169
+
170
+ ```bash
171
+ # DNS validation via Quad9
172
+ opensquat --dns
173
+
174
+ # Check Certificate Transparency logs
175
+ opensquat --ct
176
+
177
+ # Scan for open ports (80/443)
178
+ opensquat --portcheck
179
+
180
+ # Cross-reference phishing databases
181
+ opensquat --phishing results.txt
182
+ ```
183
+
184
+ ### Output Formats
185
+
186
+ ```bash
187
+ # Save as JSON
188
+ opensquat -o results.json -t json
189
+
190
+ # Save as CSV
191
+ opensquat -o results.csv -t csv
192
+ ```
193
+
194
+ ### Confidence Levels
195
+
196
+ | Level | Flag | Description |
197
+ |-------|------|-------------|
198
+ | 0 | `-c 0` | Very high (fewer results, high accuracy) |
199
+ | 1 | `-c 1` | High (default) |
200
+ | 2 | `-c 2` | Medium |
201
+ | 3 | `-c 3` | Low |
202
+ | 4 | `-c 4` | Very low (more results, more false positives) |
203
+
204
+ > **Note:** On the API side (`--api`), the five confidence levels map to four fuzziness values (`exact`, `low`, `auto`, `high`) — `-c 3` and `-c 4` both map to `high`. See [Premium and API Modes](#-premium-and-api-modes) for the full mapping and how to override with `--api-fuzziness`.
205
+
206
+ ---
207
+
208
+ ## 💎 Premium and API Modes
209
+
210
+ openSquat supports three modes. The default (Community) is unchanged — existing users need no flags. The two Premium modes share a single openSquat API key; pick **Premium Feed** if you want the same local detection pipeline with a larger feed, or **Premium API** if you want server-side detection with no local feed download.
211
+
212
+ | Mode | Flag | What it does |
213
+ |------|------|--------------|
214
+ | **Community** (default) | _(none)_ | Downloads the free NRD feed (~100k domains/day) and runs local Levenshtein detection. |
215
+ | **Premium Feed** | `--premium` | Downloads the paid NRD feed (`nrd-lite`, much larger) using your openSquat API key, then runs the same local Levenshtein detection. |
216
+ | **Premium API** | `--api` | Skips local feed download. Queries the openSquat lookalike REST API per keyword and returns server-side matches. |
217
+
218
+ ### Get an API key
219
+
220
+ Sign up at [opensquat.com](https://opensquat.com) to get a key. The same key works for both Premium Feed (`--premium`) and Premium API (`--api`).
221
+
222
+ ### Provide the API key (priority order)
223
+
224
+ 1. `--api-key YOUR_KEY` on the command line
225
+ 2. `OPENSQUAT_API_KEY` environment variable
226
+ 3. `api_key.txt` in the current directory (one key per file, `#` comments allowed)
227
+
228
+ > The CLI flag is visible in `ps` output. Prefer the env var or key file in shared environments.
229
+
230
+ ### Examples
231
+
232
+ ```bash
233
+ # Premium Feed mode — same local pipeline, larger feed
234
+ export OPENSQUAT_API_KEY=os_xxxxxxxxxxxx
235
+ opensquat -k keywords.txt --premium
236
+
237
+ # Premium API mode — server-side detection per keyword
238
+ opensquat -k keywords.txt --api
239
+
240
+ # Premium API + DNS reputation check on each returned domain
241
+ opensquat -k keywords.txt --api --dns
242
+
243
+ # Premium API with JSON output grouped by keyword
244
+ opensquat -k keywords.txt --api -t json -o results.json
245
+
246
+ # Tune the Premium API search
247
+ opensquat -k keywords.txt --api --api-fuzziness high --api-history-days 7 --api-max-results 200
248
+ ```
249
+
250
+ When `--premium` or `--api` successfully loads a key, the CLI prints a masked confirmation line so you can verify which key was picked up without leaking it:
251
+
252
+ ```
253
+ [*] API key loaded: os_gL...L5Mb
254
+ ```
255
+
256
+ In Premium API mode, the run summary reports the active mode, the number of API calls made, and your remaining balance with usage delta (for example, `4972 (used 4 of 4976 this run)`). Per-keyword progress lines appear in the same order as your keywords file even though the calls run in parallel. Quota exhaustion (HTTP 429) returns partial results gracefully; auth errors (401) and plan errors (403) abort with a clear message.
257
+
258
+ If the backend rate-limits your request (HTTP 429 with a `Retry-After` header), the tool distinguishes it from quota exhaustion: you'll see a yellow `[!] Rate limit hit (retry in Ns)` warning instead of the red `quota exhausted` message, partial results are still returned, and the summary preserves your real API balance so you can see exactly how many credits you actually used. To avoid triggering rate limits on large scans, pass `--api-rate-limit N` to cap outbound requests per second across all workers. A value of `8` is a safe starting point for most backends.
259
+
260
+ ```bash
261
+ # Throttle to 8 requests/second across all workers
262
+ opensquat -k keywords.txt --api --api-rate-limit 8
263
+ ```
264
+
265
+ ### Output format recommendation
266
+
267
+ **JSON is the recommended output format for Premium API mode** because the API returns per-domain metadata that the other formats cannot carry as cleanly: the registered TLD, the NRD first-seen date, an IDN homograph flag, and the unicode rendering of the homograph when the domain is one.
268
+
269
+ ```bash
270
+ opensquat -k keywords.txt --api -t json -o results.json
271
+ ```
272
+
273
+ Example of the richer output in Premium API mode (trimmed):
274
+
275
+ ```json
276
+ [
277
+ {
278
+ "keyword": "microsoft",
279
+ "domains": [
280
+ {"domain": "securite-microsoft.fr", "tld": "fr", "date": "09-04-2026", "idn": false},
281
+ {"domain": "xn--mirosoft-hw7c.com", "tld": "com", "date": "09-04-2026", "idn": true, "unicode": "miᴄrosoft.com"}
282
+ ]
283
+ }
284
+ ]
285
+ ```
286
+
287
+ The `idn` flag plus the `unicode` rendering let you see at a glance that `xn--mirosoft-hw7c.com` is actually `ᴄ` (Latin Letter Small Capital C) impersonating the `c` in "microsoft" — information that a plain punycode string completely hides.
288
+
289
+ CSV output is also supported and produces one row per domain with the same metadata columns, which suits analysts working in Excel or pandas:
290
+
291
+ ```bash
292
+ opensquat -k keywords.txt --api -t csv -o results.csv
293
+ ```
294
+
295
+ The CSV is written with a UTF-8 BOM so Excel on Windows correctly renders the unicode homograph column.
296
+
297
+ Community and Premium Feed modes emit the same JSON top-level shape for cross-mode consistency, but with only the `domain` field populated per entry — the NRD feed does not carry the per-domain metadata that only the hosted API has:
298
+
299
+ ```json
300
+ [
301
+ {
302
+ "keyword": "microsoft",
303
+ "domains": [
304
+ {"domain": "mirosoft.com"},
305
+ {"domain": "mcrosoft.net"}
306
+ ]
307
+ }
308
+ ]
309
+ ```
310
+
311
+ If you pass `--api-key` without also selecting `--premium` or `--api`, the CLI prints a one-line hint that the key will be ignored in Community mode (no silent mode-switching).
312
+
313
+ In Premium API mode, `-c/--confidence` is auto-mapped to API fuzziness (`0→exact`, `1→low`, `2→auto`, `3→high`, `4→high`). Use `--api-fuzziness` to override.
314
+
315
+ Premium API (`--api`) is incompatible with `--doppelganger` and `-d/--domains`.
316
+
317
+ ---
318
+
319
+ ## ⚙️ Configuration
320
+
321
+ ### Keywords File (`keywords.txt`)
322
+
323
+ ```text
324
+ # Lines starting with # are comments
325
+ mycompany
326
+ mybrand
327
+ myproduct
328
+ ```
329
+
330
+ ### VirusTotal API Key (`vt_key.txt`)
331
+
332
+ To use `--vt` or `--subdomains`, add your API key:
333
+ ```text
334
+ # Get your free API key at https://www.virustotal.com
335
+ your_api_key_here
336
+ ```
337
+
338
+ ### openSquat API Key (`api_key.txt`)
339
+
340
+ Required for `--premium` and `--api`. Create an `api_key.txt` file in the working directory:
341
+ ```text
342
+ # Get your key at https://opensquat.com
343
+ # Lines starting with # are ignored; the first non-comment line is used.
344
+ os_your_key_here
345
+ ```
346
+
347
+ The CLI resolves the key in this order: `--api-key` flag → `$OPENSQUAT_API_KEY` environment variable → `api_key.txt` file. The env var and file methods are preferred over the CLI flag in shared environments, since CLI arguments are visible via `ps`.
348
+
349
+ ---
350
+
351
+ ## 🤖 Automation
352
+
353
+ Run daily via crontab:
354
+
355
+ ```bash
356
+ # pip-installed (recommended) — every day at 8 AM, feeds update ~7:30 AM UTC
357
+ 0 8 * * * cd /path/to/workdir && opensquat -k keywords.txt -o results.json -t json
358
+
359
+ # Repo checkout — invoke opensquat.py directly with python3
360
+ 0 8 * * * cd /path/to/opensquat && python3 opensquat.py -k keywords.txt -o results.json -t json
361
+ ```
362
+
363
+ > The `cd` into a working directory matters if you rely on `api_key.txt` (resolved from the current directory) or want `results.json` written to a specific place.
364
+
365
+ ---
366
+
367
+ ## 📋 CLI Reference
368
+
369
+ | Argument | Default | Description |
370
+ |----------|---------|-------------|
371
+ | `-k, --keywords` | `keywords.txt` | Keywords file to search |
372
+ | `-o, --output` | `results.txt` | Output filename |
373
+ | `-t, --type` | `txt` | Output format: `txt`, `json`, `csv` |
374
+ | `-c, --confidence` | `1` | Confidence level (0-4). In `--api` mode this is auto-mapped to fuzziness (`-c 3` and `-c 4` both → `high`). |
375
+ | `-d, --domains` | — | Use local domain file instead of downloading |
376
+ | `-u, --url` | opensquat feed | URL to download domain feed |
377
+ | `--dns` | — | Enable Quad9 DNS validation |
378
+ | `--doppelganger` | — | Doppelganger-only mode (keyword in domain + reachability check) |
379
+ | `--ct` | — | Search Certificate Transparency logs |
380
+ | `--phishing` | — | Cross-reference phishing database |
381
+ | `--subdomains` | — | Fetch subdomains via VirusTotal |
382
+ | `--portcheck` | — | Check for open ports 80/443 |
383
+ | `--vt` | — | Validate against VirusTotal |
384
+ | `--premium` | — | **Premium Feed mode** — use the paid NRD feed (requires openSquat API key) |
385
+ | `--api` | — | **Premium API mode** — query the openSquat lookalike REST API per keyword (no local feed) |
386
+ | `--api-key` | — | openSquat API key (or set `$OPENSQUAT_API_KEY`, or use `api_key.txt`) |
387
+ | `--api-fuzziness` | _(from `-c`)_ | Premium API mode: `exact`, `low`, `high`, or `auto` |
388
+ | `--api-history-days` | — | Premium API mode: NRD history window in days (clipped to plan cap) |
389
+ | `--api-max-results` | — | Premium API mode: max results per keyword (clipped to plan cap) |
390
+ | `--api-rate-limit` | _(unlimited)_ | Premium API mode: max outbound requests per second across all workers |
391
+
392
+ ---
393
+
394
+ ## 🤝 Contributing
395
+
396
+ We welcome contributions! See our [Contributing Guide](CONTRIBUTING.md) for details.
397
+
398
+ - 🐛 **Report bugs** via [GitHub Issues](https://github.com/atenreiro/opensquat/issues)
399
+ - 💡 **Request features** by opening an issue
400
+ - 🔧 **Submit PRs** for bug fixes or enhancements
401
+ - 📝 **Release notes** — see the [CHANGELOG](CHANGELOG) for what's new in each version
402
+
403
+ ---
404
+
405
+ ## 👤 Author
406
+
407
+ **Andre Tenreiro** — [LinkedIn](https://www.linkedin.com/in/andretenreiro/) · [PGP Key](https://mail-api.proton.me/pks/lookup?op=get&search=andre@opensquat.com)
408
+
409
+ ---
410
+
411
+ ## 📜 License
412
+
413
+ This project is licensed under the [GNU GPL v3](LICENSE).