During the night and weekend hours, RAM, CPUs, and high-end video cards began disappearing from PCs in my company. The thief would shut down the units, pry open the cases, and remove the components. The next morning, users would discover the disassembled units.
Security guards patrol the facility, but because of its size and the large number of PCs, they can't watch all the computers at once. But the guards did have an idea about how to catch the thief. Users turn off their monitors at night to save energy, but many leave their PCs on to receive software pushes. The guards thought that they could apprehend the culprit if they were notified immediately when PCs were turned off. My team developed a Perl script that pings the running PCs and pages the guards when a PC is shut down.
Requirements and Solutions
In a meeting, the security guards and my team developed a list of requirements for the script. We also discussed a couple of features the guards would like but that weren't absolute requirements. My team then translated the requirements into pseudocode—that is, sentences describing what the code would do—and later into a completed script. During the translation process, we decided which scripting language would work best for this project and how to meet each requirement. Here's the list of requirements, with our considerations and solutions for each requirement.
Requirement 1: Ping a list of target PCs and notify the security guard when a PC fails the ping test.
Considerations: We could have written this code in Windows shell script or one of several other languages, but we chose Perl because it has very low CPU overhead. The script will read from input files, and Perl is efficient for this type of operation.
Solution: Use the Perl modules Net::Ping for the ping test and Mail::Sendmail for the email and pager notifications. Possibly break the code into paging and testing subroutines.
Requirement 2: The page message must indicate the PC name and building location.
Considerations: Create an input list that contains name and location information for each PC. The location descriptions might contain spaces.
Solution: Create a Comma Separated Value (CSV) file, and use Perl's split function to split the line into PC name and location elements. Verify that this input file exists, and exit with an error message if it doesn't.
Requirement 3: The page-recipient list will change, so it must be easily modified.
Considerations: We could have hard-coded the recipient list in the script, but editing a tested script is usually risky because one small typo can send you back to the debugging stage. Using an input file that the script parses is always preferable.
Solution: Create an input list that contains the page recipients' addresses. Verify that this input file exists, and exit with an error if it doesn't.
Requirement 4: Record ping failures—including time, PC name, and failure-incident details—in a log file.
Considerations: Both the security guards and management want to have a log file that details the run results to use to analyze the time and location of theft incidents.
Solution: Create an output file for logging ping failures. Use the file-creation date and time as part of the log filename. Log ping-failure incidents—including time, PC name, and incident details—to the file.
Requirement 5: When the script is launched each day, it must poll the computers and remove from the list any machines that are offline at that time.
Considerations: When the script starts, it should ping all the nodes during an initialization run, then use this list for the testing period. The next time the script runs, it should delete this temporary list and create a new one.
Solution: Use the input file with the first script run, and collect the nodes that respond in an array that will hold their names and locations during the testing period. This initialization section might warrant its own subroutine.
Requirement 6: When an online PC goes offline, send a page only once to indicate the offline condition. If the PC comes back online, send an "all clear" page.
Considerations: Track the last state of a pinged node. When the state changes, initiate a page with an appropriate message indicating the offline or online condition. We could use one file, multiple files (one per node), NTFS file streams, or a Perl list to track the PCs' state. Reading files or NTFS file streams would add overhead.
Solution: Create a Perl list of failed nodes. A node that isn't on the list was online at last ping.
Requirement 7: Minimize false alarms while providing quick detection and notification.
Considerations: Ping results can be unreliable. A ping can fail once, then succeed on the next attempt. Basing the test result on one ping could mean that a momentary network glitch would result in a false alarm. Alternatively, we could use one ping with a long timeout value. However, this approach could slow notification.
Solution: If the first ping of a node fails, ping the node again. Page only after a second ping failure.
Optional Feature 1: Indicate in the page the likelihood of a real incident. A theft is more likely to be in progress if several PCs are offline than if only one PC is offline.
Considerations: We're already tracking whether machines are online or offline. We would just need to count the number of offline machines to assess the risk level.
Solution: Count the number of failed nodes on the Perl list created to meet Requirement 6. If the list has one node, the risk is low; if two nodes, medium; if three or more nodes, high.
Optional Feature 2: When a potential incident is detected, shift detection into a sensitive mode.
Considerations: We're already pinging the machines to determine whether they're offline. If any node is offline, we could decrease the time between pings (i.e., the sleep time) to increase the script's sensitivity. If the offline machine comes back up, we could return the sleep time to the default interval.
Solution: Put the Perl list of failed nodes in an If statement, and decrease the ping interval from 30 seconds to 10 seconds after detecting the first node failure. Note: If you need to ping a large number of machines, you might need to make the cycle between pings significantly longer because the machines are pinged sequentially, not simultaneously.
Identify Required Resources
After we considered the requirements and optional features and arrived at solutions, we knew what resources we needed to write the script. As determined in Requirement 1, we needed Perl and its Net::Ping and Mail::Sendmail modules.
Perl and Net::Ping are available at the ActiveState Web site (http://www.activestate.com). We installed ActiveState's ActivePerl on the PC that was going to run the script. Then we used the commands in Listing 1, page 48, to start Perl Package Manager (PPM), which comes with ActivePerl, and to use PPM to download and install the Net::Ping and Mail::Sendmail modules.
The commands in Listing 1 download Mail::Sendmail from the RTO Web site (http://rto.dk/packages). Mail::Sendmail is also available from Comprehensive Perl Archive Network (CPAN—http://www.cpan.org). The PPM Help documents ppm.html and ppmproxy.htm are installed with ActivePerl. Refer to these documents for PPM details and for information about configuring PPM on a system behind a firewall.
To meet the security guards' requirements, we wrote TheftDetector.pl. Listing 2, page 49, shows all of TheftDetector.pl's code lines but omits some comment lines for space reasons. We've developed a template to use in laying out Perl scripts. At the beginning of a script, we put the Use statements that identify the modules that the script uses. We add Title and Color commands to customize the command-shell window if we'll run the script interactively. In header comments, we include author contact information, date written, version, usage syntax, operation, required Perl modules, input and output file information, and examples of any configuration options.
After the header comments, we add any input file and output file location variables, email addresses, and other variables that we need to configure when we deploy the script. If you bury these configurable items down in the bowels of the code, they can be difficult to locate and configure later. We also include comments and examples for the configuration options.
We try to modularize our code into subroutines. Often, we can recycle these subroutines in future scripting projects. For example, now that we've written a subroutine that sends email messages and pages, we can recycle it in future scripts that require email and pager notifications. Code recycling saves time and money and yields a better script. If in the future we identify a better way to accomplish a task, we can write a new subroutine and incorporate it into previously written scripts that perform that task.
TheftDetector.pl has three subroutines. The core code in our script is the ping routine, TestObjects, which tests each host for responsiveness. The Net::Ping module in that subroutine lets you configure the timeout period, which we've set to 5 seconds—as callout E in Listing 2 shows. (If you have a particularly slow network, you might need to increase this value.) Note the nested second ping at callout F, which retests responsiveness if the first ping fails and initiates a page only if both pings failed. The Initialize subroutine contains a third ping test at callout H; this test determines which PCs are online when the script first runs.
The Page subroutine uses the Mail::Sendmail module to send the email message or page to an email-addressable alpha pager or other device. Help for this module is available both in the notes within the sendmail.pm file that PPM installs and at http://alma.ch/perl/mail.htm.
You can download TheftDetector.pl from the Windows 2000 Magazine Web site (http://www.win2000mag.com), along with examples of the two input files that the script uses: PagerRecipients.txt and TestObjects.txt. The script includes detailed comments to help you understand the code.
We tested TheftDetector.pl on servers and workstations running Win2K and Windows NT 4.0 with Service Pack 5 (SP5) and SP6 and ActivePerl 5.6.1, build 623. Always test any script carefully in a nonproduction environment before deploying it.^^^^ ^^^