Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ This file documents the revision history for the SNClient agent.
next:
- fix remaining zombie processes after automatic updates
- check_service: add wildcard support for exclude argument
- add credential storing system to the config
- check_drivesize: windows only, add support for connecting to shares using credentials
- check_drivesize: add support for hidden shares

0.49 Fri Aug 21 08:50:54 CEST 2026
- linux: reset environment when running elevated commands (GHSA-p72w-3vw7-cg4p / CVE not yet assigned)
Expand Down
25 changes: 15 additions & 10 deletions docs/checks/commands/check_drivesize.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,19 @@ Naemon Config

## Check Specific Arguments

| Argument | Description |
| ------------------------- | ----------------------------------------------------------------------------------------- |
| drive | The drives to check, e.g. C:\ or / |
| exclude | List of drives to exclude from check |
| folder | The folders to check (parent mountpoint) |
| freespace-ignore-reserved | When false, root-reserved space is subtracted from the total size. Default: true |
| ignore-unreadable | Deprecated, use filter instead |
| magic | Magic number for use with scaling drive sizes. Note there is also a more generic magic factor in the perf-config option. |
| mounted | Deprecated, use filter instead |
| total | Include the total of all matching drives |
| Argument | Description |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| add-persistent-network-drives | Include persistent network drives (net use /persistent), even if currently disconnected, in the all/all-shares listing |
| drive | The drives to check, e.g. C:\ or / |
| exclude | List of drives to exclude from check |
| folder | The folders to check (parent mountpoint) |
| freespace-ignore-reserved | When false, root-reserved space is subtracted from the total size. Default: true |
| ignore-unreadable | Deprecated, use filter instead |
| magic | Magic number for use with scaling drive sizes. Note there is also a more generic magic factor in the perf-config option. |
| mounted | Deprecated, use filter instead |
| share-password | Windows only: password used to authenticate to the network shares given in this check. If set to an empty string, no password is used. If omitted, the cached/default password for the user is used. |
| share-user | Windows only: username used to authenticate to the network shares given in this check. The connection is established on demand and removed again after the check. |
| total | Include the total of all matching drives |

## Attributes

Expand Down Expand Up @@ -125,4 +128,6 @@ these can be used in filters and thresholds (along with the default attributes):
| hotplug | Windows only: flag drive is hotplugable (0/1) |
| remote_name | Windows only: the remote name of the drive, if it uses a network name |
| persistent | Windows only: if the network drive is mounted as persistent (0/1) |
| connected | Windows only: if the network drive is currently connected (0/1) |
| hidden | Windows only: if the network share is a hidden share, i.e. the share name ends with a dollar sign like C\$ (0/1) |
| localised_remote_path | Windows only: If the path is given as a remote path, and that remote path has an assigned logical drive, this is the replaced path under that logical drive. |
117 changes: 105 additions & 12 deletions pkg/snclient/check_drivesize.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,18 @@ func defaultExcludedFsTypes() []string {
}

type CheckDrivesize struct {
drives []string
folders []string
excludes []string
total bool
magic float64
mounted bool
ignoreUnreadable bool
hasCustomPath bool
freespaceIgnoreReserved bool
drives []string
folders []string
excludes []string
total bool
magic float64
mounted bool
ignoreUnreadable bool
hasCustomPath bool
freespaceIgnoreReserved bool
addPersistentNetworkDrives bool
shareUser string
sharePassword string
}

func NewCheckDrivesize() CheckHandler {
Expand All @@ -81,6 +84,7 @@ func NewCheckDrivesize() CheckHandler {
}
}

