Cover V11, I02

Article
Listing 1
Listing 2

feb2002.tar

Increasing Reliability Through Forensic Operations

Bob Apthorpe

If you work in a large organization, you may know of a few nightmare systems. These machines provide vital services but are so laden with legacy code and historical debris that nobody seems to know precisely what they do anymore. Systems like this are the bane of every systems administrator, but can be great learning opportunities. I recently took on the challenge of deciphering and documenting one such nightmare system.

The Situation

I am responsible for a farm of seven middle-tier Web application servers known as jpdaemons. The jpdaemons tier provides various services to Web servers, such as stock and weather information, and personalized data, such as user preferences. These middle-tier machines broker connections between the front-end Web servers (FEWS) and back-end database servers. All the machines run Solaris 2.6 and have a shared non-root role account for running daemons accessible by all developers.

The Immediate Problems

These machines suffered from many problems, the most pressing of which were the following:

  • The middle-tier machines were running out of resources (disk, swap, CPU).
  • There was no definitive, public list of the daemons running on the middle-tier servers.
  • All the machines should have been identical but weren't.
  • There were no troubleshooting documents, nor were there start/stop scripts under /etc/rc3.d/ to restart daemons in case of reboot.

These factors combined to make maintenance and troubleshooting extremely difficult.

Operational Constraints

Besides these problems, I faced some significant operational constraints:

  • The systems are occasionally heavily loaded, so resource-intensive tools could not run continuously.
  • User access could not be easily revoked.
  • The machines had to be left in service during diagnosis, so offending processes could not be arbitrarily halted.
  • There was no budget for commercial support or tools to solve this problem.
  • There were substantial communications barriers between the three affected offices (my office in Austin, TX, our corporate data center in Redwood City, CA, and the engineering office in Tokyo, Japan).

Suffice to say, it was not feasible for everyone to sit down at the same table and meet to solve the problems.

I recognized that this project was going to be a challenge. The underlying causes of the problems were social and political, not technical, and the solution would take time to implement. Sadly, understanding the managerial problems didn't absolve me of administrative responsibilities. To preserve my sanity and health, I needed to find out what these systems did and write troubleshooting documentation for the junior admins on our NOC staff. Also, I needed a list of specific problems to convince engineering management to fix their change control process and to gain my management's support in case I needed to take more drastic actions to preserve system reliability.

Step 1: Finding the Logs

My immediate concern was stopping the daemons' logs from filling the machines' disks. When this happened, the NOC would investigate and page me after realizing they didn't know which logs could be deleted. My first task was to identify rapidly growing files in a filesystem.

