HAAGE&PARTNER Computer GmbH  HAAGE&PARTNER

Sawmill Analytics

Analyse und Reporting
für Web | Netzwerk | Sicherheit

Zugriffs- und Datenanalyse von Server-Logs (Proxy, Mailserver, Firewall, Webserver) und Überwachung der Sicherheit & Performance, Schwachstellenanalyse.

Sawmill Analytics 8 | Loganalyse

Sawmill-Tutorial

Creating Custom Actions


Note: this applies only to Sawmill 8 and later; earlier versions of Sawmill do support custom actions.

Sawmill supports a number of built-in "actions" which can be run from the command line with the -a option, including bd (build database), ud (update database), srbe (send report by email) and many more. Starting in Sawmill 8, this has been extended to allow you to create your own -a actions, for performing any custom task. Custom -a actions are defined using a CFG text file, which contains some structured information (parameters, types, defaults, documentation), and a chunk of Salang code which implements the action. Several custom actions are included with the default installation of Sawmill:
and can be used as examples of the format of the custom action file. In this newsletter we will create a new action, one which creates a new user (this custom action will also be included in future versions of Sawmill).

This newsletter assumes some knowledge of programming or scripting; when you create a custom action, you are basically writing a script in Salang (Salang is the Sawmill Language, the built-in scripting language used throughout Sawmill).


Creating A Custom Action To Create A New User

Users can be created from the Sawmill web interface, or by editing users.cfg directly, but there is no command line for creating a user. This is a useful feature to have, and a good way to demonstrate custom actions and some Salang concepts, so we'll add it now. To add a new custom action, all you do is create a new CFG file in LogAnalysisInfo\actions. Here's the file for this new action; we'll explain it bit-by-bit below:


 create_user = {

   label = "Create User"
   shortcut = "cu"
   parameters = {
     username = {
       shortcut = "u"
       required = true
     }
     pass = {
       shortcut = "pw"
       required = true
     }
     roles = {
       shortcut = "r"
       required = true
     }
     profiles_allowed = {
       shortcut = "pa"
       required = false
     }
   }
   expression = `

 # Create a new subnode of users, named after the username
 node users = 'users';
 node user = users{username};

 # Set the "username" subnode to the username
 @user{'username'} = username;

 # Set the password checksum
 @user{'password_checksum'} = md5_digest(pass);

 # If the -pa option was specified, set which profiles this user can access
 if (profiles_allowed ne "(unspecified)") then (

   # Build the "access.0.profiles" node from the profiles_allowed option (which is comma-separated profile names)
   split(profiles_allowed, ',', 'v.profiles_allowed_split');
   node profile;
   foreach profile 'v.profiles_allowed_split'
     @user{'access'}{'0'}{'profiles'}{@profile} = @profile;

 ); # if profiles_allowed

 # Build the "access.0.roles" node from the roles option (which is comma-separated node names)
 split(roles, ',', 'v.roles_split');
 node role;
 foreach role 'v.roles_split'
   @user{'access'}{'0'}{'roles'}{@role} = @role;

 # Save the users.cfg file
 save_node(users);

 echo("Created user " . username);

 ` # expression
 
 } # create_user


create_user.cfg

Below, we describe each part of this file.


The CFG Node Name Wrapper

Every CFG file must start and end with the lines shown below. The name of the file is create_user.cfg, so the name of the node (nodes are a basic data type used in Sawmill and Salang; they can store any type of data, including maps/hashes) is create_user, so the file must start with "create_user = {" and end with "} # create_user":


 create_user = {

 ...
 
 } # create_user


The final "# create_user" is really just a comment, but it is strongly recommended that you include this comment, to make it easier to see which bracket you're closing.

The filename must match the node name listed on the first line, or Sawmill will give an error when it attempts to access the file.


The Label And Shortcut

Within the custom action node, there must be a label and shortcut node:


   label = "Create User"
   shortcut = "cu"



The label node is the human-readable name of the action, as it will appear in the documentation (custom actions automatically document themselves in the -a section of the documentation), and anywhere else in the web interface where custom actions might later be included.

The shortcut is a short form of the action's internal name, which can be used on the command line in place of the full action. With an action internal name of create_user and a shortcut of cu, this action can be run with either "sawmill -a create_user ..." or "sawmill -a cu".


The Custom Action Parameters


   parameters = {
     username = {
       shortcut = "u"
       required = true
     }
     pass = {
       shortcut = "pw"
       required = true
     }
     roles = {
       shortcut = "r"
       required = true
     }
     profiles_allowed = {
       shortcut = "pa"
       required = false
     }
   }



Custom actions can take command-line parameters. These parameters are listed in the "parameters" subnode of the custom action node. In this case, we have four parameters: username, pass, roles, and profiles_allowed. They all have shortcuts, too, so you can use either "-username bernice" or "-u bernice" on the command line. The "required" subnode of the parameter specifies whether a shortcut must be present on the command line--if it is true, and the action is attempted without specifying the required parameter, Sawmill will respond with an error message stating that the parameter is required.

In this case:
There is also a "description" subnode which can be included as a subnode of any parameter; if this exists, it will be included in the documentation as a description of the parameter. These are omitted here for brevity, and are not required; see the standard convert_version_7_profile custom action for a fully self-documented example.


The Expression Wrapper

The expression subnode of the action defines a Salang expression which is executed when the action is executed. The Salang expression is in a string parameter called "expression", so the following must wrap the expression:


   expression = `
<Salang code>

 ` # expression



