The Moose and Squirrel Files

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

 

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>

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>

September 24, 2008

Re-Imaging Computers in 802.1x Networks – Part 5

Filed under: Code, Network — Tags: , , , , — networknerd @ 8:24 pm

This is the final post in this series on re-imaging in 802.1x networks. It ties all the other posts together and contains the complete altiris re-imaging script in one listing.  Although I haven’t covered it here, a post-image script is also required to set the switch port back to using dot1x after the image is dropped from the computer and joined to the domain to get it’s authentication credentials.

Altiris scripting
The observant reader would have noticed that additional information is required before we can perform the previous five steps in a script. The mac address of the computer, the re-imaging vlan and the management IP address of the switch are also required. Variables are provided by altiris which help obtain the additional information. The mac address is provided straight up as %NIC1MACADDR%. The management IP address of the switch and re-imaging vlan aren’t directly available. Altiris has no knowledge of these items. However networks built to a standard allow calculation of the remaining two parameters. The computers ip address provided in the %NIC1IPADDR% variable is used for this calculation. The example network was built to the design standard below.

  • Floor vlans will be allocated in the range 100 – 299 with 10 vlans being reserved per floor.
  • Floor ip addresses will be allocated in the range 192.168.32.0 – 192.168.191.255 with 8 class C networks reserved per floor.
  • The first three networks and vlans per floor will be allocated to authenticated computers, guest/auth-fail computers and re-imaging vlan respectively.
  • The fourth network and vlan will be reserved for future IP telephony projects.
  • The fifth network will be allocated to switch management IP addresses with all others reserved for future use.
  • Switch management vlans will be allocated in the range 300 – 350.
  • Edge switch management address will start at 192.168.x.11

The ip address of the first network on a floor is calculated by masking the last three bits in the third octet of the computers ip address (%NIC1IPADDR%). The fifth network on each floor is reserved for switch management. Adding 4 to the third octet gives the switch management network. Assuming the last octet of the switch management IP addresses are also kept consistent, the address can be completed by simply changing the fourth octet to the standard value. Refer to the getSwitchMgmtAddr() function in listing 1.

The vlan of the first network on a floor is calculated using a similar technique. The vlan in which the computers mac address was found is divided, using integer division, by the number of vlans per floor. The result is then multiplied by the number of vlans per floor. The third network and vlan are reserved for re-imaging. Adding 2 to the first vlan on the floor will give the re-imaging vlan.

As an example, assume that the computer to be re-imaged has an ip address of 192.168.42.157 and its mac address was found in vlan 112 (probably due to a failed re-image job). Masking the last three bits of the third octet gives the first network on the floor.
00101010 (42)
AND 11111000 (248)
= 00101000 (40)
The management network is found by adding 4 to the third octet and gives 192.168.44.0/24, and the switch management ip address will be 192.168.44.11. The first vlan on a floor is calculated as (112\10) * 10 = 110. The re-imaging vlan is found by adding 2 to give 112. Note that the use of integer division- denoted by \ rather than / – means that remainders are ignored.

Not every network is the way we would design it with hindsight. Networks often grow in odd ways. You may have inherited a flat network that won’t lend itself to this kind of calculation. In this case you can simply build an array of switch management ip addresses and loop through steps one and two for each switch until the bridgeport on which the mac address appears is found on a non-trunking port. Then continue with steps three to five.

The script in listing 1 should be easy to customise for your environment. Pay particular attention to the constants defined at the beginning, the regular expression patterns used to match the output from the snmp commands, and the snmp commands. If you aren’t familiar with regular expressions take a look at “Mastering Regular expressions” by Jeffrey Friedl.

With snmp and a modicum of scripting know-how you can now have dot1x security without fearing an uprising of angry helpdesk staff.

References

How To Add, Modify, and Remove VLANs on a Catalyst Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801c6035.shtml

Using SNMP to Find a Port Number from a MAC Address on a Catalyst Switch. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801c9199.shtml

How To Get Dynamic CAM Entries (CAM Table) for Catalyst Switches Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a0080094a9b.shtml

How to Get VLAN Information From a Catalyst Using SNMP. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_configuration_example09186a008015773e.shtml

SNMP Community String Indexing. (October 26, 2005).
     Retrieved 11 November, 2006, from
     http://www.cisco.com/en/US/tech/tk648/tk362/technologies_tech_note09186a00801576ff.shtml

