How to set up ClamAV as a Windows Service to scan file streams on demand

by Peter Tyrrell Thursday, October 15, 2009 9:44 PM

Overview

Clam Antivirus, or ClamAV for short, is an open-source antivirus solution for UNIX. It's also the ONLY open-source antivirus solution. Naturally it would nice to have it for Windows, too, and it's absolutely possible; the hard part is finding out how to go about it.

Documentation on installing ClamAV for Windows and running its daemon clamd as a Windows Service is as scarce as hen's teeth, and the best information I could find was scattered across various unrelated forums and articles and none of it was fully up to date. So here it is, laid out as best as I can tell it, in one place: where to find a current native Win32 port of ClamAV, how to install it, how to set up clamd to run as a service, and the settings relevant to scanning file streams sent from an external client.

To give some context: I am using ClamAV to scan user-uploaded files for viruses in an ASP.NET web application, before writing them to disk. Each upload is sent as a stream from the webserver to another Windows 2003 server running the clamd service, which scans them and replies with some basic info on the outcome of the scan.

UPDATE Feb 19 2010: Extra info for installing on Windows Server 2008 x64.

Get the current version of ClamAV for Windows

Get ClamAV for Windows 0.95.2 at http://hideout.ath.cx/clamav/.

I am writing on October 15, 2009 and the current version of Clam AntiVirus is 0.95.2. The best 0.95.2 native Windows port available, in my opinion - because it has an installer - is at http://hideout.ath.cx/clamav/.

Another 0.95.2 alternative is at http://oss.netfarm.it/clamav/, but does not have an installer and requires you grab an additional assembly from Microsoft. On the other hand, it offers a 64 bit build.

You don't want ClamWin, which is a GUI aimed at the desktop crowd, and doesn't include clamd. Or it doesn't appear to right now. It might later. Maybe. It's hard to tell. See hen's teeth, above. Nor do you want the former native Win32 port at http://w32.clamav.net/ which is dead, dead, dead at version 0.92.1. Nor the Cygwin version, nor the Interix version. No.

UPDATE Dec 14 2010: Commenter J. Moore reports that MS Security Essentials reckons the hideout.ath.cx version contains a hacking tool Hideproc.c within chp.exe. The distribution from oss.netfarm.it does not contain chp.exe and doesn't throw any virus alerts.

Install ClamAV for Windows

Run the installer, ClamAV-095-2.exe. It is best to let the installer deploy to the default location, which is c:\clamav\, or you'll have to manually modify a bunch of clamav settings files afterwards.

Install clamd as a service

  1. Open a command prompt.
  2. Change directory to c:\clamav\ or wherever you installed clamav.
  3. Run clamd.exe --install
  4. Open services.msc and edit the newly installed "ClamWin Free Antivirus Scanner Service" to start Automatically and/or use credentials other than the local system account, etc.

clamav_cmd

Um, yes, that was easy.

The astute will note that the service names itself "ClamWin yada yada." Again, clamd is NOT included with ClamWin, so your guess is as good as mine as to the connection here, but there clearly *is* some connection, the true nature of which I have been unable to fathom.

You may find, if you Google for "clamd windows service", various helpful threads or articles describing how to forcibly set up clamd as a service with instsrv.exe and srvany.exe from the Windows 2003 Resource Kit Tools. And that works - but it's not necessary. I can only assume that the ClamAV Windows ports came out with a built-in "install as service" feature at some point fairly recently, but the word just hasn't got around.

I'll get to the settings for clamd next, but you need an up-to-date antivirus library first.

Schedule freshclam to update frequently

Freshclam fetches antivirus library updates. You need to run it right away to get the initial database up and going - just double-click freshclam.exe. After that, you can install it as a service in the same manner as clamd above. Or create a batch file that runs freshclam.exe on a schedule with Windows Task Scheduler. It has its own settings file: freshclam.conf.

Settings for on-demand stream scanning

Settings for clamd are in clamd.conf, and there are quite a few, but the ones most relevant for on-demand stream scanning are:

  • TCPAddr - server IP address
  • TCPSocket - port clamd will listen on, default 3310
  • StreamMaxLength - maximum size of stream to be scanned, in megabytes

Create a Windows firewall exception

The lazy way out here is to add clamd.exe as an exception to Windows firewall. Read on if you like.

