#!/usr/bin/env perl # Copyright (c) 2025, R. W. Rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # freebsd_identity # FreeBSD (and OPNsense) identity section: UUID/serial source and placeholder rules # (shared in library.pm), virtualization from kern.vm_guest, and the OPNsense product # version from opnsense-version. # # Revision History: # 2026-10-18 Claude v1.0.0 new sensor, split from unix_identity (bug 210) package freebsd_identity; use strict; use warnings; use version; our $VERSION = version->declare('v1.0.0'); use FindBin; use File::Spec; use lib File::Spec->catdir($FindBin::Bin), File::Spec->catdir("$FindBin::Bin/.."); use library qw(:all); ####################################################### # # parseOpnsenseVersion( $text ) # # Parses opnsense-version -O (JSON) into identity keys. # # Parameters: # $text - output of opnsense-version -O # # Returns: # hash reference with os_distribution, os_version, os_nickname # (only the keys the product reports); empty when not parseable # # Side Effects: # none # ####################################################### sub parseOpnsenseVersion { my $text = shift; my %out; return \%out unless defined($text) && $text =~ /^\s*\{/; my $json; eval { require JSON::PP; $json = JSON::PP::decode_json($text); }; return \%out unless ref($json) eq 'HASH'; my %keys = ('product_name' => 'os_distribution', 'product_version' => 'os_version', 'product_nickname' => 'os_nickname'); foreach my $from (sort keys %keys) { $out{$keys{$from}} = trim($json->{$from}) if defined($json->{$from}) && !ref($json->{$from}) && trim($json->{$from}) ne ''; } return \%out; } ####################################################### # # main() # # Probes identity fields and emits the identity section lines. # # Parameters: # none # # Returns: # integer exit status (1 wrong OS) # # Side Effects: # runs kenv, sysctl, cat and opnsense-version, writes STDOUT/STDERR # ####################################################### sub main { if (!checkOS({ 'freebsd' => undef })) { print STDERR "unsupported os\n"; return 1; } my %p; $p{'rawUuid'} = trim(runCommand('kenv', '-q', 'smbios.system.uuid')); $p{'rawUuid'} = trim(runCommand('sysctl', '-n', 'kern.hostuuid')) if $p{'rawUuid'} eq ''; $p{'rawSerial'} = trim(runCommand('kenv', '-q', 'smbios.system.serial')); $p{'boardSerial'} = trim(runCommand('kenv', '-q', 'smbios.planar.serial')); $p{'maker'} = trim(runCommand('kenv', '-q', 'smbios.system.maker')); $p{'product'} = trim(runCommand('kenv', '-q', 'smbios.system.product')); $p{'machineId'} = -r '/etc/hostid' ? trim(runCommand('cat', '/etc/hostid')) : ''; $p{'virt'} = trim(runCommand('sysctl', '-n', 'kern.vm_guest')); $p{'virt'} = 'none' if $p{'virt'} eq ''; # OPNsense is FreeBSD underneath; its product version comes from opnsense-version if (validCommandOnSystem({'opnsense-version' => undef})) { $p{'extra'} = parseOpnsenseVersion(runCommand('opnsense-version', '-O')); } my $out = identityReport(\%p); print join("\n", @{$out}), "\n" if @{$out}; return 0; } exit main() unless caller; 1;