//
// savdicl.cs
//
// Simple demo to illustrate some features of SSSP
// Scans a file or dir using a given TCP/IP port
//
// To build:
// Open savdicli.sln or savdicli.csproj in Microsoft Visual Studio 2005
// and choose the menu option: Build | Build Solution. The binary
// savdicli.exe will be output to .\bin\Release.
//
// Copyright (c) 2007 Sophos Plc, www.sophos.com.
//
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Diagnostics;
namespace SAVDICLI
{
///
/// Utility class that provides some global functionality.
///
public class Utility
{
///
/// Regex that picks out any non-alphanumeric characters.
///
private static Regex m_toencPattern = new Regex("([^0-9a-zA-Z])");
///
/// Regex that picks out any encoded URL escape sequences ('%' followed
/// by 2 hexadecimal digits).
///
private static Regex m_encPattern = new Regex("%([0-9a-fA-F]{2})");
///
/// A delegate used to replace characters in a string being URL-encoded.
///
/// The Match object passed in
/// The replacement string
private static string UrlEncodeMatchEvaluator(Match match)
{
return string.Format("%{0:X2}", Convert.ToByte(match.Groups[1].Value[0]));
}
///
/// This method encodes a string by replacing any non-alphanumeric
/// characters with an escape sequence ('%' followed by 2 hexadecimal digits).
///
/// The string to be encoded
/// The encoded string
public static string UrlEncode(string s)
{
return m_toencPattern.Replace(s, UrlEncodeMatchEvaluator);
}
///
/// A delegate used to replace escape sequences in a URL-encoded string with
/// corresponding characters.
///
/// The match object passed in
/// The replacement string
private static string UrlDecodeMatchEvaluator(Match match)
{
return "" + long.Parse(match.Groups[1].Value, NumberStyles.HexNumber);
}
///
/// This method decodes an encoded string by replacing any escape sequences
/// with corresponding non-alphanumeric characters.
///
/// The string to be decoded
/// The decoded string
public static string UrlDecode(string s)
{
return m_encPattern.Replace(s, UrlDecodeMatchEvaluator);
}
}
///
/// This class provides methods to connect to a SAVDI server instance
/// and send and receive data.
///
public class Session
{
///
/// The hostname of the server to connect to.
///
string m_hostname;
///
/// The port on which to connect to the server.
///
uint m_port;
///
/// Flag that indicates if a TCP connection has been established with the server.
///
bool m_connected;
///
/// Flag that indicates if the TCP session is active.
///
bool m_active;
///
/// The TcpClient object that provides the TCP connection.
///
TcpClient m_tcpcli = new TcpClient();
///
/// Flag that controls the amount of debugging information printed out.
///
bool m_debug = true;
///
/// A delegate that is used to send individual lines received from the server
/// on to the caller as soon as they are received.
///
/// The line received from the server
public delegate void RecvMessageProgress(string line);
///
/// A property that can be used to externally control the level of debugging
/// information printed out by the Session.
///
public bool DebugSocketComms
{
get { return m_debug; }
set { m_debug = value; }
}
///
/// This read-only property tells the caller how large a chunk should be when
/// using SCANDATA to send the contents of a file to the server. Currently
/// hardcoded to 4096 bytes.
///
public int RecommendedChunkSize
{
get { return 4096; }
}
///
/// A property that indicates if a TCP connection to the server has been established.
///
public bool Connected
{
get { return m_connected; }
set { m_connected = value; }
}
///
/// A property that indicates if a TCP session is active.
///
public bool Active
{
get { return m_active; }
set { m_active = value; }
}
///
/// Constructor for a Session object.
///
/// The hostname of the server
/// The port on which to connect to the server
public Session(string hostname, uint port)
{
m_hostname = hostname;
m_port = port;
Connected = false;
Active = false;
}
///
/// Sends bytes to the server.
///
/// Number of bytes to send
/// The bytes to send
/// The actual number of bytes sent
public int Send(int numbytes, byte[] data)
{
int sent = 0;
if (numbytes > 0)
{
m_tcpcli.GetStream().Write(data, 0, numbytes);
sent = numbytes;
}
return sent;
}
///
/// Receives bytes from the server.
///
/// The number of bytes to receive
/// The destination buffer for the bytes
/// The actual number of bytes received
public int Recv(int numbytes, out byte[] data)
{
int read = 0;
data = new byte[numbytes];
data.Initialize();
if (numbytes > 0)
{
read = m_tcpcli.GetStream().Read(data, 0, numbytes);
}
return read;
}
///
/// Sends a line to the server.
///
/// The line to send
/// Indicates whether the send was successful
public bool SendLine(string line)
{
ASCIIEncoding asciienc = new ASCIIEncoding();
byte[] data = asciienc.GetBytes(line + "\n");
int numsent = Send(data.Length, data);
if (DebugSocketComms)
Console.WriteLine("Sent: " + line);
return numsent == data.Length;
}
///
/// Receives a line from the server.
///
/// A variable to collect the line
/// Indicates whether the receipt was successful
public bool RecvLine(out string line)
{
bool done = false;
line = "";
while (!done)
{
// Receive only one byte at a time and append to the string
byte[] data = new byte[1];
if (1 != Recv(1, out data))
{
throw new Exception("Error receiving data from server");
}
if (data[0] == '\r')
continue;
else if (data[0] == '\n')
done = true;
else
line += Convert.ToChar(data[0]);
}
if (DebugSocketComms)
Console.WriteLine("Recv: " + line);
return true;
}
///
/// Receives a message from the server. A message consists of a set of
/// zero or more lines, terminated by a blank line.
///
/// A container to collect the message
/// Indicates whether the receipt was successful
public bool RecvMessage(out string[] lines)
{
// Forward to the actual implementation
return RecvMessage(out lines, null);
}
///
/// Receives a message from the server. A message consists of a set of
/// zero or more lines, terminated by a blank line.
///
/// A container to collect the message
/// A callback (RecvMessageProgress delegate instance) to get individual message lines
/// Indicates whether the receipt was successful
public bool RecvMessage(out string[] lines, RecvMessageProgress callback)
{
ArrayList lineslist = new ArrayList();
while (true)
{
string line = "";
if (!RecvLine(out line) || (line.Length == 0))
{
break;
}
lineslist.Add(line);
if (callback != null)
{
callback(line);
}
}
lines = new string[lineslist.Count];
IEnumerator enumerator = lineslist.GetEnumerator();
int pos = 0;
while (enumerator.MoveNext())
{
lines.SetValue(enumerator.Current, pos);
pos++;
}
// If we got even one line, it's been a success
return lines.Length > 0;
}
///
/// Connects to the server if not already connected and performs
/// a handshake.
///
public void Connect()
{
if (Connected)
return;
m_tcpcli.Connect(m_hostname, (int)m_port);
string line = "";
if (RecvLine(out line))
{
if (line.StartsWith("OK SSSP/1.0"))
{
Connected = true;
}
}
if (!Connected)
{
throw new Exception("Server failed to initiate handshake");
}
line = "SSSP/1.0";
SendLine(line);
if (RecvLine(out line))
{
if (line.StartsWith("ACC "))
{
Active = true;
}
else if (line.StartsWith("REJ "))
{
throw new Exception("The server rejected the connection request");
}
else
{
throw new Exception("Unexpected response from server: " + line);
}
}
}
///
/// Disconnects from the server if not already disconnected, but performs
/// a goodbye handshake first.
///
public void Disconnect()
{
if (Active)
{
SendLine("BYE");
string line;
RecvLine(out line);
Active = false;
}
if (Connected)
{
m_tcpcli.Close();
Connected = false;
}
}
}
///
/// A helper class that encapsulates various SAVDI server options available
/// to the client. Objects of this class are initialised with a dictionary
/// containing option name strings as keys, mapped to lists of string values.
///
public class Options
{
private string m_version = "";
private bool m_queryServerAllowed = false;
private bool m_querySAVIAllowed = false;
private bool m_queryEngineAllowed = false;
private bool m_queryOptionsAllowed = false;
private bool m_scanDataAllowed = false;
private bool m_scanFileAllowed = false;
private bool m_scanDirAllowed = false;
private bool m_scanDirRecursiveAllowed = false;
private long m_maxScanData = 0;
private long m_maxMemorySize = 0;
private long m_maxClassificationSize = 0;
private Dictionary> m_options = null;
///
/// This property holds the version string returned by the server.
///
public string Version
{
get { return m_version; }
set { m_version = value; }
}
///
/// This property indicates if the "QUERY SERVER" SSSP command is allowed.
///
public bool QueryServerAllowed
{
get { return m_queryServerAllowed; }
set { m_queryServerAllowed = value; }
}
///
/// This property indicates if the "QUERY SAVI" SSSP command is allowed.
///
public bool QuerySAVIAllowed
{
get { return m_querySAVIAllowed; }
set { m_querySAVIAllowed = value; }
}
///
/// This property indicates if the "QUERY ENGINE" SSSP command is allowed.
///
public bool QueryEngineAllowed
{
get { return m_queryEngineAllowed; }
set { m_queryEngineAllowed = value; }
}
///
/// This property indicates if the "QUERY OPTIONS" SSSP command is allowed.
///
public bool QueryOptionsAllowed
{
get { return m_queryOptionsAllowed; }
set { m_queryOptionsAllowed = value; }
}
///
/// This property indicates if the "SCANDATA" SSSP command is allowed.
///
public bool ScanDataAllowed
{
get { return m_scanDataAllowed; }
set { m_scanDataAllowed = value; }
}
///
/// This property indicates if the "SCANFILE" SSSP command is allowed.
///
public bool ScanFileAllowed
{
get { return m_scanFileAllowed; }
set { m_scanFileAllowed = value; }
}
///
/// This property indicates if the "SCANDIR" SSSP command is allowed.
///
public bool ScanDirAllowed
{
get { return m_scanDirAllowed; }
set { m_scanDirAllowed = value; }
}
///
/// This property indicates if the "SCANDIRR" SSSP command is allowed.
///
public bool ScanDirRecursiveAllowed
{
get { return m_scanDirRecursiveAllowed; }
set { m_scanDirRecursiveAllowed = value; }
}
///
/// This property indicates the maximum amount of data that the server will accept
/// for a "SCANDATA" request. Zero means it is unlimited.
///
public long MaxScanData
{
get { return m_maxScanData; }
set { m_maxScanData = value; }
}
///
/// This property indicates the maximum amount of data the server will hold in memory
/// before using a temporary file when implementing a "SCANDATA" request.
///
public long MaxMemorySize
{
get { return m_maxMemorySize; }
set { m_maxMemorySize = value; }
}
///
/// This property indicates the maximum amount of data from the start of a file that
/// the client needs to send in a "SCANDATA" request when requesting classification only.
///
public long MaxClassificationSize
{
get { return m_maxClassificationSize; }
set { m_maxClassificationSize = value; }
}
///
/// Property that gives read-only raw data access to the options dictionary.
///
public Dictionary> RawOptions
{
get { return m_options; }
}
///
/// Contructor for Options object.
///
/// A dictionary mapping an option name to a list of option values
public Options(ref Dictionary> options)
{
m_options = new Dictionary>();
foreach (string key in options.Keys)
{
m_options[key] = new List();
List valuelist = options[key];
m_options[key].AddRange(valuelist);
switch (key)
{
case "version":
Version = valuelist[0]; break;
case "method":
foreach (string value in valuelist)
{
switch (value)
{
case "QUERY SERVER":
QueryServerAllowed = true; break;
case "QUERY SAVI":
QuerySAVIAllowed = true; break;
case "QUERY ENGINE":
QueryEngineAllowed = true; break;
case "OPTIONS":
QueryOptionsAllowed = true; break;
case "SCANDATA":
ScanDataAllowed = true; break;
case "SCANFILE":
ScanFileAllowed = true; break;
case "SCANDIR":
ScanDirAllowed = true; break;
case "SCANDIRR":
ScanDirRecursiveAllowed = true; break;
default:
break;
}
}
break;
case "maxscandata":
MaxScanData = Convert.ToInt64(valuelist[0]); break;
case "maxmemorysize":
MaxMemorySize = Convert.ToInt64(valuelist[0]); break;
case "maxclassificationsize":
MaxClassificationSize = Convert.ToInt64(valuelist[0]); break;
default:
break;
}
}
}
}
///
/// A class that provides an API to query options available to interact
/// with a SAVDI server, to set certain options and to request the server to
/// scan files or directories with or without progress information.
///
public class Client
{
///
/// The Session object held by the Client.
///
private Session m_session = null;
///
/// The Options object held by the Client.
///
private Options m_options = null;
///
/// Regex pattern to match ACC status lines returned by the server.
///
private Regex m_accPattern = new Regex(@"^ACC\s+(.*?)\s*$");
///
/// Regex pattern used to scan for lines of "key: value"
/// lines returned by the server when the Client object queries it for supported options.
///
private Regex m_optionPattern = new Regex(@"^(\w+):\s*(.*?)\s*$");
///
/// Regex pattern that matches a threat detected message.
/// returned by the server
///
private Regex m_virusPattern = new Regex(@"^VIRUS\s+(\S+)\s+(.*)");
///
/// Regex pattern that matches a file type indicator message
/// returned by the server in response to a "classification" request.
///
private Regex m_typePattern = new Regex(@"^TYPE\s+(\S+)\s+(.+)$");
///
/// Regex pattern that matches a "file location" message
/// returned by the server in response to a "classification" request.
///
private Regex m_filePattern = new Regex(@"^FILE\s+(.+)$");
///
/// Regex pattern that matches DONE status lines returned by the server.
///
private Regex m_donePattern = new Regex(@"^DONE\s+(\S+)\s+(\S+)\s+(.+)$");
///
/// Regex pattern that matches OK status lines returned by the server after
/// a scan has been successfully completed.
///
private Regex m_okPattern = new Regex(@"^OK\s+(.+)$");
///
/// Regex pattern that matches FAIL status lines returned by the server.
///
private Regex m_failPattern = new Regex(@"^FAIL\s+(\S+)\s+(.+)$");
///
/// Regex pattern that matches lines returned by the server in case of an event.
///
private Regex m_eventPattern = new Regex(@"^EVENT\s+(FILE|DIR|ARCHIVE|ERROR|VIRUS)\s+(.+)$");
///
/// A delegate that is implemented as a callback by users of the Session object
/// to receive progress information during a scan. Use of the delegate is optional,
/// i.e., it is possible to request scans without specifying a callback.
///
/// The name of the event that has occurred
/// String data associated with the event
public delegate void ScanProgressMonitor(string eventname, string eventdata);
///
/// A cached copy of a callback based on the ScanProgressMonitor delegate that may have
/// been passed in by a user of the Session object.
///
private ScanProgressMonitor m_scanprogcallback = null;
///
/// Constructor for Client object.
///
/// The hostname of the server
/// The port on which to connect to the server
public Client(string hostname, uint port)
{
m_session = new Session(hostname, port);
m_session.Connect();
QueryOptions();
}
///
/// A method called explicitly to disconnect cleanly from the server.
///
public void Disconnect()
{
if (m_session != null)
m_session.Disconnect();
}
///
/// Method that queries the server for supported options and intialises the
/// Options member object.
///
private void QueryOptions()
{
m_session.SendLine("QUERY SERVER");
string[] lines;
if (!m_session.RecvMessage(out lines))
{
throw new Exception("Unexpected response from server");
}
Dictionary> opts = new Dictionary>();
foreach (string line in lines)
{
Match match = m_optionPattern.Match(line);
if (match.Success && match.Groups.Count == 3)
{
string key = match.Groups[1].Value;
string value = match.Groups[2].Value;
if (!opts.ContainsKey(key))
{
opts[key] = new List();
}
opts[key].Add(value);
}
}
m_options = new Options(ref opts);
}
///
/// A read-only property that returns the Options object.
///
public Options Options
{
get { return m_options; }
}
///
/// Sets options on the server.
///
/// A Dictionary object mapping string option
/// names to lists of string option values.
public void SetOptions(ref Dictionary> options)
{
m_session.SendLine("OPTIONS");
foreach (string optionname in options.Keys)
{
foreach (string optionvalue in options[optionname])
{
m_session.SendLine(optionname + ":" + optionvalue);
}
}
m_session.SendLine("");
string line = "";
m_session.RecvLine(out line);
string[] lines;
m_session.RecvMessage(out lines); // Message discarded on success
if (!m_accPattern.Match(line).Success)
{
throw new Exception("The server rejected the OPTIONS request. Details:" + String.Join("\n", lines));
}
}
///
/// Sets a single option on the server.
///
/// An option to set on the server
/// The value to set the option to
public void SetOption(string optionname, string optionvalue)
{
// Construct a dictionary, add the option name and value to it
// and forward to the actual implementation
Dictionary> options = new Dictionary>();
options[optionname] = new List();
options[optionname].Add(optionvalue);
SetOptions(ref options);
}
///
/// A Session.RecvMessageProgress delegate instance that is called by
/// by the m_session Session object, once per line, when receiving a long message.
///
/// A line of text sent back by the Session object
private void HandleScanResponse(string line)
{
Match match = m_eventPattern.Match(line);
if (match.Success)
{
if (m_scanprogcallback != null)
{
// The cached callback is interested only in events, so we won't
// forward every line received from the Sesion object
m_scanprogcallback(match.Groups[1].Value, match.Groups[2].Value);
}
}
else
{
match = m_okPattern.Match(line);
if (match.Success)
{
Console.WriteLine("Handled: " + line);
}
else
{
match = m_donePattern.Match(line);
if (match.Success)
{
Console.WriteLine("Handled: " + line);
}
else
{
match = m_failPattern.Match(line);
if (match.Success)
{
Console.WriteLine("Handled: " + line);
}
else
{
Console.WriteLine("NOT handled: " + line);
// Other possible matches: (Included here
// only for completeness!)
m_virusPattern.Match(line);
m_typePattern.Match(line);
m_filePattern.Match(line);
}
}
}
}
}
///
/// Method that requests the server to scan a file or a directory.
///
/// Path to a file or directory
/// The actual name of the request command to send to the server
/// A callback (ScanProgressMonitor delegate instance) to monitor the scan progress
private void ScanPath(string filepath, string request, ScanProgressMonitor callback)
{
if (callback != null)
{
m_scanprogcallback = callback;
}
string line = "";
string pathtosend = Utility.UrlEncode(filepath);
// Note that SCANDIR and SCANDIRR will also scan valid file paths,
// not just directories
Debug.Assert(request == "SCANFILE" || request == "SCANDIR" || request == "SCANDIRR");
line = request + " " + pathtosend;
m_session.SendLine(line);
m_session.RecvLine(out line);
if (!m_accPattern.IsMatch(line))
{
throw new Exception("The server rejected the " + request + " request");
}
string[] lines;
// Briefly disable the printing out of socket communication by the Session object
//
m_session.DebugSocketComms = false;
// Handle the message in a separate callback function
//
Session.RecvMessageProgress recvmsgcallback = new Session.RecvMessageProgress(HandleScanResponse);
m_session.RecvMessage(out lines, recvmsgcallback);
m_session.DebugSocketComms = true;
}
///
/// Public method to scan a file. Differs from ScanFile as it sends the entire
/// contents of the file to the server, instead of just sending the file path.
///
/// Path to a file
public void ScanData(string filepath)
{
ScanData(filepath, null);
}
///
/// Public method to scan a file. Differs from ScanFile as it sends the entire
/// contents of the file to the server, instead of just sending the file path.
///
/// Path to a file
/// A callback function to monitor scan progress
public void ScanData(string filepath, ScanProgressMonitor callback)
{
FileInfo fileinfo = new FileInfo(filepath);
if (fileinfo.Length >= Options.MaxScanData)
{
throw new Exception("File too large to send to server");
}
FileStream filestream = new FileStream(filepath, FileMode.Open);
BinaryReader filereader = new BinaryReader(filestream);
string line = "SCANDATA " + fileinfo.Length;
m_session.SendLine(line);
int CHUNKSIZE = m_session.RecommendedChunkSize;
for (int numchunks = 0; numchunks < fileinfo.Length / CHUNKSIZE; numchunks++)
{
m_session.Send(CHUNKSIZE, filereader.ReadBytes(CHUNKSIZE));
}
int lastchunksize = (int)(fileinfo.Length % CHUNKSIZE);
m_session.Send(lastchunksize, filereader.ReadBytes(lastchunksize));
filereader.Close();
filestream.Close();
Console.WriteLine("Sent " + fileinfo.Length + " bytes in " + (fileinfo.Length / CHUNKSIZE + 1) + " chunk(s) of max size " + CHUNKSIZE + " bytes");
line = "";
m_session.RecvLine(out line);
if (!m_accPattern.Match(line).Success)
{
throw new Exception("The server rejected the SCANDATA request");
}
string[] lines;
m_session.RecvMessage(out lines);
}
///
/// Public method to scan a file.
///
/// Path to a file
public void ScanFile(string filepath)
{
// Forward to the core implementation
ScanFile(filepath, null);
}
///
/// Public method to scan a file.
///
/// Path to a file
/// A callback function to monitor scan progress
public void ScanFile(string filepath, ScanProgressMonitor callback)
{
// Forward to the core implementation
ScanPath(filepath, "SCANFILE", callback);
}
///
/// Public method to scan the contents of a directory.
///
/// Path to a directory
public void ScanDir(string filepath)
{
// Forward to the core implementation
ScanDir(filepath, null);
}
///
/// Public method to scan the contents of a directory.
///
/// Path to a directory
/// A callback function to monitor scan progress
public void ScanDir(string filepath, ScanProgressMonitor callback)
{
// Forward to the core implementation
ScanPath(filepath, "SCANDIR", callback);
}
///
/// Public method to scan the contents of a directory and all-subdirectories, recursively.
///
/// Path to a directory
public void ScanDirRecursive(string filepath)
{
// Forward to the core implementation
ScanDirRecursive(filepath, null);
}
///
/// Public method to scan the contents of a directory and all-subdirectories, recursively.
///
/// Path to a directory
/// A callback function to monitor scan progress
public void ScanDirRecursive(string filepath, ScanProgressMonitor callback)
{
// Forward to the core implementation
ScanPath(filepath, "SCANDIRR", callback);
}
}
///
/// This class contains the entry point as well as methods to parse the
/// command-line parameters passed in and to display the usage/help banner.
///
public class Program
{
///
/// Prints the usage/help banner.
///
private static void Usage()
{
Console.WriteLine("Usage: savdicli.exe [OPTIONS] [] [] ");
Console.WriteLine("Options: ");
Console.WriteLine(" -a : Request the server to scan data from a local ");
Console.WriteLine(" -f : Scan a remote path on the server ");
Console.WriteLine(" -d : Request the server to scan a remote (DEFAULT) ");
Console.WriteLine(" -r : Request the server to scan a remote and all ");
Console.WriteLine(" sub-directories recursively ");
Console.WriteLine(" : The path to a local or remote file (argument not compatible ");
Console.WriteLine(" with the -d or -r flags) ");
Console.WriteLine(" : The path to a remote directory on the server (argument not ");
Console.WriteLine(" compatible with the -a or -f flags) ");
Console.WriteLine(" ");
Console.WriteLine(" -h : Shows this help message ");
Console.WriteLine(" ");
Console.WriteLine("Notes: ");
Console.WriteLine(" 1) This program sends the contents of a file or the path to a file or a ");
Console.WriteLine(" directory to a local or remote instance of the SAV Dynamic Interface ");
Console.WriteLine(" server. The item is scanned via SSSP (Sophos Simple Scanning Protol). ");
Console.WriteLine(" 2) The default is 'localhost', and the default 4010. ");
Console.WriteLine(" 3) Only one option must be specified. The default is '-d'. ");
Console.WriteLine(" 4) If happens to be remote file, it will be scanned as a file, ");
Console.WriteLine(" i.e., '-f' will be implicitly assumed. ");
}
///
/// This method scans the list of command-line parameters supplied to it and returns
/// interesting values to the caller.
///
/// The list of command-line parameters to scan
/// The scanning method that will be used by the server
/// The path to the file or directory to be scanned
/// The hostname of the server
/// The port on which to connect to the server
private static void ParseArgs(ref string[] args,
out string option,
out string filepath,
out string hostname,
out uint port)
{
option = "";
filepath = "";
hostname = "localhost";
port = 4010;
// Scan the arguments list
//
if (args.Length < 1 || args.Length > 4)
{
Usage();
Environment.Exit(2);
}
int nextargpos = 0;
Regex validArgsPattern = new Regex("^-([?ahfdr])$");
Match match = validArgsPattern.Match(args[0]);
if (match.Success)
{
option = match.Groups[1].Value;
nextargpos++;
}
if (option == "h" || option == "?")
{
Usage();
Environment.Exit(1);
}
if (args.Length > nextargpos)
{
filepath = args[nextargpos];
nextargpos++;
}
if (args.Length > nextargpos)
{
hostname = args[nextargpos];
nextargpos++;
}
if (args.Length > nextargpos)
{
try
{
port = Convert.ToUInt32(args[nextargpos]);
}
catch (Exception)
{
Usage();
Environment.Exit(2);
}
}
// Check that we have all the arguments we need
if (filepath.Length == 0 || hostname.Length == 0 || port == 0)
{
Usage();
Environment.Exit(2);
}
}
private static void PrintRecursiveScandirProgress(string eventname, string eventdata)
{
Console.WriteLine("(Callback) Event: " + eventname + " " + eventdata);
}
///
/// Method that performs the core task of connecting to the server, requesting
/// a scan and disconnecting.
///
/// The scanning method that will be used by the server
/// The path to the file or directory to be scanned
/// The hostname of the server
/// The port on which to connect to the server
private static void RunClient(string option, string filepath, string hostname, uint port)
{
// Construct the client
Client client = new Client(hostname, port);
try
{
if (option == "d" || option == "") // By default, assume user wants to scan a directory
{
if (client.Options.ScanDirAllowed)
client.ScanDir(filepath);
else
throw new Exception("Scanning of directories is not available on the server");
}
else if (option == "r")
{
if (client.Options.ScanDirRecursiveAllowed)
{
// Demonstration of how to use a callback function to receive scanning events
// from the server as they occur. This implementation simply prints out the event.
Client.ScanProgressMonitor callback = new Client.ScanProgressMonitor(PrintRecursiveScandirProgress);
// How to switch on detailed reporting using SetOptions.
// In this case the following events will be reported back by the server:
// file : When a file is found
// error: When a file/scanning error is encountered
// virus: When a virus is found
//
Dictionary> options = new Dictionary>();
options["report"] = new List();
options["report"].Add("file");
options["report"].Add("error");
options["report"].Add("virus");
client.SetOptions(ref options);
client.ScanDirRecursive(filepath, callback);
// Switch back to the default options for the rest of this session
//
client.SetOption("savidefault", "");
}
else
throw new Exception("Recursive scanning of directories is not available on the server");
}
else if (option == "f")
{
if (client.Options.ScanFileAllowed)
client.ScanFile(filepath);
else
throw new Exception("Scanning of files is disabled on the server");
}
else if (option == "a")
{
// Ensure that the file exists
//
FileInfo fileinfo = new FileInfo(filepath);
if (fileinfo.Exists)
{
if (client.Options.ScanDataAllowed)
client.ScanData(Path.GetFullPath(filepath));
else
throw new Exception("Scanning of data via SSSP is disabled on the server");
}
else
{
throw new Exception("File not found: " + filepath);
}
}
}
finally
{
client.Disconnect();
}
}
///
/// The entry point into the assembly.
///
/// The list of string objects, one for each command-line parameter
public static void Main(string[] args)
{
string option = "";
string filepath = "";
string hostname = "localhost";
uint port = 4010;
ParseArgs(ref args, out option, out filepath, out hostname, out port);
try
{
RunClient(option, filepath, hostname, port);
}
catch (Exception e)
{
Console.WriteLine("FATAL Error: " + e.Message);
Environment.Exit(1);
}
Environment.Exit(0);
}
}
}