The Moose and Squirrel Files

March 26, 2011

Converting Unix (Epoch) Times with Excel

Filed under: Code — Tags: , , , — networknerd @ 9:56 pm

Unix time is defined by wikipedia as “…a system for describing points in time, defined as the number of seconds elapsed since midnight Coordinated Universal Time (UTC) of January 1, 1970, not counting leap seconds.”

Unix times are used by a number of Cisco products like in callmanager call record reports, as the OSPF cryptographic sequence number, and as the time measurements were taken using snmp bulkstats.

Taking leap seconds and leap years into account can be a messy business, but excel can help simplify the calculations to normal date and time by using the  vba DateAdd function.

Unfortunately it can’t be used directly in a cell formula but you can create a macro (vba function) that can be used in a cell formula.  The vba code I used is shown below.  Note that there is a second optional parameter  UTCOffset, the number of hours from UTC , that can be used to calculate local times. If omitted you will get UTC times.

Private Const SecondsPerHour = 3600
Private Const EpochStart = "1/1/1970" '1 Jan 1970 00:00:00 UTC
Function epochconvert(epochtime, Optional UTCOffset)
    If IsMissing(UTCOffset) Then
        epochconvert = DateAdd("s", epochtime, EpochStart)
    Else
        epochconvert = DateAdd("s", epochtime + SecondsPerHour * UTCOffset, EpochStart)
    End If
End Function

Function ToEpoch(dtDate, Optional UTCOffset)
    If IsMissing(UTCOffset) Then
        ToEpoch = DateDiff("s", dtDate, EpochStart)
    Else
        ToEpoch = DateDiff("s", EpochStart, dtDate) - SecondsPerHour * UTCOffset
    End If
End Function

November 3, 2009

Detecting which .Net Framework Versions are Installed

Filed under: Code — Tags: , — networknerd @ 4:30 pm

In the previous post I used some C# code to detect if bootworks was installed prior to installing full disk encryption.  That all works well provided the appropriate .Net framework is installed. Unfortunately with a freshly re-imaged computer there is no .Net framework in the base image causing the bootworks detection to bomb out.

After a bit of googling I came up with this small script to gather all the installed .Net versions and a support function to test for a particular release version.  The same thing could be accomplished using a registry key as described by Aaron Stebner’s blog post. 

option explicit
'Detect which versions of DotNet Framework are installed.
'From Microsoft KB Article http://support.microsoft.com/kb/318785/
'By NetworkNerd 3/11/2009

Const WindowsFolder = 0
Const SystemFolder = 1
Const TemporaryFolder = 2
const DOTNET_10 = "v1.0.3705"
const DOTNET_11 = "v1.1.4322"
const DOTNET_20 = "v2.0.50727"
const DOTNET_30 = "v3.0"
const DOTNET_35 = "v3.5"

dim objFrameworkVers

set objFrameworkVers = CreateObject("Scripting.Dictionary")
wscript.echo "Found " & getFrameWorkVersions(objFrameworkVers) & " .NET Frameworks installed."
if HasDotNet(DOTNET_20) then
  wscript.echo "Has .Net Framework 2.0 installed"
end if

function HasDotNet(ver)
  if objFrameworkVers.exists(ver) then
    HasDotNet = True
  else
    HasDotNet = False
  end if
end function

function getFrameWorkVersions(byref objDict)
  dim fso, winfolder, strPath, basefolder, f
  Set fso = CreateObject("Scripting.FileSystemObject")
  set winfolder = fso.GetSpecialFolder(WindowsFolder)
  strPath = winfolder.path & "\Microsoft.NET\Framework"
  set basefolder = fso.getfolder(strPath)
  objDict.removeAll
  for each f in basefolder.subfolders
	objDict.add f.name, f.name
  next
  getFrameWorkVersions = objDict.count
end function

 

October 22, 2009

Detecting when Altiris Bootworks is Installed

Filed under: Code — Tags: , , , — networknerd @ 6:14 am

When installing Checkpoint full disk encryption we ran into some problems on computers with Altiris Bootworks still installed. Normally Bootworks can be detected through a registry key, and uninstalled if the key is present. However we found a number of computers with Bootworks were missing the key.

The quick solution to detect bootworks was to read the bootsector of the disk and look for some identifying strings. The code below shows how to read from a physical disk.  Note this has only been tested in Windows XP, and you require admin privileges.  The code below was called from a startup script so privilege wasn’t an issue.

When reading from a physical disk we need to seek, read and write in multiples of sector size and on sector boundaries. See Microsoft KB article 100027.  I use WMI to get the number of bytes per sector for the drive.

I found the signature by extracting the bootsector using a copy of dcfldd that was compiled for cygwin. I dumped it to file using the command below.
dcfldd if=”\\\\.\\physicaldrive0 of =”bootsec.bin” count=1

The file bootsec.bin can then be opened using good old debug to get the hex/ascii display

The same result can be achieved by booting to a linux live cd and using the command below.

 dd if=/dev/sda count=1 | hexdump -C

 Listing 1


using System;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace bwcheck
{
    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
        internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
            IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, SafeFileHandle hTemplateFile);

        internal const int GENERIC_READ = unchecked((int)0x80000000);
        internal const int OPEN_EXISTING = 3;
        internal const int FILE_ATTRIBUTE_NORMAL = 0x80;
        const String SIGNATURE = "Altiris EBootMastr";
        const int SEEKOFFSET = 3;
        const int LENGTH_TO_READ = 18;   // LENGTH_TO_READ = SIGNATURE.Length;
        const int RETCODE_SUCCESS = 0;
        const int RETCODE_IOERROR = 1;
        const int RETCODE_BADSIGNATURE = 2;
        const int RETCODE_HIT_EOF = 3;
// NB The where clause requires additional escaping even with the @string literal
        const String WMIQRY = @"Select * from win32_diskdrive where Name='\\\\.\\PhysicalDrive0'";
        const String WMI_NS = @"\\.\root\cimv2";
        //const String WMIQRY = "Select * from win32_diskdrive ";

        public static int Main(string[] args)
        {

            // TODO: Implement Functionality Here
            int retcode = RETCODE_SUCCESS;
            SafeFileHandle h = null;
            h = CreateFile("\\\\.\\PhysicalDrive0",
                            GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                            new SafeFileHandle(IntPtr.Zero, true));

            if (! h.IsInvalid ) {
                try {
                    //Find the bytes per sector for the disk
                    //We must read, write and seek in multiples of sector size (ref: http://support.microsoft.com/kb/q100027)
                    //This is true even when we convert the handle to a filestream.
                    ManagementObjectSearcher objSearch = new ManagementObjectSearcher(WMI_NS, WMIQRY);
                    int bytespersector = 0;
                    foreach (ManagementObject objResult in objSearch.Get()){
                        bytespersector = Convert.ToInt32(objResult["BytesPerSector"]);
                    }
                    FileStream fstream = new FileStream(h, FileAccess.Read);
                    // Read from stream
                    Byte[] chunk = new Byte[bytespersector];
                    int bytesRead;
                    int bytesTotal = 0;
                    int bytesToRead =  bytespersector;
                    while (bytesToRead > 0) {
                        bytesRead = fstream.Read(chunk,bytesTotal,bytesToRead);
                        if (bytesRead == 0) {
                            break; //end of filestream condition
                        }
                        bytesToRead -= bytesRead;
                        bytesTotal += bytesRead;
                    }
                    if (bytesToRead > 0 ) {
                        retcode = RETCODE_HIT_EOF;
                    }
                    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
                    String s = enc.GetString(chunk,SEEKOFFSET,LENGTH_TO_READ);
                    Console.WriteLine("{0}", s);
                    if (! s.Equals(SIGNATURE)){
                        retcode = RETCODE_BADSIGNATURE;
                    }

                } catch (Exception e) {
                    Console.WriteLine(e.ToString());
                    retcode = RETCODE_IOERROR;
                }
            }
            else
            {
                // get error code and throw
                int error = Marshal.GetLastWin32Error();
                Console.WriteLine("Last WIN32 Error: {0}", error);
                retcode = RETCODE_IOERROR;
            }
            return retcode;
        }
    }
}

September 11, 2009

Cisco Embedded Event Manager Applets

Filed under: Code, Network — Tags: , , , — networknerd @ 4:11 pm

Embedded event manager is a feature incorporated into most new cisco equipment. You can find more information on the Cisco site and some excellent examples at the IOSHINTS blog.

This applet is used to add/delete static arp entries on 6509 core switches in a HSRP pairing, since you can’t have a static arp configured more than once on the HSRP cluster.  The mac address is a multicast mac address of a windows network load balanced server.

Listing 1

event manager applet deletearpvlan234
event syslog occurs 1 pattern "%HSRP-5-STATECHANGE: Vlan234 Grp 1 state Speak -> Standby"
action 0.0 cli command "enable"
action 1.0 cli command "configure terminal"
action 2.0 cli command "no arp vrf SERVERS 192.168.1.100 0100.5e7f.00ac ARPA"
action 4.0 syslog msg "EEM deleted arp entry for 192.168.1.100"

event manager applet addarpvlan234
event syslog occurs 1 pattern "%HSRP-5-STATECHANGE: Vlan234 Grp 1 state Standby -> Active"
action 0.0 cli command "enable"
action 1.0 cli command "configure terminal"
action 2.0 cli command "arp vrf SERVERS 192.168.1.100 0100.5e7f.00ac ARPA"
action 4.0 syslog msg "EEM added arp entry for 192.168.1.100"

August 5, 2009

SwitchPort Visualisations

Filed under: audit, Code, Network, visualisation — Tags: , , — networknerd @ 2:58 pm

Richard Bejtlich wrote late last year about an idea for security visualisations which inspired me to look for opportunities to apply visualisations in my work. Well I found that opportunity while performing an audit of switch configurations.

The objectives of the visualisation are to provide at a glance.

  • whether 802.1x is enabled,
  • Vlan/function by color code,
  • trunking mode of the port and
  • the ability to drill down for further information.

I’ve divided the solution into two parts

  1. A script using snmp to query the switch port configuration and save the data in xml format. This can be run as a scheduled task at suitable intervals to ensure that the information remains current. See the listing of spview.wsf below for details.
  2. Converting the saved xml data into a suitable visual representation. For this project an xml stylesheet is used to render the  data as html in a web browser. The drill down component is provided by javascript with a “tooltip text” display when the mouse is moved onto a table cell. See the listing for spview.xsl for more details. Note that the javascript for the ” tooltip text” is courtesy of Nelson and can be found here. The only modifications I made were to the initialisation code so that I could safely move the code from the body of the html to the head section.

Cisco catalyst 3750 48 port switches were used for this project so the snmp OID’s used for checking the configuration are obviously cisco specific.

One thing that really had me stumped was how to include the javascript in the stylesheet without causing errors. I eventually worked out that wrapping everything between, but not including the <script> </script> tags in a <![CDATA[ ….]]> element did the trick.

Switchport Visualisation

Switchport Visualisation

 

 

 

 

 

 

 

spview.xsl

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>

<xsl:variable name="color_legacy" select="'#7bb31a'"/>
<xsl:variable name="color_vc" select="'#ff9d7f'"/>
<xsl:variable name="color_wireless" select="'Red'"/>
<xsl:variable name="color_dot1xauth" select="'#8b88ff'"/>
<xsl:variable name="color_dot1xguest" select="'#8b88ff'"/>
<xsl:variable name="color_printer" select="'lightGrey'"/>
<xsl:variable name="color_reimage" select="'#ff9c00'"/>
<xsl:variable name="color_unknown" select="'#eedb00'"/>
<xsl:variable name="color_nodot1x" select="'#cc3333'"/>
<xsl:variable name="cellheight" select="'35'"/>
<xsl:variable name="cellwidth" select="'35'"/>

<xsl:template match="/">
<html>
<head>
<title><xsl:value-of select="concat('Switch Stack View ',/stack/@hostname)"/></title>
<style type="text/css">
#dhtmltooltip{
position: absolute;
border: 1px solid red;
width: 150px;
padding: 2px;
background-color: lightyellow;
visibility: hidden;
z-index: 100;
filter: progid:DXImageTransform.Microsoft.Shadow(color=gray,direction=115);
}
</style>
<style type="text/css">
table.switch {
table-layout: fixed
}
</style>