IEEE Standard for Local and metropolitan area networks—Port-Based Network Access Control. (2004).
     Retrieved 11 November, 2006, from
     http://standards.ieee.org/getieee802/download/802.1X-2004.pdf

Friedl, J. (2002). Mastering Regular Expressions (Second ed.): O'Reilly Media Inc.
 (more...)

Re-Imaging Computers in 802.1x Networks – Part 4

Filed under: Code, Network — Tags: , , , , — networknerd @ 9:13 am

Finding the interface index
The bridgeport number is converted to an interface index by concatenating it to
dot1dBasePortIfIndex and once again performing an snmp get operation.

G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@100 192.168.36.11 .1.3.6.1.2.1.17.1.4.1.2.108
.1.3.6.1.2.1.17.1.4.1.2.108 11002

The interface index returned is 11002, and once again we can extract it from the output using a
regular expression as shown in the getIFIndex() function in listing 1.

Setting Dot1x port control
To allow the computer to connect to the network without a supplicant, the port is first placed into
forced authorised mode. The interface index is concatenated to
dot1xAuthAuthControlledPortControl and an snmp set operation is used with an integer
argument of 3 (FORCEAUTHORISED). The same process can be used to return the switch port
to normal operation by specifying an argument of 2 (AUTO). This operation is performed by the
setPortControl() function in listing 2.

G:\usr\bin>snmpset.exe -v 2c -c private 192.168.36.11 .1.0.8802.1.1.1.1.2.1.1.6.11002 i 3
iso.0.8802.1.1.1.1.2.1.1.6.11002 = INTEGER: 3
G:\usr\bin>snmpset.exe -v 2c -c private 192.168.36.11 .1.0.8802.1.1.1.1.2.1.1.6.11002 i 2
iso.0.8802.1.1.1.1.2.1.1.6.11002 = INTEGER: 2

Setting the vlan
Strictly speaking, changing the vlan is not required to perform the re-imaging operation.
Restricting access on the re-imaging vlan provides a small amount of protection against the risk
of leaving the switch port in the force authorised state if the re-imaging job fails or is cancelled.
This operation is performed by the setVlan() function in listing 3.

G:\usr\bin>snmpget -OnqUe -v 2c –c public 192.168.36.11 vmVlan.11002
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 100
G:\usr\bin>snmpset -OnqUe -v 2c –c private 192.168.36.11 vmVlan.11002 i 102
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 102
G:\usr\bin>snmpget -OnqUe -v 2c –c public 192.168.36.11 vmVlan.11002
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 102
G:\usr\bin>snmpset -OnqUe -v 2c -c private 192.168.36.11 vmVlan.11002 i 100
.1.3.6.1.4.1.9.9.68.1.2.2.1.2.11002 100

Listing 1


const DOT1DBASEPORTIFINDEX = " .1.3.6.1.2.1.17.1.4.1.2."
const SNMPGETCMD = "f:\usr\bin\snmpget.exe -OnqUe -v 2c -c "
const SNMPREADV = " public@" 'need community name and vlan for some info

'************************************************************************
'FUNCTION:                                                              *
' getIFIndex(strAgent, intVlan, intBridgePort)                          *
'                                                                       *
'Purpose:                                                               *
' convert a bridgeport value to an interface index suitable for         *
' use with the setvlan() and setportcontrol() functions                 *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intVlan : the vlan specific instance of the forwarding table          *
' intBridgePort: bridgeport value returned from getBridgePort()         *
'                                                                       *
'Returns:                                                               *
' String containing the interface index, or an empty string on          *
' failure                                                               *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD - constant defining the path to an external                *
' program and options used to perform an snmp get                       *
'                                                                       *
'Comments:                                                              *
' Uses community string indexing to reference the per vlan mib          *
' instance.                                                             *
' Reference cisco Document ID: 44800                                    *
' "Using SNMP to Find a Port Number from a MAC Address on a             *
' Catalyst Switch" viewed at                                            *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c9199.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function getIFIndex(strAgent, intVlan, intBridgePort)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD & SNMPREADV & intVlan & " " & _
strAgent & " " & DOT1DBASEPORTIFINDEX & intBridgePort)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
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.17.1.4.1.2.108 11002"
re.pattern = "^" & trim(DOT1DBASEPORTIFINDEX) & intBridgePort & _
                 "\s+(\d+)$"
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = match.submatches(0)
next
getIFIndex = tempstr
end function

