filezilla: update to 3.71.1.
3.71.1 (2026-08-26)
- macOS: Work around a bug in macOS 27 where setting the locale to non-English triggers crashes in AppKit
- SFTP: Improved heuristic when keyboard-interactive requests are treated as a simple password prompt on servers not offering the password authentication method
fd-find: update to 10.5.0.
## Features
- Add `--ignore-parent` option to override `--no-ignore-parent`, see #1958 (@tmchow)
- Add `--exact` option to match the entire filename exactly (literal, non-substring).
## Bugfixes
- Sanitize control characters and bidirectional override characters in filenames
when output goes to a terminal, to prevent terminal escape-sequence injection.
Also reject a placeholder as the executable for `--exec-batch`, while still
allowing it for `--exec`.
- Handle invalid working directories gracefully when using `--full-path`, see #1900 (@Xavrir).
- Fire the "search pattern contains a path separator" diagnostic for any pattern containing `/`, not just patterns that happen to name an existing directory. Preserves the legacy Windows behaviour that also flags native `\` separators when the pattern resolves to a real directory. See #1873.
- Also fire the "search pattern contains a path separator" diagnostic for `--and` patterns, not only the primary positional pattern. `--and` patterns are matched against the file name just like the primary pattern, so a path separator in them silently returned zero results. See #1873.
- Fix bug where passing "-" as a directory argument didn't actually search that directory, see #849 (@Sean-Kenneth-Doherty).
- Fix panic when `--changed-before`/`--changed-within` is given an out-of-range `@` Unix timestamp; the value is now rejected gracefully, see #2081 (@nikolauspschuetz).
Split get_facts out of get_entitlement
This commit adds changes to expose the entitlement facts for the running system as a public get_facts, so a caller that needs the facts themselves rather than a policy answer has one sanctioned way to read them instead of building EntitlementFacts by hand.
It also records, next to get_license, that a caller currently cannot tell "this system has no license" from "we could not find out" -- the fallback codes collapse an unreachable daemon into the same bare None a genuinely unlicensed system produces.
Resolve tier entitlements against their matrix row
This commit adds changes to make TierRule resolve against its product matrix cells instead of ignoring them, so a tier row that grants on one hardware side and not the other actually takes effect. The cell is read before the tier qualifier because the matrix is authoritative and a sufficient tier should not rescue a cell the product never granted. The column also resolves against the feature carrying the tier rather than the policy key, which no license holds, so the key columns had been unreachable.
A tier is read off a feature key and cannot be evaluated without one, so cells outside the two key columns are now rejected at construction instead of being silently ignored. Proactive support's row is corrected to key-only accordingly: the old cells granted it on a license alone with no tier consulted, which only looked right because a missing key was denied before any cell was read. Nothing moves behaviourally, entitled, reason and message are identical in all six columns, and the spreadsheet row still needs the same correction.
Drive consumer entitlement tests through the live engine
This commit adds changes to replace the canned entitlement stubs in the consumer unit tests with a shared helper that points truenas.entitlements.check and .feature at the live policy over synthesized EntitlementFacts. Those tests used to hand the gate an answer and then assert it came back, so none of them could fail on a wrong POLICY row, a flipped vector cell or a missing legacy injection; the test now owns only the facts and the verdict is the engine's.
Also covers the seams around the engine that nothing touched before: the DMI to HardwareClass to check_entitlement join, including the first direct assertion of is_appliance, which is what picks the half of the matrix a machine reads; the legacy injection set, derived from production rather than hand-copied and pinned separately so the swap does not go tautological; the KMIP enable-transition carve-out that keeps a system which lost the entitlement able to disable KMIP and recover its escrowed keys; support ticket routing across all six columns, with the outbound request made structurally impossible; and a scan of tests/ that catches an integration mock naming a feature POLICY no longer carries, which today degrades to no mock at all with nothing logged.
Adapt dict of model fields when converting between API versions
This commit fixes an issue where a field declared as dict[str, Model] was never version adapted, so an older client silently got the raw payload back rather than a converted one. The new clause has to come after the single nested model check because both shapes are a plain dict at runtime, and reversing them feeds map values into the model adapter.
Gate support ticketing through the entitlement engine
This commit adds changes to route the proactive support ticket gate and the support endpoint selection through the entitlement engine instead of system.is_enterprise, and to inject SUPPORT into every legacy license so nobody loses the flag on upgrade.
Contract types that are not support tiers now collapse to BRONZE, which the proactive support tier gate rejects. That stamp is load bearing: unconditional injection paired with a tier blind gate would hand proactive support to the entire legacy installed base, so the interlock is pinned by a test.
Format license utils package
This commit fixes an issue where the files added when license retrieval moved into a shared utils package were never run through ruff format, so the formatter check fails on them. Reformatting only reflows multi-line collection literals and parametrize arguments to match line length; no logic changes.
Gate directory services authentication through the entitlement engine
This commit adds changes to gate ds_auth, which controls whether directory services accounts may authenticate to the UI and API, through the entitlement engine instead of a bare is_enterprise read. Directory services themselves stay completely ungated on every product including Community Edition, so AD, LDAP and IPA are unaffected and only the UI and API login path is licensed. DIRECTORY_SERVICES also moves into the unconditional legacy injection bucket so every legacy licensee keeps it regardless of model.
Move license retrieval into a shared utils package
This commit adds changes to move license retrieval and legacy license normalization out of plugins/truenas and into middlewared.utils.license, so there is a single get_license() that every consumer reads and one normalized shape it returns regardless of whether the answer came from the license daemon or the legacy on-disk blob. is_licensed_for_ha had quietly grown its own composition of the same two normalizers with a different fallback rule, which is what this collapses.
There is one behaviour change worth calling out: a v2 license that exists but fails verification is now authoritative and no longer falls back to the legacy blob, while a wedged or erroring daemon does now fall back instead of reporting the system unlicensed. LicenseInfo also splits its conflated expires_at into separate support and license expiry fields and keys features by name rather than flattening them into a list. The public truenas.license.info payload is unchanged and is now pinned byte for byte by an explicit projection and a golden test, since it is still an untyped dict and nothing else held its shape.
Type the entitlement lookup boundary
This commit adds changes to give the two vocabularies a policy lookup can be keyed by - the license feature keys, and the entitlements derived from license type or tier - a single name, EntitlementKey, and to use it on get_entitlement and the truenas.entitlements.check endpoint so a bogus or misspelled key is caught there rather than raising out of the engine whenever a user first happens to hit that gate. LicenseFeature is re-exported from the entitlements package as well, so callers no longer need to know that half the vocabulary is defined outside this tree.
The endpoint's field carries a validator that resolves a name to its member first, because our API models are strict and a feature arrives over the wire as a plain string, which a bare enum field would refuse outright. check_entitlement itself still takes a str on purpose: it is what decides whether a key is known at all, so its own unknown-key error path would be unreachable if the type could not express an unknown key.
Remove LTS as a licensed feature
This commit adds changes to drop LTS from the entitlement engine, since the key never acquired a consumer: it carried a matrix row but no policy entry, so nothing ever resolved for it and system.feature_enabled answered it only by reading the raw license list and bypassing the policy entirely. The matching vocabulary removal lands in truenas_license, and the unit suite pins the two together so neither can move alone.
Add a hardware identity package and route entitlements through it
This commit adds changes to pull hardware detection out of the entitlement engine into utils/hardware, split into a pure classifier that takes DMI values as arguments and a probe that owns the one impure step. Detecting what a machine is was never an entitlement concern, and keeping the classifier pure means it is testable without mocking anything.
HA virtual machines are now recognised. IXKVM comes out of the QEMU manufacturer plus the node serial convention, and bhyve is only treated as HA when the TrueNAS_A/TrueNAS_B backplane device is actually attached, so ordinary development bhyve VMs are not swept in. Both map to TrueNAS hardware, which moves them from the community to the hardware side of the feature matrix: an unlicensed HA VM loses dedup and gains SED, and a licensed HA VM without the fibre channel key gains fibre channel. That is a deliberate call - an HA VM stands in for an appliance and should be entitled as one.
The whole answer is cached for the life of the process since nothing it reads can change without a reboot, and the backplane scan only runs when DMI already says the guest is bhyve. The backplane probe reads sysfs rather than going through udev; the two resolve to the same file and sysfs does not need udevd running. The mypy workflow picks up the new package, without which it would not be type checked at all.
Phase out system.is_enterprise from HA and alert plumbing
This commit adds changes to replace system.is_enterprise with system.is_ha_capable wherever the decision is really about HA hardware rather than licensing, and to route the two decisions that genuinely are licensed - sudo audit logging and the force_topology guard - through the entitlement engine instead.
The cron template drops its product check entirely since failover.status already returns SINGLE on anything without an HA license, and the alert base and failover hooks it touches all guard on failover.licensed internally, so widening them to HA-capable hardware is inert on unlicensed boxes.
The force_topology error message and its API field description said "Enterprise-licensed systems", which stopped being accurate once the gate moved onto the support entitlement - a licensed Mini would have been told it was Enterprise. Both now say what the gate actually checks.
Remove JBOF as a licensed feature
This commit adds changes to drop JBOF from the entitlement engine entirely, since shelf ownership is a quantity that lives in the license enclosure counts and a boolean feature key cannot express it. jbof.licensed goes back to reading those counts directly, and the addhw injection bucket that existed only to synthesise the key goes with it.
The system serial check that used to sit alongside that count stays removed, which is deliberate: the enclosure counts are enough on their own.
Let hardware detection failures surface
This commit adds changes to stop swallowing detect_platform exceptions in the hardware probe, so a detector that fails raises instead of degrading to a chassis-only classification. A chassis tag cannot say whether a machine is one half of an HA pair, so answering from it alone invents an answer that was never obtained.
This restores the behaviour every existing caller already has: failover.hardware has reached detect_platform on the boot path for years with no handling of its own.
Reconcile license derived config when the license changes
This commit adds changes to converge every subsystem whose configuration is derived from the license once that license changes. `truenas.license.upload` regenerated exactly one etc group and then fired `system.post_license_update` detached. That was survivable while gates asked `is_enterprise`, which expanded to `is_ha_capable OR (licence AND model AND not freenas)` -- on appliance hardware the first limb was already true before any license was installed, so those gates did not change their answer when one arrived and never re-rendering them cost nothing. Now that they are feature key checks they do change their answer, and a spread of etc groups were left silently stale until something unrelated regenerated them or the box rebooted. The plainest case is sudoers, where sudo command auditing simply did not turn on when an appliance was licensed.
Each affected subsystem now registers a LicenseReconcileDelegate from its own setup, naming the etc groups it owns and what should happen once they have been re-rendered, and a runner walks the eight of them on the hook. Doing this per plugin rather than centrally in etc matters because the right action differs between them, and not always for the obvious reason. The block target delegates only render, but they still decline to run when their service is stopped, because for nvmet and LIO writing the config is itself the reconfiguration rather than a file something reads later. smb and discovery reload a real daemon. The user delegate reloads a pseudo service that has no daemon behind it at all, so there the reload is simply how the group gets regenerated. And ctdb gets a restart because the presence of its config file is what decides whether the daemon can start. Registration refuses a duplicate name or a group another delegate already claims, so two plugins cannot quietly both own one.
The hook is still fired detached, so the upload itself does not wait on the pass. failover.status is dropped from its cache ahead of it, since a good half of these groups read that while rendering and would otherwise reconcile against the status cached under the old license, and truesearch is registered async so that waiting on its service job cannot hold the other consumers up behind it.
The cron template also goes back to rendering unless failover.status is definitively BACKUP. Keying it on SINGLE or MASTER instead, which is where it had drifted to, meant a transient ELECTING, IMPORTING or ERROR read emptied the whole schedule -- every user cronjob, rsync task, cloud sync, scrub and update check -- with nothing to put it back until the group happened to be regenerated again.
Fold platform detection into the hardware package
This commit adds changes to move detect_platform into middlewared.utils.hardware and have the classifier consult it first, so the QEMU and bhyve rules live in one place rather than two copies that can drift apart. The function is moved verbatim; MANUAL is not treated as an answer and falls through to the chassis tag, so R-series, Z-series, Minis and whiteboxes classify exactly as they did before.
One population changes: a QEMU virtual machine stamped as an HA node now classifies as IXKVM even when its chassis tag names a Mini, so it lands in the appliance column instead of the Mini one. detect_platform also picks up its first test coverage, having had none.
Drop the license level expiry from every surface
This commit adds changes to remove the license level expiry, now that the license schema has no top-level expires_at at all. The daemon replaced it with an issued_at mint date, so from_license_status was reading an attribute LicenseStatus no longer defines and raised AttributeError on every valid v2 license. Expiry lives on the features themselves now and SUPPORT is the only one we act on, so LicenseInfo drops license_expires_at, expired() becomes support_lapsed(), system.license drops its expired key, truenas.license.info drops its top-level expires_at, and the legacy adapter stops stamping the contract end onto features that were bought outright.
contract_end and contract_start stay, and both now carry the SUPPORT contract's dates — contract_start was hardcoded null and is populated for the first time since 25.10. contract_end in particular cannot go: the 25.10 dashboard reads remote_info.license.contract_end without a null guard, and failover.upgrade only reboots the standby, so an un-upgraded active merges an upgraded standby's payload for as long as the operator defers the failover. Dropping license_expires_at is a no-op for the fielded fleet either way, since the legacy adapter always hardcoded it to None and the or already fell through to the support date.
The TrueNAS Connect registration gate goes with it. A lapsed support contract does not invalidate a perpetual license, so withholding the PEM was wrong on its own merits, and the gate read .get('expired', True) — with the key gone it would have failed closed and silently stopped sending the license with no exception and no log line.
Expose a public entitlement endpoint with an open feature vocabulary
This commit adds changes to expose truenas.entitlements.feature, a public endpoint that answers the entitlement question for a single feature without constraining the feature name, so an identifier we do not gate is reported as NOT_GATED instead of being rejected by the schema. The private truenas.entitlements.check is left alone: it keeps the enum argument, the matrix column and the loud raise, which are what internal gates want.
system.feature_enabled now delegates to the new endpoint and is marked removed_in v26, so it finally agrees with the entitlement engine rather than answering from raw license key membership. That is a behaviour change for external callers on unlicensed and partially licensed systems, always in the permissive direction.