Luckily, I was familiar with FCheck (http://www.geocities.com/fcheck2000/fcheck.html) from Sys Admin ("FCheck: A Solution to Host-Based Intrusion Detection" by Ron McCarty, December 2000, http://www.sysadminmag.com/documents/sam0012h/). FCheck is normally used as a file integrity checker as part of a host intrusion detection system. In this case, it made a perfect developer detector. I configured FCheck to look at likely directories, took a baseline snapshot, waited a few hours, then took another snapshot and compared the two. Configuring FCheck on one machine took some time, but once that was done I could quickly install it on the other six machines.

After I found the offending logs, it was a simple matter to get them under control. I wrote a simple script to compress, truncate, and delete the logs and I updated FCheck to ignore known log files (Listing 1).

I assumed that the developers were writing logs for a reason, so I didn't just delete them outright unless there was no other option (e.g., the Oracle logs.) The fuser command also helped determine which user and process was writing to a particular log. Also, because I had FCheck installed and tuned, I could automate it to watch for other changes to these systems while I continued my analysis.

Step 2: Associating Daemons with Ports

Next, I found all the running daemons and determined the ports used by each daemon. I was lucky because all the daemons were running under a single developer account (excited) and not as root, though my analytical method wouldn't have changed substantially even if the daemons had been running as root. I created a concise list of processes running under developer accounts:

# /bin/ps -u excited -o args | /bin/sort -u | /bin/egrep 
-v COMMAND ../../bin/rel5/univdbd -port 90017 -connect 
zzzzzzzzzz/zzzzzzzzzz@prel -numchil ../../bin/rel5/univdbd 
-port 90019 -connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil 
../../bin/rel5/univdbd -port 90023 -connect vvvvv/vvvvvvv@oesjp 
-numchildren 10 ../../bin/rel5/univdbd -port 90024 -connect 
zzzzzzzzzz/zzzzzzzzzz@prel -numchil ../../bin/rel5/univdbd 
-port 90049 -connect vvvvv/vvvvvvv@oesjp -numchildren 10 
../../bin/rel8/univdbd -port 14001 -connect zzzzzzzzzz/zzzzzzzzzz@prel 
-numchil ../../bin/rel8/univdbd -port 14002 -connect wwwwwwww/wwwwwwww@prel 
-numchildren ../../bin/rel8/univdbd -port 14003 -connect vvvvv/vvvvvvv@oesjp 
-numchildren 10 ../../bin/rel8/univdbd -port 14004 -connect vvvvv/vvvvvvv@oesjp 
-numchildren 10 ../../bin/rel8/univdbd -port 14005 -connect 
zzzzzzzzzz/zzzzzzzzzz@prel -numchil ../../bin/rel8/univdbd -port 14006 
-connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil ../../bin/rel8/univdbd 
-port 14007 -connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil ../../bin/rel8/univdbd 
-port 60057 -connect wwwwwwww/wwwwwwww@testprel -numchil ../../bin/rel8/univdbd 
-port 90054 -connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil ./nerd -kids 0 
-port 80020 -qprefix real-jp -log ./real-jp.log -qcount 1 
./phd_jp -port 8111 -cwd /excite/daemon/phd_jp -children 30 -loglevel 2 
-atime /usr/local/bin/perl -T ./enstoud.pl
RELEASES/release/sol24/acctd -port 60005 -connect xxxx/xxxx@jpurs.excite.com 
-n RELEASES/release/sol24/authd -port 60003 -connect xxxx/xxxx@jpurs.excite.com 
-n RELEASES/release/sol24/infod -port 60006 -connect xxxx/xxxx@jpurs.excite.com 
-n sol24/jpminibbs -port 60222 -connect yyyyyyy/yyyyyyy@jpurs -numchildren 10 
-log sol24/jpnewtransd -port 60797 -numchildren 20 -loglevel 10 -logfile 
./logtrans/ sol24/jpnewtransd -port 60798 -numchildren 17 -loglevel 10 
-logfile ./logtrans/ sol24/jpnewtransd -port 60799 -numchildren 20 
-loglevel 10 -logfile ./logtrans/ sol24/jpursplusd -port 60680 -connect 
uuuuuuuuu/uuuuuu@jpurs -numchildren 10 -l
Note that under Solaris 2.6, the default (System V) version of ps truncates the argument list. Berkeley ps (/usr/ucb/ps) yields more information at the expense of format configurability. To generate the same results as above, I used:

# /usr/ucb/ps auxww | /bin/egrep '^ *excited' | /bin/cut -c62- | /bin/sort -u
Field width is variable, so the arguments to /bin/cut need to be adjusted accordingly.

Next, I listed the services listening on the host:

# /bin/netstat -na | /bin/egrep LISTEN | /bin/sort -n -b -k 1.3,1.7  
      *.22                 *.*                0      0     0      0 LISTEN
      *.25                 *.*                0      0     0      0 LISTEN
      *.111                *.*                0      0     0      0 LISTEN
      *.4045               *.*                0      0     0      0 LISTEN
      *.8111               *.*                0      0     0      0 LISTEN
      *.13722              *.*                0      0     0      0 LISTEN
      *.13782              *.*                0      0     0      0 LISTEN
      *.13783              *.*                0      0     0      0 LISTEN
      *.14001              *.*                0      0     0      0 LISTEN
      *.14002              *.*                0      0     0      0 LISTEN
      *.14003              *.*                0      0     0      0 LISTEN
      *.14004              *.*                0      0     0      0 LISTEN
      *.14005              *.*                0      0     0      0 LISTEN
      *.14006              *.*                0      0     0      0 LISTEN
      *.14007              *.*                0      0     0      0 LISTEN
      *.14484              *.*                0      0     0      0 LISTEN
      *.24481              *.*                0      0     0      0 LISTEN
      *.24483              *.*                0      0     0      0 LISTEN
      *.24487              *.*                0      0     0      0 LISTEN
      *.24488              *.*                0      0     0      0 LISTEN
      *.24513              *.*                0      0     0      0 LISTEN
      *.24518              *.*                0      0     0      0 LISTEN
      *.32771              *.*                0      0     0      0 LISTEN
      *.40001              *.*                0      0     0      0 LISTEN
      *.60003              *.*                0      0     0      0 LISTEN
      *.60005              *.*                0      0     0      0 LISTEN
      *.60006              *.*                0      0     0      0 LISTEN
      *.60057              *.*                0      0     0      0 LISTEN
      *.60222              *.*                0      0     0      0 LISTEN
      *.60680              *.*                0      0     0      0 LISTEN
      *.60797              *.*                0      0     0      0 LISTEN
      *.60798              *.*                0      0     0      0 LISTEN
      *.60799              *.*                0      0     0      0 LISTEN
As with /bin/cut, you will need to adjust the arguments to /bin/sort.

At this point, I had a list of running daemons, a list of which ports had daemons listening on them, and I could regenerate those lists at will. This allowed me to take a snapshot of the running system and compare it to the the baseline snapshot at a later time, much as I had done with FCheck and the filesystem.

A Curious Observation

I removed well-known services (ssh, smtp, sunrpc/portmapper) from the netstat output (see RFC 1340). I initially trusted ps to identify which ports each daemon used, until I hit the following curious entry:

/../bin/rel8/univdbd -port 90054 -connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil
Since port number is defined in RFC 973 "" as a 16-bit unsigned integer, it can't exceed 65535. So, if a process is told to listen to a port greater than 65535, what does it do? Apparently, the process doesn't die or it wouldn't show up under ps. This is when I began using lsof to confirm associations between ports and daemons.

First I retrieved a process ID:

/bin/ps -u excited -o pid,args | /bin/egrep 90054 | /bin/sort -b -n | /bin/head -1
1905 ../../bin/rel8/univdbd -port 90054 -connect zzzzzzzzzz/zzzzzzzzzz@prel -numchil
I fed that into lsof to find out which files and ports the process was using:

# lsof -p 1905
COMMAND  PID    USER   FD   TYPE     DEVICE  SIZE/OFF      NODE NAME
univdbd 1905 excited  cwd   VDIR      32,10       512     71421   \
   /excite/daemon/jp/udbd/sct/travel
univdbd 1905 excited  txt   VREG      32,10   2264140     71233   \
   /excite/daemon/jp/udbd/bin/rel8/univdbd
univdbd 1905 excited  txt   VREG       32,0   6795316    359900   \
   /u/oracle/product/8.0.5/lib/libclntsh.so.1.0
univdbd 1905 excited  txt   VREG        0,1     70168 205389403 /tmp (swap)
univdbd 1905 excited  txt   VREG       32,0     16932    173297   \
   /usr/platform/sun4u/lib/libc_psr.so.1
univdbd 1905 excited  txt   VREG       32,0   1024888     88720   \
   /usr/lib/libc.so.1
univdbd 1905 excited  txt   VREG       32,0     19304     88302   \
   /usr/lib/libmp.so.2
univdbd 1905 excited  txt   VREG       32,0    105788     88347   \
   /usr/lib/libm.so.1
univdbd 1905 excited  txt   VREG       32,0    343432     88541 /   \
   (/dev/dsk/c0t0d0s0)
univdbd 1905 excited  txt   VREG       32,0    727712     88530   \
   /usr/lib/libnsl.so.1
univdbd 1905 excited  txt   VREG       32,0     36508     88533   \
   /usr/lib/libaio.so.1
univdbd 1905 excited  txt   VREG        0,1         4 205389377 /tmp (swap)
univdbd 1905 excited  txt   VREG       32,0     33588     88305   \
   /usr/lib/libposix4.so.1
univdbd 1905 excited  txt   VREG       32,0      6708     88320   \
   /usr/lib/libw.so.1
univdbd 1905 excited  txt   VREG       32,0     53656     88314   \
   /usr/lib/libsocket.so.1
univdbd 1905 excited  txt   VREG       32,0      4304     88632   \
   /usr/lib/libdl.so.1
univdbd 1905 excited  txt   VREG       32,0    181840     88304   \
   /usr/lib/ld.so.1
univdbd 1905 excited    0r  VCHR       13,2       0t0    268012   \
   /devices/pseudo/mm@0:null
univdbd 1905 excited    1w  VCHR        0,0   0t49177    268004   \
   /devices/pseudo/cn@0:console
univdbd 1905 excited    2u  VCHR        0,0 0t4008043    268004   \
   /devices/pseudo/cn@0:console
univdbd 1905 excited    3w  VREG       32,3     64114     30534 /logs   \
   (/dev/dsk/c0t0d0s3)
univdbd 1905 excited    4u  inet 0x6201e410       0t0       TCP *:24518 (LISTEN)
univdbd 1905 excited    5u  FIFO 0x61da6264       0t4      1379 PIPE->0x61da61e0
univdbd 1905 excited    6u  FIFO 0x62121904       0t4      1380 PIPE->0x62121880
univdbd 1905 excited    7u  FIFO 0x62121b84       0t4      1381 PIPE->0x62121b00
univdbd 1905 excited    8u  FIFO 0x621217c4       0t4      1382 PIPE->0x62121740
univdbd 1905 excited    9u  FIFO 0x62121684       0t4      1383 PIPE->0x62121600
univdbd 1905 excited   10u  FIFO 0x62121544       0t4      1384 PIPE->0x621214c0
univdbd 1905 excited   11u  FIFO 0x62121404       0t4      1385 PIPE->0x62121380
univdbd 1905 excited   12u  FIFO 0x62121a44       0t4      1386 PIPE->0x621219c0
univdbd 1905 excited   13u  FIFO 0x61b76244       0t4      1387 PIPE->0x61b761c0
univdbd 1905 excited   14u  FIFO 0x621212c4       0t4      1388 PIPE->0x62121240
univdbd 1905 excited   15u  FIFO 0x62121184       0t4      1389 PIPE->0x62121100
Some important lines are:

COMMAND  PID    USER   FD   TYPE     DEVICE  SIZE/OFF      NODE NAME
univdbd 1905 excited  cwd   VDIR      32,10       512     71421   \
   /excite/daemon/jp/udbd/sct/travel
univdbd 1905 excited  txt   VREG      32,10   2264140     71233   \
   /excite/daemon/jp/udbd/bin/rel8/univdbd
univdbd 1905 excited    3w  VREG       32,3     64114     30534 /logs   \
   (/dev/dsk/c0t0d0s3)
univdbd 1905 excited    4u  inet 0x6201e410       0t0       TCP *:24518 (LISTEN)
Line 1 shows that this process was run from /excite/daemon/jp/udbd/sct/travel. Line 2 shows the full path to the executable (/excite/daemon/jp/udbd/bin/rel8/univdbd). Line 3 shows the process is writing to something in /logs/. Line 4 shows the process is listening on TCP port 24518, not port 90054 as the results from ps indicate. Where does 24518 come from? Simple -- truncate 90054 to 16 bits by dividing it by 2**16 (65536) and taking the remainder:

# echo '90054 % 65536' | /bin/bc
24518
This was an unexpected discovery that greatly simplified the process of documenting the daemons and turned the analysis into a fairly tedious exercise. The security implications here should not be underestimated -- can a non-privileged user set code to listen on a privileged port by specifying a port number greater than 65535? This is yet another reason to view setuid scripts with suspicion.

Step 3: Building the Start Scripts

In my case, after I found where executables were stored, I usually found start scripts nearby. It was simple to write a wrapper script and put it in /etc/init.d and symlink it into /etc/rc3.d (Listing 2).

Note the use of /bin/logger in Listing 2 to report daemon activity to syslog and the use of /bin/tty to selectively send output to the console. Also note that unrecognized directives (e.g., anything other than "stop", "start", or "restart") yield process and network status information.

I added chkconfig headers to these startup scripts to simplify deployment on IRIX and Linux systems. They cause no harm under Solaris, and they leave a terse documentation trail for anyone that comes later.

If you are not so fortunate as to find a startup script during your investigations, use the "e" flag to Berkeley ps to show environment information:

# /usr/ucb/ps eauxww | /bin/egrep 1905 | /bin/egrep -v grep
excited   1905  0.0  0.1 8024  488 ?        S   Jul 21  1:44 
../../bin/rel8/univdbd -port 90054 -connect zzzzzzzzzz/zzzzzzzzzz@prel 
-numchildren 10 -logall -loglevel 1 
-logfile /logs/udbd/travel_univdbd.ed.log GROUP=excite HOME=/u/excited 
HOST=caligula HOSTTYPE=sun4 HZ= LD_LIBRARY_PATH=/u/oracle/product/8.0.5/lib: 
LOGNAME=excited MACHTYPE=sparc NLS_LANG=Japanese_japan.JA16SJIS 
ORACLE_HOME=/u/oracle/product/8.0.5 OSTYPE=solaris 
PATH=/usr/sbin:/usr/bin:/usr/local/bin:/usr/ccs/bin:/usr/local/news/bin:/bin:/ \
  usr/ucb:/usr/openwin/bin:/opt/vxva/bin:/etc:. 
PWD=/excite/daemon/jp/udbd/sct/travel SHELL=/usr/local/bin/tcsh SHLVL=1 
TNS_ADMIN=/var/opt/oracle TZ=US/Pacific USER=excited VENDOR=sun
This won't create your startup scripts for you, but it does show you an environment that the daemon will run under.

Moving Targets

It took several weeks to identify and catalog all the ports and daemons and to create start scripts. During the project, I sent progress reports and technical data to my group, my department, and the developers in Tokyo. I also explicitly told everyone to inform me before making any changes to these machines, because I was in the process of documenting the existing system. I did not want to spend a lot of time and effort documenting the system only to have it change, invalidating everything I had done. That, of course, is precisely what happened.

However, two things saved me. First, I put FCheck in place to watch for file changes. When seven new daemons appeared on the machines, I knew about it in a few hours -- my developer detection system worked perfectly! Second, I added all the daemons to /etc/services so I could use netstat to detect new daemons. By using netstat without the "-n" option, known daemons would show up with their proper names while new daemons would only appear with a numeric port number:

# /bin/netstat -a | /bin/egrep LISTEN | /bin/sort -b -k 1.3,1.7
      *.14001              *.*                0      0     0      0 LISTEN
      *.14002              *.*                0      0     0      0 LISTEN
      *.14003              *.*                0      0     0      0 LISTEN
      *.14004              *.*                0      0     0      0 LISTEN
      *.14005              *.*                0      0     0      0 LISTEN
      *.14006              *.*                0      0     0      0 LISTEN
      *.14007              *.*                0      0     0      0 LISTEN
      *.acctd              *.*                0      0     0      0 LISTEN
      *.authd              *.*                0      0     0      0 LISTEN
      *.bpcd               *.*                0      0     0      0 LISTEN
      *.bpjava-msvc        *.*                0      0     0      0 LISTEN
      *.chanceit           *.*                0      0     0      0 LISTEN
      *.infod              *.*                0      0     0      0 LISTEN
      *.jpaddrbookd        *.*                0      0     0      0 LISTEN
      *.jpencode           *.*                0      0     0      0 LISTEN
      *.jpnewtransd        *.*                0      0     0      0 LISTEN
      *.jpnewtransd2       *.*                0      0     0      0 LISTEN
      *.jpnewtransd3       *.*                0      0     0      0 LISTEN
      *.jpursplusd         *.*                0      0     0      0 LISTEN
      *.lockd              *.*                0      0     0      0 LISTEN
      *.music_test2        *.*                0      0     0      0 LISTEN
      *.newsedit           *.*                0      0     0      0 LISTEN
      *.nphoto             *.*                0      0     0      0 LISTEN
      *.phd_jp             *.*                0      0     0      0 LISTEN
      *.real               *.*                0      0     0      0 LISTEN
      *.recjnew            *.*                0      0     0      0 LISTEN
      *.simpfeed           *.*                0      0     0      0 LISTEN
      *.smtp               *.*                0      0     0      0 LISTEN
      *.ssh                *.*                0      0     0      0 LISTEN
      *.statd              *.*                0      0     0      0 LISTEN
      *.sunrpc             *.*                0      0     0      0 LISTEN
      *.travel2            *.*                0      0     0      0 LISTEN
      *.vopied             *.*                0      0     0      0 LISTEN
Note the top seven entries. Assuming the developers have not changed ports at random, I only have to investigate and document seven more daemons by the process outlined so far. Also, I haven't lost any of the work I've done to this point. The systems administrators I most respect usually have two notable characteristics -- they hate repetitive work and they hate wasted effort, so I tried to follow their examples and work defensively.

From Damage Control to Efficiency

After the existing daemons were documented and startup scripts were in place, I started monitoring daemon usage. I originally assumed that every daemon running on the systems was actually in use. Since I had solved the problem of logs filling the disks, the major problem was with memory and swap consumption. While I could add memory or swap, it might be easier to turn off any unused daemons. The key was finding which daemons were in use.

Initially, I used netstat and lsof to watch for connections. This method didn't work very well because the connections were transient; I needed a tool to log network activity over time. On a coworker's recommendation, I chose ntop (http://www.ntop.org/):

#!/bin/sh
/usr/local/bin/ntop -S 1 -t 0 -p /var/adm/z/ntop.protocols.txt \
-P /var/adm/z -d

ntop.protocols.txt:
acctd=60005,authd=60003,chanceit=24481,infod=60006,jpcontentsd=60228,
jpencode=40001,jpminibbs=60222,jpnetcoop=60257,jpnewtrans2=60798,
jpnewtrans3=60797,jpnewtrans4=60796,jpnewtrans=60799,jpursplus=60680,
music=24519,music_test=24521,newsedit=24513,nphoto=24487,phd_jp=8111,
real2=14486,real=14484,recjnew=24483,simpfeed=24488,travel=24518,
traveld=60106
The ntop GUI normally runs on port 3000. I was primarily concerned with incoming TCP traffic on a machine called caligula, so the results were located at:

http://caligula.excite.com:3000/sortDataReceivedIP.html
Ntop recorded traffic volume for the daemons in which I was interested and clearly indicated all the unused daemons.

Social Engineering

While I was happy that I had solved the immediate technical problems, I knew the underlying software deployment process was still broken. Unless Engineering notified Operations prior to launching new daemons, we would continue to have problems with the jpdaemons service. Using the information I had gathered about system usage and hardware capacity, I explained to Engineering and Operations management that the service was unable to be maintained, and I proposed a number of changes to our launch process. I also explained that I would turn off any new daemons I found unless someone sent me troubleshooting information so I could write start and stop scripts and update /etc/services.

Conclusion

It took about nine months to clean up this system and it's still not finished. I initially reported the problem via established channels and, as one might expect, nothing happened. This is not surprising because I've faced this problem at every organization I've worked for. My goal was to solve the root cause of the problem -- the daemon launch and upgrade process.

I considered the history of this system and our organization: international developers had received less support than their domestic counterparts; production standards and processes were vague at best because of the company's explosive growth and the newness of our technology and management. Also, the implied standards we did have had not been effectively communicated to overseas developers. Added to this were cultural, language, organizational, and temporal differences. As bad as the system might seem at present, it's easy to imagine it devolving into chaos, despite our good intentions.

With that as background, I presented my recommendations:

  • Temporarily disable the "excited" account until the service was rationalized, and the daemon launch process was fixed.
  • Notify Operations before launching a new daemon. Necessary information is, for example:

Service Name -- jpencode

Purpose -- Convert Shift-JIS text to Unicode

Start script -- /excite/daemon/jpencode/start-enstoud.sh

Stop script -- /excite/daemon/jpencode/stop-enstoud.sh Daemon -- /excite/daemon/jpencode/enstoud.pl

Port -- 40001

User -- excited

Monitoring -- Check whether daemon answers on port 40001

  • Replace jpdaemons service in site configuration management database with individual services for each of the 30+ daemons found.
  • Rationalize and load balance existing services and make machines as similar as possible.
  • Find an effective liaison between Operations and Tokyo Engineering.
  • Add all daemons to /etc/services.
  • Fix daemons listening on irrational ports (> 65535).

At the end of this project, I felt as though I had won all the battles and lost the war. Still, I don't feel too bad about the project. I made the system usable and maintainable, and I solved every technical problem I faced. I clearly communicated my findings and suggested ways to fix the launch process. Ultimately, I reduced a technical problem to a social problem and left it as an exercise for management. As systems administrators, that's often the best we can do.

Tools

chkconfig -- http://www.buttsoft.com/~thumper/software/ \

sysadmin/chkconfig/

FCheck -- http://www.geocities.com/fcheck2000/fcheck.html

lsof -- ftp://ic.cc.purdue.edu/pub/tools/unix/lsof/

ntop -- http://www.ntop.org/

Bob Apthorpe is a Sr. System Administrator with Excite@Home, Inc. Prior to joining Excite, he was a risk and reliability analyst in the nuclear power industry. Off the clock, he is sometimes found performing improvisational comedy in Austin, Texas. He can be contacted at: arclight@jump.net.