Listing 2


const FORCEUNAUTHORISED = 1
const AUTO = 2
const FORCEAUTHORISED = 3
const dot1xAuthAuthControlledPortControl = ".1.0.8802.1.1.1.1.2.1.1.6."
const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPWRITE = " private "

'************************************************************************
'FUNCTION:                                                              *
' setPortControl(strAgent,intIFIndex, intPortControl)                   *
'                                                                       *
'Purpose:                                                               *
' sets the PaeControlledPortControl value which controls whether        *
' dot1x authentication is required.                                     *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intIFIndex: port interface index returned from getIFIndex()           *
' intPortControl : the control values of the authenticator PAE          *
' controlled port. Allowed values are                                   *
' forceUnauthorized(1), auto(2),forceAuthorized(3)                      *
'                                                                       *
'Returns:                                                               *
' Integer, 0 if successful or a positive value on failure.              *
'                                                                       *
'Calls:                                                                 *
' SNMPSETCMD - constant defining the path to an external                *
' program and options used to perform an snmp set                       *
'                                                                       *
'Comments:                                                              *
' Reference IEEE Std 802.1X-2001                                        *
' "IEEE Standard for Local and metropolitan area networks—              *
' Port-Based Network Access Control"                                    *
' viewed at                                                             *
' http://standards.ieee.org/getieee802/download/802.1X-2001.pdf         *
' on 16/11/2006                                                         *
'************************************************************************
function setPortControl(strAgent,intIFIndex, intPortControl)
dim WshShell, oExec
dim stroutput
if (intPortControl < FORCEUNAUTHORISED or _
  intPortControl > FORCEAUTHORISED) then
  setPortControl = 1
  exit function
end if
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPSETCMD & SNMPWRITE & " " & strAgent & _
                                      " " & dot1xAuthAuthControlledPortControl & intIFIndex &_
                                      " i " & intPortControl)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
  Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
setPortControl = instr(1, stroutput, "Error")
end function

Listing 3


const VMVLAN = ".1.3.6.1.4.1.9.9.68.1.2.2.1.2."
const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPWRITE = " private "

'************************************************************************
'FUNCTION:                                                              *
' setVlan(strAgent,intIFIndex, intVlan)                                 *
'                                                                       *
'Purpose:                                                               *
' set the port specified by the interface index suitable to             *
' the specified vlan                                                    *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intIFIndex: port interface index returned from getIFIndex()           *
' intVlan : the vlan to which the port should be configured             *
'                                                                       *
'Returns:                                                               *
' Integer, 0 if successful or a positive value on failure.              *
'                                                                       *
'Calls:                                                                 *
' SNMPSETCMD - constant defining the path to an external                *
' program and options used to perform an snmp set                       *
'                                                                       *
'Comments:                                                              *
' CISCO-VTP-MIB is cisco specific.                                      *
' Reference cisco Document ID: 45080                                    *
' "How To Add, Modify, and Remove VLANs on a Catalyst Using SNMP"       *
' viewed at                                                             *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c6035.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function setVlan(strAgent,intIFIndex, intVlan)
dim WshShell, oExec
dim stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPSETCMD & SNMPWRITE & " " & strAgent & _
" " & VMVLAN & intIFIndex & " i " & intVlan)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
setVlan = instr(1, stroutput, "Error")
end function

September 21, 2008

Re-Imaging Computers in 802.1x Networks – Part 3

Filed under: Code, Network — Tags: , , , , — networknerd @ 10:53 am

Finding the Bridgeport number
Some MIBs, such as the BRIDGE-MIB, have a separate instance for each VLAN configured on the switch. Mac addresses in the forwarding table are examined using community string indexing to reference the instance maintained for each vlan. The community string is formatted as read_community@vlan_number.
Armed with a list of vlans and the mac address of the computer the bridgeport number can now be found. Hexadecimal digits of the mac address are converted to decimal and concatenated to the dot1dTpFdbPort OID. An snmp get operation is performed using this OID for each vlan in the list until an instance of the OID is found, or the list of vlans is exhausted. The bridgeport number and the vlan configured for the port are returned when an instance of this OID is found.