<script type="text/javascript">
<![CDATA[
/***********************************************
* Freejavascriptkit.com
* Visit http://www.freejavascriptkit.com for more free Javascripts source code
***********************************************/

var offsetxpoint=-60 //Customize x offset of tooltip
var offsetypoint=20 //Customize y offset of tooltip
var ie=document.all
var ns6=document.getElementById && !document.all
var enabletip=false
var tipobj;

function initTipObj(){
if (ie||ns6)
tipobj=document.all? document.all["dhtmltooltip"] : document.getElementById? document.getElementById("dhtmltooltip") : ""
}
function ietruebody(){
return (document.compatMode && document.compatMode!="BackCompat")? document.documentElement : document.body
}

function ddrivetip(thetext, thecolor, thewidth){
if (ns6||ie){
if (typeof thewidth!="undefined") tipobj.style.width=thewidth+"px"
if (typeof thecolor!="undefined" && thecolor!="") tipobj.style.backgroundColor=thecolor
tipobj.innerHTML=thetext
enabletip=true
return false
}
}

function positiontip(e){
if (enabletip){
var curX=(ns6)?e.pageX : event.clientX+ietruebody().scrollLeft;
var curY=(ns6)?e.pageY : event.clientY+ietruebody().scrollTop;
//Find out how close the mouse is to the corner of the window
var rightedge=ie&&!window.opera? ietruebody().clientWidth-event.clientX-offsetxpoint : window.innerWidth-e.clientX-offsetxpoint-20
var bottomedge=ie&&!window.opera? ietruebody().clientHeight-event.clientY-offsetypoint : window.innerHeight-e.clientY-offsetypoint-20

var leftedge=(offsetxpoint<0)? offsetxpoint*(-1) : -1000

//if the horizontal distance isn't enough to accomodate the width of the context menu
if (rightedge<tipobj.offsetWidth)
//move the horizontal position of the menu to the left by it's width
tipobj.style.left=ie? ietruebody().scrollLeft+event.clientX-tipobj.offsetWidth+"px" : window.pageXOffset+e.clientX-tipobj.offsetWidth+"px"
else if (curX<leftedge)
tipobj.style.left="5px"
else
//position the horizontal position of the menu where the mouse is positioned
tipobj.style.left=curX+offsetxpoint+"px"

//same concept with the vertical position
if (bottomedge<tipobj.offsetHeight)
tipobj.style.top=ie? ietruebody().scrollTop+event.clientY-tipobj.offsetHeight-offsetypoint+"px" : window.pageYOffset+e.clientY-tipobj.offsetHeight-offsetypoint+"px"
else
tipobj.style.top=curY+offsetypoint+"px"
tipobj.style.visibility="visible"
}
}

function hideddrivetip(){
if (ns6||ie){
enabletip=false
tipobj.style.visibility="hidden"
tipobj.style.left="-1000px"
tipobj.style.backgroundColor=''
tipobj.style.width=''
}
}

document.onmousemove=positiontip
]]>
</script>
</head>
<body onload="initTipObj();">
  <div id="dhtmltooltip"></div>
  <xsl:apply-templates select=".//switch"/>
  <table Border='1' cellspacing='2'>
    <tr>
      <td align='center' bgcolor='{$color_legacy}'></td>
      <td><xsl:text>Legacy Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_dot1xauth}"></td>
      <td><xsl:text>Authenticated Computers Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_dot1xguest}"></td>
      <td>Guest Vlan</td>
    </tr>
    <tr>
      <td bgcolor="{$color_vc}"></td>
      <td><xsl:text>VC Equipment Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_wireless}"></td>
      <td><xsl:text>Wireless or Management Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_printer}"></td>
      <td><xsl:text>Printer Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_reimage}"></td>
      <td><xsl:text>Re-imaging Vlan</xsl:text></td>
    </tr>
    <tr>
      <td bgcolor="{$color_unknown}"></td>
      <td><xsl:text>Unknown Vlan</xsl:text></td>
    </tr>
    <tr>
      <td style="color:{$color_nodot1x}">Text</td>
      <td><xsl:text>Font color indicates access port with no dot1x</xsl:text></td>
    </tr>
  </table>
</body>
</html>
</xsl:template>

<xsl:template match="switch">
  <table class='switch' Border='1' width='100%' cellspacing='2'>
  <tr>
<xsl:comment>Display the odd numbered ports in the top row</xsl:comment>
    <xsl:apply-templates select="port[starts-with(@IfName,'Fa') and substring-after(@IfName,'0/') mod 2 = 1]"/>
  </tr>
  <tr>
<xsl:comment>Display the even numbered ports in the bottom row</xsl:comment>
    <xsl:apply-templates select="port[starts-with(@IfName,'Fa') and substring-after(@IfName,'0/') mod 2 = 0]"/>
  </tr>
  </table>
  <p/><p/>
</xsl:template>

<xsl:template match="port">

  <xsl:variable name="tiptext">
    <xsl:call-template name="tiptext"/>
  </xsl:variable>

  <xsl:variable name="bgcolor">
    <xsl:call-template name="bgcolor"/>
  </xsl:variable>

  <xsl:variable name="cellstyle">
    <xsl:if test="@Dot1x != 2 and @Trunking = 2">
    <xsl:value-of select="concat('color:',$color_nodot1x)"/>
    </xsl:if>
  </xsl:variable>

  <td  align='center' width="{$cellheight}" height="{$cellwidth}" bgcolor="{$bgcolor}" style="{$cellstyle}"
       onMouseover='ddrivetip("{$tiptext}","yellow", 180)'
       onMouseout="hideddrivetip()">
    <xsl:value-of select="substring-after(@IfName,'0/')"/>
    <xsl:if test="@Dot1x = 2">
    <xsl:text>.</xsl:text>
    </xsl:if>
  </td>
</xsl:template>

<xsl:template name="bgcolor">
    <xsl:choose>
      <xsl:when test="@Vlan = 5">
        <xsl:value-of select="$color_legacy"/>
      </xsl:when>
      <xsl:when test="@Vlan = 910">
        <xsl:value-of select="$color_vc"/>
      </xsl:when>
      <xsl:when test="(@Vlan &gt;= 100) and (@Vlan &lt;= 199)and ((@Vlan mod 4) = 0)">
        <xsl:value-of select="$color_wireless"/>
      </xsl:when>
      <xsl:when test="(@Vlan &gt;= 200) and (@Vlan &lt;= 387) and ((@Vlan mod 20) = 0)">
        <xsl:value-of select="$color_dot1xauth"/>
      </xsl:when>
      <xsl:when test="(@Vlan &gt;= 200) and (@Vlan &lt;= 387) and ((@Vlan mod 20) = 5)">
        <xsl:value-of select="$color_dot1xguest"/>
      </xsl:when>
      <xsl:when test="(@Vlan &gt;= 200) and (@Vlan &lt;= 387) and ((@Vlan mod 20) = 4)">
        <xsl:value-of select="$color_printer"/>
      </xsl:when>
      <xsl:when test="(@Vlan &gt;= 200) and (@Vlan &lt;= 387) and ((@Vlan mod 20) = 7)">
        <xsl:value-of select="$color_reimage"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$color_unknown"/>
      </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="tiptext">
    <xsl:value-of select="concat('Port: ',@IfName)"/>
    <xsl:text>&lt;BR/&gt;</xsl:text>
    <xsl:choose>
      <xsl:when test="@Dot1x = 1">
        <xsl:value-of select="'Dot1x: Force-Unauthorised'"/>
      </xsl:when>
      <xsl:when test="@Dot1x = 2">
        <xsl:value-of select="'Dot1x: Auto'"/>
      </xsl:when>
      <xsl:when test="@Dot1x = 3">
        <xsl:value-of select="'Dot1x: Force-Authorised'"/>
      </xsl:when>
      <xsl:when test="@Dot1x = ''">
        <xsl:value-of select="'Dot1x: None'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="'Dot1x: Unknown'"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text>&lt;BR/&gt;</xsl:text>
    <xsl:choose>
      <xsl:when test="@Trunking = 1">
        <xsl:value-of select="'Trunking: On'"/>
      </xsl:when>
      <xsl:when test="@Trunking = 2">
        <xsl:value-of select="'Trunking: Off'"/>
      </xsl:when>
      <xsl:when test="@Trunking = 3">
        <xsl:value-of select="'Trunking: Desirable'"/>
      </xsl:when>
      <xsl:when test="@Trunking = 4">
        <xsl:value-of select="'Trunking: Auto'"/>
      </xsl:when>
      <xsl:when test="@Trunking = 5">
        <xsl:value-of select="'Trunking: OnNoNegotiate'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="'Trunking: Unknown'"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text>&lt;BR/&gt;</xsl:text>
    <xsl:value-of select="concat('Vlan: ',@Vlan)"/>
    <xsl:text>&lt;BR/&gt;</xsl:text>
    <xsl:value-of select="concat('Desc: ',@Description)"/>
    <xsl:text>&lt;BR/&gt;</xsl:text>
</xsl:template>
</xsl:stylesheet>

spview.wsf

<package>
<job id="spview">
<runtime>
<named
  name="stylesheet"
  helpstring="File path of the xml stylesheet to be referenced in the xml file."
  many="false"
  required="True"
  type=string
/>
<named
  name="agent"
  helpstring="Host name or ip address of the switch to query"
  many="false"
  required="True"
  type=string
/>
<named
  name="outfile"
  helpstring="Path to the file where the configuration data will be output"
  many="false"
  required="True"
  type=string
/>
</runtime>

<script language=vbscript>
option explicit

dim counter
dim REQ_ARGNAMES

'Constants to validate our Arguments against expectations
Const REQ_ARGCOUNT=3
Const cSSARG="stylesheet"
Const cAGENT="agent"
Const cOFILE="outfile"

'*****************************************************************************
'Argument Validation  Code - "Time spent on reconaissance is seldom wasted!" *
'*****************************************************************************
'Validate all our arguments before proceeding - add more validation code as required
'Initialise the array of Argument names for the named arguments
REQ_ARGNAMES=array(cSSARG,cAGENT)

'Do we have the correct number of named arguments
If wscript.Arguments.named.count < REQ_ARGCOUNT Then
  wscript.echo "Incorrect number of named arguments were specified!"
  wscript.Arguments.ShowUsage
  wscript.sleep 1000
  wscript.Quit
End If

'Was the script passed a named argument value for all arguments we expected
for counter = lbound(REQ_ARGNAMES) to ubound(REQ_ARGNAMES)
  If wscript.arguments.named(REQ_ARGNAMES(counter)) = "" then
     wscript.echo "Expected a named argument for " & REQ_ARGNAMES(counter)
     wscript.Arguments.ShowUsage
     wscript.sleep 1000
     wscript.Quit
  End If
next ' counter

'Validation to do list
'The existence of the stylesheet is not validated
'The ability to create the output file is not checked.
'Return codes from snmp query to switch are not checked
'*****************************************************************************
'End Argument Validation - insert more validation tests above this line.     *
'*****************************************************************************

const PORTIFINDEX = " .1.3.6.1.4.1.9.5.1.4.1.1.11 "
const IFNAME = " .1.3.6.1.2.1.31.1.1.1.1 "
const VMVLAN = " .1.3.6.1.4.1.9.9.68.1.2.2.1.2. "
const IFALIAS = " .1.3.6.1.2.1.31.1.1.1.18 "
const VLANPORTVLAN = " .1.3.6.1.4.1.9.5.1.9.3.1.3 "
const VLANPORTISLADMINSTATUS = " .1.3.6.1.4.1.9.5.1.9.3.1.7 "
const SYSNAME = " .1.3.6.1.2.1.1.5.0 "
const dot1xAuthAuthControlledPortControl = " .1.0.8802.1.1.1.1.2.1.1.6 "
const SNMPWALKCMD = "f:\usr\bin\snmpwalk.exe -OnqUe -v 2c -c "
const SNMPREAD = " public "

dim strRetcode, key, value, strHostName
dim intCurrentSwitch, intSwitch

dim objIfindexName ' holds a dictionary filled by the getIfName function
dim objPIfindex ' holds a dictionary filled by the getPortIfIndex function
dim objIfindexAlias ' holds a dictionary filled by the getIfAlias function
dim objIfindexDot1x ' holds a dictionary filled by the getDot1x function
dim objPIfindexTrunk ' holds a dictionary filled by the getTrunkStatus function
dim objPIfindexVlan ' holds a dictionary filled by the getVlan function
dim intSwitchCount ' number of switches in the stack
dim strAttrIfName, strAttrDot1x, strAttrVlan
dim strAttrTrunk, strAttrDesc, strtemp
dim objDom 'As DOMDocument
dim objStackElement 'As IXMLDOMElement
dim objSwitch 'As IXMLDOMElement
dim objSwitchCount 'As IXMLDOMAttribute
dim objStackName 'As IXMLDOMAttribute
dim objSwitchNum 'As IXMLDOMAttribute
dim objText 'As IXMLDOMText
dim objPort 'As IXMLDOMElement
dim objPortAttrib 'As IXMLDOMAttribute
dim objPI  'As IXMLDOMProcessingInstruction

'****************************************************************************
'* Retrieve the switch port configuration data                              *
'****************************************************************************
strHostName = ""
strRetcode =  getHostName(wscript.arguments.named(cAGENT),strHostName)

strRetcode =  getPortIfIndex(wscript.arguments.named(cAGENT),objPIfindex)
' Get the number of switches in the stack.
intSwitchCount = 0
for each key in objPIfindex.keys
  if (cint(left(key,instr(key,".")-1)) > intSwitchCount) then
    intSwitchCount = cint(left(key,instr(key,".")-1))
  end if
