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 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  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.Status.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.Utilities;
  17. using Teknik.Models;
  18. using System.Threading.Tasks;
  19. using Teknik.Utilities.Cryptography;
  20. namespace ServerMaint
  21. {
  22. public class Program
  23. {
  24. private static string currentPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
  25. private static string virusFile = Path.Combine(currentPath, "virusLogs.txt");
  26. private static string errorFile = Path.Combine(currentPath, "errorLogs.txt");
  27. private static string configPath = currentPath;
  28. private const string TAKEDOWN_REPORTER = "Teknik Automated System";
  29. private static readonly object dbLock = new object();
  30. private static readonly object scanStatsLock = new object();
  31. public static event Action<string> OutputEvent;
  32. public static int Main(string[] args)
  33. {
  34. try
  35. {
  36. ArgumentOptions options = new ArgumentOptions();
  37. var parser = new CommandLine.Parser(config => config.HelpWriter = Console.Out);
  38. if (parser.ParseArguments(args, options))
  39. {
  40. if (!string.IsNullOrEmpty(options.Config))
  41. configPath = options.Config;
  42. if (Directory.Exists(configPath))
  43. {
  44. Config config = Config.Load(configPath);
  45. Output(string.Format("[{0}] Started Server Maintenance Process.", DateTime.Now));
  46. using (TeknikEntities db = new TeknikEntities())
  47. {
  48. // Scan all the uploads for viruses, and remove the bad ones
  49. if (options.ScanUploads && config.UploadConfig.VirusScanEnable)
  50. {
  51. ScanUploads(config, db);
  52. }
  53. // Warns all the invalid accounts via email
  54. if (options.WarnAccounts)
  55. {
  56. WarnInvalidAccounts(config, db);
  57. }
  58. // Cleans all inactive users
  59. if (options.CleanUsers)
  60. {
  61. CleanAccounts(config, db, options.DaysBeforeDeletion);
  62. }
  63. // Cleans the email for unused accounts
  64. if (options.CleanEmails)
  65. {
  66. CleanEmail(config, db);
  67. }
  68. // Cleans all the git accounts that are unused
  69. if (options.CleanGit)
  70. {
  71. CleanGit(config, db);
  72. }
  73. // Generates a file for all of the user's last seen dates
  74. if (options.GenerateLastSeen)
  75. {
  76. GenerateLastSeen(config, db, options.LastSeenFile);
  77. }
  78. // Generates a file for all of the invalid accounts
  79. if (options.GenerateInvalid)
  80. {
  81. GenerateInvalidAccounts(config, db, options.InvalidFile);
  82. }
  83. // Generates a file for all of the accounts to be cleaned
  84. if (options.GenerateCleaning)
  85. {
  86. GenerateCleaningList(config, db, options.CleaningFile, options.DaysBeforeDeletion);
  87. }
  88. }
  89. Output(string.Format("[{0}] Finished Server Maintenance Process.", DateTime.Now));
  90. return 0;
  91. }
  92. else
  93. {
  94. string msg = string.Format("[{0}] Config File does not exist.", DateTime.Now);
  95. File.AppendAllLines(errorFile, new List<string> { msg });
  96. Output(msg);
  97. }
  98. }
  99. else
  100. {
  101. Output(options.GetUsage());
  102. }
  103. }
  104. catch (Exception ex)
  105. {
  106. string msg = string.Format("[{0}] Exception: {1}", DateTime.Now, ex.GetFullMessage(true));
  107. File.AppendAllLines(errorFile, new List<string> { msg });
  108. Output(msg);
  109. }
  110. return -1;
  111. }
  112. public static void ScanUploads(Config config, TeknikEntities db)
  113. {
  114. Output(string.Format("[{0}] Started Virus Scan.", DateTime.Now));
  115. List<Upload> uploads = db.Uploads.ToList();
  116. int totalCount = uploads.Count();
  117. int totalScans = 0;
  118. int totalViruses = 0;
  119. List<Task> runningTasks = new List<Task>();
  120. foreach (Upload upload in uploads)
  121. {
  122. int currentScan = totalScans++;
  123. Task scanTask = Task.Factory.StartNew(() => ScanUpload(config, db, upload, totalCount, currentScan, ref totalViruses));
  124. if (scanTask != null)
  125. {
  126. runningTasks.Add(scanTask);
  127. }
  128. }
  129. bool running = true;
  130. while (running)
  131. {
  132. running = runningTasks.Exists(s => s != null && !s.IsCompleted && !s.IsCanceled && !s.IsFaulted);
  133. }
  134. Output(string.Format("Scanning Complete. {0} Scanned | {1} Viruses Found | {2} Total Files", totalScans, totalViruses, totalCount));
  135. }
  136. private static void ScanUpload(Config config, TeknikEntities db, Upload upload, int totalCount, int currentCount, ref int totalViruses)
  137. {
  138. // Initialize ClamAV
  139. ClamClient clam = new ClamClient(config.UploadConfig.ClamServer, config.UploadConfig.ClamPort);
  140. clam.MaxStreamSize = config.UploadConfig.MaxUploadSize;
  141. string subDir = upload.FileName[0].ToString();
  142. string filePath = Path.Combine(config.UploadConfig.UploadDirectory, subDir, upload.FileName);
  143. if (File.Exists(filePath))
  144. {
  145. // If the IV is set, and Key is set, then scan it
  146. if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV))
  147. {
  148. byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key);
  149. byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV);
  150. FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
  151. AesCounterStream aesStream = new AesCounterStream(fs, false, keyBytes, ivBytes);
  152. // We have the data, let's scan it
  153. ClamScanResult scanResult = clam.SendAndScanFile(aesStream);
  154. // Close file stream
  155. fs.Close();
  156. switch (scanResult.Result)
  157. {
  158. case ClamScanResults.Clean:
  159. string cleanMsg = string.Format("[{0}] Clean Scan: {1}/{2} Scanned | {3} - {4}", DateTime.Now, currentCount, totalCount, upload.Url, upload.FileName);
  160. Output(cleanMsg);
  161. break;
  162. case ClamScanResults.VirusDetected:
  163. string msg = string.Format("[{0}] Virus Detected: {1} - {2} - {3}", DateTime.Now, upload.Url, upload.FileName, scanResult.InfectedFiles.First().VirusName);
  164. Output(msg);
  165. lock (scanStatsLock)
  166. {
  167. totalViruses++;
  168. File.AppendAllLines(virusFile, new List<string> { msg });
  169. }
  170. lock (dbLock)
  171. {
  172. string urlName = upload.Url;
  173. // Delete from the DB
  174. db.Uploads.Remove(upload);
  175. // Delete the File
  176. if (File.Exists(filePath))
  177. {
  178. File.Delete(filePath);
  179. }
  180. // Add to transparency report if any were found
  181. Takedown report = db.Takedowns.Create();
  182. report.Requester = TAKEDOWN_REPORTER;
  183. report.RequesterContact = config.SupportEmail;
  184. report.DateRequested = DateTime.Now;
  185. report.Reason = "Malware Found";
  186. report.ActionTaken = string.Format("Upload removed: {0}", urlName);
  187. report.DateActionTaken = DateTime.Now;
  188. db.Takedowns.Add(report);
  189. // Save Changes
  190. db.SaveChanges();
  191. }
  192. break;
  193. case ClamScanResults.Error:
  194. string errorMsg = string.Format("[{0}] Scan Error: {1}", DateTime.Now, scanResult.RawResult);
  195. File.AppendAllLines(errorFile, new List<string> { errorMsg });
  196. Output(errorMsg);
  197. break;
  198. case ClamScanResults.Unknown:
  199. string unkMsg = string.Format("[{0}] Unknown Scan Result: {1}", DateTime.Now, scanResult.RawResult);
  200. File.AppendAllLines(errorFile, new List<string> { unkMsg });
  201. Output(unkMsg);
  202. break;
  203. }
  204. }
  205. }
  206. }
  207. public static void WarnInvalidAccounts(Config config, TeknikEntities db)
  208. {
  209. Output(string.Format("[{0}] Started Warning of Invalid Accounts.", DateTime.Now));
  210. List<string> invalidAccounts = GetInvalidAccounts(config, db);
  211. foreach (string account in invalidAccounts)
  212. {
  213. // Let's send them an email :D
  214. string email = UserHelper.GetUserEmailAddress(config, account);
  215. SmtpClient client = new SmtpClient();
  216. client.Host = config.ContactConfig.EmailAccount.Host;
  217. client.Port = config.ContactConfig.EmailAccount.Port;
  218. client.EnableSsl = config.ContactConfig.EmailAccount.SSL;
  219. client.DeliveryMethod = SmtpDeliveryMethod.Network;
  220. client.UseDefaultCredentials = true;
  221. client.Credentials = new NetworkCredential(config.ContactConfig.EmailAccount.Username, config.ContactConfig.EmailAccount.Password);
  222. client.Timeout = 5000;
  223. try
  224. {
  225. MailMessage mail = new MailMessage(config.ContactConfig.EmailAccount.EmailAddress, email);
  226. mail.Subject = "Invalid Account Notice";
  227. mail.Body = string.Format(@"
  228. The account {0} does not meet the requirements for a valid username.
  229. The username must meet the following requirements: {1}
  230. It must also be greater than or equal to {2} characters in length, and less than or equal to {3} characters in length.
  231. This email is to let you know that this account will be deleted in {4} days ({5}) in order to comply with the username restrictions. If you would like to keep your data, you should create a new account and transfer the data over to the new account.
  232. In order to make the process as easy as possible, you can reply to this email to ask for your current account to be renamed to another available account. This would keep all your data intact, and just require you to change all references to your email/git/user to the new username. If you wish to do this, please respond within {6} days ({7}) with the new username you would like to use.
  233. Thank you for your continued use of Teknik!
  234. - Teknik Administration", account, config.UserConfig.UsernameFilterLabel, config.UserConfig.MinUsernameLength, config.UserConfig.MaxUsernameLength, 30, DateTime.Now.AddDays(30).ToShortDateString(), 15, DateTime.Now.AddDays(15).ToShortDateString());
  235. mail.BodyEncoding = UTF8Encoding.UTF8;
  236. mail.DeliveryNotificationOptions = DeliveryNotificationOptions.Never;
  237. client.Send(mail);
  238. }
  239. catch (Exception ex)
  240. {
  241. Output(string.Format("[{0}] Unable to send email to {1}. Exception: {2}", DateTime.Now, email, ex.Message));
  242. }
  243. }
  244. Output(string.Format("[{0}] Finished Warning of Invalid Accounts. {1} Accounts Warned.", DateTime.Now, invalidAccounts.Count));
  245. }
  246. public static void CleanAccounts(Config config, TeknikEntities db, int maxDays)
  247. {
  248. Output(string.Format("[{0}] Started Cleaning of Inactive/Invalid Users.", DateTime.Now));
  249. List<string> invalidAccounts = GetInvalidAccounts(config, db);
  250. List<string> inactiveAccounts = GetInactiveAccounts(config, db, maxDays);
  251. // Delete invalid accounts
  252. foreach (string account in invalidAccounts)
  253. {
  254. UserHelper.DeleteAccount(db, config, UserHelper.GetUser(db, account));
  255. }
  256. if (invalidAccounts.Count > 0)
  257. {
  258. // Add to transparency report if any users were removed
  259. Takedown report = db.Takedowns.Create();
  260. report.Requester = TAKEDOWN_REPORTER;
  261. report.RequesterContact = config.SupportEmail;
  262. report.DateRequested = DateTime.Now;
  263. report.Reason = "Username Invalid";
  264. report.ActionTaken = string.Format("{0} Accounts Removed", invalidAccounts.Count);
  265. report.DateActionTaken = DateTime.Now;
  266. db.Takedowns.Add(report);
  267. db.SaveChanges();
  268. }
  269. // Delete inactive accounts
  270. foreach (string account in inactiveAccounts)
  271. {
  272. UserHelper.DeleteAccount(db, config, UserHelper.GetUser(db, account));
  273. }
  274. if (invalidAccounts.Count > 0)
  275. {
  276. // Add to transparency report if any users were removed
  277. Takedown report = db.Takedowns.Create();
  278. report.Requester = TAKEDOWN_REPORTER;
  279. report.RequesterContact = config.SupportEmail;
  280. report.DateRequested = DateTime.Now;
  281. report.Reason = "Account Inactive";
  282. report.ActionTaken = string.Format("{0} Accounts Removed", inactiveAccounts.Count);
  283. report.DateActionTaken = DateTime.Now;
  284. db.Takedowns.Add(report);
  285. db.SaveChanges();
  286. }
  287. Output(string.Format("[{0}] Finished Cleaning of Inactive/Invalid Users. {1} Accounts Removed.", DateTime.Now, invalidAccounts.Count + inactiveAccounts.Count));
  288. }
  289. public static void CleanEmail(Config config, TeknikEntities db)
  290. {
  291. Output(string.Format("[{0}] Started Cleaning of Orphaned Email Accounts.", DateTime.Now));
  292. List<string> emails = GetOrphanedEmail(config, db);
  293. foreach (string email in emails)
  294. {
  295. // User doesn't exist, and it isn't reserved. Let's nuke it.
  296. UserHelper.DeleteUserEmail(config, email);
  297. }
  298. if (emails.Count > 0)
  299. {
  300. // Add to transparency report if any users were removed
  301. Takedown report = db.Takedowns.Create();
  302. report.Requester = TAKEDOWN_REPORTER;
  303. report.RequesterContact = config.SupportEmail;
  304. report.DateRequested = DateTime.Now;
  305. report.Reason = "Orphaned Email Account";
  306. report.ActionTaken = string.Format("{0} Accounts Removed", emails.Count);
  307. report.DateActionTaken = DateTime.Now;
  308. db.Takedowns.Add(report);
  309. db.SaveChanges();
  310. }
  311. Output(string.Format("[{0}] Finished Cleaning of Orphaned Email Accounts. {1} Accounts Removed.", DateTime.Now, emails.Count));
  312. }
  313. public static void CleanGit(Config config, TeknikEntities db)
  314. {
  315. Output(string.Format("[{0}] Started Cleaning of Orphaned Git Accounts.", DateTime.Now));
  316. List<string> gitAccounts = GetOrphanedGit(config, db);
  317. foreach (string account in gitAccounts)
  318. {
  319. // User doesn't exist, and it isn't reserved. Let's nuke it.
  320. UserHelper.DeleteUserGit(config, account);
  321. }
  322. if (gitAccounts.Count > 0)
  323. {
  324. // Add to transparency report if any users were removed
  325. Takedown report = db.Takedowns.Create();
  326. report.Requester = TAKEDOWN_REPORTER;
  327. report.RequesterContact = config.SupportEmail;
  328. report.DateRequested = DateTime.Now;
  329. report.Reason = "Orphaned Git Account";
  330. report.ActionTaken = string.Format("{0} Accounts Removed", gitAccounts.Count);
  331. report.DateActionTaken = DateTime.Now;
  332. db.Takedowns.Add(report);
  333. db.SaveChanges();
  334. }
  335. Output(string.Format("[{0}] Finished Cleaning of Orphaned Git Accounts. {1} Accounts Removed.", DateTime.Now, gitAccounts.Count));
  336. }
  337. public static void GenerateLastSeen(Config config, TeknikEntities db, string fileName)
  338. {
  339. Output(string.Format("[{0}] Started Generation of Last Activity List.", DateTime.Now));
  340. List<User> curUsers = db.Users.ToList();
  341. StringBuilder sb = new StringBuilder();
  342. sb.AppendLine("Username,Last Activity,Creation Date,Last Website Activity,Last Email Activity,Last Git Activity");
  343. foreach (User user in curUsers)
  344. {
  345. sb.AppendLine(string.Format("{0},{1},{2},{3},{4},{5}",
  346. user.Username,
  347. UserHelper.GetLastAccountActivity(db, config, user).ToString("g"),
  348. user.JoinDate.ToString("g"),
  349. user.LastSeen.ToString("g"),
  350. UserHelper.UserEmailLastActive(config, UserHelper.GetUserEmailAddress(config, user.Username)).ToString("g"),
  351. UserHelper.UserGitLastActive(config, user.Username).ToString("g")));
  352. }
  353. string dir = Path.GetDirectoryName(fileName);
  354. if (!Directory.Exists(dir))
  355. Directory.CreateDirectory(dir);
  356. File.WriteAllText(fileName, sb.ToString());
  357. Output(string.Format("[{0}] Finished Generating Last Activity List.", DateTime.Now));
  358. }
  359. public static void GenerateInvalidAccounts(Config config, TeknikEntities db, string fileName)
  360. {
  361. Output(string.Format("[{0}] Started Generation of Invalid Account List.", DateTime.Now));
  362. List<string> invalidAccounts = GetInvalidAccounts(config, db);
  363. StringBuilder sb = new StringBuilder();
  364. sb.AppendLine("Username,Last Activity,Creation Date,Last Website Activity,Last Email Activity,Last Git Activity");
  365. foreach (string account in invalidAccounts)
  366. {
  367. User user = UserHelper.GetUser(db, account);
  368. sb.AppendLine(string.Format("{0},{1},{2},{3},{4},{5}",
  369. user.Username,
  370. UserHelper.GetLastAccountActivity(db, config, user).ToString("g"),
  371. user.JoinDate.ToString("g"),
  372. user.LastSeen.ToString("g"),
  373. UserHelper.UserEmailLastActive(config, UserHelper.GetUserEmailAddress(config, user.Username)).ToString("g"),
  374. UserHelper.UserGitLastActive(config, user.Username).ToString("g")));
  375. }
  376. string dir = Path.GetDirectoryName(fileName);
  377. if (!Directory.Exists(dir))
  378. Directory.CreateDirectory(dir);
  379. File.WriteAllText(fileName, sb.ToString());
  380. Output(string.Format("[{0}] Finished Generating Invalid Account List.", DateTime.Now));
  381. }
  382. public static void GenerateCleaningList(Config config, TeknikEntities db, string fileName, int maxDays)
  383. {
  384. Output(string.Format("[{0}] Started Generation of Accounts to Clean List.", DateTime.Now));
  385. List<string> invalidAccounts = GetInvalidAccounts(config, db);
  386. List<string> inactiveAccounts = GetInactiveAccounts(config, db, maxDays);
  387. List<string> emailAccounts = GetOrphanedEmail(config, db);
  388. List<string> gitAccounts = GetOrphanedGit(config, db);
  389. StringBuilder sb = new StringBuilder();
  390. sb.AppendLine("Invalid Account Cleaning");
  391. sb.AppendLine("Username,Last Activity,Creation Date,Last Website Activity,Last Email Activity,Last Git Activity");
  392. foreach (string account in invalidAccounts)
  393. {
  394. User user = UserHelper.GetUser(db, account);
  395. sb.AppendLine(string.Format("{0},{1},{2},{3},{4},{5}",
  396. user.Username,
  397. UserHelper.GetLastAccountActivity(db, config, user).ToString("g"),
  398. user.JoinDate.ToString("g"),
  399. user.LastSeen.ToString("g"),
  400. UserHelper.UserEmailLastActive(config, UserHelper.GetUserEmailAddress(config, user.Username)).ToString("g"),
  401. UserHelper.UserGitLastActive(config, user.Username).ToString("g")));
  402. }
  403. sb.AppendLine();
  404. sb.AppendLine("Inactive Account Cleaning");
  405. sb.AppendLine("Username,Last Activity,Creation Date,Last Website Activity,Last Email Activity,Last Git Activity");
  406. foreach (string account in inactiveAccounts)
  407. {
  408. User user = UserHelper.GetUser(db, account);
  409. sb.AppendLine(string.Format("{0},{1},{2},{3},{4},{5}",
  410. user.Username,
  411. UserHelper.GetLastAccountActivity(db, config, user).ToString("g"),
  412. user.JoinDate.ToString("g"),
  413. user.LastSeen.ToString("g"),
  414. UserHelper.UserEmailLastActive(config, UserHelper.GetUserEmailAddress(config, user.Username)).ToString("g"),
  415. UserHelper.UserGitLastActive(config, user.Username).ToString("g")));
  416. }
  417. sb.AppendLine();
  418. sb.AppendLine("Orphaned Email Cleaning");
  419. sb.AppendLine("Email,Last Activity");
  420. foreach (string account in emailAccounts)
  421. {
  422. sb.AppendLine(string.Format("{0},{1}",
  423. account,
  424. UserHelper.UserEmailLastActive(config, account).ToString("g")));
  425. }
  426. sb.AppendLine();
  427. sb.AppendLine("Orphaned Git Cleaning");
  428. sb.AppendLine("Username,Last Activity");
  429. foreach (string account in gitAccounts)
  430. {
  431. sb.AppendLine(string.Format("{0},{1}",
  432. account,
  433. UserHelper.UserGitLastActive(config, account).ToString("g")));
  434. }
  435. string dir = Path.GetDirectoryName(fileName);
  436. if (!Directory.Exists(dir))
  437. Directory.CreateDirectory(dir);
  438. File.WriteAllText(fileName, sb.ToString());
  439. Output(string.Format("[{0}] Finished Generating Accounts to Clean List.", DateTime.Now));
  440. }
  441. public static List<string> GetInvalidAccounts(Config config, TeknikEntities db)
  442. {
  443. List<string> foundUsers = new List<string>();
  444. List<User> curUsers = db.Users.ToList();
  445. foreach (User user in curUsers)
  446. {
  447. // If the username is reserved, let's add it to the list
  448. if (UserHelper.UsernameReserved(config, user.Username))
  449. {
  450. foundUsers.Add(user.Username);
  451. continue;
  452. }
  453. // If the username is invalid, let's add it to the list
  454. if (!UserHelper.ValidUsername(config, user.Username) && user.Username != "Server Admin")
  455. {
  456. foundUsers.Add(user.Username);
  457. continue;
  458. }
  459. }
  460. return foundUsers;
  461. }
  462. public static List<string> GetInactiveAccounts(Config config, TeknikEntities db, int maxDays)
  463. {
  464. List<string> foundUsers = new List<string>();
  465. List<User> curUsers = db.Users.ToList();
  466. foreach (User user in curUsers)
  467. {
  468. // If the username is reserved, don't worry about it
  469. if (UserHelper.UsernameReserved(config, user.Username))
  470. {
  471. continue;
  472. }
  473. #region Inactivity Finding
  474. DateTime lastActivity = UserHelper.GetLastAccountActivity(db, config, user);
  475. TimeSpan inactiveTime = DateTime.Now.Subtract(lastActivity);
  476. // If older than max days, check their current usage
  477. if (inactiveTime >= new TimeSpan(maxDays, 0, 0, 0, 0))
  478. {
  479. // Check the user's usage of the service.
  480. bool noData = true;
  481. // Any blog comments?
  482. var blogCom = db.BlogComments.Where(c => c.UserId == user.UserId);
  483. noData &= !(blogCom != null && blogCom.Any());
  484. // Any blog posts?
  485. var blogPosts = db.BlogPosts.Where(p => p.Blog.UserId == user.UserId);
  486. noData &= !(blogPosts != null && blogPosts.Any());
  487. // Any podcast comments?
  488. var podCom = db.PodcastComments.Where(p => p.UserId == user.UserId);
  489. noData &= !(podCom != null && podCom.Any());
  490. // Any email?
  491. if (config.EmailConfig.Enabled)
  492. {
  493. var app = new hMailServer.Application();
  494. app.Connect();
  495. app.Authenticate(config.EmailConfig.Username, config.EmailConfig.Password);
  496. try
  497. {
  498. var domain = app.Domains.ItemByName[config.EmailConfig.Domain];
  499. var account = domain.Accounts.ItemByAddress[UserHelper.GetUserEmailAddress(config, user.Username)];
  500. noData &= ((account.Messages.Count == 0) && ((int)account.Size == 0));
  501. }
  502. catch { }
  503. }
  504. // Any git repos?
  505. if (config.GitConfig.Enabled)
  506. {
  507. string email = UserHelper.GetUserEmailAddress(config, user.Username);
  508. // We need to check the actual git database
  509. MysqlDatabase mySQL = new MysqlDatabase(config.GitConfig.Database.Server, config.GitConfig.Database.Database, config.GitConfig.Database.Username, config.GitConfig.Database.Password, config.GitConfig.Database.Port);
  510. string sql = @"SELECT * FROM gogs.repository
  511. LEFT JOIN gogs.action ON gogs.user.id = gogs.action.act_user_id
  512. WHERE gogs.user.login_name = {0}";
  513. var results = mySQL.Query(sql, new object[] { email });
  514. noData &= !(results != null && results.Any());
  515. }
  516. if (noData)
  517. {
  518. // They have no data, so safe to delete them.
  519. foundUsers.Add(user.Username);
  520. }
  521. continue;
  522. }
  523. #endregion
  524. }
  525. return foundUsers;
  526. }
  527. public static List<string> GetOrphanedEmail(Config config, TeknikEntities db)
  528. {
  529. List<string> foundEmail = new List<string>();
  530. if (config.EmailConfig.Enabled)
  531. {
  532. List<User> curUsers = db.Users.ToList();
  533. // Connect to hmailserver COM
  534. var app = new hMailServer.Application();
  535. app.Connect();
  536. app.Authenticate(config.EmailConfig.Username, config.EmailConfig.Password);
  537. var domain = app.Domains.ItemByName[config.EmailConfig.Domain];
  538. var accounts = domain.Accounts;
  539. for (int i = 0; i < accounts.Count; i++)
  540. {
  541. var account = accounts[i];
  542. bool userExists = curUsers.Exists(u => UserHelper.GetUserEmailAddress(config, u.Username) == account.Address);
  543. bool isReserved = UserHelper.GetReservedUsernames(config).Exists(r => UserHelper.GetUserEmailAddress(config, r).ToLower() == account.Address.ToLower());
  544. if (!userExists && !isReserved)
  545. {
  546. foundEmail.Add(account.Address);
  547. }
  548. }
  549. }
  550. return foundEmail;
  551. }
  552. public static List<string> GetOrphanedGit(Config config, TeknikEntities db)
  553. {
  554. List<string> foundGit = new List<string>();
  555. if (config.GitConfig.Enabled)
  556. {
  557. List<User> curUsers = db.Users.ToList();
  558. // We need to check the actual git database
  559. MysqlDatabase mySQL = new MysqlDatabase(config.GitConfig.Database.Server, config.GitConfig.Database.Database, config.GitConfig.Database.Username, config.GitConfig.Database.Password, config.GitConfig.Database.Port);
  560. string sql = @"SELECT gogs.user.login_name AS login_name, gogs.user.lower_name AS username FROM gogs.user";
  561. var results = mySQL.Query(sql);
  562. if (results != null && results.Any())
  563. {
  564. foreach (var account in results)
  565. {
  566. bool userExists = curUsers.Exists(u => UserHelper.GetUserEmailAddress(config, u.Username).ToLower() == account["login_name"].ToString().ToLower());
  567. bool isReserved = UserHelper.GetReservedUsernames(config).Exists(r => UserHelper.GetUserEmailAddress(config, r) == account["login_name"].ToString().ToLower());
  568. if (!userExists && !isReserved)
  569. {
  570. foundGit.Add(account["username"].ToString());
  571. }
  572. }
  573. }
  574. }
  575. return foundGit;
  576. }
  577. public static void Output(string message)
  578. {
  579. Console.WriteLine(message);
  580. if (OutputEvent != null)
  581. {
  582. OutputEvent(message);
  583. }
  584. }
  585. }
  586. }