For example, a computer whose mac address is 00-40-CA-69-34-EE, has a corresponding OID of dot1dTpFdbPort.0.64.202.105.52.238 (.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238).

G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@1 192.168.36.11 .1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238
.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 No Such Instance currently exists at this OID
G:\usr\bin>snmpget.exe -OnqU -v 2c -c public@100 192.168.36.11.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238
.1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 108

The mac address was learnt on bridgeport 108, which is configured in vlan 100. The bridgeport
is easily extracted from the output using a regular expression as shown in the getBridgePort()
function in listing 1.

const SNMPSETCMD = "f:\usr\bin\snmpset.exe -v 2c -c "
const SNMPREADV = " public@" 'need community name and vlan for some info
const DOT1DTPFDBPORT = " .1.3.6.1.2.1.17.4.3.1.2"

'************************************************************************
'FUNCTION:                                                              *
' getBridgePort(strAgent, intVlan, strmac)                              *
'                                                                       *
'Purpose:                                                               *
' examine the switch forwarding tables for the specified mac            *
' address in the specified vlan                                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
' intVlan : the vlan specific instance of the forwarding table          *
' strmac : Mac address string in the format 0040CA6934EE                *
'                                                                       *
'Returns:                                                               *
' String containing the bridgeport if found or an empty string          *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD - constant defining the path to an external                *
' program and options used to perform an snmp get                       *
'                                                                       *
'Comments:                                                              *
' Uses community string indexing to reference the per vlan mib          *
' instance.                                                             *
' Reference cisco Document ID: 44800                                    *
' "Using SNMP to Find a Port Number from a MAC Address on a             *
' Catalyst Switch" viewed at                                            *
' http://www.cisco.com/en/US/tech/tk648/tk362/                          *
' technologies_tech_note09186a00801c9199.shtml                          *
' on 16/11/2006                                                         *
'************************************************************************
function getBridgePort(strAgent, intVlan, strmac)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD & SNMPREADV & intVlan & " " & _
strAgent & " " & DOT1DTPFDBPORT & mac2oid(strmac))
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
set re = new regexp
re.global = True
re.multiline = True
'output lines from SNMPCMD should look like
' ".1.3.6.1.2.1.17.4.3.1.2.0.64.202.105.52.238 108"
'Pattern to capture the snmpget output
re.pattern = "^" & trim(DOT1DTPFDBPORT) & mac2oid(strmac) & "\s+(\d+)$"
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = match.submatches(0)
next
getBridgePort = tempstr
end function

'************************************************************************
'FUNCTION:                                                              *
' mac2OID(strmac)                                                       *
'Purpose:                                                               *
' Convert mac address string to a decimal string for snmp queries.      *
'                                                                       *
'Inputs:                                                                *
' strmac: Mac address string in the format 0040CA6934EE                 *
'                                                                       *
'Returns:                                                               *
' snmp OID string of the form .0.64.202.105.52.238 or an empty          *
' string ("") if an error occured.                                      *
'                                                                       *
'Calls:                                                                 *
' Hex2Dec                                                               *
'                                                                       *
'Comments:                                                              *
' No error checking is performed on the input character set.            *
' The input string is validated by length only.                         *
'************************************************************************
function mac2OID(strmac)
dim intOctet
dim arrOctet
dim strOID
strOID = ""
if len(strmac) = 12 then
  for intOctet = 1 to 11 step 2
    strOID = strOID & "." & Hex2Dec(mid(strmac,intOctet,2))
  next
end if
mac2OID = strOID
end function

'************************************************************************
'FUNCTION:                                                              *
' Hex2Dec(strHex)                                                       *
'Purpose:                                                               *
' Convert a string representation of a hexadecimal number to a          *
' decimal string.                                                       *
'                                                                       *
'Inputs:                                                                *
' strHex: string containing hexadecimal characters [0-9a-fA-F]          *
'                                                                       *
'Returns:                                                               *
' string containing the input converted to decimal characters[0-9]      *
'                                                                       *
'Calls:                                                                 *
' Nothing                                                               *
'                                                                       *
'Comments:                                                              *
' No error checking is performed on the input.                          *
' Beware of overflow in CInt function. Consider modifying to Clng       *
' before using in other code.                                           *
'************************************************************************
Function Hex2Dec(strHex)
Hex2Dec = "" & CInt("&H" & strHex)
End Function

