Tasks, notifications, triggers and conditions

The following task types can be configured in Advanced settings, under Tasks and actions:

  • Log file maintenance. By default, the SSH Server will archive its old textual log files by compressing them into a subdirectory of the log file directory.

  • Execute command. You can configure the SSH Server to run commands, including PowerShell or Windows Command Prompt scripts. An Execute command task runs in the security context of a configured Task profile. It can be scheduled to run regularly based on calendar time, or triggered by log events recorded by the SSH Server. Log event triggers can involve complex scripted conditions.

  • Email notification. Built-in support for email sending can be enabled in Advanced settings under SMTP sending. If enabled, you can then configure Email notification tasks, triggered primarily by log events recorded by the SSH Server. Log event triggers can involve complex scripted conditions.

Task profiles and network shares

If you configure a task of type Execute command, the command will run under the Task profile which you configure.

A task should run under a task profile that provides the Windows permissions it needs. You can use existing task profiles, or configure your own, so that the task will run as a specific Windows account of your choice.

The SSH Server usually runs as Local System. If you use the task profile Run as SSH Server service account, the command will run as Local System and will have the Windows permissions of Local System.

If a task needs to access network shares, you can edit the associated task profile and configure one or more Windows file shares entries. This will instruct the SSH Server to establish a connection to the network share in the Windows logon session used to run the task.

Handling event data

For Execute command tasks triggered by a log event, the SSH Server defines the environment variable SSHLOGEVENT. This variable contains JSON-encoded data for the event that triggered the command. This can be loaded by a PowerShell script as follows:

$e = ConvertFrom-Json $env:SSHLOGEVENT

For example, if the event is I_SFS_TRANSFER_FILE, the local path can then be accessed as:

$e.event.sfs.parameters.path

On-upload command vs. I_SFS_TRANSFER_FILE

The following SSH Server functionality has some overlap:

  • The On-upload command can be configured in Advanced settings - either for an individual account in its account settings entry, or in a group settings entry, as a default for multiple users.

  • Tasks can be configured in Advanced settings, under Tasks and actions, using many possible trigger events, including I_SFS_TRANSFER_FILE.

The following are the most significant differences:

  • The On-upload command is smarter. In recent SSH Server versions, it will wait for some time until the client has closed the file, to see if the file is going to rename the file, move it, or update its modification time. The On-upload command is run only after the client has closed the file and has not touched it for some time.

  • A task triggered by I_SFS_TRANSFER_FILE will be scheduled to run as soon as the event is logged. The task may run concurrently with any actions the client is still taking, including renaming the file, moving it, or updating its modification time.

  • The On-upload command can be configured to run either in the security context of the client's logon session, or in the security context of the main SSH Server service. It cannot be configured to run in other security contexts.

  • A task can be configured to run in an arbitrary security context configured under Task profiles. However, it cannot be configured to run in the security context of the client's logon session.

  • A task can use the SSH Server's built-in email support to send a notification. An On-upload command can also send an email notification, but has to use external functionality such as a PowerShell script. An On-upload command cannot leverage the SSH Server's built-in email support.

Recommendation:

  • Use the On-upload command for actions which process a file, move a file, or make changes to a file after it is uploaded. The On-upload command attempts to start after the client is done working with the file, to avoid interfering with the client's actions.

  • Use a task triggered by I_SFS_TRANSFER_FILE for actions which do not interact with the uploaded file, but perform an independent effect. For example, use a task to send an email notification.

Scripting trigger conditions

If you want an Execute command or Email notification task to run every time the SSH Server logs an event, no trigger condition is necessary. You can leave the Condition setting for the trigger empty, and the task will be scheduled every time the event is logged.

But you may want to avoid running a task unless specific conditions are met. In this case, you must know the layout of the log event for which you are scripting the condition. To discover the event layout:

  1. Reproduce the event which you want to trigger your condition, e.g. by performing an action that should trigger it.

  2. Find the event in the textual log files recorded by the SSH Server. By default, textual log files can be found in the Logs subdirectory of the SSH Server installation directory.

The event I_SFS_TRANSFER_FILE is an Information-level event which is logged by the SSH Server every time a client reads or writes file content and then closes a file using any file transfer method - SFTP, SCP, FTPS, or BvShell. Example:

