Saturday, 22 May 2010

Fun with bash: crc16, hashes and plugins

Today I released simple plugin architecture for bash scripts.

To do what it does, it uses three interesting features of bash:


  • The source command to read in the plugins

  • Bash arrays turned into hashes using a crc16 function written in bash

  • Bash functions within functions to limit the scope of plugin commands



This post describes those three interesting features.

Bash Plugin Architecture

The project page is at: http://code.google.com/p/b-p-a/

It allows you to create plugins like:


user@host:~/bpa$ cat plugins/security/nmap.shp
#!/bin/bash

registerPlugin "NMap" "nmap" "The mighty portscanner"

registerCommand 'SynScan' 'syn' 'TCP Syn scan of hosts'
registerCommand 'Ping' 'ping' 'Ping scan of hosts'

# Name of the plugin
NMap()
{
# Name of the command.
# Each command goes inside the Plugin() function
SynScan()
{
# Code for this command
hosts=$@
nmap -sS -P0 $hosts
}

Ping()
{
hosts=$@
nmap -v -sP $hosts
}
}


which can be used like


$ sudo security.sh --nmap --syn -- 192.168.0.1


For this to work, security.sh just needs to source the BPA.sh script and run a few commands (see the project page for details).

Sourcing plugins

This feature is probably the simplest, and part of the code of BPA.sh is below:


#
# Registers a new application.
#
# This must be the first thing called inside the application script.
#
# @param name
# Name of the application.
#
# @param debug
# (optional) If set to '1' debugging is enabled for the application. Defaults
# to '0'.
#
# @param plugins
# (optional) Sets the plugins directory for the application. Defaults to
# './plugins' inside the directory of the application script.
#
# @return
# Does not return a value
#
# Example:
# @code
# new 'MyApp' 1 '/var/lib/plugins/myapp'
#
new() {
sAppName="$1"
bDebug=${2:-"0"}
sAppPluginsDir=${3:-"./plugins"}

doDebug=$bDebug

debug "New application $sAppName"
for plugin in $(ls ${sAppPluginsDir}/*.shp); do
debug "Sourcing $plugin"
source "$plugin"
done
}


Basically, when the application script security.sh sources the main script BPA.sh and calls the new function, looks inside a plugins directory for all files ending in .shp (shell plugin). For each file it finds it calls the source command.

Now, when you source a plugin, you run it's registerPlugin function and all of it's registerCommand functions which in turn uses the hash functions as described next.

Bash hashes

The registerPlugin function looks something like:


#
# Registers a plugin for the application.
#
# This should the first thing a plugin (.shp) script does.
#
# @param name
# The name of the plugin.
#
# @param switch
# The command-line switch used to access the plugin, using --switch.
#
# @param description
# A description of the plugin. This is displayed when the --help switch
# is used.
#
# @return
# Does not return a value
#
# Example
# @code
# registerPlugin 'MyPlugin' 'my' 'An example plugin'
#
registerPlugin()
{
sPluginName=$1
sPluginSwitch=$2
sPluginDescription=$3

debug "Registering $sPluginName"

hash_set $sPluginName name "$sPluginName"
hash_set $sPluginName description "$sPluginDescription"

hash_set hPlugins "$sPluginSwitch" "$sPluginName"
}


The interesting thing in that function is the use of hash_set, which is:


#
# Creates hash with given name and sets key/value pair.
#
# @param name
# Name of the hash.
#
# @param key
# Hash key value.
#
# @param value
# Hash value for key.
#
# @return
# Does not return a value.
#
hash_set()
{
name=$1
key=$2
value=$3
lookup="${name}_keys"

id=$(crc16 $key)
eval $name\[\$id\]=\"\$value\"
array_push $lookup $key
}


This function takes a name for the hash as its first argument, then a key followed by the value for that key. It then uses the crc16 function (shown below) to generate a numeric index for the actual bash array. It then uses the bash eval command to create a real array from the hash name that was supplied. Finally, it uses a simple array_push function to keep track of the keys in case we want to iterate over them.

Quite simple, and definitely overkill ;) It would be possible to just create two parallel arrays to track the key/value pairs, but this is more fun.

The crc16 function looks like this:


#
# Generates a crc16 value for the given string.
#
# @param string
# String to be crc16'd.
#
# @return
# Decimal crc16 value of string
#
crc16()
{
# This could probably be more efficient...
string=$1
aString=($(echo $string | fold -w 1))
cnt=${#aString[@]}
crcPolyNom=0x8408
crcPreset=0xFFFF
crc=$crcPreset

for ((x=0;x<$cnt;x++)); do
char=${aString[$x]}
byte=$(printf "%d" \'$char)
for ((i=0;i<8;i++)); do
(( charLsb = $byte & 0x0001 ))
(( crcLsb = $crc & 0x0001 ))
(( doAdd = $charLsb ^ $crcLsb ))

(( byte = $byte >> 1 ))
(( crc = $crc >> 1))

if [ "$doAdd" -eq "1" ]; then
(( crc = $crc ^ $crcPolyNom ))
fi
done
done
echo $crc
}


To get a value from the array, we just have to generate the crc16 for the key:


#
# Gets the value for key from the given hash.
#
# @param name
# Name of the hash.
#
# @param key
# The key value.
#
# @return
# The value associated with the key
#
hash_get()
{
name=$1
key=$2

id=$(crc16 $key)
eval echo \$\{$name\[\$id\]\}
}


So yeah, this is pretty slow... but also pretty cool.

Functions within functions

You can declare a function within a function. For example:


NMap()
{
# Name of the command.
# Each command goes inside the Plugin() function
SynScan()
{
# Code for this command
hosts=$@
nmap -sS -P0 $hosts
}
}


In order to run the SynScan function, you must first run the NMap function. At first this might not seem very useful. But for a plugin system it is, as it allows multiple plugins to have the same commands, without any clashing.

BPA.sh calls the plugin function and command in the run function:


#
# Runs the actual application.
#
# @return
# Does not return a value.
#
# Example:
# @code
# run
#
run() {
debug "Running $sAppName"
parse $aAppArgs
debug "Plugin: $sSelectedPlugin"
debug "Command: $sSelectedCommand"
$sSelectedPlugin
$sSelectedCommand $aCmdArgs
}


So we could happily have another plugin, perhaps like:


OtherCoolPortScanner()
{
# Name of the command.
# Each command goes inside the Plugin() function
SynScan()
{
# Code for this command
hosts=$@
othercoolportscanner -s $hosts
}
}


...without worrying about mixups.

3 comments:

  1. I was looking for a CRC16 function in bash, and yours is perfect! Do you mind if i use in in a little personal (GPL'ed) project?

    If so, stay tuned for wine-import-extensions ;)

    ReplyDelete
  2. Improved version of crc16, also in a standalone script: http://pastebin.com/rMZBxKqW

    Thanks!

    ReplyDelete