next

strRetcode =  getIfName(wscript.arguments.named(cAGENT),objIfindexName)

strRetcode =  getIfAlias(wscript.arguments.named(cAGENT),objIfindexAlias)

strRetcode =  getDot1x(wscript.arguments.named(cAGENT),objIfindexDot1x)

strRetcode =  getTrunkStatus(wscript.arguments.named(cAGENT),objPIfindexTrunk)

strRetcode =  getVlan(wscript.arguments.named(cAGENT),objPIfindexVlan)

'****************************************************************************
'* Write the data to xml nodes and save to file                             *
'****************************************************************************
'Create the DOM document and processing instructions for version & stylesheet
Set objDom = CreateObject("Msxml2.DomDocument.4.0")
objDom.preserveWhiteSpace = 1
Set objPI = objDom.createProcessingInstruction("xml-stylesheet", "type='text/xsl' href='" & wscript.arguments.named(cSSARG) &"'")
objDom.insertBefore objPI, objDom.childNodes.Item(0)
Set objPI = objDom.createProcessingInstruction("xml", "version='1.0'")
objDom.insertBefore objPI, objDom.childNodes.Item(0)

'Creates root element, stack to represent a switch stack
Set objStackElement = objDom.createElement("stack")
objDom.appendChild objStackElement
objStackElement.Text = vbcrlf

'Creates Attribute to the stack Element = hostname of stack
Set objStackName = objDom.createAttribute("hostname")
objStackName.nodeValue = strHostName
objStackElement.setAttributeNode objStackName

'Creates Attribute to the stack Element = number of switches in stack
Set objSwitchCount = objDom.createAttribute("SwitchCount")
objSwitchCount.nodeValue = intSwitchCount
objStackElement.setAttributeNode objSwitchCount

'Loop through all the ports in the switch stack and save their config
intCurrentSwitch = 0
for each key in objPIfindex.keys()
  intSwitch = cint(left(key,instr(key,".")-1))
  if (intSwitch <> intCurrentSwitch) then 'next switch in the stack
     intCurrentSwitch = intSwitch
     ' Creates Switch element as a child of stack
     Set objSwitch = objDom.createElement("switch")
     objStackElement.appendChild objSwitch
     objSwitch.Text = vbcrlf
     ' Creates Attribute to the switch Element
     Set objSwitchNum = objDom.createAttribute("Number")
     objSwitchNum.nodeValue = intSwitch
     objSwitch.setAttributeNode objSwitchNum
     Set objSwitchNum = Nothing
     'Make the xml prettier else it is saved on a single line
     set objText = objDom.CreateTextNode(vbcrlf)
     objStackElement.appendChild objText
     set objText = Nothing
  end if

  'Create a port element and set the attributes
  Set objPort = objDom.createElement("port")
  objSwitch.appendChild objPort
  ' Creates PortIfIndex Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("PortIfIndex")
  objPortAttrib.nodeValue = key
  objPort.setAttributeNode objPortAttrib

  if objIfindexName.exists(objPIfindex.item(key)) then
    strAttrIfName = objIfindexName.item(objPIfindex.item(key))
  else
    strAttrIfName = ""
  end if
  ' Creates IfName Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("IfName")
  objPortAttrib.nodeValue = strAttrIfName
  objPort.setAttributeNode objPortAttrib

  if objIfindexDot1x.exists(objPIfindex.item(key)) then
    strAttrDot1x = objIfindexDot1x.item(objPIfindex.item(key))
  else
    strAttrDot1x = ""
  end if
  ' Creates Dot1x Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("Dot1x")
  objPortAttrib.nodeValue = strAttrDot1x
  objPort.setAttributeNode objPortAttrib

  if objPIfindexVlan.exists(key) then
    strAttrVlan = objPIfindexVlan.item(key)
  else
    strAttrVlan = ""
  end if
  ' Creates Vlan Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("Vlan")
  objPortAttrib.nodeValue = strAttrVlan
  objPort.setAttributeNode objPortAttrib

  if objPIfindexTrunk.exists(key) then
    strAttrTrunk = objPIfindexTrunk.item(key)
  else
    strAttrTrunk = ""
  end if
  ' Creates Trunking Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("Trunking")
  objPortAttrib.nodeValue = strAttrTrunk
  objPort.setAttributeNode objPortAttrib

  if objIfindexAlias.exists(objPIfindex.item(key)) then
    strAttrDesc = objIfindexAlias.item(objPIfindex.item(key))
  else
    strAttrDesc = ""
  end if
  ' Creates Description Attribute to the port Element
  Set objPortAttrib = objDom.createAttribute("Description")
  objPortAttrib.nodeValue = strAttrDesc
  objPort.setAttributeNode objPortAttrib
  'Make the xml prettier else it is saved on a single line
  set objText = objDom.CreateTextNode(vbcrlf)
  objSwitch.appendChild objText
  set objText = Nothing
  ' Saves XML data to disk.
  objDom.save (wscript.arguments.named(cOFILE))
next

wscript.quit

'************************************************************************
'FUNCTION:                                                              *
'       getPortIfIndex(strAgent,objPortIfIndex)                         *
'                                                                       *
'Purpose:                                                               *
'       walk the CISCO-STACK-MIB::portIfIndex OID and populate the      *
'       dictionary object passed                                        *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objPortIfIndex: dictionary to hold the port interface indices   *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       CISCO-STACK-MIB is cisco specific.                              *
'************************************************************************
function  getPortIfIndex(strAgent,objPortIfIndex)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objPortIfIndex = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				PORTIFINDEX)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.4.1.9.5.1.4.1.1.11.4.49 11545"
    re.pattern = "^" & replace(trim(PORTIFINDEX),".","\.") & _
			"\.(\d+\.\d+)\s+(\d+)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objPortIfIndex.add match.submatches(0), match.submatches(1)
    next
  end if
  getPortIfIndex = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getIfName(strAgent,objIfIndexName)                              *
'                                                                       *
'Purpose:                                                               *
'       walk the IF-MIB::ifName OID and populate the dictionary object  *
'        passed                                                         *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objIfIndexName: dictionary to hold the port interface index to  *
'        name mappings                                                  *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       CISCO-STACK-MIB is cisco specific.                              *
'************************************************************************
function  getIfName(strAgent,objIfIndexName)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objIfIndexName = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				IFNAME)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.2.1.31.1.1.1.1.10001 Fa1/0/1"
    re.pattern = "^" & replace(trim(IFNAME),".","\.") & _
			"\.(\d+)\s+([^\r\n]+)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objIfIndexName.add match.submatches(0), match.submatches(1)
    next
  end if
  getIfName = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getIfAlias(strAgent,objIfIndexDesc)                              *
'                                                                       *
'Purpose:                                                               *
'       walk the IF-MIB::ifAlias OID and populate the dictionary object *
'        passed                                                         *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objIfIndexDesc: dictionary to hold the port interface index to  *
'        description mappings                                           *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       CISCO-STACK-MIB is cisco specific.                              *
'************************************************************************
function  getIfAlias(strAgent,objIfIndexDesc)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objIfIndexDesc = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				IFALIAS)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.2.1.31.1.1.1.18.11543 Printer port"
    re.pattern = "^" & replace(trim(IFALIAS),".","\.") & _
			"\.(\d+)\s{1}([^\r\n]*)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objIfIndexDesc.add match.submatches(0), match.submatches(1)
    next
  end if
  getIfAlias = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getDot1x(strAgent,objIfIndexDot1x)                              *
'                                                                       *
'Purpose:                                                               *
'       walk the IEEE8021-PAE-MIB::dot1xAuthAuthControlledPortControl   *
'        OID and populate the dictionary object passed                  *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objIfIndexDot1x: dictionary to hold the port interface index to *
'        802.1x mappings                                                *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       None.                                                           *
'************************************************************************
function  getDot1x(strAgent,objIfIndexDot1x)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objIfIndexDot1x = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				dot1xAuthAuthControlledPortControl)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop

  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
'                ".1.0.8802.1.1.1.1.2.1.1.6.11515 2"
    re.pattern = "^" & replace(trim(dot1xAuthAuthControlledPortControl),".","\.") & _
			"\.(\d+)\s+(\d)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objIfIndexDot1x.add match.submatches(0), match.submatches(1)
    next
  end if
  getDot1x = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getTrunkStatus(strAgent,objPortIfIndexTrunk)                    *
'                                                                       *
'Purpose:                                                               *
'       walk the CISCO-STACK-MIB::vlanPortIslAdminStatus OID and        *
'       populate the dictionary object passed                           *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objPortIfIndexTrunk: dictionary to hold the port interface index*
'         to trunk status                                               *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       CISCO-STACK-MIB is cisco specific.                              *
'************************************************************************
function  getTrunkStatus(strAgent,objPortIfIndexTrunk)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objPortIfIndexTrunk = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				VLANPORTISLADMINSTATUS)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.4.1.9.5.1.9.3.1.7.4.44 2"
    re.pattern = "^" & replace(trim(VLANPORTISLADMINSTATUS),".","\.") & _
			"\.(\d+\.\d+)\s+(\d+)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objPortIfIndexTrunk.add match.submatches(0), match.submatches(1)
    next
  end if
  getTrunkStatus = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getVlan(strAgent,objPortIfIndexVlan)                         *
'                                                                       *
'Purpose:                                                               *
'       walk the CISCO-STACK-MIB::vlanPortIslAdminStatus OID and        *
'       populate the dictionary object passed                           *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       objPortIfIndexVlan: dictionary to hold the port interface index *
'         to vlan mapping                                               *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       CISCO-STACK-MIB is cisco specific.                              *
'************************************************************************
function  getVlan(strAgent,objPortIfIndexVlan)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  set objPortIfIndexVlan = CreateObject("Scripting.Dictionary")
  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				VLANPORTVLAN)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the last digits of the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.4.1.9.5.1.9.3.1.3.4.46 810"
    re.pattern = "^" & replace(trim(VLANPORTVLAN),".","\.") & _
			"\.(\d+\.\d+)\s+(\d+)$"

    set matches = re.execute(stroutput)
    for each match in matches
      objPortIfIndexVlan.add match.submatches(0), match.submatches(1)
    next
  end if
  getVlan = len(strErr)
end function

'************************************************************************
'FUNCTION:                                                              *
'       getHostName(strAgent,strHostName)                               *
'                                                                       *
'Purpose:                                                               *
'       walk the SNMPv2-MIB::sysName.0 OID and                          *
'       copy the hostname into strHostName                              *
'                                                                       *
'Inputs:                                                                *
'       strAgent: management IP address of the switch                   *
'       strHostName:  string variable reference to hold the hostname    *
'                                                                       *
'Returns:                                                               *
'       Integer, 0 if successful or a positive value on failure.        *
'                                                                       *
'Calls:                                                                 *
'       SNMPWALKCMD - constant defining the path to an external         *
'       program and options used to perform an snmp set                 *
'                                                                       *
'Comments:                                                              *
'       None.                                                           *
'************************************************************************
function  getHostName(strAgent,strHostName)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput, strErr

  Set WshShell = CreateObject("wscript.Shell")
  Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & strAgent & " " & _
				SYSNAME)
  Do while Not oExec.StdOut.AtEndOfStream
    stroutput = oExec.StdOut.readall
  Loop
  Do while Not oExec.StdErr.AtEndOfStream
    strErr = oExec.StdErr.readall
  Loop
  Do While oExec.Status <> 1
    wscript.Sleep 100
  Loop
  if (len(strErr) = 0) then
    set re = new regexp
    re.global = True
    re.multiline = True
'Pattern to capture the snmp output
'output lines from SNMPCMD should look like
'                ".1.3.6.1.2.1.1.5.0 Sydney-Switch"
    re.pattern = "^" & replace(trim(SYSNAME),".","\.") & _
			"\s{1}([^\r\n]*)$"

    set matches = re.execute(stroutput)
    for each match in matches
      strHostname = match.submatches(0)
    next
  end if
  getHostName = len(strErr)
end function

</script>
</job>
</package>

February 28, 2009

Combine Wireshark Summary and Detail Information with XML Joins

Filed under: Code, Network — Tags: , , , , — networknerd @ 8:45 am

Wireshark users may occasionally find themselves wishing for the ability the add some packet detail to the summary information. In the previous post I was looking at DSCP  and TOS information and wanted to add that to the summary rather than drilling down into every packet.  The first solution is to use Tshark and a lua script as I did in the previous post.  The second solution is to export the capture file in both PSML and PDML format packet detail and render them in a web browser.

PSML and PDML are both XML files, so they can be rendered using an XML stylesheet and displayed in a web browser. Sensibly parsing the output of two different XML files can be a bit tricky. Fortunately there is a common field in each file, the packet number, which can be used to perform the equivalent of an SQL join on the two files.

