/// <summary> /// Reads data from a file that contains database entries that were /// persisted from a previous session. If the data files cannot /// be found, the action is aborted. If invalid entries are /// found in the file, they will be skipped, and the method /// will continue reading the file after that entry. If /// any other unforeseen I/O errors occur, the method will /// simply stop where it is in the file and return. /// /// It is possible to record any errors reading the file /// in the system event logs, but this is not done here /// for simplicity. /// </summary> /// <param name="sourcePath">The path to the file that the data should be read from.</param> public void ReadPersistedData(string sourcePath) { try { using (StreamReader source = new StreamReader(File.OpenRead(sourcePath))) { lock (entryCountLock) { while (!source.EndOfStream && this.entryCount < this.maxEntries) { MailFilterEntry newEntry = MailFilterEntry.TryParse(source.ReadLine()); if (newEntry != null) { this.SaveEntry(newEntry); } } } } } catch (IOException e) { Debug.WriteLine(e.ToString()); return; } catch (UnauthorizedAccessException e) { Debug.WriteLine(e.ToString()); return; } }
/// <summary> /// Records an entry in the database, either by updating an existing /// entry or by creating a new one. /// </summary> /// <param name="entry">The entry to be added to the database.</param> public void SaveEntry(MailFilterEntry entry) { int index = this.CalcIndex(entry.TripletHash); LinkedList <MailFilterEntry> currentRow = (LinkedList <MailFilterEntry>) this.table[index]; lock (((ICollection)currentRow).SyncRoot) { // If the triplet is already there, remove it // so that you can put it back at the start of the list. if (currentRow.Contains(entry)) { currentRow.Remove(entry); this.DecrementEntryCount(); } // Insert the triplet at the front of the list. entry.TimeStamp = DateTime.UtcNow; currentRow.AddFirst(entry); this.IncrementEntryCount(); // If the bucket is full, delete the oldest entry in it. if (currentRow.Count > this.bucketSize) { currentRow.RemoveLast(); this.DecrementEntryCount(); } } }
/// <summary> /// An override of the Object class's Equals method. /// </summary> /// <param name="obj">The other object to be compared to.</param> /// <returns>True if the other object is a MailFilterEntry and has the same TripletHash as this.</returns> public override bool Equals(Object obj) { MailFilterEntry entry = obj as MailFilterEntry; if (entry == null) { return(false); } return(this.Equals(entry)); }
/// <summary> /// The core of the MailFilter algorithm. Determines whether /// a triplet matches a known sender/recipient relationship and should /// be accepted. /// </summary> /// <param name="remoteIP">The remote host's IP address.</param> /// <param name="sender">The sender's address.</param> /// <param name="recipient">The recipient's address.</param> /// <returns>Whether the triplet is verified by the MailFilter.</returns> private bool VerifyTriplet(IPAddress remoteIP, RoutingAddress sender, RoutingAddress recipient) { // Create a MailFilterEntry object for the current session. // This code uses senderAddress.DomainPart to truncate // the sender's address to use only the domain. To use the full // address, use ToString() as with recipient. UInt64 tripletHash = this.HashTriplet( remoteIP, sender.DomainPart, recipient.ToString()); MailFilterEntry currentEntry = new MailFilterEntry(tripletHash); // Determine whether a matching entry is in the verified database. // If it is, save with an updated time stamp. // This ensures that the most recent entries are // at the start of the bucket lists in the array tables. if (this.verifiedDatabase.GetEntry(currentEntry.TripletHash) != null) { currentEntry.TimeStamp = DateTime.UtcNow; this.verifiedDatabase.SaveEntry(currentEntry); return(true); } // If the entry is in the unverified table passed in // the initial blocking period, remove it. MailFilterEntry entry = this.unverifiedDatabase.GetEntry(currentEntry.TripletHash); if (entry != null) { if (entry.IsPastPeriod(this.settings.InitialBlockingPeriod)) { this.verifiedDatabase.SaveEntry(currentEntry); this.unverifiedDatabase.DeleteEntry(currentEntry); return(true); } else { // The entry was in the table of unverified entries, // but the blocking period has not passed yet. return(false); } } else { // The triplet is not in either database. Send a rejection and // put it in the unverified database. this.unverifiedDatabase.SaveEntry(currentEntry); return(false); } }
/// <summary> /// Removes an entry from the database. /// </summary> /// <param name="entry">The entry to be removed.</param> /// <returns>True if the entry was removed; false if it could not be found.</returns> public bool DeleteEntry(MailFilterEntry entry) { // The return value. bool retval = false; // Calculate the hash index. int index = this.CalcIndex(entry.TripletHash); LinkedList <MailFilterEntry> currentRow = (LinkedList <MailFilterEntry>) this.table[index]; lock (((ICollection)currentRow).SyncRoot) { retval = currentRow.Remove(entry); } if (retval) { this.DecrementEntryCount(); } return(retval); }
/// <summary> /// Tries to parse the contents of a string into a new entry. /// </summary> /// <param name="currentLine">The string to be parsed.</param> /// <returns>The new entry, or null if unsuccessful.</returns> public static MailFilterEntry TryParse(String currentLine) { // Set up a return value variable. MailFilterEntry retval = null; // Split the two values in the line. string[] splitLine = currentLine.Split(','); // Verify that you got exactly two values from the line in the file. if (splitLine.Length == 2) { // Try parsing the hash code from the file. If you // can't parse one, do not return an // entry object at all. UInt64 inputHash; if (!UInt64.TryParse(splitLine[0].Trim(), out inputHash)) { return(null); } long inputTicks; DateTime inputTime; if (long.TryParse(splitLine[1].Trim(), out inputTicks)) { inputTime = new DateTime(inputTicks); } else { return(null); } retval = new MailFilterEntry(inputHash); retval.timeStamp = inputTime; } return(retval); }
/// <summary> /// The copy constructor. /// </summary> /// <param name="other">The entry object to be copied.</param> public MailFilterEntry(MailFilterEntry other) { this.tripletHash = other.TripletHash; this.timeStamp = other.TimeStamp; }
/// <summary> /// Compares this MailFilterEntry's tripletHash to that of another MailFilterEntry. /// </summary> /// <param name="entry">The other MailFilterEntry that this will be compared to.</param> /// <returns>True if the other entry has the same hash as this entry.</returns> public bool Equals(MailFilterEntry entry) { return(this.tripletHash == entry.tripletHash); }