PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/Clamson.Clamd/ClamdClient.cs

http://github.com/michaelhans/Clamson
C# | 296 lines | 160 code | 36 blank | 100 comment | 13 complexity | 72a74803d15ff0f377de49deffb29bf6 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Threading.Tasks;
  9. using Clamson.Clamd.Model;
  10. namespace Clamson.Clamd
  11. {
  12. /// <summary>
  13. /// Clamd Tcp Wrapper Client
  14. /// </summary>
  15. /// <remarks>
  16. /// Written insanely quick for new web property... ymmv
  17. /// Commands taken from command enum in session.h in ClamAV/clamd source.
  18. /// </remarks>
  19. /// <author>Michael Hans</author>
  20. public class ClamdClient
  21. {
  22. #region Properties & Command Helper
  23. /// <summary>
  24. /// Stream Chunk Size in Bytes
  25. /// </summary>
  26. public Int32 ChunkSize { get; private set; }
  27. /// <summary>
  28. /// Clamd Server Port
  29. /// </summary>
  30. public Int32 Port { get; private set; }
  31. /// <summary>
  32. /// Clamd Server
  33. /// </summary>
  34. public string Server { get; private set; }
  35. /// <summary>
  36. /// Clamd Supported Commands
  37. /// </summary>
  38. internal static class ClamdCommand
  39. {
  40. public const string PING = "zPING\0";
  41. public const string VERSION = "zVERSION\0";
  42. public const string RELOAD = "zRELOAD\0";
  43. public const string SHUTDOWN = "zSHUTDOWN\0";
  44. public const string INSTREAM = "zINSTREAM\0";
  45. public const string STATS = "zSTATS\0";
  46. //Formatted Commands
  47. public const string SCAN = "zSCAN";
  48. public const string CONTSCAN = "zCONTSCAN";
  49. public const string MULTISCAN = "zMULTISCAN";
  50. }
  51. #endregion
  52. #region Constructors
  53. /// <summary>
  54. /// Clamd CTOR
  55. /// </summary>
  56. /// <param name="server">Clamd Server</param>
  57. /// <param name="port">Clamd Server Port</param>
  58. /// <remarks>Defaults Chunk Size to 1024</remarks>
  59. public ClamdClient(string server, Int32 port) : this(server, port, 1024) { }
  60. /// <summary>
  61. /// Clamd CTOR
  62. /// </summary>
  63. /// <param name="server">Clamd Server</param>
  64. /// <param name="port">Clamd Server Port</param>
  65. /// <param name="chunkSize">Stream Chunk Size in Bytes</param>
  66. public ClamdClient(string server, Int32 port, Int32 chunkSize)
  67. {
  68. this.Port = port;
  69. this.Server = server;
  70. this.ChunkSize = chunkSize;
  71. }
  72. #endregion
  73. #region Commands
  74. /// <summary>
  75. /// Ping Clamd Daemon
  76. /// </summary>
  77. /// <returns>Whether "PONG" response was received</returns>
  78. public bool Ping()
  79. {
  80. //HACK: Need to refactor all method results to include IsSuccess for clamd failures
  81. try
  82. {
  83. var commandResult = ExecuteCommand(ClamdCommand.PING);
  84. if (ExecuteCommand(ClamdCommand.PING) == "PONG\0")
  85. return true;
  86. else
  87. return false;
  88. }
  89. catch (Exception ex)
  90. {
  91. return false;
  92. }
  93. }
  94. /// <summary>
  95. /// Return ClamAV & Databaes Version
  96. /// </summary>
  97. /// <returns>ClamAV & Databaes Version</returns>
  98. public string Version()
  99. {
  100. return ExecuteCommand(ClamdCommand.VERSION);
  101. }
  102. /// <summary>
  103. /// Reload the signature database
  104. /// </summary>
  105. /// <returns>Whether database was successfully reloaded</returns>
  106. public bool Reload()
  107. {
  108. if (ExecuteCommand(ClamdCommand.RELOAD) == "RELOADING\0")
  109. return true;
  110. else
  111. return false;
  112. }
  113. /// <summary>
  114. /// Shutdown Clamd
  115. /// </summary>
  116. public void Shutdown()
  117. {
  118. ExecuteCommand(ClamdCommand.SHUTDOWN);
  119. }
  120. /// <summary>
  121. /// Scan a file or Directory for Virus Signatures
  122. /// </summary>
  123. /// <param name="path">File or Path to Scan</param>
  124. /// <returns>Clamd Command Result (File/Signatures)</returns>
  125. /// <remarks>Stops scanning on virus found</remarks>
  126. public ClamdResult Scan(string path)
  127. {
  128. var command = string.Format("{0} {1}\0", ClamdCommand.SCAN, path);
  129. var response = ExecuteCommand(command);
  130. var files = ParseFileResponse(response);
  131. return new ClamdResult(files);
  132. }
  133. /// <summary>
  134. /// Scan a Stream for Virus Signatures
  135. /// </summary>
  136. /// <param name="data">Stream to Scan</param>
  137. /// <returns>Clamd Command Result (File/Signatures)</returns>
  138. public ClamdResult Instream(Stream data)
  139. {
  140. var response = ExecuteCommand(ClamdCommand.INSTREAM, data);
  141. var files = ParseFileResponse(response);
  142. return new ClamdResult(files);
  143. }
  144. /// <summary>
  145. /// Scan a file or directory for Virus Signatures. Continue on when
  146. /// virus found.
  147. /// </summary>
  148. /// <param name="path">File or Path to Scan</param>
  149. /// <returns>Clamd Command Result (File/Signatures)</returns>
  150. public ClamdResult ContScan(string path)
  151. {
  152. var command = string.Format("{0} {1}\0", ClamdCommand.CONTSCAN, path);
  153. var response = ExecuteCommand(command);
  154. var files = ParseFileResponse(response);
  155. return new ClamdResult(files);
  156. }
  157. /// <summary>
  158. /// Scan a file or directory using multiple threads.
  159. /// </summary>
  160. /// <param name="path">File or Path to Scan</param>
  161. /// <returns>Clamd Command Result (File/Signatures)</returns>
  162. public ClamdResult MultiScan(string path)
  163. {
  164. var command = string.Format("{0} {1}\0", ClamdCommand.MULTISCAN, path);
  165. var response = ExecuteCommand(command);
  166. var files = ParseFileResponse(response);
  167. return new ClamdResult(files);
  168. }
  169. /// <summary>
  170. /// Return Clamd statistics
  171. /// </summary>
  172. /// <returns>Clamd contents of scan queue and memory statistics</returns>
  173. public string Stats()
  174. {
  175. return ExecuteCommand(ClamdCommand.STATS);
  176. }
  177. /// <summary>
  178. /// Not Implemented
  179. /// </summary>
  180. /// <remarks></remarks>
  181. //public void IDSession() { throw new NotImplementedException(); }
  182. #endregion
  183. #region Network & File Processing
  184. /// <summary>
  185. /// Parse a Clam Response for File/Signatures
  186. /// </summary>
  187. /// <param name="resp">Clamd Response</param>
  188. /// <returns>List of Infected Files/Signatures (if any)</returns>
  189. private List<InfectedFile> ParseFileResponse(string resp)
  190. {
  191. var returnItems = new List<InfectedFile>();
  192. foreach (string respItem in resp.Split(new string[] { "\0" }, StringSplitOptions.RemoveEmptyEntries))
  193. {
  194. //Split Item {0} File Name : {1} Status OR Virus Signature
  195. var itemSplit = respItem.Split(new string[] { ": " }, StringSplitOptions.RemoveEmptyEntries);
  196. if (itemSplit[1].Contains("FOUND"))
  197. returnItems.Add(new InfectedFile(itemSplit[0],itemSplit[1]));
  198. else if (!itemSplit[1].Contains("OK")) //if it's not OK or FOUND throw Error
  199. throw new Exception("Clamd Response Not Recognized");
  200. }
  201. return returnItems;
  202. }
  203. /// <summary>
  204. /// Execute Clamd Command & Return String Response
  205. /// </summary>
  206. /// <param name="command">Clamd Command (ClamdCommand.)</param>
  207. /// <returns>Clamd String Response</returns>
  208. private string ExecuteCommand(string command)
  209. {
  210. return ExecuteCommand(command, null);
  211. }
  212. /// <summary>
  213. /// Execute Clamd Command & Return String Response
  214. /// </summary>
  215. /// <param name="command">Clamd Command (ClamdCommand.)</param>
  216. /// <param name="data">Stream for Instream Scan</param>
  217. /// <returns>Clamd String Response</returns>
  218. private string ExecuteCommand(string command, Stream data)
  219. {
  220. TcpClient client = new TcpClient(Server, Port);
  221. NetworkStream stream = client.GetStream();
  222. //Send Clamd Command
  223. stream.Write(ASCIIEncoding.ASCII.GetBytes(command), 0, command.Length);
  224. //Chunk & Send Stream Data (if any)
  225. if (data != null)
  226. {
  227. int dataIndex = 0;
  228. int workChunkSize = ChunkSize;
  229. byte[] chunkBytesLen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(workChunkSize));
  230. while (stream.CanWrite && dataIndex < data.Length)
  231. {
  232. if (dataIndex + workChunkSize >= data.Length)
  233. {
  234. workChunkSize = (int)data.Length - dataIndex;
  235. chunkBytesLen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(workChunkSize));
  236. }
  237. var fileBytes = new byte[workChunkSize];
  238. data.Read(fileBytes, 0, workChunkSize);
  239. stream.Write(chunkBytesLen, 0, chunkBytesLen.Length);
  240. stream.Write(fileBytes, 0, workChunkSize);
  241. dataIndex += workChunkSize;
  242. }
  243. byte[] nullByte = BitConverter.GetBytes(0);
  244. stream.Write(nullByte, 0, nullByte.Length);
  245. }
  246. //Retrieve Response
  247. StringBuilder respBuilder = new StringBuilder();
  248. byte[] respBytes = new Byte[256];
  249. int bytes;
  250. while ((bytes = stream.Read(respBytes, 0, respBytes.Length)) > 0)
  251. {
  252. respBuilder.Append(ASCIIEncoding.ASCII.GetString(respBytes, 0, bytes));
  253. }
  254. stream.Close();
  255. client.Close();
  256. return respBuilder.ToString();
  257. }
  258. #endregion
  259. }
  260. }