First I captured the request and response of two single pings, one with and one without the ToS byte set.  Then I exported the files in PDML and PSML formats as shown below.  Note the reference in line 2 of packetsumm2.xml to the stylesheet table7.xsl.  When using a browser to transform the xml we have to modify it manually to include the reference rather than pass it as a command line parameter like we would with saxon or xalan.

The important parts of the stylesheet are highlighted in red.  The first of the highlighted lines creates a reference to the document element of the packet details file. The template below does all the hard work of performing the join.

<xsl:template match="/psml/packet">
      <tr>
  <xsl:variable name="packetnum" select="section[1]"/>
  <xsl:for-each select="section">
    <xsl:if test="position()=last()">
        <td><xsl:value-of select= "$packetdetail/pdml/packet/proto[@name = 'geninfo']/field[@name = 'num' and @show = $packetnum]/../../proto[@name = 'ip']/field[@name='ip.dsfield']/@showname"/></td>
    </xsl:if>
    <td><xsl:value-of select="."/></td>
  </xsl:for-each>
      </tr>
</xsl:template>

The packet number is in the first element and is saved in the variable packetnum.  A for-each  loop is created to output all the section elements of the packet summary.  In this case they are “No.”,  “Time”,  ” Source”,  ” Destination”,  ” Protocol” and  “Info”. As the loop progresses we test to see if we are at the last “section” element, and if we are it inserts the DS field information from the packet detail file. Now this is where the going gets heavy. We insert the DS field information using a <xsl:value-of> tag,  which contains a select attribute with an xpath expression to that information.

There are two main parts to this xpath expression

  1. $packetdetail/pdml/packet/proto[@name = 'geninfo']/field[@name = 'num' and @show = $packetnum]/
  2. ../../proto[@name = 'ip']/field[@name='ip.dsfield']/@showname

Part 1 drills down to the proto element of the packet that has  a field element with a name attribute equal to num and a show attribute equal to the packetnum variable.  This is the common part where we perform the join on the two files.

Part 2 solves the problem of accessing the DS field information.  This is a problem because our xpath expression has already taken us down one proto element and we need to be in another proto element at the same level. To get there we need to go back to the common ancestor of the two proto elements, using the ../.. expression and then match the proto element with a name attribute of ip and field element with a name attribute of ip.dsfield and finally selecting the showname attribute.

Conclusion

I admit the example using wireshark is a little contrived.  However, the ability to join xml files on a common field is a powerful technique worth keeping in the toolkit.  Who knows somebody may actually wish to display packet capture information in a web browser.

Table7.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:variable name="packetdetail" select="document('packets2.xml')"/>

<xsl:template match="/psml">
  <html>
  <body>
    <table border="2" bgcolor="lightgrey">
      <xsl:apply-templates/>
    </table>
  </body>
  </html>
</xsl:template>

<xsl:template match="/psml/structure">
      <tr>
  <xsl:for-each select="section">
  <xsl:if test="position() = last()">
        <th>DS Field</th>
  </xsl:if>
        <th><xsl:value-of select="."/></th>
  </xsl:for-each>
      </tr>
</xsl:template>
<xsl:template match="/psml/packet">
      <tr>
  <xsl:variable name="packetnum" select="section[1]"/>
  <xsl:for-each select="section">
    <xsl:if test="position()=last()">
        <td><xsl:value-of select= "$packetdetail/pdml/packet/proto[@name = 'geninfo']/field[@name = 'num' and @show = $packetnum]/../../proto[@name = 'ip']/field[@name='ip.dsfield']/@showname"/></td>
    </xsl:if>
    <td><xsl:value-of select="."/></td>
  </xsl:for-each>
      </tr>
</xsl:template>
</xsl:stylesheet>

Packetsumm2.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="table7.xsl"?>
<psml version="0" creator="wireshark/1.0.2">
<structure>
<section>No.</section>
<section>Time</section>
<section>Source</section>
<section>Destination</section>
<section>Protocol</section>
<section>Info</section>
</structure>

<packet>
<section>1</section>
<section>0.000000</section>
<section>192.168.0.7</section>
<section>74.125.19.147</section>
<section>ICMP</section>
<section>Echo (ping) request</section>
</packet>

<packet>
<section>2</section>
<section>0.181465</section>
<section>74.125.19.147</section>
<section>192.168.0.7</section>
<section>ICMP</section>
<section>Echo (ping) reply</section>
</packet>

<packet>
<section>3</section>
<section>9.897599</section>
<section>192.168.0.7</section>
<section>74.125.19.147</section>
<section>ICMP</section>
<section>Echo (ping) request</section>
</packet>

<packet>
<section>4</section>
<section>10.079768</section>
<section>74.125.19.147</section>
<section>192.168.0.7</section>
<section>ICMP</section>
<section>Echo (ping) reply</section>
</packet>

</psml>

Packets2.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="packets.xsl"?>
<pdml version="0" creator="wireshark/1.0.2">
<packet>
  <proto name="geninfo" pos="0" showname="General information" size="74">
    <field name="num" pos="0" show="1" showname="Number" value="1" size="74"/>
    <field name="len" pos="0" show="74" showname="Packet Length" value="4a" size="74"/>
    <field name="caplen" pos="0" show="74" showname="Captured Length" value="4a" size="74"/>
    <field name="timestamp" pos="0" show="Jan  8, 2009 10:14:57.971166000" showname="Captured Time" value="1231373697.971166000" size="74"/>
  </proto>
  <proto name="frame" showname="Frame 1 (74 bytes on wire, 74 bytes captured)" size="74" pos="0">
    <field name="frame.time" showname="Arrival Time: Jan  8, 2009 10:14:57.971166000" size="0" pos="0" show="Jan  8, 2009 10:14:57.971166000"/>
    <field name="frame.time_delta" showname="Time delta from previous captured frame: 0.000000000 seconds" size="0" pos="0" show="0.000000000"/>
    <field name="frame.time_delta_displayed" showname="Time delta from previous displayed frame: 0.000000000 seconds" size="0" pos="0" show="0.000000000"/>
    <field name="frame.time_relative" showname="Time since reference or first frame: 0.000000000 seconds" size="0" pos="0" show="0.000000000"/>
    <field name="frame.number" showname="Frame Number: 1" size="0" pos="0" show="1"/>
    <field name="frame.pkt_len" showname="Packet Length: 74 bytes" hide="yes" size="0" pos="0" show="74"/>
    <field name="frame.len" showname="Frame Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.cap_len" showname="Capture Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.marked" showname="Frame is marked: False" size="0" pos="0" show="0"/>
    <field name="frame.protocols" showname="Protocols in frame: eth:ip:icmp:data" size="0" pos="0" show="eth:ip:icmp:data"/>
  </proto>
  <proto name="eth" showname="Ethernet II, Src: Intel_28:c7:f7 (00:18:de:28:c7:f7), Dst: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="14" pos="0">
    <field name="eth.dst" showname="Destination: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="0" show="00:09:5b:ea:06:78" value="00095bea0678">
      <field name="eth.addr" showname="Address: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="0" show="00:09:5b:ea:06:78" value="00095bea0678"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="0" show="0" value="0" unmaskedvalue="00095b"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="0" show="0" value="0" unmaskedvalue="00095b"/>
    </field>
    <field name="eth.src" showname="Source: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="6" show="00:18:de:28:c7:f7" value="0018de28c7f7">
      <field name="eth.addr" showname="Address: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="6" show="00:18:de:28:c7:f7" value="0018de28c7f7"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="6" show="0" value="0" unmaskedvalue="0018de"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="6" show="0" value="0" unmaskedvalue="0018de"/>
    </field>
    <field name="eth.type" showname="Type: IP (0x0800)" size="2" pos="12" show="0x0800" value="0800"/>
  </proto>
  <proto name="ip" showname="Internet Protocol, Src: 192.168.0.7 (192.168.0.7), Dst: 74.125.19.147 (74.125.19.147)" size="20" pos="14">
    <field name="ip.version" showname="Version: 4" size="1" pos="14" show="4" value="45"/>
    <field name="ip.hdr_len" showname="Header length: 20 bytes" size="1" pos="14" show="20" value="45"/>
    <field name="ip.dsfield" showname="Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00)" size="1" pos="15" show="0" value="00">
      <field name="ip.dsfield.dscp" showname="0000 00.. = Differentiated Services Codepoint: Default (0x00)" size="1" pos="15" show="0x00" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ect" showname=".... ..0. = ECN-Capable Transport (ECT): 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ce" showname=".... ...0 = ECN-CE: 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.len" showname="Total Length: 60" size="2" pos="16" show="60" value="003c"/>
    <field name="ip.id" showname="Identification: 0x84c6 (33990)" size="2" pos="18" show="0x84c6" value="84c6"/>
    <field name="ip.flags" showname="Flags: 0x00" size="1" pos="20" show="0x00" value="00">
      <field name="ip.flags.rb" showname="0... = Reserved bit: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.df" showname=".0.. = Don't fragment: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.mf" showname="..0. = More fragments: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.frag_offset" showname="Fragment offset: 0" size="2" pos="20" show="0" value="0000"/>
    <field name="ip.ttl" showname="Time to live: 128" size="1" pos="22" show="128" value="80"/>
    <field name="ip.proto" showname="Protocol: ICMP (0x01)" size="1" pos="23" show="0x01" value="01"/>
    <field name="ip.checksum" showname="Header checksum: 0x973b [correct]" size="2" pos="24" show="0x973b" value="973b">
      <field name="ip.checksum_good" showname="Good: True" size="2" pos="24" show="1" value="973b"/>
      <field name="ip.checksum_bad" showname="Bad : False" size="2" pos="24" show="0" value="973b"/>
    </field>
    <field name="ip.src" showname="Source: 192.168.0.7 (192.168.0.7)" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.addr" showname="Source or Destination Address: 192.168.0.7 (192.168.0.7)" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.src_host" showname="Source Host: 192.168.0.7" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.host" showname="Source or Destination Host: 192.168.0.7" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.dst" showname="Destination: 74.125.19.147 (74.125.19.147)" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.addr" showname="Source or Destination Address: 74.125.19.147 (74.125.19.147)" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.dst_host" showname="Destination Host: 74.125.19.147" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.host" showname="Source or Destination Host: 74.125.19.147" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
  </proto>
  <proto name="icmp" showname="Internet Control Message Protocol" size="40" pos="34">
    <field name="icmp.type" showname="Type: 8 (Echo (ping) request)" size="1" pos="34" show="8" value="08"/>
    <field name="icmp.code" showname="Code: 0 ()" size="1" pos="35" show="0x00" value="00"/>
    <field name="icmp.checksum" showname="Checksum: 0x415c [correct]" size="2" pos="36" show="0x415c" value="415c"/>
    <field name="icmp.ident" showname="Identifier: 0x0200" size="2" pos="38" show="0x0200" value="0200"/>
    <field name="icmp.seq" showname="Sequence number: 2560 (0x0a00)" size="2" pos="40" show="2560" value="0a00"/>
    <field name="data" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      <field name="data.data" showname="Data: 6162636465666768696A6B6C6D6E6F707172737475767761..." size="32" pos="42" show="61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:61:62:63:64:65:66:67:68:69" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      </proto>
</packet>