The string is quoted using backtick quotes (`), so that both single and double quotes can be used without escaping, within the code itself. As earlier, the "# expression" section is a comment, and is technically optional, but is strongly recommended to make it easier to see what the quote is closing.


The Expression

The work of the action is done by the Salang code itself:


 # Create a new subnode of users, named after the username
 node users = 'users';
 node user = users{username};

 # Set the "username" subnode to the username
 @user{'username'} = username;

 # Set the password checksum
 @user{'password_checksum'} = md5_digest(pass);

 # If the -pa option was specified, set which profiles this user can access
 if (profiles_allowed ne "(unspecified)") then (

   # Build the "access.0.profiles" node from the profiles_allowed option (which is comma-separated profile names)
   split(profiles_allowed, ',', 'v.profiles_allowed_split');
   node profile;
   foreach profile 'v.profiles_allowed_split'
     @user{'access'}{'0'}{'profiles'}{@profile} = @profile;

 ); # if profiles_allowed

 # Build the "access.0.roles" node from the roles option (which is comma-separated node names)
 split(roles, ',', 'v.roles_split');
 node role;
 foreach role 'v.roles_split'
   @user{'access'}{'0'}{'roles'}{@role} = @role;

 # Save the users.cfg file
 save_node(users);

 echo("Created user " . username);


When executed with a command like this:

  sawmill -a cu -u cindy -pw "cnd#yZpAswd" -r role_2 -pa "ae,ae2"

this code adds the following node to the users.cfg file in LogAnalysisInfo, which adds user "cindy" to Sawmill:


  cindy = {
    username = "cindy"
    password_checksum = "794be188061e1443d2e3def9e4de88f6"
    access = {
      0 = {
        profiles = {
          ae = "ae"
          ae2 = "ae2"
        } # profiles
        roles = {
          role_2 = "role_2"
        } # roles
      } # 0
    } # access
  } # cindy



The code is documented with general comments, but for programmers unfamiliar with Salang, here is a detailed analysis of what the code is doing, and how. The code is the same as above; but with blue explanations interleaved below each section of code:


 # Create a new subnode of users, named after the username
 node users = 'users';

When used in a node context, a string is treated as a full nodename. Therefore "users", when used in node context (e.g., assigning to a node variable, as above), tells Sawmill to find the node whose full nodename is "users". The root of the node hierarchy is the LogAnalysisInfo folder, so "users" refers to the node named "users" within LogAnalysisInfo. This could be a folder called "users", whose value is its structured contents; or a file called "users.cfv" whose value is its literal contents; but since neither exist, it finds the file "users.cfg" whose value is its structured contents. So "node users = 'users'" gets a node points to users.cfg, and automatically loads it into memory, using the structured contents of the file (i.e., parsing "name = value" pairs in the text of the file, and treating {} sections as subnodes).

 node user = users{username};

users{username} gets a pointer to the subnode of users (which points to users.cfg), whose name is the value of the username parameter. So this finds the "cindy" subnode of "users". If it does not exist, it creates it with an empty value. Then it assigns it to the "user" variable, which can then be used through the remainder of the code to access the new user node.

 # Set the "username" subnode to the username
 @user{'username'} = username;

@user{'username'} refers to the value of the "username" subnode of the user node (as opposed to user{'username'}, which would be a pointer to the node, not its value). Put another way, @ dereferences the pointer to the "username" subnode. Assigning the value of the username variable to this sets the value of that subnode.

 # Set the password checksum
 @user{'password_checksum'} = md5_digest(pass);

md5_digest() is a built-in Salang function which returns the MD5 digest of its parameter; this then assigns that to the "password_checksum" subnode of the user node.

 # If the -pa option was specified, set which profiles this user can access
 if (profiles_allowed ne "(unspecified)") then (

When a custom action parameter is not specified, its value is set to literally "(unspecified)". So this checks if the -pa option was specified.

   # Build the "access.0.profiles" node from the profiles_allowed option (which is comma-separated profile names)
   split(profiles_allowed, ',', 'v.profiles_allowed_split');

split() is a built-in Salang function which is being used in this case to split the profiles_allowed list on commas into a temporary node (v.profiles_allowed_split) for use in the loop below.

   node profile;
   foreach profile 'v.profiles_allowed_split'

This loop cycles through all subnodes of the temporary node created above. The variable "profile" points to each subnode in turn, so @profile is the name of each profile in turn.

     @user{'access'}{'0'}{'profiles'}{@profile} = @profile;

By stringing together a sequence of {} operators, this line creates an "access" subnode of the user record, and within that a "0" subnode, and within that a "profiles" subnode, and within that a subnode whose name is the profile name; then it assigns the value of that bottom subnode to the name of the profile.

 ); # if profiles_allowed

 # Build the "access.0.roles" node from the roles option (which is comma-separated node names)
 split(roles, ',', 'v.roles_split');
 node role;
 foreach role 'v.roles_split'
   @user{'access'}{'0'}{'roles'}{@role} = @role;

This loop does the same as the profiles loop above, but for roles.

 # Save the users.cfg file
 save_node(users);

The users.cfg was automatically loaded from disk by the reference to "users" at the top. All modifications to this node have been made in memory so far. This line writes the node back to disk, replacing the disk version of users.cfg with the memory version.

 echo("Created user " . username);

Finally, this echoes information to the standard output stream, to describe what it just did.


[Article revision v1.0]


Professionelle Dienstleistungen

Sollten Sie die Anpassung von Sawmill Analytics nicht selbst vornehmen wollen, können wir Ihnen dies als Dienstleisung anbieten. Unsere Experten setzen sich gerne mit Ihnen in Verbindung, um die Reports oder sonstige Aspekte von Sawmill Analytics an Ihre Gegebenheiten und Wünsche anzupassen. Kontakt

Zur Tutorial-Übersicht

Weitere Informationen

      Live-Demonstrationen »    
© 1995-2011 HAAGE & PARTNER Computer GmbH · Impressum · Datenschutz · www.haage-partner.de