September 20, 2008

Re-Imaging Computers in 802.1x Networks – Part 2

Filed under: Code, Network — Tags: , , , , — networknerd @ 9:40 am

Obtaining a list of vlans

It’s not obvious why we’d need to obtain a list of vlans on a switch. This will become evident in the next post, but for now I will say that  Cisco uses a thing called community string indexing where a per vlan instance of an SNMP MIB is maintained on a device.  The index to the MIB is the vlan number and we’ll need to use community string indexing later on to gather more information.

There are two possible snmp object identifiers (OID’s) to query to get a list of vlans and your mileage will vary here depending on switch configuration. The vlanPortIslVlansAllowed entry from the CISCO-STACK-MIB is convenient as it returns only the vlans allowed on a specified trunk, and reduces the number of snmp operations required to find a port. The vtpVlanState entry from the CISCO-VTP-MIB returns all the vlans propagated through VTP. Vlans that are not configured on any port of the switch we are examining are included, and the number of redundant snmp operations will increase.

VlanPortIslVlansAllowed is defined as

An indication of which Virtual LANs are allowed on this Inter-Switch Link. This is an octet
string value with bits set to indicate allowed VLANs. It can be interpreted as a sum of f(x) as x
goes from 0 to 1023, where f(x) = 0 for VLAN x not allowed and f(x) = exp(2, x) for VLAN x
allowed.

For VlanPortIslVlansAllowed the return value from the query could be up to 1024 bits or 128 hexadecimal digits. The output for a switch on the example network is shown below.

G:\usr\bin>snmpget.exe -Ov -v 2c -c public 192.168.36.11 vlanPortIslVlansAllowed.1.1
Hex-STRING: 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 07 01 C0 70 00 00 00 00 00 00
00 00 00 00 00 00

Fortunately large number arithmetic is not required to reveal the allowed vlans. Each pair of
hexadecimal digits is converted to a binary string and concatenated to form the binary
representation of the number. Each bit that is set corresponds to a vlan. The string is reversed
using the strreverse() function and then searched for the character ‘1’ using the instr() function.
The index value returned from instr is the vlan number + 1. The vlan is obtained by subtracting 1
from the returned index and the search repeated until no more matches are found in the string.
More detail is available by referring to the enum_AllowedVLAN()  and supporting functions in listing 1.

Walking vtpVlanState returns output similar to that shown below. The last group of digits in the
OID is the vlan number and the number that follows is the operational state of the vlan. The vlan
number is easily extracted using a regular expression as shown in the enum_VLAN() function in
listing 2.

G:\usr\bin>snmpwalk.exe -OnqUe -v 2c -c public 192.168.36.11 vtpVlanState
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.100 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.101 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.102 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.110 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.111 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.112 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.120 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.121 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.122 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.300 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.301 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.302 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1002 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1003 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1004 1
.1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.1005 1

Listing 1

const vlanPortIslVlansAllowed = " .1.3.6.1.4.1.9.5.1.9.3.1.5.1.1 "
const SNMPGETCMD2 = "f:\usr\bin\snmpget.exe -Ov -v 2c -c "
'************************************************************************
'FUNCTION:                                                              *
' enum_AllowedVLAN(strAgent)                                            *
'Purpose:                                                               *
' enumerate the vlans configured on the switch.                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
'                                                                       *
'Returns:                                                               *
' Array with each element containing a vlan number                      *
'                                                                       *
'Calls:                                                                 *
' SNMPGETCMD2 - constant defining the path to an external               *
' program and options used to perform an snmp get operation.            *
' fmtBinary - function to left pad a binary number with zeros           *
' ToBinary - function to convert an integer to a binary string          *
'                                                                       *
'Comments:                                                              *
' CISCO-STACK-MIB is cisco specific.                                    *
' Reference Cisco SNMP Object Navigator viewed at                       *
' http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?                  *
' objectInput=vlanPortIslVlansAllowed&translate=Translate&              *
' submitValue=SUBMIT&submitClicked=true                                 *
' on 16/11/2006                                                         *
'************************************************************************
function enum_AllowedVLAN(strAgent)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match, submatch
dim tempstr, stroutput, index, vlans
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPGETCMD2 & SNMPREAD & " " & _
strAgent & " " & vlanPortIslVlansAllowed)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
tempstr = ""
set re = new regexp
re.global = True
re.multiline = True
'Pattern to capture the hex digits representing the allowed vlans.
re.pattern = "[0-9a-fA-F]{2}"
vlans = ""
if instr(1,stroutput, "Hex-STRING:") > 0 then
  set matches = re.execute(stroutput)
  for each match in matches
    tempstr = tempstr & fmtBinary(ToBinary(cint("&H" & match)), 8)
  next
  tempstr = strreverse(tempstr)
  index = 1
  do
    index = instr(index, tempstr, "1")
    if index <> 0 then
      vlans = vlans & " " & index - 1
      index = index + 1
    end if
  loop until index = 0