//nolint:funlen // there are lots of attributes in this check
func (l *CheckDrivesize) Build() *CheckData {
return &CheckData{
name: "check_drivesize",
Expand All @@ -100,6 +104,17 @@ func (l *CheckDrivesize) Build() *CheckData {
"mounted": {value: &l.mounted, description: "Deprecated, use filter instead"}, // deprecated and unused, but should not result in unknown argument
"ignore-unreadable": {value: &l.ignoreUnreadable, description: "Deprecated, use filter instead"}, // same
"freespace-ignore-reserved": {value: &l.freespaceIgnoreReserved, description: "When false, root-reserved space is subtracted from the total size. Default: true"},
"add-persistent-network-drives": {
value: &l.addPersistentNetworkDrives, description: "Include persistent network drives (net use /persistent), even if currently disconnected, in the all/all-shares listing",
},
"share-user": {
value: &l.shareUser, description: "Windows only: username used to authenticate to the network shares given in this check. " +
"The connection is established on demand and removed again after the check.",
},
"share-password": {
value: &l.sharePassword, description: "Windows only: password used to authenticate to the network shares given in this check. " +
"If set to an empty string, no password is used. If omitted, the cached/default password for the user is used.",
},
},
defaultFilter: l.getDefaultFilter(),
defaultWarning: "used_pct > 80",
Expand Down Expand Up @@ -152,6 +167,8 @@ func (l *CheckDrivesize) Build() *CheckData {

{name: "remote_name", description: "Windows only: the remote name of the drive, if it uses a network name"},
{name: "persistent", description: "Windows only: if the network drive is mounted as persistent (0/1)", unit: UBool},
{name: "connected", description: "Windows only: if the network drive is currently connected (0/1)", unit: UBool},
{name: "hidden", description: "Windows only: if the network share is a hidden share, i.e. the share name ends with a dollar sign like C$ (0/1)", unit: UBool},
{name: "localised_remote_path", description: "Windows only: If the path is given as a remote path, and that remote path has an assigned logical drive," +
" this is the replaced path under that logical drive."},
},
Expand All @@ -165,7 +182,7 @@ func (l *CheckDrivesize) Build() *CheckData {
}
}

//nolint:funlen // no need to split the function, it is simple as is
//nolint:funlen,gocyclo,maintidx,contextcheck,nolintlint // no need to split the function, it is simple as is , context is constructed when needed
func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData, _ []Argument) (*CheckResult, error) {
enabled, _, _ := snc.config.Section("/modules").GetBool("CheckDisk")
if !enabled {
Expand Down Expand Up @@ -234,6 +251,78 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData

l.tidyThresholdDriveValues(check)

// resolve the credential to use for each UNC share in this check.
// share-user / share-password override any credentials from the config section.
shareCredentials := map[string]Credential{}
for _, k := range keys {
drive := requiredDisks[k]
if !isNetworkSharePath(drive["drive_or_id"]) {
continue
}
root := shareRoot(drive["drive_or_id"])
if root == "" {
continue
}
if _, ok := shareCredentials[root]; ok {
continue
}

if check.hasArgsSupplied["share-user"] {
// user is always needed, but password can be empty for a valid login
shareCredentials[root] = Credential{
Type: CredentialTypeWindowsShare,
Target: shareTargetFromUNCPath(root),
Username: qualifyUsername(l.shareUser, currentUserDomain()),
Password: l.sharePassword,
PasswordSet: check.hasArgsSupplied["share-password"],
Strategy: CredentialStrategyOnDemand,
}

continue
}

if cred, ok := findOnDemandCredential(snc.config, shareTargetFromUNCPath(root)); ok {
shareCredentials[root] = cred
}
}

// keep track of the connections snclient established, later tear down only the newly added connections
addedConnections := map[string]bool{}

for root, cred := range shareCredentials {
// drop a stale session first, otherwise Windows SMB path redirector keeps reusing stale session, new credential is not used.
if err := deleteShareConnection(root); err != nil {
log.Debugf("credentials: could not drop existing connection for %s: %s", root, err.Error())
}

if err := addShareConnection(&cred, root); err != nil {
// a connection with different credentials may still be around, force it away and try once more
if errors.Is(err, errSessionCredentialConflict) {
_ = deleteShareConnection(root)
err = addShareConnection(&cred, root)
}
if err != nil {
log.Errorf("credentials: failed to connect to %s: %s", root, err.Error())

continue
}
}
log.Debugf("credentials: established connection for %s", root)
addedConnections[root] = true
}

// remove all newly added connections again after the check finished
defer func() {
for root := range addedConnections {
if err := deleteShareConnection(root); err != nil {
log.Errorf("credentials: failed to remove connection for %s: %s", root, err.Error())

continue
}
log.Debugf("credentials: removed connection for %s", root)
}
}()

for _, k := range keys {
if ctxErr := ctx.Err(); ctxErr != nil {
return nil, fmt.Errorf("disk scan canceled: %w", ctxErr)
Expand Down Expand Up @@ -267,10 +356,14 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData

// remove errored paths unless custom path is specified
if !l.hasCustomPath {
for i, entry := range check.listData {
for idx, entry := range check.listData {
if errMsg, ok := entry["_error"]; ok {
// persistent network drives added via add-persistent-network-drives are treated like custom paths, so surface their errors instead of skipping them
if l.addPersistentNetworkDrives && entry["persistent"] == "1" {
continue
}
log.Debugf("drivesize failed for %s: %s", entry["drive_or_id"], errMsg)
check.listData[i]["_skip"] = "1"
check.listData[idx]["_skip"] = "1"
}
}
}
Expand Down
Loading