<packet>
  <proto name="geninfo" pos="0" showname="General information" size="74">
    <field name="num" pos="0" show="2" showname="Number" value="2" size="74"/>
    <field name="len" pos="0" show="74" showname="Packet Length" value="4a" size="74"/>
    <field name="caplen" pos="0" show="74" showname="Captured Length" value="4a" size="74"/>
    <field name="timestamp" pos="0" show="Jan  8, 2009 10:14:58.152631000" showname="Captured Time" value="1231373698.152631000" size="74"/>
  </proto>
  <proto name="frame" showname="Frame 2 (74 bytes on wire, 74 bytes captured)" size="74" pos="0">
    <field name="frame.time" showname="Arrival Time: Jan  8, 2009 10:14:58.152631000" size="0" pos="0" show="Jan  8, 2009 10:14:58.152631000"/>
    <field name="frame.time_delta" showname="Time delta from previous captured frame: 0.181465000 seconds" size="0" pos="0" show="0.181465000"/>
    <field name="frame.time_delta_displayed" showname="Time delta from previous displayed frame: 0.181465000 seconds" size="0" pos="0" show="0.181465000"/>
    <field name="frame.time_relative" showname="Time since reference or first frame: 0.181465000 seconds" size="0" pos="0" show="0.181465000"/>
    <field name="frame.number" showname="Frame Number: 2" size="0" pos="0" show="2"/>
    <field name="frame.pkt_len" showname="Packet Length: 74 bytes" hide="yes" size="0" pos="0" show="74"/>
    <field name="frame.len" showname="Frame Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.cap_len" showname="Capture Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.marked" showname="Frame is marked: False" size="0" pos="0" show="0"/>
    <field name="frame.protocols" showname="Protocols in frame: eth:ip:icmp:data" size="0" pos="0" show="eth:ip:icmp:data"/>
  </proto>
  <proto name="eth" showname="Ethernet II, Src: Netgear_ea:06:78 (00:09:5b:ea:06:78), Dst: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="14" pos="0">
    <field name="eth.dst" showname="Destination: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="0" show="00:18:de:28:c7:f7" value="0018de28c7f7">
      <field name="eth.addr" showname="Address: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="0" show="00:18:de:28:c7:f7" value="0018de28c7f7"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="0" show="0" value="0" unmaskedvalue="0018de"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="0" show="0" value="0" unmaskedvalue="0018de"/>
    </field>
    <field name="eth.src" showname="Source: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="6" show="00:09:5b:ea:06:78" value="00095bea0678">
      <field name="eth.addr" showname="Address: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="6" show="00:09:5b:ea:06:78" value="00095bea0678"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="6" show="0" value="0" unmaskedvalue="00095b"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="6" show="0" value="0" unmaskedvalue="00095b"/>
    </field>
    <field name="eth.type" showname="Type: IP (0x0800)" size="2" pos="12" show="0x0800" value="0800"/>
  </proto>
  <proto name="ip" showname="Internet Protocol, Src: 74.125.19.147 (74.125.19.147), Dst: 192.168.0.7 (192.168.0.7)" size="20" pos="14">
    <field name="ip.version" showname="Version: 4" size="1" pos="14" show="4" value="45"/>
    <field name="ip.hdr_len" showname="Header length: 20 bytes" size="1" pos="14" show="20" value="45"/>
    <field name="ip.dsfield" showname="Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00)" size="1" pos="15" show="0" value="00">
      <field name="ip.dsfield.dscp" showname="0000 00.. = Differentiated Services Codepoint: Default (0x00)" size="1" pos="15" show="0x00" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ect" showname=".... ..0. = ECN-Capable Transport (ECT): 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ce" showname=".... ...0 = ECN-CE: 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.len" showname="Total Length: 60" size="2" pos="16" show="60" value="003c"/>
    <field name="ip.id" showname="Identification: 0x57d9 (22489)" size="2" pos="18" show="0x57d9" value="57d9"/>
    <field name="ip.flags" showname="Flags: 0x00" size="1" pos="20" show="0x00" value="00">
      <field name="ip.flags.rb" showname="0... = Reserved bit: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.df" showname=".0.. = Don't fragment: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.mf" showname="..0. = More fragments: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.frag_offset" showname="Fragment offset: 0" size="2" pos="20" show="0" value="0000"/>
    <field name="ip.ttl" showname="Time to live: 239" size="1" pos="22" show="239" value="ef"/>
    <field name="ip.proto" showname="Protocol: ICMP (0x01)" size="1" pos="23" show="0x01" value="01"/>
    <field name="ip.checksum" showname="Header checksum: 0x5528 [correct]" size="2" pos="24" show="0x5528" value="5528">
      <field name="ip.checksum_good" showname="Good: True" size="2" pos="24" show="1" value="5528"/>
      <field name="ip.checksum_bad" showname="Bad : False" size="2" pos="24" show="0" value="5528"/>
    </field>
    <field name="ip.src" showname="Source: 74.125.19.147 (74.125.19.147)" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.addr" showname="Source or Destination Address: 74.125.19.147 (74.125.19.147)" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.src_host" showname="Source Host: 74.125.19.147" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.host" showname="Source or Destination Host: 74.125.19.147" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.dst" showname="Destination: 192.168.0.7 (192.168.0.7)" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.addr" showname="Source or Destination Address: 192.168.0.7 (192.168.0.7)" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.dst_host" showname="Destination Host: 192.168.0.7" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.host" showname="Source or Destination Host: 192.168.0.7" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
  </proto>
  <proto name="icmp" showname="Internet Control Message Protocol" size="40" pos="34">
    <field name="icmp.type" showname="Type: 0 (Echo (ping) reply)" size="1" pos="34" show="0" value="00"/>
    <field name="icmp.code" showname="Code: 0 ()" size="1" pos="35" show="0x00" value="00"/>
    <field name="icmp.checksum" showname="Checksum: 0x495c [correct]" size="2" pos="36" show="0x495c" value="495c"/>
    <field name="icmp.ident" showname="Identifier: 0x0200" size="2" pos="38" show="0x0200" value="0200"/>
    <field name="icmp.seq" showname="Sequence number: 2560 (0x0a00)" size="2" pos="40" show="2560" value="0a00"/>
    <field name="data" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      <field name="data.data" showname="Data: 6162636465666768696A6B6C6D6E6F707172737475767761..." size="32" pos="42" show="61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:61:62:63:64:65:66:67:68:69" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      </proto>
</packet>

<packet>
  <proto name="geninfo" pos="0" showname="General information" size="74">
    <field name="num" pos="0" show="3" showname="Number" value="3" size="74"/>
    <field name="len" pos="0" show="74" showname="Packet Length" value="4a" size="74"/>
    <field name="caplen" pos="0" show="74" showname="Captured Length" value="4a" size="74"/>
    <field name="timestamp" pos="0" show="Jan  8, 2009 10:15:07.868765000" showname="Captured Time" value="1231373707.868765000" size="74"/>
  </proto>
  <proto name="frame" showname="Frame 3 (74 bytes on wire, 74 bytes captured)" size="74" pos="0">
    <field name="frame.time" showname="Arrival Time: Jan  8, 2009 10:15:07.868765000" size="0" pos="0" show="Jan  8, 2009 10:15:07.868765000"/>
    <field name="frame.time_delta" showname="Time delta from previous captured frame: 9.716134000 seconds" size="0" pos="0" show="9.716134000"/>
    <field name="frame.time_delta_displayed" showname="Time delta from previous displayed frame: 9.716134000 seconds" size="0" pos="0" show="9.716134000"/>
    <field name="frame.time_relative" showname="Time since reference or first frame: 9.897599000 seconds" size="0" pos="0" show="9.897599000"/>
    <field name="frame.number" showname="Frame Number: 3" size="0" pos="0" show="3"/>
    <field name="frame.pkt_len" showname="Packet Length: 74 bytes" hide="yes" size="0" pos="0" show="74"/>
    <field name="frame.len" showname="Frame Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.cap_len" showname="Capture Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.marked" showname="Frame is marked: False" size="0" pos="0" show="0"/>
    <field name="frame.protocols" showname="Protocols in frame: eth:ip:icmp:data" size="0" pos="0" show="eth:ip:icmp:data"/>
  </proto>
  <proto name="eth" showname="Ethernet II, Src: Intel_28:c7:f7 (00:18:de:28:c7:f7), Dst: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="14" pos="0">
    <field name="eth.dst" showname="Destination: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="0" show="00:09:5b:ea:06:78" value="00095bea0678">
      <field name="eth.addr" showname="Address: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="0" show="00:09:5b:ea:06:78" value="00095bea0678"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="0" show="0" value="0" unmaskedvalue="00095b"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="0" show="0" value="0" unmaskedvalue="00095b"/>
    </field>
    <field name="eth.src" showname="Source: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="6" show="00:18:de:28:c7:f7" value="0018de28c7f7">
      <field name="eth.addr" showname="Address: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="6" show="00:18:de:28:c7:f7" value="0018de28c7f7"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="6" show="0" value="0" unmaskedvalue="0018de"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="6" show="0" value="0" unmaskedvalue="0018de"/>
    </field>
    <field name="eth.type" showname="Type: IP (0x0800)" size="2" pos="12" show="0x0800" value="0800"/>
  </proto>
  <proto name="ip" showname="Internet Protocol, Src: 192.168.0.7 (192.168.0.7), Dst: 74.125.19.147 (74.125.19.147)" size="20" pos="14">
    <field name="ip.version" showname="Version: 4" size="1" pos="14" show="4" value="45"/>
    <field name="ip.hdr_len" showname="Header length: 20 bytes" size="1" pos="14" show="20" value="45"/>
    <field name="ip.dsfield" showname="Differentiated Services Field: 0x88 (DSCP 0x22: Assured Forwarding 41; ECN: 0x00)" size="1" pos="15" show="136" value="88">
      <field name="ip.dsfield.dscp" showname="1000 10.. = Differentiated Services Codepoint: Assured Forwarding 41 (0x22)" size="1" pos="15" show="0x22" value="22" unmaskedvalue="88"/>
      <field name="ip.dsfield.ect" showname=".... ..0. = ECN-Capable Transport (ECT): 0" size="1" pos="15" show="0" value="0" unmaskedvalue="88"/>
      <field name="ip.dsfield.ce" showname=".... ...0 = ECN-CE: 0" size="1" pos="15" show="0" value="0" unmaskedvalue="88"/>
    </field>
    <field name="ip.len" showname="Total Length: 60" size="2" pos="16" show="60" value="003c"/>
    <field name="ip.id" showname="Identification: 0x8519 (34073)" size="2" pos="18" show="0x8519" value="8519"/>
    <field name="ip.flags" showname="Flags: 0x00" size="1" pos="20" show="0x00" value="00">
      <field name="ip.flags.rb" showname="0... = Reserved bit: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.df" showname=".0.. = Don't fragment: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.mf" showname="..0. = More fragments: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.frag_offset" showname="Fragment offset: 0" size="2" pos="20" show="0" value="0000"/>
    <field name="ip.ttl" showname="Time to live: 128" size="1" pos="22" show="128" value="80"/>
    <field name="ip.proto" showname="Protocol: ICMP (0x01)" size="1" pos="23" show="0x01" value="01"/>
    <field name="ip.checksum" showname="Header checksum: 0x9660 [correct]" size="2" pos="24" show="0x9660" value="9660">
      <field name="ip.checksum_good" showname="Good: True" size="2" pos="24" show="1" value="9660"/>
      <field name="ip.checksum_bad" showname="Bad : False" size="2" pos="24" show="0" value="9660"/>
    </field>
    <field name="ip.src" showname="Source: 192.168.0.7 (192.168.0.7)" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.addr" showname="Source or Destination Address: 192.168.0.7 (192.168.0.7)" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.src_host" showname="Source Host: 192.168.0.7" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.host" showname="Source or Destination Host: 192.168.0.7" hide="yes" size="4" pos="26" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.dst" showname="Destination: 74.125.19.147 (74.125.19.147)" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.addr" showname="Source or Destination Address: 74.125.19.147 (74.125.19.147)" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.dst_host" showname="Destination Host: 74.125.19.147" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.host" showname="Source or Destination Host: 74.125.19.147" hide="yes" size="4" pos="30" show="74.125.19.147" value="4a7d1393"/>
  </proto>
  <proto name="icmp" showname="Internet Control Message Protocol" size="40" pos="34">
    <field name="icmp.type" showname="Type: 8 (Echo (ping) request)" size="1" pos="34" show="8" value="08"/>
    <field name="icmp.code" showname="Code: 0 ()" size="1" pos="35" show="0x00" value="00"/>
    <field name="icmp.checksum" showname="Checksum: 0x405c [correct]" size="2" pos="36" show="0x405c" value="405c"/>
    <field name="icmp.ident" showname="Identifier: 0x0200" size="2" pos="38" show="0x0200" value="0200"/>
    <field name="icmp.seq" showname="Sequence number: 2816 (0x0b00)" size="2" pos="40" show="2816" value="0b00"/>
    <field name="data" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      <field name="data.data" showname="Data: 6162636465666768696A6B6C6D6E6F707172737475767761..." size="32" pos="42" show="61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:61:62:63:64:65:66:67:68:69" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      </proto>
</packet>