end if
enum_AllowedVLAN = split(trim(vlans))
end function
'************************************************************************
'FUNCTION:                                                              *
' fmtBinary(strNumber, intLength)                                       *
'PURPOSE:                                                               *
' function to left pad a binary number with zeros                       *
'                                                                       *
'INPUTS:                                                                *
' strNumber: binary number to left pad with zeros                       *
' intLength: The desired bit length of the binary number                *
'                                                                       *
'RETURNS:                                                               *
' string containing the binary representation of the input.             *
'                                                                       *
'CALLS:                                                                 *
' Nothing                                                               *
'                                                                       *
'COMMENTS:                                                              *
'************************************************************************
function fmtBinary(strNumber, intLength)
  fmtBinary = string(intLength - len(strNumber), "0") & strNumber
end function
'************************************************************************
'FUNCTION:                                                              *
' ToBinary(intNumber)                                                   *
'                                                                       *
'PURPOSE:                                                               *
' convert an integer number to binary.                                  *
'                                                                       *
'Inputs:                                                                *
' intNumber: Number to convert to binary                                *
'                                                                       *
'Returns:                                                               *
' string containing the binary representation of the input.             *
'                                                                       *
'Calls:                                                                 *
' Nothing                                                               *
'                                                                       *
'Comments:                                                              *
' note the use of \ (integer division operator) rather than /           *
'************************************************************************
function ToBinary(intNumber)
if intNumber > 0 then
  ToBinary = ToBinary(intNumber\2) & intNumber mod 2
end if
end function

Listing2

const SNMPWALKCMD = "f:\usr\bin\snmpwalk.exe -OnqUe -v 2c -c "
const SNMPREAD = " public "
const VTPVLANSTATE = " .1.3.6.1.4.1.9.9.46.1.3.1.1.2 "
'************************************************************************
'FUNCTION:                                                              *
' enum_VLAN(strAgent)                                                   *
'Purpose:                                                               *
' enumerate the vlans configured on the switch.                         *
'                                                                       *
'Inputs:                                                                *
' strAgent: management IP address of the switch                         *
'                                                                       *
'Returns:                                                               *
' Array with each element containing a vlan number                      *
'                                                                       *
'Calls:                                                                 *
' SNMPWALKCMD - constant defining the path to an external               *
' program used to perform an snmp walk                                  *
'                                                                       *
'Comments:                                                              *
' CISCO-VTP-MIB is cisco specific.                                      *
' Reference cisco Document ID: 41003                                    *
' "How to Get VLAN Information From a Catalyst Using SNMP" viewed       *
' at http://www.cisco.com/en/US/tech/tk648/tk362/technologies_          *
' configuration_example09186a008015773e.shtml                           *
' on 16/11/2006                                                         *
'************************************************************************
function enum_VLAN(strAgent)
dim WshShell, oExec
dim re 'as regexp
dim matches
dim match
dim tempstr, stroutput
set re = new regexp
re.global = True
re.multiline = True
'output lines from SNMPCMD should look like
' ".1.3.6.1.4.1.9.9.46.1.3.1.1.2.1.125 1"
'Pattern to capture the last digits of the snmp OID
'Include VTPVLANSTATE in the pattern to minimise regex engine backtracking
re.pattern = "^" & VTPVLANSTATE & "(?:\.\d+)+\.(\d+)\s+\d+$"
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec(SNMPWALKCMD & SNMPREAD & " " & strAgent & " " & VTPVLANSTATE)
Do while Not oExec.StdOut.AtEndOfStream
  stroutput = oExec.StdOut.readall
Loop
Do While oExec.Status <> 1
  WScript.Sleep 100