You can exception just the port clamd listens on (default 3310), but a call to its STREAM method generates a reply on a random port with a range you can set in clamd.conf, which would mean you would have to add exceptions for the entire range. To problematize still further, STREAM has been deprecated in favour of INSTREAM, which sticks to the port you called it on in the first place. For now I'm just going to exception clamd.exe, though I did restrict its scope to the local subnet.

Call clamd from ASP.NET

I've found just one available .NET library that will call a clamd service, called WRAVLib: http://www.wolfereiter.com/antivirus.aspx. Unfortunately, it's somewhat out of date and written for .NET 1.1, but happily the source code is freely available, so you can compile for .NET 3.5 if you like. Direct link to source code is here: http://www.wolfereiter.com/Downloads/wravlib/wravlib-1.1-src.zip.

It does target the deprecated STREAM method instead of INSTREAM, which I touched on above, but it's still the fastest way to get up and running. Here's a bit of pseudocode to give an idea:

// create scan agent
IVirusScanAgent agent = new ClamdStreamAgent("127.0.0.1", 3310, false);
 
// create unique scan id
string scanId = Guid.NewGuid().ToString();
 
agent.VirusFound += ((sender, args) => {
   // do something
});
 
agent.ItemScanCompleted += ((sender, args) =>  {
   // do something
});                                  
                                    
// scan filestream
agent.Scan(scanId, file.InputStream);

Conclusion

I wish I had more time to fill in the gaps and provide more detail, but I just don't. Even this amount of information took way too long to gather in the first place! It should provide a good foundation nevertheless.

Acknowledgements 

  • This how-to on setting up ClamAV for Kerio MailServer on Windows convinced me I could run clamd as a service in the first place.
  • This forum thread on installing ClamAV Win32 with hMailserver was instrumental, guiding me with sample settings files and good pointers.

Tags: ClamAV

How to install Inmagic Webpublisher 12 on Windows 7

by Peter Tyrrell Wednesday, October 14, 2009 1:12 PM

Disclaimer: Not officially supported. Plunging heedlessly on...

1 - Back up INI files

First back up all your .ini files. Be sure you are getting the CORRECT copy of the ini file: the ini files in the Program Files directory are access-protected in Vista and Win7 because the Program Files area is a forbidden zone. You must open the ini file with elevated privileges, like with Notepad "run as administrator", and save it somewhere safe.

  • dbtwpub.ini
  • inmagic.ini
  • dbtext.ini

2 - Upgrade from previous version

If you want to upgrade from previous version of WPP instead installing a fresh copy, uninstall the previous version MANUALLY first, because the WPP 12 installer tries to uninstall without elevated permission, and thus fails.

3 - Run installer as admin

Run the WPP 12 installer with administrator privileges. If you have a setup.exe you can right-click to "run as administrator".

My preview version of the installer is an *.msi file only, which doesn't have a right-click "run as admin" option. Instead, I launch the msi with msiexec from an elevated command prompt:

  1. Search for "cmd.exe" from Windows Start Menu
  2. Right-click cmd.exe and "run as administrator"
  3. Change directory to location of the msi, e.g. cd c:\users\ptyrrell\downloads
  4. Run the msi with the msiexec /i option, e.g. msiexec /i "Inmagic DBText WebPublisher PRO.msi"

4 - Fulfill prerequisites

The installer is cleverer than previous versions when checking for prerequisites, so you'll probably have to go and install or enable various Windows features before continuing.

wpp12_prereqs 

IIS 6 Compatibility (IIS7 only) seems to be satisfied by enabling the "IIS Metabase and IIS 6 configuration compatibility" Windows feature.

wpp12_iis6

Be warned: if you don't run the installer with elevated privileges, you will continue to fail the prerequisite check even after installing/enabling the right prerequisites!

 

5 - Test the install

Restore your backed up ini files. Run a query on the sample cars textbase to ensure WPP is returning results as expected.

If you are installing on a 64 bit machine, you need to enable 32 bit applications on the relevant application pool as covered in this previous post called How to Install Webpublisher on 64-bit IIS 7.

6 - Be the star you know you are

You did it! Now cut out a cardboard star with safety scissors, pencil "STAR HACKER" on it, and get your mum to pin it to your chest, glitter optional. Salute yourself in the mirror. Star! Hacker!

Handling 404 errors with ASP.NET

by Ted Jardine Friday, October 02, 2009 3:21 PM