<packet>
  <proto name="geninfo" pos="0" showname="General information" size="74">
    <field name="num" pos="0" show="4" showname="Number" value="4" size="74"/>
    <field name="len" pos="0" show="74" showname="Packet Length" value="4a" size="74"/>
    <field name="caplen" pos="0" show="74" showname="Captured Length" value="4a" size="74"/>
    <field name="timestamp" pos="0" show="Jan  8, 2009 10:15:08.050934000" showname="Captured Time" value="1231373708.050934000" size="74"/>
  </proto>
  <proto name="frame" showname="Frame 4 (74 bytes on wire, 74 bytes captured)" size="74" pos="0">
    <field name="frame.time" showname="Arrival Time: Jan  8, 2009 10:15:08.050934000" size="0" pos="0" show="Jan  8, 2009 10:15:08.050934000"/>
    <field name="frame.time_delta" showname="Time delta from previous captured frame: 0.182169000 seconds" size="0" pos="0" show="0.182169000"/>
    <field name="frame.time_delta_displayed" showname="Time delta from previous displayed frame: 0.182169000 seconds" size="0" pos="0" show="0.182169000"/>
    <field name="frame.time_relative" showname="Time since reference or first frame: 10.079768000 seconds" size="0" pos="0" show="10.079768000"/>
    <field name="frame.number" showname="Frame Number: 4" size="0" pos="0" show="4"/>
    <field name="frame.pkt_len" showname="Packet Length: 74 bytes" hide="yes" size="0" pos="0" show="74"/>
    <field name="frame.len" showname="Frame Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.cap_len" showname="Capture Length: 74 bytes" size="0" pos="0" show="74"/>
    <field name="frame.marked" showname="Frame is marked: False" size="0" pos="0" show="0"/>
    <field name="frame.protocols" showname="Protocols in frame: eth:ip:icmp:data" size="0" pos="0" show="eth:ip:icmp:data"/>
  </proto>
  <proto name="eth" showname="Ethernet II, Src: Netgear_ea:06:78 (00:09:5b:ea:06:78), Dst: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="14" pos="0">
    <field name="eth.dst" showname="Destination: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="0" show="00:18:de:28:c7:f7" value="0018de28c7f7">
      <field name="eth.addr" showname="Address: Intel_28:c7:f7 (00:18:de:28:c7:f7)" size="6" pos="0" show="00:18:de:28:c7:f7" value="0018de28c7f7"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="0" show="0" value="0" unmaskedvalue="0018de"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="0" show="0" value="0" unmaskedvalue="0018de"/>
    </field>
    <field name="eth.src" showname="Source: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="6" show="00:09:5b:ea:06:78" value="00095bea0678">
      <field name="eth.addr" showname="Address: Netgear_ea:06:78 (00:09:5b:ea:06:78)" size="6" pos="6" show="00:09:5b:ea:06:78" value="00095bea0678"/>
      <field name="eth.ig" showname=".... ...0 .... .... .... .... = IG bit: Individual address (unicast)" size="3" pos="6" show="0" value="0" unmaskedvalue="00095b"/>
      <field name="eth.lg" showname=".... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)" size="3" pos="6" show="0" value="0" unmaskedvalue="00095b"/>
    </field>
    <field name="eth.type" showname="Type: IP (0x0800)" size="2" pos="12" show="0x0800" value="0800"/>
  </proto>
  <proto name="ip" showname="Internet Protocol, Src: 74.125.19.147 (74.125.19.147), Dst: 192.168.0.7 (192.168.0.7)" size="20" pos="14">
    <field name="ip.version" showname="Version: 4" size="1" pos="14" show="4" value="45"/>
    <field name="ip.hdr_len" showname="Header length: 20 bytes" size="1" pos="14" show="20" value="45"/>
    <field name="ip.dsfield" showname="Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00)" size="1" pos="15" show="0" value="00">
      <field name="ip.dsfield.dscp" showname="0000 00.. = Differentiated Services Codepoint: Default (0x00)" size="1" pos="15" show="0x00" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ect" showname=".... ..0. = ECN-Capable Transport (ECT): 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.dsfield.ce" showname=".... ...0 = ECN-CE: 0" size="1" pos="15" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.len" showname="Total Length: 60" size="2" pos="16" show="60" value="003c"/>
    <field name="ip.id" showname="Identification: 0x57db (22491)" size="2" pos="18" show="0x57db" value="57db"/>
    <field name="ip.flags" showname="Flags: 0x00" size="1" pos="20" show="0x00" value="00">
      <field name="ip.flags.rb" showname="0... = Reserved bit: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.df" showname=".0.. = Don't fragment: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
      <field name="ip.flags.mf" showname="..0. = More fragments: Not set" size="1" pos="20" show="0" value="0" unmaskedvalue="00"/>
    </field>
    <field name="ip.frag_offset" showname="Fragment offset: 0" size="2" pos="20" show="0" value="0000"/>
    <field name="ip.ttl" showname="Time to live: 239" size="1" pos="22" show="239" value="ef"/>
    <field name="ip.proto" showname="Protocol: ICMP (0x01)" size="1" pos="23" show="0x01" value="01"/>
    <field name="ip.checksum" showname="Header checksum: 0x5526 [correct]" size="2" pos="24" show="0x5526" value="5526">
      <field name="ip.checksum_good" showname="Good: True" size="2" pos="24" show="1" value="5526"/>
      <field name="ip.checksum_bad" showname="Bad : False" size="2" pos="24" show="0" value="5526"/>
    </field>
    <field name="ip.src" showname="Source: 74.125.19.147 (74.125.19.147)" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.addr" showname="Source or Destination Address: 74.125.19.147 (74.125.19.147)" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.src_host" showname="Source Host: 74.125.19.147" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.host" showname="Source or Destination Host: 74.125.19.147" hide="yes" size="4" pos="26" show="74.125.19.147" value="4a7d1393"/>
    <field name="ip.dst" showname="Destination: 192.168.0.7 (192.168.0.7)" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.addr" showname="Source or Destination Address: 192.168.0.7 (192.168.0.7)" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.dst_host" showname="Destination Host: 192.168.0.7" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
    <field name="ip.host" showname="Source or Destination Host: 192.168.0.7" hide="yes" size="4" pos="30" show="192.168.0.7" value="c0a80007"/>
  </proto>
  <proto name="icmp" showname="Internet Control Message Protocol" size="40" pos="34">
    <field name="icmp.type" showname="Type: 0 (Echo (ping) reply)" size="1" pos="34" show="0" value="00"/>
    <field name="icmp.code" showname="Code: 0 ()" size="1" pos="35" show="0x00" value="00"/>
    <field name="icmp.checksum" showname="Checksum: 0x485c [correct]" size="2" pos="36" show="0x485c" value="485c"/>
    <field name="icmp.ident" showname="Identifier: 0x0200" size="2" pos="38" show="0x0200" value="0200"/>
    <field name="icmp.seq" showname="Sequence number: 2816 (0x0b00)" size="2" pos="40" show="2816" value="0b00"/>
    <field name="data" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      <field name="data.data" showname="Data: 6162636465666768696A6B6C6D6E6F707172737475767761..." size="32" pos="42" show="61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:61:62:63:64:65:66:67:68:69" value="6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869"/>
      </proto>
</packet>
</pdml>

January 20, 2009

DSCP to Type of Service Mappings

Filed under: Code, Network — Tags: , , , — networknerd @ 11:45 am

When working with quality of service configurations you’ll inevitably come across the need to perform packet markings with DSCP.  With routers this is generally fairly simple.  Performing the same function on a host is not always that simple.  All the socket API’s were designed around type of service (ToS),as defined in RFC791, rather than DSCP as defined in RFC2474.

If you’re in a hurry, the conversion is simply ToS = DSCP * 4. If you have the time the rfc’s are full of interesting information. The DSCP field with the DS field contains six bits allowing for 64 possible codepoint values. RFC2474 defines 3 pools of DSCP values, one for standardised use and the other two for local/experimental use. The standardised pool has the least significant bit set to 0, which means only the even numbers from 0 to 62 are used. A cheat sheet with the standardised DSCP values and equivalent IP precedence and ToS values can be found here.

We can test the ToS/DSCP mapping using the ping program and specifying a ToS byte value. Using the windows ping program we specify the ToS byte as a numeric argument to the -v command line switch. In this example we specify the the ToS byte as 136 which corresponds to a DSCP value of 34  or AF41.

ping -n 1 -v 136 http://www.google.com

Performing a similar test for tcp connection requires a little bit of coding effort. Listing 1 shows some c# code to do a simple get request to http://www.google.com.au,  using the SetSocketOption function to set the ToS byte to 136.  The wireshark output for both tests is shown below.  The  lua script shown in listing 2 was used to extract the diffserv field and dscp values while running the capture with Tshark. It is worth noting that even though the socketoption is set prior to the connection, no dscp values are set on the two packets from the client during the TCP three way handshake (frame 5 and 7 below).

tshark  -X lua_script:getdscp.lua -i 2 host http://www.google.com.au
Frame #1    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 ICMP Echo (ping) request

Frame #2    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  ICMP Echo (ping) reply

Frame #3    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 ICMP Echo (ping) request

Frame #4    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  ICMP Echo (ping) reply

Frame #5    Diffserv Field: 0    DSCP: 0
192.168.0.7 -> 74.125.19.103 TCP linx > http [SYN] Seq=0 Win=16384 Len=0 MSS=1260

Frame #6    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP http > linx [SYN, ACK] Seq=0 Ack=1 Win=5720 Len=0 MSS=1430

Frame #7    Diffserv Field: 0    DSCP: 0
192.168.0.7 -> 74.125.19.103 TCP linx > http [ACK] Seq=1 Ack=1 Win=17640 Len=0

Frame #8    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 HTTP GET / HTTP/1.0

Frame #9    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP http > linx [ACK] Seq=1 Ack=41 Win=5720 Len=0

Frame #10    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP [TCP segment of a reassembled PDU]

Frame #11    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP [TCP segment of a reassembled PDU]

Frame #12    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 TCP linx > http [ACK] Seq=41 Ack=2521 Win=17640 Len=0

Frame #13    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP [TCP segment of a reassembled PDU]

Frame #14    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP [TCP segment of a reassembled PDU]

Frame #15    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 TCP linx > http [ACK] Seq=41 Ack=5041 Win=17640 Len=0

Frame #16    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP [TCP segment of a reassembled PDU]

Frame #17    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  HTTP HTTP/1.0 200 OK  (text/html)

Frame #18    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 TCP linx > http [ACK] Seq=41 Ack=6611 Win=17640 Len=0

Frame #19    Diffserv Field: 136    DSCP: 34
192.168.0.7 -> 74.125.19.103 TCP linx > http [FIN, ACK] Seq=41 Ack=6611 Win=17640 Len=0

Frame #20    Diffserv Field: 0    DSCP: 0
74.125.19.103 -> 192.168.0.7  TCP http > linx [ACK] Seq=6611 Ack=42 Win=5720 Len=0
Listing  1

using System;
using System.Text;
using System.Net.Sockets;

namespace tcptos
{
	class Program
	{
		public static void Main(string[] args)
		{
			Console.WriteLine("Testing DSCP packet marking.Wireshark should already be running!\n");

			TcpClient cli = new TcpClient();
			cli.Client.SetSocketOption(SocketOptionLevel.IP,
			                           SocketOptionName.TypeOfService, 136);
			cli.Connect("www.google.com.au",80);
			byte[] buf = Encoding.ASCII.GetBytes("GET / HTTP/1.0\nHost: www.google.com.au\n\n");
			byte[] readbuf = new byte[4096];
			int bytesRead;
			StringBuilder response = new StringBuilder();
			NetworkStream mystream = cli.GetStream();
			mystream.Write(buf,0,buf.Length);
			do{
				bytesRead = mystream.Read(readbuf, 0, readbuf.Length);
				response.AppendFormat("{0}", Encoding.ASCII.GetString(readbuf, 0, bytesRead));
			}
			while(bytesRead > 0);
			Console.Write(response.ToString());
			Console.Write("\nPress any key to continue . . . ");
			Console.ReadKey(true);
			cli.Close();
		}
	}
}

Listing 2

local dsfield = Field.new("ip.dsfield")
local dscp = Field.new("ip.dsfield.dscp")
local fnum = Field.new("frame.number")
do
    packets = 0;
    local function init_listener()
        local tap = Listener.new("frame","ip.version == 4")

        function tap.reset()
        end
        function tap.packet(pinfo,tvb,ip)
            local fnumber = tostring(fnum())
            local diffserv = tostring(dsfield())
            local codepoint = tostring(dscp())
            local stroutput = "\nFrame #" .. fnumber .. "\tDiffserv Field: " ..
				diffserv .. "\tDSCP: " ..codepoint
            print(stroutput)
        end
        function tap.draw()

        end
    end
    init_listener()
end

January 1, 2009

Accessing Firefox 3 Discontinuous Selections with Javascript

Filed under: Code — Tags: , , — networknerd @ 1:02 pm

Firefox 3 now has the ability to highlight discontinuous sections of a web page by holding down the control key while making the selections. Very handy for note taking where you want to cut and paste only the relevant sections.

It occurred to me though that it might be useful to be able to access the individual parts of the selection.  My first guess was to use the window.getSelection() method and pass it an integer which refers to the part of the selection.  Bad guess, that just gave me all parts of the selection concatenated.  After referring to the documentation it seems that the selection object has the rangeCount property and the getRangeAt method for accessing individual parts of the selection.

I decided to use a bookmarklet as a quick and dirty way to check it out.  The code is listed below in a readable format or you can copy the whole url if you want to test it as is.

It’s worth pointing out that your mileage may vary here if your trying to extract a selection from an input control such as a text box.

javascript:
(
  function()
  {
    var sel=window.getSelection();
    var cnt=sel.rangeCount;
    var ranges=[];
    for(var i = 0; i < sel.rangeCount; i++)
    {
      ranges[i] = sel.getRangeAt(i);
      alert(ranges[i]);
    }
    alert(cnt);
  }()
);
javascript:(function(){var%20sel=window.getSelection();var%20cnt=sel.rangeCount;var%20ranges=[];for(var%20i%20=%200;%20i%20<%20sel.rangeCount;%20i++){ranges[i]%20=%20sel.getRangeAt(i);alert(ranges[i]);}alert(cnt);}());

