A Centralized
and Flexible Antivirus Solution
Dani Pardo
Viruses can represent a huge problem, specially now that information
flows so fast. If your network has a reasonable number of clients,
you probably don't want to spend much time regularly installing
and upgrading antivirus software. Using client-side antiviruses
also has the disadvantage of slowing down performance, and your
users might even find them difficult to run.
Client/Server Antivirus
The solution I present here uses Linux smb support to scan
viruses in your network client's hard disk. The idea is to
share the client's hard disk or directory, and let the server
scan it via the network. This solution saves you from installing,
operating, and maintaining separate antivirus installations on each
client. However, this approach also has some limitations. Performing
virus scans from the server consumes a lot of network bandwith.
Also, I do not recommend this approach if you are concerned about
privacy and internal security. This solution also has the drawback
that it won't be able to scan the MBR (the boot sector) of
the hard disk and the partition table.
It uses McAffe's AVP (Antiviral Toolkit Pro), with the kernel's
ability to mount NetBIOS shares. The user only requires a Web browser
to tell the server which shared resource needs to be scanned, and
AVP's output report will be emailed to him (or to the sys admin).
So, you just need the server to run something like:
# smbmount //novita/C /mnt/smb/novita/C
# uvscan -c -r /mnt/smb/novita/C |mail user@domain
In this example, we suppose machine "novita" wants to scan
share "C" (usually the hard disk). If we want to write this
CGI, the first question to ask is: How do we know the client's
Netbios name, and how do we get his shared resources? Here is an easy
way using Perl's CGI and socket modules:
#!/usr/bin/perl
use CGI;
use Socket;
use Sys::Hostname;
$query=new CGI;
my $hostname = gethostbyaddr(inet_aton($query->remote_host()),AF_INET);
$hostname=~s/.domain.com//; # Cut the domain
$hostname=uc($hostname); # Uppercase the name
Thus, you get the TCP/IP hostname, via a DNS server. (You'll
need a DNS server in your network.) In my network, TCP/IP names
and Netbios names are the same. If that's not true in your
case, you might need to add some extra code to get the Netbios name
having the TCP/IP name -- probably by using a database or text
file. To get a client's availiable resources and store them
in an array using Samba's smbclient:
my @shares;
open (FILE,"/usr/local/samba/bin/smbclient -L $hostname -U \
nobody%nopass |grep Disk| cut -f 1 -d \" \" |");
while (<FILE>)
{
$_=~s/^\s+//; # Delete spaces
$_=~s/Disk//;
if (! ($_=~/PRINTER\$/)) # We dont want the PRINTER$ share.
{push @shares, $_;}
}
close FILE;
Notice that we login to the client machine with a username/password
nobody%nopass. This is because if omitted, smbclient will interactively
prompt us for a password and use our login name as the netbios username.
We always send user=nobody, and password=nopass in our script. This
will work for Win95/98 clients, but not for NT. NT will require us
to be a user of the system. Now if you're using NT clients, you'll
have to adapt this section to get the available resources (e.g., adding
a user "nobody" with password "nopass" in all
NT clients in our network). Note that if somebody also has a shared
printer, you'll find a shared directory named PRINTER$,
which you don't want to take care of. With the shared resources
in an array, you can present them to the users via a pop-up menu in
a browser for users to select the desired directory to scan:
print $query->start_form;
if ($#shares==-1) # Empty array, nothing shared from the client
{
print "<B>You don't have any shared resource to scan!B><P>";
# A link to an HTML help page
print '<a href="http://calix:85/help.html"><img SRC="/help.gif" \
BORDER=0 height=40 width=200></a>'; }
else
{
print "Select desired resource: " . \
$query->popup_menu('Shares',\@shares) . "</P>";
print "Send report (e-mail) to: " . $query->textfield(-name=>'email',
-size=>30) . "</P>";
print $query->checkbox(-name=>'ScanNow',-label=>'Check now');
print '<a href="http://calix:85/help.html"><img SRC="/help.gif" \
BORDER=0 height=40 width=200></a>'; print "<P>" . \
$query->submit(-name=>'Iniciar');
}
print $query->end_form;
print $query->end_html;
I've also included the textfield "email", in which
the user can enter his email address, and a check button that, when
active, the system will start scanning immediately. If not activated,
the system will scan at 23:00 p.m.
#!/usr/bin/perl
use CGI
$query=new CGI;
if ($query->param())
{
# Use parameters and do the scanning, or program an at job that
# does the scanning
}
else
{
# Check hostname, resources, and ask for input
}
Mount and Scan
When users press the submit button, this script will begin the second
part, receiving $query->param('Shares'), $query->param('e-mail'),
and $query->param('ScanNow') as parameters.
First, it creates the appropiate directory in which to mount the
share. To support multiple simultaneous clients, I used the convention
of /mnt/smb/$hostname/$share. Also, check the given email
to be a valid (allowed) address:
if (!($email=~/[a-z-A-Z-0-1]+\@domain.com/)) # only numeric or
# alfanumeric address
# from our domain
{
print "<B>Error:</B> Invalid e-mail address: $email";
exit(-1);
}
Since there are two cases (scan inmediately or scanning at night),
we'll program only one at job to be run "now"
or at 23:00, for example):
if ($query->param('ScanNow') eq 'on')
{
$time='now';
print "<B>You'll soon receive the antivirus report by e-mail.</B>";
}
else
{
$time='23:00';
print "<B>Ready<P>Remember to leave the computer on \
tonight...</B>";
}
system ("at $time -f /tmp/atjob.$hostname.$share");
I've checked parameters, created the appropiate directory, written
the commands file, and called at to execute this file at a
programmed time:
mkdir "/mnt/smb/$hostname",500;
mkdir "/mnt/smb/$hostname/$share",500;
open FD, ">/tmp/atjob.$hostname.$share" || die "Can't create tmp file";
print FD "/usr/bin/smbmount //$hostname/$share \
/mnt/smb/$hostname/$share -N > /dev/null \n";
print FD "/usr/local/bin/uvscan -c -r \
/mnt/smb/$hostname/$share |mail -s \"Antivirus repot\" $email \n";
print FD "/usr/bin/smbumount /mnt/smb/$hostname/$share\n";
# We delete in two steps, instead to do a rm -r (just in case)
print FD "rmdir /mnt/smb/$hostname/$share\n";
print FD "rmdir /mnt/smb/$hostname\n";
close FD;
chmod 500,"/tmp/atjob.$hostname.$share";
To Do
Listing 1 provides the entire script. The script can be optimized,
but it gets the job done. If you're concerned with security or
privacy, you will probably find this solution too high-risk. (It's
not the best CGI to run in your main server where all your users have
a shell login.) Security can be enhanced by adding support for password-protected
shares. This can be achieved by adding a text field where the user
could enter the password of the share, so you'll receive $password=$query->param("password")
as a parameter. That way you'll be able to invoke smbmount
with:
/usr/bin/smbmount //$hostname/$share /mnt/smb/$hostname/$share \
-U nobody%$password -N >/dev/null
In my case, omitting passwords made things simpler.
Another issue with this solution is the need for a Linux kernel.
At first, I wanted it to run Samba's smbsh (Samba shell)
in order to run the CGI in other UNIXes. smbsh also has the
advantage that you can scan all your network at one time (using
something like uvscan -r /SMB). The biggest advantage is,
of course, that now you only need to upgrade the server's antivirus
information (the DAT files) and forget about the clients. Enjoy
it.
Dani Pardo is a 27-year-old programmer and system manager in
Enplater s.a., a packaging factory in Estartit (Spain). When not
working, he spends his weekends going out with his friends until
noon. He can be reached at: dani@enpl.es.
|