As mentioned at the end of my previous post on handling errors with ASP.NET, handling "404 Not Found" errors are particularly problematic (if you haven't read it yet, please do so). And looking around, the vast majority of information out there on it is not complete, misinformed, or flat-out wrong (but I greatly appreciate all efforts!). And I would argue that this is because ASP.NET implementation of 404 error handling is flat-out-wrong. So with my super hero cape on, here I come to wobbly save the day!

The typical ASP.NET way to handle 404 errors is to put something like the following in your Web.config:

<customErrors mode="On" defaultRedirect="~/error.aspx">
    <error statusCode="404" redirect="~/page-not-found.aspx" />
</customErrors>

Make a page-not-found.aspx page and voila! Ya got 'er dun! If you're a little more on the ball, you'll realise that while this configuration works for end users (gives them a pretty page to look at hopefully clearly explaining that you can't find what they're looking for), it's bad for SEO (search engine optimization) because it sends back a 302 temporary redirect to your 404 page which in turn sends back a "200 OK" message. In other words, "Yeehah! No problems here as you've found what you're looking for! Index away!"

So, bright developer that you are, you add in some applicable status code into your 404 page thinking that should take care of it:

protected override void OnLoad(System.EventArgs e)
{
    Response.TrySkipIisCustomErrors = true; // For IIS 7 Integrated Pipeline - see previous post
    Response.Status = "404 Not Found";
    Response.StatusCode = 404;
    base.OnLoad(e);
}

Well, fire up Fiddler and you'll discover that you're still getting a 302 temporary redirect to your 404 page. So you fire up your error handling code and for 404s, you Server.Transfer to your 404 page just like all your other error transfers take place! But no, bafflingly enough, even running through a debug session to ensure you're properly catching your 404, ASP.NET still insists on 302'ing your precious response (although at least now your 404 page is sending back the proper "404 Not Found" error status). So go out there and google everywhere and try every suggestion (just a sampling) and then breathe a prayer of thanksgiving for me and my super-duper super hero cape, because this is how you make your way to the 404 handling bliss (caveat: thoroughly tested only on IIS 7):

First, make something like the following in Application_Error in your global.asax (or better yet, a custom error module):

protected void Application_Error(Object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    {
        if (exception.InnerException == null)
        {
            Server.Transfer(ERROR_PAGE_LOCATION, false);
            return;
        }
        exception = exception.InnerException;
    }

    if (exception is HttpException)
    {
        if (((HttpException)exception).GetHttpCode() == 404)
        {

            // Log if wished.
            Server.ClearError();
            Server.Transfer(NOT_FOUND_PAGE_LOCATION, false);
            return;
        }
    }

    if (Context != null && Context.IsCustomErrorEnabled)
        Server.Transfer(ERROR_PAGE_LOCATION, false);
    else
        Log.Error("Unhandled Exception trapped in Global.asax", exception);
}

Second, put something like the following in your 404 page (or even better, add it to your above custom error module):

protected override void OnLoad(System.EventArgs e)
{
    Response.TrySkipIisCustomErrors = true; // For IIS 7 Integrated Pipeline - see previous post
    Response.Status = "404 Not Found";
    Response.StatusCode = 404;
    base.OnLoad(e);
}

And lastly, put whatever you want in your customErrors section in your Web.config for 404 - remove the 404 references if you want. The only time it would get used is if you keep the confusion level down by reading that section in your error module to find out what your 404 page is.

And now, you don't get any nasty 302 redirects. You get a blissfully pure and pretty 404.

Note: you can have everything in place as above, but if you forget to Server.ClearErrror(), it'll all be for naught as you'll still get 302 redirected (thanks to http://stackoverflow.com/questions/667053/best-way-to-implement-a-404-in-asp-net for finally helping me get this one nailed).

UPDATE: If for some reason you cannot change your code, a good option to pursue is flipping a switch in your applicationHost.config file that passes your response through without IIS hijacking it. This is the sledgehammer approach, but could be applicable for your situation. For example, in order to allow Umbraco's alternative status code responses to work, this is the only way to get them to avoid being hijacked by IIS (i.e. NotFoundHandlers, booting screens, etc.).

<location path="Site Description">
    <system.webServer>
        <httpErrors existingResponse="PassThrough" />
    </system.webServer>
</location>

Thanks to Fabian Heussser's comment on Rick Strahl's post that helped with this.

Tags: ASP.NET

Errors: Sending the Right Message

by Ted Jardine Friday, October 02, 2009 2:36 PM

I've encountered many twists and turns in my journey towards handling application errors with aplomb. You have to make sure:

  1. Users are happy (by providing informative error pages) - well, as happy as they can be under the circumstances.
  2. Developers who need to fix the problem are happy (extensive logging and notification to help re-enact, locate, and then fix the bug so it doesn't happen again).
  3. Search bots are happy (by sending them the right message).

For example, when a user requests a resource that can't be found you want to help the user out with at least a pretty page that explains what has happened. Most users don't have a clue what a "404 error" is and could care less. And almost just as important, when a search bot encounters an error it needs to receive a proper error code back or else it gets all uppity on you and knocks down the page's ranking. In other words, when a page can't be found:

  1. Redirect to a helpful "Sorry, we can't find the resource you're looking for" page that hopefully looks somewhat decent and gives the user some options on how to proceed (perhaps like our "smart" page not found functionality on our soon-to-be-launched www.andornot.com).  
  2. Log the 404 so that it can be dealt with (if applicable).
  3. Set the proper 404 error code for SEO purposes.

However, sometimes getting the three above objectives dealt with in one fell swoop is problematic. For example, if you just redirect after having an error occur (say a "500 Internal Server Error") to a pretty page, users will be happy, but search bots will receive something like the following:

Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Fri, 02 Oct 2009 18:30:47 GMT
Content-Length: 7719

200 OK

200 OK for something that really is not okay. It should be "500 Internal Server Error"! Erk. So how about we code in something like:

Response.StatusCode = 500;
Response.StatusDescription = "500 Internal Server Error";

But then things start to get interesting. In IIS 6 all appears to look good, but when you're using MasterPages, Internet Explorer 6 (in a rare case of actually being helpful) shows that what's being sent back is wacked as it wraps the custom error page with the default IIS error page. Gee, now two out of three of the above objectives are out the window. Suffice it to say, the result is grotesque, and I can't imagine what search bots think of it!

Moving on to IIS 7 and its Integrated Pipeline (doesn't apply to Classic Pipeline mode), things get even more interesting as it ignores your custom stuff altogether and sends back the applicable default IIS error page. What the? Rick Strahl has an excellent post addressing this issue. Essentially, the error is still trapped by ASP.NET (so it's logged, redirected, and whatever else you ask it to do), but then it is ultimately handled by IIS which sees the error code and hijacks the entire response and returns its ugly default error page (and who designed those anyway? Even IIS 6's were better).

So where do we go from here? In IIS 6, the problem only occurs when using MasterPages in your error page. Over simplifying, the page's MasterPage renders later on in the page lifecycle and resets the error code to 200 OK. And it just gets messier from there (see http://stackoverflow.com/questions/347281/asp-net-custom-404-returning-200-ok-instead-of-404-not-found for more info). So reset it back again further along in the page's lifecycle!

protected override void Render(HtmlTextWriter writer)
{
    base.Render(writer);
    Response.StatusCode = 500;
    Response.StatusDescription = "500 Internal Server Error";
}

But what about IIS 7? If you look further along in Rick Strahl's post mentioned above, there's a new flag in ASP.NET 3.5 called Response.TrySkipIisCustomErrors. As Rick says, "In a nutshell this property when set to true at any point in the request prevents IIS from injecting its custom error pages. This flag is new in ASP.NET 3.5 - so if you're running 2.0 only you're out of luck."

    Response.TrySkipIisCustomErrors = true;
    Response.StatusCode = 500;
    Response.StatusDescription = "500 Internal Server Error";

And Shazam!

HTTP/1.1 500 500 Internal Server Error
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/7.0
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Fri, 02 Oct 2009 20:02:48 GMT
Content-Length: 6879

Error handled and logged, developers notified, user somewhat mollified, and Google/Bing happy. How's that for making the best of a bad situation?

For more information, see some of the following:

Of course, there's a ton of caveats in the above. However, one that is definitely worth addressing is how 404 handling is extra-special-different in ASP.NET. Very extra-special-different. And badly very different. But we'll save that for the next post.

UPDATE: If for some reason you cannot change your code, a good option to pursue is flipping a switch in your applicationHost.config file that passes your response through without IIS hijacking it. This is the sledgehammer approach, but could be applicable for your situation. For example, in order to allow Umbraco's alternative status code responses to work, this is the only way to get them to avoid being hijacked by IIS (i.e. NotFoundHandlers, booting screens, etc.).

<location path="Site Description">
    <system.webServer>
        <httpErrors existingResponse="PassThrough" />
    </system.webServer>
</location>

Thanks to Fabian Heussser's comment on Rick Strahl's post that helped with this.

Tags: ASP.NET

Month List