|  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" \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.BORDER=0 height=40 width=200></a>'; print "<P>" . \
 $query->submit(-name=>'Iniciar');
 }
 
 print $query->end_form;
 print $query->end_html;
 
              #!/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. 
           |