Loop
tempstr = ""
set matches = re.execute(stroutput)
for each match in matches
  tempstr = tempstr & match.submatches(0) & " "
next
enum_VLAN = split(trim(tempstr))
end function

September 18, 2008

Re-Imaging Computers in 802.1x Networks – Part 1

Filed under: Code, Network — Tags: , , , , — networknerd @ 7:37 pm

Postings are a little slow lately because I’ve just been to my first Cisco Networkers conference.  I did meet a guy Paul at a techtorial and we were discussing 802.1x.  Paul’s problem was how to deal with re-imaging.

Fortunately I’d already tackled and documented that issue and was able to email my document to him.  Extrapolating from the fact that one person found it useful, I thought I might re-write as a series of blog posts here. At least google will index it for others to find.  If you can’t wait for the blog posts to finish, leave a comment with your email address (obfuscated) and I’ll send you the whole document.

802.1x Basics

Port-based network access control is defined by the dot1x standard as a means of authenticating
devices attached to the network edge, and preventing access to the port where authentication
fails.

Dot1x defines three roles within the port based access control system.

  • Authenticator – usually a switch port that is configured to require authentication before passing traffic.
  • Supplicant – the device that requires access to the network.  The supplicant function is performed by software, either as part of the operating system (windows xp), as an add-on provided by companies such as Juniper/Funk and Cisco/Meetinghouse, or open source such as X-supplicant.
  • Authentication Server – checks the credentials of the supplicant on behalf of the authenticator. The authentication is usually performed via radius.

Problem Definition

The preboot execution environment (PXE) is an essential part of the re-imaging process. PXE is
built into the computer BIOS to allow network boot and automatic download of software images
and configuration parameters. Unfortunately the PXE environment has no supplicant or
credentials with which to authenticate. The switch port will attempt to initiate authentication,
performing a number of retries before timing out and leaving the port in the unauthenticated
state. The computer can neither obtain a DHCP address nor perform boot server discovery to
obtain a boot image.

Possible Solutions

Our options at this point are:

  1. Move the computer to a port in a physically secure location with dot1x disabled.
  2. Have the network administrator disable dot1x on the port, and enable dot1x again when re-imaging is completed.

EDITED TO ADD: Kevin’s comment points out a third option, Mac Authentication Bypass. I write more on that after I have finished this series of posts.

Drawbacks of Option 1 – Computer relocations are extremely unpopular amongst helpdesk staff
charged with re-imaging computers. Also, this option doesn’t scale well when deploying a new
image across the whole organisation.
Drawbacks of Option 2 – Enabling and disabling dot1x on the port will almost certainly fail.
Ultimately someone will forget to enable dot1x on the port, and the number of unsecured ports
will increase over time. The dot1x security will eventually become like swiss cheese.

An automated method that is part of the re-image operations is required.  If your re-imaging program allows scripting (like Altiris in this example) then a completely automated solution is possible.

SNMP to the Rescue

The essence of the solution is to use altiris scripting to perform snmp operations during the reimaging
job. SNMP is used to temporarily disable dot1x authentication on the switch port until
the image is delivered. But the security versus utility tradeoff also comes in to play here.
Compromises made for this solution are listed below.

  1. SNMP must be enabled on the edge switches where the computers to be re-imaged connect.
  2. SNMP read and write access must be granted to the altiris deployment server.
  3. The SNMP read and write community names to the switches are stored in plain text in the altiris scripts.

The only protections we can apply are snmp access control lists to the switches, and ensuring
operating system and file system security is set appropriately on the altiris server. Re-imaging is
optionally performed in a vlan that has limited connectivity in the local network. This protects to
some extent against failure to re-enable dot1x on the switch port. Firewall rules or access control
lists on the switches are used to achieve this.

The process of configuring a port for re-imaging consists of five steps.

  1. Obtain a list of vlans on the switch.
  2. Use the mac address to identify the bridgeport number associated with the port.
  3. Use the bridgeport number to identify the interface index of the port to which the computer is connected.
  4. Use the interface index to set dot1x port control.
  5. Use the interface index to set the vlan. (Optional, only if re-imaging vlans are used)

The next series of posts will discuss each of these steps in depth. I’ll provide detail of the method used to acquire the information, as well as vbscript functions that can be used in re-imaging scripts.

Blog at WordPress.com.