November 23, 2008

Active Directory Account Auditing With Excel

Filed under: Code — Tags: , , , — networknerd @ 10:56 am

One of the less attractive aspects of a career in security is routine auditing.  It’s boring, but it still needs to be done.  As users leave the HR department should notify IT and the account should be disabled or set to expire on their last day in the office.  The process can go wrong at HR or in IT.

To make the audit task a little easier I came up with a VBA macro for excel. Active directory is queried for all user accounts that are not disabled by running the AD_QUERY macro.  The spreadsheet is populated with a list of all active accounts.  The CopyPW and  CopyLstLogon macros will add an additional worksheet each which contains filtered lists of accounts with passwords that don’t expire, and last logons greater than 90 days.

The need to change passwords is obvious, but the last logon timestamp being greater than 90 days is a good indicator that someone has left and there was a process failure, or that it was a test account that should have been deleted.

To use the macros just create a new spreadsheet and open the VBA editor using ALT-F11, right click on VBAPROJECT(Book1) and select insert then module from the context menu.  Copy the code from listing 1 and paste it into the new module.  Close the VBA editor and your ready to run the macros.

Hope this makes life a little easier for you if your doing the account audits.

Listing 1

Option Explicit
Const ADS_UF_ACCOUNTDISABLE = 2
Const ADS_SCOPE_SUBTREE = 2
Const ADS_UF_DONT_EXPIRE_PASSWD = 65536
Const FLD_FULLNAME = 1
Const FLD_SAM_ACCTNAME = 2
Const FLD_CREATEDATE = 3
Const FLD_PWD_LASTCHNG = 4
Const FLD_PWD_DONTEXPIRE = 5
Const FLD_UAC = 6
Const FLD_LASTLOGON = 7
Const FLD_ADSPATH = 8
Const FLD_MAX = 8
Const HEADROW = 1
Const ASCII_OFFSET = 64
Sub AD_QUERY()
Dim objUser, objLogon, objConnection, objCommand, objRecordSet
Dim strPath, strFullName, strSamAccountName
Dim intUAC, intLogonTime
Dim createdate, pwdchanged
Dim Disabled, PWDexpire, intCounter
Dim objsheet As Excel.Worksheet
Dim rngData As Excel.Range
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Properties("ADSI Flag") = 1
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 10000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
'Search AD Global catalog for user objects that are not disabled
objCommand.CommandText =  "<GC://dc=acme,dc=com,dc=au>;" & _
  "(&(objectClass=user)(objectCategory=person)(!userAccountControl:1.2.840.113556.1.4.803:=2));" & _
  "adspath, samAccountName; subtree"
Application.StatusBar = "Executing AD Query. Please wait..."
Set objRecordSet = objCommand.Execute
Application.StatusBar = "Populating Worksheet with data. Please wait..."
Set objsheet = Application.ActiveWorkbook.Worksheets.Add()
objsheet.Name = Format(Date, "dd-mm-yyyy") & " Raw Data"
intCounter = 2 'Initialise worksheet row counter
objsheet.Cells(HEADROW, FLD_FULLNAME).Value = "Full Name"
objsheet.Cells(HEADROW, FLD_SAM_ACCTNAME).Value = "SAM Account name"
objsheet.Cells(HEADROW, FLD_CREATEDATE).Value = "Create Date (UTC)"
objsheet.Cells(HEADROW, FLD_PWD_LASTCHNG).Value = "PWD Last Changed"
objsheet.Cells(HEADROW, FLD_PWD_DONTEXPIRE).Value = "PWD Don't Expire"
objsheet.Cells(HEADROW, FLD_UAC).Value = "UAC"
objsheet.Cells(HEADROW, FLD_LASTLOGON).Value = "LastLogonTimestamp"
objsheet.Cells(HEADROW, FLD_ADSPATH).Value = "ADSPATH"
objRecordSet.MoveFirst
Do Until objRecordSet.EOF
  strPath = objRecordSet.Fields("adspath")
'Change the global catalog path to an ldap path so that we can access
'all the attributes when binding to the object.
  strPath = Replace(strPath, "GC://", "LDAP://")
  Set objUser = GetObject(strPath) intUAC = objUser.userAccountControl
  If (intUAC And ADS_UF_DONT_EXPIRE_PASSWD) = 0 Then
    PWDexpire = False
  Else
    PWDexpire = True
  End If
  On Error Resume Next
  Err.Clear
  Set objLogon = objUser.LastLogonTimestamp
  If Err.Number <> 0 Then
    intLogonTime = 0
    Err.Clear
  Else
    intLogonTime = objLogon.HighPart * (2 ^ 32) + objLogon.LowPart
    intLogonTime = intLogonTime / (60 * 10000000)
    intLogonTime = intLogonTime / 1440
  End If
  strFullName = objUser.FullName
  If Err.Number <> 0 Then
    strFullName = ""
    Err.Clear
  End If
  createdate = objUser.whenCreated
  If Err.Number <> 0 Then
    createdate = ""
    Err.Clear
  End If
  pwdchanged = objUser.passwordLastChanged
  If Err.Number <> 0 Then
    pwdchanged = ""
    Err.Clear
  End If
  On Error GoTo 0
  strSamAccountName = objUser.SamAccountName
  objsheet  .Cells(intCounter, FLD_FULLNAME).Value = strFullName
  objsheet.Cells(intCounter, FLD_SAM_ACCTNAME).Value = strSamAccountName
  objsheet.Cells(intCounter, FLD_CREATEDATE).Value = createdate
  objsheet.Cells(intCounter, FLD_PWD_LASTCHNG).Value = pwdchanged
  objsheet.Cells(intCounter, FLD_PWD_DONTEXPIRE).Value = PWDexpire
  objsheet.Cells(intCounter, FLD_UAC).Value = intUAC
  If intLogonTime <> 0 Then
    objsheet.Cells(intCounter, FLD_LASTLOGON).Value = intLogonTime + #1/1/1601#
  Else
    objsheet.Cells(intCounter, FLD_LASTLOGON).Value = "#1/1/1601#"
  End If
  objsheet.Cells(intCounter, FLD_ADSPATH).Value = strPath
  objRecordSet.MoveNext intCounter = intCounter + 1
Loop
Set rngData = objsheet.Range("A1:" & Chr(ASCII_OFFSET + FLD_MAX) & intCounter - 1)
'if the named range already exists we need to delete is before we create it again.
'This will allow more than one audit set to be retained in the same workbook.
On Error Resume Next
ActiveWorkbook.Names("AD_DATA_SET").Delete
On Error GoTo 0
rngData.Name = "AD_DATA_SET"
rngData.Columns.AutoFit
Application.StatusBar = "Ready"
End Sub

Sub filter_lastlogon()
Dim rngData As Excel.Range
Set rngData = Range("AD_DATA_SET")
rngData.Worksheet.AutoFilterMode = False
'Filter function seems to ignore locale info so dates must be in US format
rngData.autofilter Field:=FLD_LASTLOGON, Criteria1:="=#1/1/1601#", Operator:=xlOr, _
  Criteria2:="<" & Format(Now() - 90, "mm/dd/yyyy")
End Sub

Sub filter_pwd_dontexpire()
Dim rngData As Excel.Range
Set rngData = Range("AD_DATA_SET")
rngData.Worksheet.AutoFilterMode = False
rngData.autofilter Field:=FLD_PWD_DONTEXPIRE, Criteria1:="=True"
End Sub

Sub RemoveFilter()
Dim rngData As Excel.Range
Set rngData = Range("AD_DATA_SET")
rngData.Worksheet.AutoFilterMode = False
End Sub

Sub CopyPW()
'Copies the filtered data to a new Worksheet
'Code modified from http://www.contextures.com/xlautofilter03.html#Copy
'Viewed 7/6/2007
Dim rngData As Excel.Range
Dim rng As Range
Dim rng2 As Range
Dim objsheet As Worksheet
Set rngData = Range("AD_DATA_SET")
Call filter_pwd_dontexpire
If Not rngData.Worksheet.FilterMode Then
  MsgBox "Filter Data before selecting this option", vbExclamation
  Exit Sub
End If
With rngData.Worksheet.autofilter.Range
  On Error Resume Next
  Set rng2 = .Offset(1, 0).Resize(.Rows.Count - 1, 1) _
    .SpecialCells(xlCellTypeVisible)
  On Error GoTo 0
End With
If rng2 Is Nothing
  Then MsgBox "No data to copy"
Else
  Set objsheet = Application.ActiveWorkbook.Worksheets.Add()
  objsheet.Name = Format(Date, "dd-mm-yyyy") & " Password dont expire"
  Set rng = rngData.Worksheet.autofilter.Range rng.Offset(1, 0).Resize(rng.Rows.Count - 1).Copy _
    Destination:=objsheet.Range("A2")
  objsheet.Cells(HEADROW, FLD_FULLNAME).Value = "Full Name"
  objsheet.Cells(HEADROW, FLD_SAM_ACCTNAME).Value = "SAM Account name"
  objsheet.Cells(HEADROW, FLD_CREATEDATE).Value = "Create Date (UTC)"
  objsheet.Cells(HEADROW, FLD_PWD_LASTCHNG).Value = "PWD Last Changed"
  objsheet.Cells(HEADROW, FLD_PWD_DONTEXPIRE).Value = "PWD Don't Expire"
  objsheet.Cells(HEADROW, FLD_UAC).Value = "UAC"
  objsheet.Cells(HEADROW, FLD_LASTLOGON).Value = "LastLogonTimestamp"
  objsheet.Cells(HEADROW, FLD_ADSPATH).Value = "ADSPATH"
  objsheet.Columns.AutoFit
End If
End Sub

Sub CopyLstLogon()
'Copies the filtered data to a new Worksheet
'Code modified from http://www.contextures.com/xlautofilter03.html#Copy
'Viewed 7/6/2007
Dim rngData As Excel.Range
Dim rng As Range
Dim rng2 As Range
Dim objsheet As Worksheet
Set rngData = Range("AD_DATA_SET")
Call filter_lastlogon
If Not rngData.Worksheet.FilterMode Then
  MsgBox "Filter Data before selecting this option", vbExclamation
  Exit Sub
End If
With rngData.Worksheet.autofilter.Range
  On Error Resume Next
  Set rng2 = .Offset(1, 0).Resize(.Rows.Count - 1, 1) _
    .SpecialCells(xlCellTypeVisible)
  On Error GoTo 0
End With
If rng2 Is Nothing Then
  MsgBox "No data to copy"
Else
  Set objsheet = Application.ActiveWorkbook.Worksheets.Add()
  objsheet.Name = Format(Date, "dd-mm-yyyy") & " LastLogon > 90 days"
  Set rng = rngData.Worksheet.autofilter.Range rng.Offset(1, 0).Resize(rng.Rows.Count - 1).Copy _
    Destination:=objsheet.Range("A2")
  objsheet.Cells(HEADROW, FLD_FULLNAME).Value = "Full Name"
  objsheet.Cells(HEADROW, FLD_SAM_ACCTNAME).Value = "SAM Account name"
  objsheet.Cells(HEADROW, FLD_CREATEDATE).Value = "Create Date (UTC)"
  objsheet.Cells(HEADROW, FLD_PWD_LASTCHNG).Value = "PWD Last Changed"
  objsheet.Cells(HEADROW, FLD_PWD_DONTEXPIRE).Value = "PWD Don't Expire"
  objsheet.Cells(HEADROW, FLD_UAC).Value = "UAC"
  objsheet.Cells(HEADROW, FLD_LASTLOGON).Value = "LastLogonTimestamp"
  objsheet.Cells(HEADROW, FLD_ADSPATH).Value = "ADSPATH"
  objsheet.Columns.AutoFit
End If
End Sub

October 7, 2008

HTA to Set Exchange “Out of Office” Message

Filed under: Code — Tags: , , , , — networknerd @ 8:18 pm

This HTA was created to help streamline a common helpdesk task, setting the OOF message for users who have gone on holidays and failed to set the OOF message.

The original process involved the helpdesk giving themselves access to the mailbox in question, creating an outlook profile for the users mailbox,  and  starting outlook to set the OOF message, and finally revoking the permissions to the users mailbox. After performing an audit of mailbox permissions it became obvious that the final step of revoking permissions was being frequently overlooked.

The script consists of a few simple steps

  1. Perform an AD search for the users samaccountname and return their exchange server.
  2. Grant full control to the mailbox for the helpdesk staff member.
  3. Create a mapi profile for the mailbox.
  4. Get/Set the current OOFmessage.
  5. Toggle the Out of Office status flag.

The process of managing the removal of mailbox permissions is handled in the window unload function of the browser.

Listing 1 – OOF.HTA

