ferm 0.1.0a1__py3-none-any.whl
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.
- ferm-0.1.0a1.dist-info/METADATA +308 -0
- ferm-0.1.0a1.dist-info/RECORD +26 -0
- ferm-0.1.0a1.dist-info/WHEEL +4 -0
- ferm-0.1.0a1.dist-info/entry_points.txt +3 -0
- ferm-0.1.0a1.dist-info/licenses/COPYING +339 -0
- pyferm/__init__.py +16 -0
- pyferm/__main__.py +8 -0
- pyferm/backend/__init__.py +1 -0
- pyferm/backend/base.py +226 -0
- pyferm/backend/iptables.py +781 -0
- pyferm/backend/nft.py +998 -0
- pyferm/cli.py +729 -0
- pyferm/config.py +63 -0
- pyferm/domains.py +296 -0
- pyferm/errors.py +161 -0
- pyferm/functions.py +787 -0
- pyferm/import_ferm.py +919 -0
- pyferm/modules.py +705 -0
- pyferm/parser.py +1283 -0
- pyferm/py.typed +0 -0
- pyferm/resolver.py +423 -0
- pyferm/rules.py +234 -0
- pyferm/scope.py +271 -0
- pyferm/streams.py +64 -0
- pyferm/tokenizer.py +322 -0
- pyferm/values.py +262 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ferm
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: For Easy Rule Making - a frontend for iptables (Python port)
|
|
5
|
+
Project-URL: Homepage, https://github.com/6RUN0/ferm
|
|
6
|
+
Project-URL: Repository, https://github.com/6RUN0/ferm
|
|
7
|
+
Project-URL: Changelog, https://github.com/6RUN0/ferm/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Auke Kok, Max Kellermann
|
|
9
|
+
Maintainer-email: Boris Talovikov <boris@talovikov.ru>
|
|
10
|
+
License-Expression: GPL-2.0-or-later
|
|
11
|
+
License-File: COPYING
|
|
12
|
+
Keywords: firewall,iptables,netfilter,nftables
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: System :: Networking :: Firewalls
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Provides-Extra: dns
|
|
25
|
+
Requires-Dist: dnspython>=2.2.1; extra == 'dns'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ferm (Python port)
|
|
29
|
+
|
|
30
|
+
`ferm` ("For Easy Rule Making", pronounced "firm") is a frontend for
|
|
31
|
+
`iptables`. It reads firewall rules from a structured, high-level
|
|
32
|
+
configuration language — with variables, functions, arrays, blocks and
|
|
33
|
+
includes — and installs them into the running kernel by calling
|
|
34
|
+
`iptables(8)` / `iptables-restore`. It also drives the `ip6tables`,
|
|
35
|
+
`arptables` and `ebtables` families.
|
|
36
|
+
|
|
37
|
+
The goal is to make rules easy to write *and* easy to read, so the
|
|
38
|
+
administrator spends time designing good rules rather than transcribing
|
|
39
|
+
them.
|
|
40
|
+
|
|
41
|
+
## Project status
|
|
42
|
+
|
|
43
|
+
This repository is **mid-migration** from the original Perl
|
|
44
|
+
implementation to Python:
|
|
45
|
+
|
|
46
|
+
- **`src/pyferm/`** — the Python port. **Phase 1 is complete**: it parses
|
|
47
|
+
the full ferm configuration language and emits `iptables` rulesets
|
|
48
|
+
(`ip`, `ip6`, `arp`, `eb` families), with both the fast
|
|
49
|
+
(`iptables-restore`) and slow (per-rule) execution paths, the
|
|
50
|
+
`--interactive` rollback safety net, and the `import-ferm` save-file
|
|
51
|
+
round-trip. Output is validated **byte-for-byte against the Perl
|
|
52
|
+
oracle**.
|
|
53
|
+
- **`reference/`** — the original Perl implementation, kept verbatim as
|
|
54
|
+
the semantic oracle. Run its own test suite with
|
|
55
|
+
`make -C reference check`.
|
|
56
|
+
|
|
57
|
+
**Phase 2 (opt-in / experimental)**: a native `nftables` backend behind
|
|
58
|
+
`--nft` translates the same configuration into a native nft ruleset and
|
|
59
|
+
applies it atomically via `nft -f -`. The default backend stays
|
|
60
|
+
`iptables`, so existing configurations and output are unchanged unless
|
|
61
|
+
`--nft` is passed. `--nft` is opt-in and experimental: it carries
|
|
62
|
+
documented semantic differences (a `policy DROP` shift under the own-table
|
|
63
|
+
model, `@preserve` unsupported) and has golden + `nft -c` coverage but no
|
|
64
|
+
Perl-oracle differential test. The roadmap lives in
|
|
65
|
+
[`docs/ROADMAP.md`](docs/ROADMAP.md).
|
|
66
|
+
|
|
67
|
+
### Branches
|
|
68
|
+
|
|
69
|
+
- **`main`** — the new default branch (release line).
|
|
70
|
+
- **`develop`** — active development; branch your work from here.
|
|
71
|
+
- **`python-port`** — the porting-process branch.
|
|
72
|
+
- **`master`** — frozen; kept for historical reference only.
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
- Python **3.11–3.14**
|
|
77
|
+
- `iptables` (including `iptables-save` / `iptables-restore`) and a
|
|
78
|
+
netfilter-capable kernel, at runtime
|
|
79
|
+
- [`uv`](https://docs.astral.sh/uv/) for development
|
|
80
|
+
|
|
81
|
+
There are no required runtime dependencies. `@resolve()` uses
|
|
82
|
+
[`dnspython`](https://www.dnspython.org/) when it is installed (full record
|
|
83
|
+
vocabulary, including `NS`/`MX`); otherwise it falls back to the system stub
|
|
84
|
+
resolver (`getaddrinfo`, honouring `/etc/nsswitch.conf`), which answers only
|
|
85
|
+
`A`/`AAAA` records — other types then raise a clear error. Install the `dns`
|
|
86
|
+
extra (`pip install pyferm[dns]`) for the full set of record types. The stub
|
|
87
|
+
resolver consults system sources such as `/etc/hosts` and mDNS that dnspython
|
|
88
|
+
bypasses, so the two backends can diverge when those local sources differ from
|
|
89
|
+
authoritative DNS.
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
Three distribution forms are available. All carry the same version derived
|
|
94
|
+
from the same git tag.
|
|
95
|
+
|
|
96
|
+
### WARNING: the default configuration blocks all inbound traffic
|
|
97
|
+
|
|
98
|
+
Before enabling the `ferm` systemd service, read this section carefully.
|
|
99
|
+
|
|
100
|
+
The starter `/etc/ferm/ferm.conf` installed by the `.deb` package applies a
|
|
101
|
+
**DROP policy to the `INPUT` chain**. Only two narrow exceptions are open by
|
|
102
|
+
default: SSH on port 22 (identified by the service name `ssh`) and the
|
|
103
|
+
ICMPv6 essentials required by RFC 4890 (neighbour discovery, etc.). Any
|
|
104
|
+
other inbound traffic is dropped immediately on service start.
|
|
105
|
+
|
|
106
|
+
**Before running `systemctl enable --now ferm`:**
|
|
107
|
+
|
|
108
|
+
- Add every service you need to reachable through your firewall as a
|
|
109
|
+
drop-in fragment in `/etc/ferm/ferm.d/` (for example, HTTP/HTTPS,
|
|
110
|
+
custom application ports).
|
|
111
|
+
- If SSH runs on a port other than 22, edit `$SSH_PORT` in
|
|
112
|
+
`/etc/ferm/ferm.conf` before enabling. Enabling the service with the
|
|
113
|
+
wrong SSH port will lock you out of a remote machine.
|
|
114
|
+
- Fragments in `/etc/ferm/ferm.d/*.conf` are executed as root. Keep them
|
|
115
|
+
owned by `root:root` with permissions `0644` or stricter. A
|
|
116
|
+
world-writable fragment is a privilege-escalation vector.
|
|
117
|
+
|
|
118
|
+
**Migrating from the Perl `ferm` package:** the `pyferm` `.deb` installs
|
|
119
|
+
over the Perl package via `Provides/Conflicts/Replaces: ferm`, but it does
|
|
120
|
+
**not** automatically enable or start `ferm.service`. If you relied on the
|
|
121
|
+
Perl package having the service enabled, you must re-enable it explicitly
|
|
122
|
+
after installing `pyferm`. This is intentional: the default configuration
|
|
123
|
+
above would otherwise lock you out on first boot.
|
|
124
|
+
|
|
125
|
+
**Third-party packages that declare `Depends: ferm`** (automation tooling,
|
|
126
|
+
configuration managers) will have their dependency satisfied by `pyferm`,
|
|
127
|
+
but any `systemctl enable ferm` those tools may run will apply the default
|
|
128
|
+
DROP configuration. Audit what your automation does before installing.
|
|
129
|
+
|
|
130
|
+
### PyPI (pip)
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
pip install ferm
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For full DNS record-type support in `@resolve()` (including `NS`/`MX`),
|
|
137
|
+
install the `dns` extra:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
pip install ferm[dns]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `ferm` and `import-ferm` console scripts are placed on `PATH` by pip.
|
|
144
|
+
|
|
145
|
+
### Native .deb package
|
|
146
|
+
|
|
147
|
+
Download `pyferm_<version>_all.deb` from the
|
|
148
|
+
[GitHub Releases](https://github.com/6RUN0/ferm/releases) page and install
|
|
149
|
+
it:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
sudo apt install ./pyferm_<version>_all.deb
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`apt install ./...` (with the explicit `./` path) resolves dependencies
|
|
156
|
+
automatically. Do not use `dpkg -i` directly unless you handle dependencies
|
|
157
|
+
yourself.
|
|
158
|
+
|
|
159
|
+
The package name is `pyferm` but it declares `Provides: ferm`,
|
|
160
|
+
`Conflicts: ferm`, and `Replaces: ferm`. Installing it removes any
|
|
161
|
+
existing Perl `ferm` package and satisfies packages that depend on `ferm`.
|
|
162
|
+
|
|
163
|
+
When migrating from the Perl `ferm`, your edited `/etc/ferm/ferm.conf` is
|
|
164
|
+
kept: an interactive `apt` prompts you to keep it (the default), and for an
|
|
165
|
+
unattended upgrade pass `-o Dpkg::Options::=--force-confold` to keep it
|
|
166
|
+
without prompting.
|
|
167
|
+
|
|
168
|
+
After installation the service is **not** enabled. Review the warning above,
|
|
169
|
+
customise `/etc/ferm/ferm.conf` and add fragments to `/etc/ferm/ferm.d/`,
|
|
170
|
+
then opt in:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
systemctl enable --now ferm
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Installation (standalone binary)
|
|
177
|
+
|
|
178
|
+
A self-contained binary is published for **Linux x86_64** (glibc **2.28**
|
|
179
|
+
or newer). It carries its own Python runtime and a bundled `dnspython`,
|
|
180
|
+
so no Python installation is needed on the target host. It does **not**
|
|
181
|
+
bundle `iptables` or `nft` — those must already be present on the system,
|
|
182
|
+
because ferm calls them to install the rules.
|
|
183
|
+
|
|
184
|
+
Download the release tarball `ferm-<version>-linux-x86_64.tar.gz` and
|
|
185
|
+
unpack it, preserving symlinks:
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
tar xzf ferm-<version>-linux-x86_64.tar.gz
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This produces a `ferm.dist/` directory containing the `ferm` binary and,
|
|
192
|
+
next to it, an `import-ferm` symlink.
|
|
193
|
+
|
|
194
|
+
### Keep the binary inside its directory
|
|
195
|
+
|
|
196
|
+
The `ferm` binary loads its bundled shared objects from its own directory
|
|
197
|
+
(via an `$ORIGIN`-relative runtime path). **Do not move or copy the bare
|
|
198
|
+
`ferm` binary out of `ferm.dist/`** — a lone copy can no longer find its
|
|
199
|
+
libraries and will fail to start. To run it from a directory on `PATH`,
|
|
200
|
+
create a **symlink** to the binary instead of copying it; `$ORIGIN` still
|
|
201
|
+
resolves through the symlink:
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
ln -s /opt/ferm/ferm.dist/ferm /usr/local/bin/ferm
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Unpack into a root-owned directory
|
|
208
|
+
|
|
209
|
+
ferm runs as root. Because the binary loads shared objects from its own
|
|
210
|
+
directory, a world- or group-writable dist directory lets a local
|
|
211
|
+
attacker plant a malicious shared object next to the binary that then
|
|
212
|
+
runs with root privileges. **Unpack into a directory owned by root and
|
|
213
|
+
not writable by other users** (for example `/opt/ferm`, mode `0755`,
|
|
214
|
+
owner `root`), and verify the permissions *before* the first run as root:
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
sudo install -d -o root -g root -m 0755 /opt/ferm
|
|
218
|
+
sudo tar xzf ferm-<version>-linux-x86_64.tar.gz -C /opt/ferm
|
|
219
|
+
ls -ld /opt/ferm /opt/ferm/ferm.dist
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Verifying the download
|
|
223
|
+
|
|
224
|
+
The release ships a `SHA256SUMS` file. It guards against accidental
|
|
225
|
+
corruption in transfer — **integrity, not authenticity.** A matching
|
|
226
|
+
checksum does **not** prove the file was not maliciously substituted,
|
|
227
|
+
because an attacker who can replace the tarball can replace the checksum
|
|
228
|
+
file too.
|
|
229
|
+
|
|
230
|
+
For authenticity, verify the build provenance attestation with the GitHub
|
|
231
|
+
CLI:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
gh attestation verify ferm-<version>-linux-x86_64.tar.gz --repo 6RUN0/ferm
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Attestation only protects those who actually run the check, so verify
|
|
238
|
+
every download rather than trusting the file blindly.
|
|
239
|
+
|
|
240
|
+
### `@resolve()` and the host resolver
|
|
241
|
+
|
|
242
|
+
`@resolve()` looks names up through the host's `/etc/resolv.conf`, and
|
|
243
|
+
ferm runs as root. If a rule's correctness depends on the resolved
|
|
244
|
+
address (for example, restricting access to a named host), a tampered or
|
|
245
|
+
spoofed DNS answer can change which addresses the installed ruleset
|
|
246
|
+
trusts. For security-significant rules, use a trusted or local resolver,
|
|
247
|
+
or write static addresses directly.
|
|
248
|
+
|
|
249
|
+
## Usage
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
# Inspect the generated rules without touching the kernel (the safe way):
|
|
253
|
+
uv run ferm --noexec --lines /etc/ferm/ferm.conf
|
|
254
|
+
|
|
255
|
+
# Install the ruleset into the running kernel (needs root):
|
|
256
|
+
uv run ferm /etc/ferm/ferm.conf
|
|
257
|
+
|
|
258
|
+
# Convert an existing firewall into a ferm config:
|
|
259
|
+
uv run import-ferm > /etc/ferm/ferm.conf
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Be careful not to lock yourself out of a remote machine — use the
|
|
263
|
+
interactive mode (`--interactive`, `-i`) often. It installs the new
|
|
264
|
+
ruleset, then rolls back to the previous one unless you confirm in time.
|
|
265
|
+
|
|
266
|
+
The `ferm(1)` man page (authored in `reference/doc/ferm.pod`) is the
|
|
267
|
+
extensive reference for the configuration syntax.
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
The project is managed entirely with `uv` and orchestrated with `nox`:
|
|
272
|
+
|
|
273
|
+
```sh
|
|
274
|
+
uv sync # create .venv from uv.lock
|
|
275
|
+
uv run nox -s lint tests typecheck # the everyday inner loop
|
|
276
|
+
uv run nox -s preflight # the full binding gate
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Selected `nox` sessions:
|
|
280
|
+
|
|
281
|
+
| Session | Purpose |
|
|
282
|
+
| --------------- | --------------------------------------------------------------- |
|
|
283
|
+
| `lint` | `ruff` lint + format check |
|
|
284
|
+
| `tests` | unit + golden-file suite |
|
|
285
|
+
| `typecheck` | `mypy` + `pyright` (`verifytypes` 100%) |
|
|
286
|
+
| `golden_oracle` | golden output diffed against the Perl oracle + differential fuzz |
|
|
287
|
+
| `coverage` | coverage with an enforced floor |
|
|
288
|
+
| `matrix` | the suite across Python 3.11–3.14 |
|
|
289
|
+
| `fuzz` | Hypothesis differential fuzzing vs. the oracle |
|
|
290
|
+
| `crashfuzz` | `atheris` crash fuzzing of both parsers (opt-in) |
|
|
291
|
+
| `mutation` | `mutmut` mutation testing (opt-in, nightly) |
|
|
292
|
+
| `lockout` | containerised anti-lockout `--interactive` e2e (opt-in) |
|
|
293
|
+
|
|
294
|
+
### Testing
|
|
295
|
+
|
|
296
|
+
The suite is golden-file ("expected output") based: each fixture pairs a
|
|
297
|
+
`.ferm` input with a checked-in expected output, and ferm's output is
|
|
298
|
+
diffed after canonicalisation (tables/chains emit in non-deterministic
|
|
299
|
+
order). On top of that, the port is continuously checked **differentially
|
|
300
|
+
against the Perl oracle** — both on a corpus of real-world configs and on
|
|
301
|
+
Hypothesis-generated inputs — so divergences are caught automatically.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
GPL-2.0-or-later. See `reference/COPYING`.
|
|
306
|
+
|
|
307
|
+
Original authors: Auke Kok and Max Kellermann. Python port maintained by
|
|
308
|
+
Boris Talovikov.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
pyferm/__init__.py,sha256=JanXy9P2H2q6i9HWvd6zgbdnquCDrQ2mLMg9xEpTquQ,478
|
|
2
|
+
pyferm/__main__.py,sha256=I-t3PLy8gr7IF47yGt7JDlqZtYuLRHJoP1_ucL4X4QU,162
|
|
3
|
+
pyferm/cli.py,sha256=tXD7iSuM4sk3AUm1uCZgpNtcoP4EHPYxuYTmeK4CxS4,28197
|
|
4
|
+
pyferm/config.py,sha256=hib0EoIESS7lxEKkWMo9IZ6K9L75Pq2yfEHmMeRNze8,2839
|
|
5
|
+
pyferm/domains.py,sha256=Yd_7H-iO7zq1e87phkNj28clZRjW8ODJcpTekYNq3QE,11766
|
|
6
|
+
pyferm/errors.py,sha256=Jq-TrR2xnhAeca0XKXhBGKgsTzl5izagKPTARusMoyc,5346
|
|
7
|
+
pyferm/functions.py,sha256=IQHlvm8ii3G_K418zVJGDqtT7r-Vgi8zVsO8X_HV-yA,30363
|
|
8
|
+
pyferm/import_ferm.py,sha256=q0MUtnLH-8KSDhx5iT27AIqI8uuvmxdlUioVzAnpIHw,32537
|
|
9
|
+
pyferm/modules.py,sha256=cB53rigqXAWAtvRjXehhG42HzZtM-tcrjBf553zPlwI,20639
|
|
10
|
+
pyferm/parser.py,sha256=4epz-xy0Mf4GVqrh4ow7SVNevR4rSx-0WmXJimjMCMU,50708
|
|
11
|
+
pyferm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
pyferm/resolver.py,sha256=nck5bUX771Dly9XCfJW5kZ-rWtQTqNt2irqHw2-ssyM,15907
|
|
13
|
+
pyferm/rules.py,sha256=3hvGRTuHGyskNuRnxi_IpI78wa-_8QIPNHHfWWfwmGo,8915
|
|
14
|
+
pyferm/scope.py,sha256=2ts2ORL0naLXxuXFLjdD6ZNqcdinf11CUGljfzZx55E,10440
|
|
15
|
+
pyferm/streams.py,sha256=8JLV-uIv5toPi5UNNor_4P80f4BMKRCg86woiNmxQTM,2752
|
|
16
|
+
pyferm/tokenizer.py,sha256=JmzEbWUlE3szfzXPYo4ncVIjIDs0_BY719kPdbUT5ek,11570
|
|
17
|
+
pyferm/values.py,sha256=WmuBrOt_MVtmPChqBwR4fbqUyEfVGRWGA6S9lGcfEDw,8235
|
|
18
|
+
pyferm/backend/__init__.py,sha256=Rgb-imQ2q1GHMQs1v9qdKHgoleHaDPgwlhuCk-bGzbA,75
|
|
19
|
+
pyferm/backend/base.py,sha256=B1_czmgC9ln3qSL5V38bVC1GSlMCecq6SiE_5SxLBRE,9104
|
|
20
|
+
pyferm/backend/iptables.py,sha256=mGXbXc_h79YhpzmAfAYeaRbqUlTUUXjVKhOWstiqP3M,29437
|
|
21
|
+
pyferm/backend/nft.py,sha256=4ceQpGWTjDDApb2zaQANdljWX5CZj4A5TUL8a7S9rBE,38463
|
|
22
|
+
ferm-0.1.0a1.dist-info/METADATA,sha256=aeAfinVQstSZlfTkZRKIx6TPVrIdn_QOathx3JryMKc,12642
|
|
23
|
+
ferm-0.1.0a1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
24
|
+
ferm-0.1.0a1.dist-info/entry_points.txt,sha256=Ceo1Tb3G1DMFaCqlWqXjR3kZdi_GC0pOSLs8Vnuem0g,79
|
|
25
|
+
ferm-0.1.0a1.dist-info/licenses/COPYING,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
|
26
|
+
ferm-0.1.0a1.dist-info/RECORD,,
|