<event seq="84" time="2022-01-30 08:21:16.663977 -0600" app="BvSshServer 9.14" type="Info" name="I_SFS_TRANSFER_FILE" desc="Virtual filesystem: transfer file."> <conn id="1001" winSesId="C1001" service="SSH" remoteAddress="10.1.1.17:55123" windowsAccount="COMPUTER\User" winSes="new"/> <channel type="session" id="1"/> <sfs moduleName="FlowSfsWin" mountPath="/" code="90000" desc="File transfer ended."> <parameters path="C:\Path\UploadExample.txt" timeMs="2" bytesRead="0" bytesWritten="12091" readRangeOffset="0" readRangeLength="0" writeRangeOffset="0" writeRangeLength="12091" createdNewFile="true" resizedFile="false" startSize="0" finalSize="12091" endedBy="Client" download="none" upload="full"/> <help message="File transfer ended by client."/> </sfs> </event>

This event contains the following variables. All of these can be used in trigger conditions:

seq, time, app, type, name, desc conn.id, conn.winSesId, conn.service, conn.remoteAddress, conn.virtualAccount, conn.windowsAccount, conn.winSes channel.type, channel.id sfs.moduleName, sfs.mountPath, sfs.code, sfs.desc sfs.parameters.path, sfs.parameters.timeMs, sfs.parameters.bytesRead, sfs.parameters.bytesWritten, ..., sfs.parameters.startSize, sfs.parameters.finalSize, sfs.parameters.endedBy, sfs.parameters.download, sfs.parameters.upload sfs.help.message

In order to be used as a trigger condition, an event must actually be logged. An event that is not configured to be logged, either in the Windows Event Viewer or in the SSH Server's textual log files, will not work as a trigger.

Boolean operators

The SSH Server supports the boolean operators and, or and not. These are used as follows:

(expression) and (expression) (expression) or (expression) not (expression)

Parentheses are optional, but recommended for clarity. The following also works:

expression and expression expression or expression not expression

Comparison operators

The SSH Server supports simple comparison operators, all of which take the form:

variable.name operator value

The value can be a decimal integer; a string in single or double quotes; a boolean literal (true, false); or a date/time.

Comparison operators:

  • eq: true if the variable equals the value.
  • ne: true if the variable does not equal the value.
  • ge: true if the variable is greater than or equal to the value.
  • gt: true if the variable is greater than the value.
  • le: true if the variable is less than or equal to the value.
  • lt: true if the variable is less than the value.
  • startsWith: true if the variable starts with a specified string prefix.
  • endsWith: true if the variable ends with a specified string suffix.
  • contains: true if the variable contains a specified substring.

