The next generation of the Teknik Services. Written in ASP.NET. Fork for blog tags.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Program.cs 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. using nClam;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Data.Entity;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Mail;
  9. using System.Reflection;
  10. using System.Text;
  11. using Teknik.Areas.Transparency.Models;
  12. using Teknik.Areas.Upload.Models;
  13. using Teknik.Areas.Users.Models;
  14. using Teknik.Areas.Users.Utility;
  15. using Teknik.Configuration;
  16. using Teknik.Helpers;
  17. using Teknik.Models;
  18. namespace ServerMaint
  19. {
  20. public class Program
  21. {
  22. private static string currentPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
  23. private static string virusFile = Path.Combine(currentPath, "virusLogs.txt");
  24. private static string errorFile = Path.Combine(currentPath, "errorLogs.txt");
  25. private static string configPath = currentPath;
  26. private const string TAKEDOWN_REPORTER = "Teknik Automated System";
  27. public static event Action<string> OutputEvent;
  28. public static int Main(string[] args)
  29. {
  30. try
  31. {
  32. ArgumentOptions options = new ArgumentOptions();
  33. var parser = new CommandLine.Parser(config => config.HelpWriter = Console.Out);
  34. if (parser.ParseArguments(args, options))
  35. {
  36. if (!string.IsNullOrEmpty(options.Config))
  37. configPath = options.Config;
  38. if (Directory.Exists(configPath))
  39. {
  40. Config config = Config.Load(configPath);
  41. TeknikEntities db = new TeknikEntities();
  42. // Scan all the uploads for viruses, and remove the bad ones
  43. if (options.ScanUploads && config.UploadConfig.VirusScanEnable)
  44. {
  45. ScanUploads(config, db);
  46. }
  47. // Cleans all inactive users
  48. if (options.CleanUsers)
  49. {
  50. CleanUsers(config, db, options.DaysBeforeDeletion, options.EmailsToSend);
  51. }
  52. Output(string.Format("[{0}] Finished Server Maintainence Process.", DateTime.Now));
  53. return 0;
  54. }
  55. else
  56. {
  57. string msg = string.Format("[{0}] Config File does not exist.", DateTime.Now);
  58. File.AppendAllLines(errorFile, new List<string> { msg });
  59. Output(msg);
  60. }
  61. }
  62. else
  63. {
  64. Output(options.GetUsage());
  65. }
  66. }
  67. catch (Exception ex)
  68. {
  69. string msg = string.Format("[{0}] Exception: {1}", DateTime.Now, ex.GetFullMessage(true));
  70. File.AppendAllLines(errorFile, new List<string> { msg });
  71. Output(msg);
  72. }
  73. return -1;
  74. }
  75. public static void ScanUploads(Config config, TeknikEntities db)
  76. {
  77. List<Upload> uploads = db.Uploads.ToList();
  78. int totalCount = uploads.Count();
  79. int totalScans = 0;
  80. int totalClean = 0;
  81. int totalViruses = 0;
  82. foreach (Upload upload in uploads)
  83. {
  84. totalScans++;
  85. string subDir = upload.FileName[0].ToString();
  86. string filePath = Path.Combine(config.UploadConfig.UploadDirectory, subDir, upload.FileName);
  87. if (File.Exists(filePath))
  88. {
  89. // Read in the file
  90. byte[] data = File.ReadAllBytes(filePath);
  91. // If the IV is set, and Key is set, then decrypt it
  92. if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV))
  93. {
  94. // Decrypt the data
  95. data = AES.Decrypt(data, upload.Key, upload.IV);
  96. }
  97. // We have the data, let's scan it
  98. ClamClient clam = new ClamClient(config.UploadConfig.ClamServer, config.UploadConfig.ClamPort);
  99. clam.MaxStreamSize = config.UploadConfig.MaxUploadSize;
  100. ClamScanResult scanResult = clam.SendAndScanFile(data);
  101. switch (scanResult.Result)
  102. {
  103. case ClamScanResults.Clean:
  104. totalClean++;
  105. string cleanMsg = string.Format("[{0}] Clean Scan: {1}/{2} Scanned | {3} - {4}", DateTime.Now, totalScans, totalCount, upload.Url, upload.FileName);
  106. Output(cleanMsg);
  107. break;
  108. case ClamScanResults.VirusDetected:
  109. totalViruses++;
  110. string msg = string.Format("[{0}] Virus Detected: {1} - {2} - {3}", DateTime.Now, upload.Url, upload.FileName, scanResult.InfectedFiles.First().VirusName);
  111. File.AppendAllLines(virusFile, new List<string> { msg });
  112. Output(msg);
  113. // Delete from the DB
  114. db.Uploads.Remove(upload);
  115. db.SaveChanges();
  116. // Delete the File
  117. if (File.Exists(filePath))
  118. {
  119. File.Delete(filePath);
  120. }
  121. break;
  122. case ClamScanResults.Error:
  123. string errorMsg = string.Format("[{0}] Scan Error: {1}", DateTime.Now, scanResult.RawResult);
  124. File.AppendAllLines(errorFile, new List<string> { errorMsg });
  125. Output(errorMsg);
  126. break;
  127. case ClamScanResults.Unknown:
  128. string unkMsg = string.Format("[{0}] Unknown Scan Result: {1}", DateTime.Now, scanResult.RawResult);
  129. File.AppendAllLines(errorFile, new List<string> { unkMsg });
  130. Output(unkMsg);
  131. break;
  132. }
  133. }
  134. }
  135. // Add to transparency report if any were found
  136. Takedown report = db.Takedowns.Create();
  137. report.Requester = TAKEDOWN_REPORTER;
  138. report.RequesterContact = config.SupportEmail;
  139. report.DateRequested = DateTime.Now;
  140. report.Reason = "Malware Found";
  141. report.ActionTaken = string.Format("{0} Uploads removed", totalViruses);
  142. report.DateActionTaken = DateTime.Now;
  143. db.Takedowns.Add(report);
  144. db.SaveChanges();
  145. Output(string.Format("Scanning Complete. {0} Scanned | {1} Viruses Found | {2} Total Files", totalScans, totalViruses, totalCount));
  146. }
  147. public static void CleanUsers(Config config, TeknikEntities db, int maxDays, int numEmails)
  148. {
  149. int totalUsers = 0;
  150. List<User> curUsers = db.Users.ToList();
  151. foreach (User user in curUsers)
  152. {
  153. // If the username isn't valid, don't clean it (Reserved, not formatted correctly, etc)
  154. if (!UserHelper.ValidUsername(db, config, user.Username))
  155. {
  156. continue;
  157. }
  158. #region Inactivity Cleaning
  159. DateTime lastActivity = UserHelper.GetLastActivity(db, config, user);
  160. TimeSpan inactiveTime = DateTime.Now.Subtract(lastActivity);
  161. // If older than max days, delete
  162. if (inactiveTime >= new TimeSpan(maxDays, 0, 0, 0, 0))
  163. {
  164. UserHelper.DeleteUser(db, config, user);
  165. continue;
  166. }
  167. // Otherwise, send an email if they are within +-1 day of email days
  168. int daysForEmail = (int)Math.Floor((double)(maxDays / (numEmails + 1)));
  169. for (int i = daysForEmail; i < maxDays; i = i + daysForEmail)
  170. {
  171. if (inactiveTime.Days == i)
  172. {
  173. string email = string.Format("{0}@{1}", user.Username, config.EmailConfig.Domain);
  174. // Let's send them an email
  175. SmtpClient client = new SmtpClient();
  176. client.Host = config.ContactConfig.Host;
  177. client.Port = config.ContactConfig.Port;
  178. client.EnableSsl = config.ContactConfig.SSL;
  179. client.DeliveryMethod = SmtpDeliveryMethod.Network;
  180. client.UseDefaultCredentials = true;
  181. client.Credentials = new NetworkCredential(config.ContactConfig.Username, config.ContactConfig.Password);
  182. client.Timeout = 5000;
  183. MailMessage mail = new MailMessage(config.SupportEmail, email);
  184. mail.Subject = "Inactive Account Notice";
  185. mail.Body = string.Format(@"
  186. The account {0} has not had any activity for {1} days. After {2} days of inactivity, this account will be deleted permanently.
  187. In order to avoid this, login into your email, or teknik website.
  188. Thank you for your use of Teknik and I hope you decide to come back.
  189. - Teknik Administration", user.Username, inactiveTime.Days, maxDays);
  190. mail.BodyEncoding = UTF8Encoding.UTF8;
  191. mail.DeliveryNotificationOptions = DeliveryNotificationOptions.Never;
  192. client.Send(mail);
  193. break;
  194. }
  195. }
  196. #endregion
  197. #region Missing Email Accounts
  198. if (!UserHelper.UserEmailExists(config, user.Username))
  199. {
  200. // They are missing an email account. Something bad happened, so let's delete their account so they can start over. :D
  201. UserHelper.DeleteUser(db, config, user);
  202. }
  203. #endregion
  204. #region Missing Git Accounts
  205. if (!UserHelper.UserGitExists(config, user.Username))
  206. {
  207. // They are missing a git account. Something bad happened, so let's delete their account so they can start over. :D
  208. UserHelper.DeleteUser(db, config, user);
  209. }
  210. #endregion
  211. }
  212. // Add to transparency report if any users were removed
  213. Takedown report = db.Takedowns.Create();
  214. report.Requester = TAKEDOWN_REPORTER;
  215. report.RequesterContact = config.SupportEmail;
  216. report.DateRequested = DateTime.Now;
  217. report.Reason = "User Inactive";
  218. report.ActionTaken = string.Format("{0} Users Removed", totalUsers);
  219. report.DateActionTaken = DateTime.Now;
  220. db.Takedowns.Add(report);
  221. db.SaveChanges();
  222. }
  223. public static void Output(string message)
  224. {
  225. Console.WriteLine(message);
  226. if (OutputEvent != null)
  227. {
  228. OutputEvent(message);
  229. }
  230. }
  231. }
  232. }