<html>
<head>
<title>Set out of Office Message</title>
<HTA:APPLICATION
ID="OOF"
APPLICATIONNAME="Set Out Of Office Message"
SCROLL="yes"
SINGLEINSTANCE="yes"
>
</head>
<SCRIPT LANGUAGE="VBScript"> option explicit
Rem reference http://www.cdolive.com/outofofficecalendar.htm Rem Updated to grant and remove permissions to the mailbox automatically CONST ADS_ACEFLAG_INHERIT_ACE = 2 CONST ADS_RIGHT_DS_CREATE_CHILD = 1 CONST ADS_ACETYPE_ACCESS_ALLOWED = 0 Const ACE_MB_FULL_ACCESS = &h1 Rem Define all our variables Dim strProfileInfo, CDOSession, strOOFText, objButton, objInfostore,CdoFolderRoot Dim objConnection, objCommand, objRecordSet, intRetcode, objOption Dim strExchsvr, strPath, objUser, objTrustee, strTrustee, WshNetwork,boolRightsSet, objshell Dim objMBXlist, oSecurityDescriptor, dacl, ace, arrTemp '***************************************************************************** '* function window_onload '* Purpose: Initialise all the global variables required to proceed or '* terminate the application. '* Inputs: none '* Returns: nothing '***************************************************************************** sub window_onload on error resume next set objMBXlist = createobject("scripting.dictionary") if err.number = 0 then on error goto 0 strTrustee = getTrusteeName() else msgbox "Fatal Error - Could not create dictionary object." & vbcrlf & "Application will now close.", VBCRITICAL self.close() end if if strTrustee = "" then msgbox "Fatal Error - Could not get logged on user info." & vbcrlf & "Application will now close.", VBCRITICAL self.close() end if inorout.checked = False end sub '***************************************************************************** '* function window_onunload '* Purpose: ensure the removal of access rights from all accessed mailboxes '* terminate the application. '* Inputs: none '* Returns: nothing '***************************************************************************** sub window_onunload for each strPath in objMBXlist.keys removeMbxRights strpath,objMBXlist.item(strPath) next end sub '***************************************************************************** '* function getTrusteeName '* Purpose: get the username and domain for the helpdesk staff to be added to '* the access control list on the users '* Inputs: none '* Returns: String in the format domain\username '***************************************************************************** function getTrusteeName on error resume next Set WshNetwork = CreateObject("WScript.Network") if err.number = 0 then getTrusteeName = WshNetwork.UserDomain & "\" & WshNetwork.UserName else getTrusteeName = "" err.clear end if on error goto 0 end function '***************************************************************************** '* function get_OOF_TEXT '* Purpose: get the users current "Out of Office" Message into a text box '* Inputs: none '* Returns: nothing '***************************************************************************** sub get_OOF_TEXT disablecontrols(True) strProfileInfo = strExchsvr & vbLf & staffcode.value Set CDOSession = CreateObject("MAPI.SESSION") on error resume next CDOSession.Logon "", "", False, True, 0, False, strProfileInfo if err.number = 0 then OOF_TEXT.value = CDOSession.OutOfOfficeText if CDOSession.OutOfOffice = True then inorout.checked = True else inorout.checked = False end if inorout.disabled = "false" CDOSession.Logoff else msgbox "Error logging on to mailbox." & vbcrlf & err.number & vbcrlf _ & err.description & vbcrlf & _ "Wait a few minutes for AD permissions to replicate and try again!", VBCRITICAL end if on error goto 0 Set CDOSession = Nothing disablecontrols(False) end sub '***************************************************************************** '* function set_OOF_TEXT '* Purpose: set the users current "Out of Office" Message from text box value '* Inputs: none '* Returns: nothing '***************************************************************************** sub set_OOF_TEXT disablecontrols(False) strProfileInfo = strExchsvr & vbLf & staffcode.value Set CDOSession = CreateObject("MAPI.SESSION") on error resume next CDOSession.Logon "", "", False, True, 0, False, strProfileInfo if err.number = 0 then CDOSession.OutOfOfficeText = OOF_TEXT.value CDOSession.OutOfOffice = True inorout.checked = True inorout.disabled = "false" CDOSession.Logoff else msgbox "Error logging on to mailbox." & vbcrlf & err.number & vbcrlf _ & err.description & vbcrlf & _ "Wait a few minutes for AD permissions to replicate and try again!", VBCRITICAL end if on error goto 0 Set CDOSession = Nothing disablecontrols(False) end sub '***************************************************************************** '* function finduser '* Purpose: perform active directory query '* Inputs: none '* Returns: nothing '***************************************************************************** sub finduser() Set objConnection = CreateObject("ADODB.Connection") objConnection.Open "Provider=ADsDSOObject;" Set objCommand = CreateObject("ADODB.Command") objCommand.ActiveConnection = objConnection ' search for the users staffcode from accounts that aren't disabled objCommand.CommandText = _ "<GC://dc=acme,dc=com,dc=au>;" & _ "(&(&(objectClass=user)(objectCategory=person))(&(samaccountname=" & staffcode.value & _ ")(!userAccountControl:1.2.840.113556.1.4.803:=2)));" & _ "name,adspath,msExchHomeServerName;subtree" Set objRecordSet = objCommand.Execute if objRecordSet.recordcount > 1 then intRetcode = msgbox("Error - More than one active account with staffcode " & _ staffcode.value & " found!" & vbcrlf & "List ldap path of accounts?",VBCRITICAL+VBYESNO) if intRetcode = VBYES then do While Not objRecordset.EOF Set objOption = Document.createElement("OPTION") objOption.Text = objRecordset.Fields("adspath") objOption.Value = objRecordset.Fields("adspath") SearchResults.Add(objOption) objRecordset.MoveNext loop SearchResults.style.visibility ="Visible" else SearchResults.style.visibility ="Hidden" end if exit sub end if if objRecordSet.recordcount = 0 then msgbox "Failed to find staffcode in active directory" & VBCRLF & "Check the staffcode is correct", VBCRITICAL exit sub end if intRetcode = msgbox("StaffCode " & staffcode.value & " found!" & vbcrlf & _ "Grant full control to mailbox for " & strTrustee,VBINFORMATION+VBYESNO) if intRetcode = VBNO then setbutton.disabled = "True" getbutton.disabled = "True" inorout.disabled = "True" inorout.checked = False exit sub end if do While Not objRecordset.EOF strExchsvr = objRecordset.Fields("msExchHomeServerName") arrTemp = split(strExchsvr, "=") strExchsvr = arrtemp(ubound(arrtemp)) strPath = replace(objRecordset.Fields("adspath"),"GC://", "LDAP://") objRecordset.MoveNext loop objConnection.Close if setMbxRights(strPath, strTrustee) = True then setbutton.disabled = "false" getbutton.disabled = "false" end if end sub '***************************************************************************** '* function disablecontrols '* Purpose: activate/de-activate controls as appropriate to application state '* Inputs: none '* Returns: nothing '***************************************************************************** sub disablecontrols(booldisable) progress.style.visibility = "Visible" if booldisable = True then setbutton.disabled = "True" getbutton.disabled = "True" inorout.disabled = "True" else progress.style.visibility = "hidden" setbutton.disabled = "False" getbutton.disabled = "False" inorout.disabled = "False" end if end sub '***************************************************************************** '* function setMbxRights '* Purpose: add trustee to the users mailbox with full control '* Inputs: string - the adspath of the users mailbox '* string - the trustee's domain & username, formatted domain\username '* Returns: boolean, true if succesful '***************************************************************************** function setMbxRights(adspath,strTrustee) setMbxRights = False if not objMBXlist.exists(adspath) then objMBXlist.add adspath,strTrustee set objUser = GetObject(adspath) on error resume next Set oSecurityDescriptor = objUser.MailboxRights if err.number <> 0 then if err.number = 438 then msgbox "This application must be run on a workstation with" & vbcrlf _ & "the exchange management tools installed!", vbcritical err.clear exit function else msgbox "Error getting mailbox security Descriptor." & vbcrlf _ & err.description & vbcrlf and err.number, vbcritical exit function end if end if on error goto 0 Set dacl = oSecurityDescriptor.DiscretionaryAcl AddAce dacl, strTrustee, ADS_RIGHT_DS_CREATE_CHILD, _ ADS_ACETYPE_ACCESS_ALLOWED, ADS_ACEFLAG_INHERIT_ACE, 0, 0, 0 oSecurityDescriptor.DiscretionaryAcl = dacl ' Save new SD onto the user. objUser.MailboxRights = oSecurityDescriptor ' Commit changes from the property cache to the information store. objUser.SetInfo setMbxRights = True end function '***************************************************************************** '* function removeMbxRights '* Purpose: remove trustee from all mailboxes to which it was added '* Inputs: string - the adspath of the users mailbox '* string - the trustee's domain & username, formatted domain\username '* Returns: boolean, true if succesful '***************************************************************************** sub removeMbxRights(adspath,strTrustee) set objUser = GetObject(adspath) Set oSecurityDescriptor = objUser.MailboxRights Set dacl = oSecurityDescriptor.DiscretionaryAcl For Each ace In Dacl If (LCase(ace.trustee) = LCase(strTrustee)) and _ ((ace.AccessMask AND ACE_MB_FULL_ACCESS)=ACE_MB_FULL_ACCESS) Then Dacl.RemoveAce ace MsgBox "Mailbox rights have been removed", VBINFORMATION End If Next oSecurityDescriptor.DiscretionaryAcl = dacl ' Save new SD onto the user. objUser.MailboxRights = oSecurityDescriptor ' Commit changes from the property cache to the information store. objUser.SetInfo end sub '******************************************************************** '* Code shamelessly copied from Microsoft KB310866 '* http://support.microsoft.com/kb/310866 '* Function AddAce(dacl, TrusteeName, gAccessMask, gAceType, '* gAceFlags, gFlags, gObjectType, gInheritedObjectType) '* '* Purpose: Adds an ACE to a DACL '* Input: dacl Object's Discretionary Access Control List '* TrusteeName SID or Name of the trustee user account '* gAccessMask Access Permissions '* gAceType ACE Types '* gAceFlags Inherit ACEs from the owner of the ACL '* gFlags ACE has an object type or inherited object type '* gObjectType Used for Extended Rights '* gInheritedObjectType '* '* Output: Object - New DACL with the ACE added '* '******************************************************************** Function AddAce(dacl, TrusteeName, gAccessMask, gAceType, gAceFlags, gFlags, gObjectType, gInheritedObjectType) Dim Ace1 ' Create a new ACE object. Set Ace1 = CreateObject("AccessControlEntry") Ace1.AccessMask = gAccessMask Ace1.AceType = gAceType Ace1.AceFlags = gAceFlags Ace1.Flags = gFlags Ace1.Trustee = TrusteeName 'See whether ObjectType must be set If CStr(gObjectType) <> "0" Then Ace1.ObjectType = gObjectType End If 'See whether InheritedObjectType must be set. If CStr(gInheritedObjectType) <> "0" Then Ace1.InheritedObjectType = gInheritedObjectType End If dacl.AddAce Ace1 ' Destroy objects. Set Ace1 = Nothing End Function '***************************************************************************** '* function setINOUT '* Purpose: set the users OOF flag to activate/de-activate OOF processing '* Inputs: none '* Returns: nothing '***************************************************************************** sub setINOUT disablecontrols(True) strProfileInfo = strExchsvr & vbLf & staffcode.value Set CDOSession = CreateObject("MAPI.SESSION") on error resume next CDOSession.Logon "", "", False, True, 0, False, strProfileInfo if err.number = 0 then if inorout.checked = True then CDOSession.OutOfOffice = True else CDOSession.OutOfOffice = False end if CDOSession.Logoff else msgbox "Error logging on to mailbox." & vbcrlf & err.number & vbcrlf _ & err.description, VBCRITICAL end if on error goto 0 Set CDOSession = Nothing disablecontrols(False) end sub </SCRIPT> <body> <B>Step 1. Enter the user's staff code</B><P> <input type="text" name="staffcode" size="30"> <input id=srchbutton class="button" type="button" value="Search for User" name="set_text_button" onClick="finduser"> <select size="5" name="SearchResults" style="Visibility:hidden"> </select> <P><P> <B>Step 2. Retrieve/Set the users Out of Office message</B><p><p> <textarea name="OOF_TEXT" rows=5 cols=70></textarea><p> <input disabled id=getbutton class="button" type="button" value="Get Message" name="get_text_button" onClick="get_OOF_TEXT"> <input disabled id=setbutton class="button" type="button" value="Set Message" name="set_text_button" onClick="set_OOF_TEXT"> <input disabled id=inorout type="checkbox" name="InorOUT" value="IN" checked="False" onClick="setINOUT"> I am currently out of the office <P> <span id="Progress" style="visibility:Hidden"> Operation in progress - please wait&nbsp;&nbsp;&nbsp;&nbsp;<img src="loading.gif" border="0" width="165" height="15"> </span> <p><p><B>Step 3. Send a test email</B><p><p> </body></html>
Older Posts »

Blog at WordPress.com.