Operations on strings are case-insensitive using NTFS-like rules. Strings that contain spaces must be quoted. If a string is single-quoted, special characters are not escaped, and the single quote cannot appear in the string. If a string is double-quoted, special characters (", `) must be escaped using the grave accent (`).

Date/time values can be specified in any of the following ISO 8601 or ISO-like formats:

"HH:MM" 13:00
"YYYY-MM-DD HH:MM" "2020-01-01 13:00"
"YYYY-MM-DDTHH:MM:SS" "2020-01-01T13:00:30"
"YYYY-MM-DD HH:MM +/-ZONE" "2020-01-01 13:00 +1000"
"YYYY-MM-DDTHH:MM:SS+/-ZONE" "2020-01-01T13:00:30-2000"

If a time zone is not specified, local time is assumed.

"any" and "contains"

The any operator tests if a variable is defined:

any variable.name

The contains operator tests if a structure or list contains any member that matches an expression:

variable.name contains (expression) variable.name not contains (expression)

When using the contains operator in this way, parentheses around the expression are required.

Syntax

[...] means the inside of square brackets is optional [...]* means zero or more repetitions of an optional part "..." means the quoted text appears literally ... | ... means one of the options { ... } means the inside of curly brackets appears together mainExpr := expr | { ["not"] ["("] mainExpr [booleanOp mainExpr]* [")"] } booleanOp := "and" | "or" expr := ["not"]{ simpleExpr | advancedExpr } simpleExpr := { "any" memberName } | { memberName simpleOp value } simpleOp := "eq" | "ne" | "ge" | "gt" | "le" | "lt" | "startsWith" | "endsWith" | "contains" advancedExpr := memberName ["not"] advancedOp "(" mainExpr ")" advancedOp := "contains"

Examples using I_CONNECT_ACCEPTED

Condition triggers when the SSH Server is handling more than 100 SSH connections, or more than 100 FTPS connections, whether the clients logged in or not:

(conns.ssh gt 100) or (conns.ftp gt 100)

Condition triggers when the SSH Server is handling more than 100 authenticated SSH connections, or more than 100 authenticated FTPS connections:

(conns.sshAuth gt 100) or (conns.ftpAuth gt 100)

Examples using I_SFS_TRANSFER_FILE

Condition triggers when a client creates a new file:

sfs.parameters.createdNewFile eq true

Condition triggers when virtual account "User" writes more than 100 MB into a file before closing it:

(conn.virtualAccount eq "User") and (sfs.parameters.bytesWritten gt 104857600)

Condition triggers for an upload into the virtual filesystem mount point /incoming where the client wrote at least one byte to the file, or created the file, and the file extension is .txt:

(sfs.mountPath eq "/incoming") and (sfs.parameters.path endsWith ".txt") and (sfs.parameters.upload ne "none")

Condition triggers for a download from server-side directory C:\Dir where the file extension is .pdf, and the client did not write to the file or create it:

(sfs.parameters.path startsWith "C:\Dir\") and (sfs.parameters.path endsWith ".pdf") and (sfs.parameters.download ne "none") and (sfs.parameters.upload eq "none")

In the I_SFS_TRANSFER_FILE event, the download and upload attributes attempt to categorize if the client interacted with the file in a way that can be considered a download, an upload, or both. In the above examples:

  • The first example considers an "upload" a file interaction which creates or writes to the file, whether or not it also reads from it. A resumed upload may involve reading some of a file before writing.

  • The second example considers a "download" a file interaction which reads from the file, but does not create it or write to it.

Condition triggers for any complete download by virtual account "User" where the client did not write to the file or create it:

(conn.virtualAccount eq "User") and (sfs.parameters.endedBy eq "Client") and (sfs.parameters.download eq "full") and (sfs.parameters.upload eq "none")

Same example, but for a Windows domain account:

(not any conn.virtualAccount) and ((conn.windowsAccount eq "DOMAINNAME\User") or (conn.windowsAccount eq "user@domainname.com")) and (sfs.parameters.endedBy eq "Client") and (sfs.parameters.download eq "full") and (sfs.parameters.upload eq "none")

Examples using I_SERVICE_CONFIG_DESCRIPTION

Condition triggers when the SSH Server configuration contains any host key that uses an algorithm other than RSA:

(parameters.configPart eq "HostKeys") and (event contains (keypair.algorithm ne "RSA"))

Condition triggers when the SSH Server configuration contains no host authentication keypairs:

(parameters.configPart eq "HostKeys") and (not any keypair)

Examples using I_LOGON_AUTH_COMPLETED

Condition triggers if any account logs in before 6 AM or after 8 PM in the local time zone:

(time lt 06:00) or (time gt 20:00)

Condition triggers when virtual account "AccountName" logs in:

conn.virtualAccount eq "AccountName"

Condition triggers when a Windows account logs in which is a member of "DOMAINNAME" or "domainname.com":

(not any conn.virtualAccount) and ((conn.windowsAccount startsWith "DOMAINNAME\") or (conn.windowsAccount endsWith "@domainname.com"))

Condition triggers when a user logs in after more than 2 authentication attempts:

authentication.attemptNr gt 2

Diagnosing issues with triggers

When a trigger is filtered out by a condition, so that the action does not run, you can monitor this in the SSH Server's textual log files by enabling the Trace-level event T_TASK_RUNNER_TRIGGER_FILTERED.

If the SSH Server cannot evaluate a condition because of an error - for example, if a string data type is used where an integer is expected - then the SSH Server will log the event W_TASK_RUNNER_TRIGGER_NOT_EVALUATED.