Restore SEBPatch

This commit is contained in:
2025-06-01 11:44:20 +02:00
commit 8c656e3137
1297 changed files with 142172 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using Ionic.Zip;
namespace SebWindowsConfig.Utilities
{
public class FileCompressor : IFileCompressor
{
private static readonly string TempDirectory = SEBClientInfo.SebClientSettingsAppDataDirectory + "temp\\";
private static readonly string TempIconFilename = SEBClientInfo.SebClientSettingsAppDataDirectory + "temp\\tempIcon.png";
public static void CleanupTempDirectory()
{
try
{
if (Directory.Exists(TempDirectory))
{
DeleteDirectory(TempDirectory);
Logger.AddInformation("Successfully deleted temporary directory.");
}
}
catch (Exception e)
{
Logger.AddError("Error when trying to delete temporary directory: ", null, e);
}
}
/// <summary>
/// Attempt to fix the issue happening when deleting the TempDirectory (see SEBWIN-49).
/// Source: https://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/1703799#1703799
/// </summary>
private static void DeleteDirectory(string path)
{
foreach (string directory in Directory.GetDirectories(path))
{
DeleteDirectory(directory);
}
try
{
Directory.Delete(path, true);
}
catch (IOException e)
{
Logger.AddWarning(String.Format("Failed to delete {0} with IOException: {1}", path, e.Message), null);
Thread.Sleep(100);
Directory.Delete(path, true);
}
catch (UnauthorizedAccessException e)
{
Logger.AddWarning(String.Format("Failed to delete {0} with UnauthorizedAccessException: {1}", path, e.Message), null);
Thread.Sleep(100);
Directory.Delete(path, true);
}
}
public string CompressAndEncodeFile(string filename)
{
var zip = new ZipFile();
zip.AddFile(filename, "");
var stream = new MemoryStream();
zip.Save(stream);
return base64_encode(stream.ToArray());
}
public string CompressAndEncodeIcon(Icon icon)
{
//Save the file first locally
icon.ToBitmap().Save(TempIconFilename, ImageFormat.Png);
return CompressAndEncodeFile(TempIconFilename);
}
public string CompressAndEncodeFavicon(Uri uri)
{
if (File.Exists(TempIconFilename))
{
File.Delete(TempIconFilename);
}
if (!Directory.Exists(TempDirectory))
{
Directory.CreateDirectory(TempDirectory);
}
var client = new System.Net.WebClient();
client.DownloadFile(
string.Format(@"https://www.google.com/s2/favicons?domain_url={0}", uri.Host),
TempIconFilename);
return CompressAndEncodeFile(TempIconFilename);
}
public string CompressAndEncodeDirectory(string path, out List<string> containingFilenames)
{
var zip = new ZipFile();
zip.AddDirectory(path, "");
var stream = new MemoryStream();
zip.Save(stream);
containingFilenames = zip.Entries.Select(x => x.FileName.Replace(path, "")).ToList();
return base64_encode(stream.ToArray());
}
/// <summary>
/// Compresses the entire specified directory (preserving its relative structure) and returns the data as Base64-encoded string.
/// </summary>
public string CompressAndEncodeEntireDirectory(string path)
{
using (var stream = new MemoryStream())
using (var zip = new ZipFile())
{
var data = default(string);
var directory = new DirectoryInfo(path);
zip.AddDirectory(path, directory.Name);
zip.Save(stream);
data = base64_encode(stream.ToArray());
return data;
}
}
/// <summary>
/// Decodes the given Base64-encoded archive into the specified target directory, overwrites existing files if the overwrite flag
/// is set and returns the absolute paths of all extracted elements.
/// </summary>
public IEnumerable<string> DecodeAndDecompressDirectory(string base64, string targetDirectory, bool overwrite = true)
{
var data = base64_decode(base64);
var paths = new List<string>();
var policy = overwrite ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite;
using (var zipStream = new MemoryStream(data))
using (var zip = ZipFile.Read(zipStream))
{
foreach (var entry in zip.Entries)
{
var path = Path.Combine(targetDirectory, entry.FileName.Replace('/', '\\'));
entry.ExtractExistingFile = policy;
entry.Extract(targetDirectory);
paths.Add(path);
}
}
return paths;
}
/// <summary>
/// Saves the file to a temporary directory and returns the path to the file (without filename)
/// </summary>
/// <param name="base64">the encoded and compressed file content</param>
/// <param name="filename">the filename of the file to save</param>
/// <param name="directoryName">the subdirectory of the tempdir (usually the id of the additional resource</param>
/// <returns></returns>
public string DecompressDecodeAndSaveFile(string base64, string filename, string directoryName)
{
string tempPath = TempDirectory + directoryName + "\\";
if (Directory.Exists(tempPath))
{
return tempPath;
}
Directory.CreateDirectory(tempPath);
var data = base64_decode(base64);
var stream = new MemoryStream(data);
var zip = ZipFile.Read(stream);
zip.ExtractAll(tempPath);
return tempPath;
}
public MemoryStream DeCompressAndDecode(string base64)
{
var data = base64_decode(base64);
var zipStream = new MemoryStream(data);
var zip = ZipFile.Read(zipStream);
var stream = new MemoryStream();
zip.Entries.First().Extract(stream);
return stream;
}
public IEnumerable<string> GetFileList(string base64)
{
var data = base64_decode(base64);
var zipStream = new MemoryStream(data);
var zip = ZipFile.Read(zipStream);
return zip.EntryFileNames;
}
private string base64_encode(byte[] data)
{
if (data == null)
throw new ArgumentNullException("data");
return Convert.ToBase64String(data);
}
private byte[] base64_decode(string encodedData)
{
byte[] encodedDataAsBytes = Convert.FromBase64String(encodedData);
return encodedDataAsBytes;
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace SebWindowsConfig.Utilities
{
public interface IFileCompressor
{
string CompressAndEncodeFile(string filename);
string CompressAndEncodeIcon(Icon icon);
string CompressAndEncodeFavicon(Uri uri);
string CompressAndEncodeDirectory(string path, out List<string> containingFileNames);
string DecompressDecodeAndSaveFile(string base64, string filename, string subdirectory);
MemoryStream DeCompressAndDecode(string base64);
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Ionic.Zip;
namespace SebWindowsConfig.Utilities
{
internal class LogCollector
{
private readonly IWin32Window owner;
internal LogCollector(IWin32Window owner)
{
this.owner = owner;
}
internal void Run()
{
var description = "Please select the location where you would like to save the collected log files:";
var password = default(string);
var path = default(string);
using (var dialog = new FolderBrowserDialog { Description = description })
{
if (dialog.ShowDialog(owner) == DialogResult.OK)
{
path = dialog.SelectedPath;
}
}
if (path != default(string))
{
var encrypt = AskUserForDataEncrpytion();
if (!encrypt || (encrypt && TryAskForPassword(out password)))
{
CollectLogFiles(path, password);
}
}
}
private bool AskUserForDataEncrpytion()
{
var message = "Log files can contain sensitive information about you and your computer. Would you like to protect the data with a password?";
var result = MessageBox.Show(owner, message, "Data Protection", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
return result == DialogResult.Yes;
}
private bool TryAskForPassword(out string password)
{
password = default(string);
using (var dialog = new SebPasswordDialogForm())
{
dialog.Text = "Data Protection";
dialog.LabelText = "Please enter the password to be used to encrypt the data:";
if (dialog.ShowDialog(owner) == DialogResult.OK)
{
password = dialog.txtSEBPassword.Text;
}
}
return password != default(string);
}
private void CollectLogFiles(string outputPath, string password = null)
{
try
{
var zipPath = Path.Combine(outputPath, $"SEB_Logs_{DateTime.Today.ToString("yyyy-MM-dd")}.zip");
var logFiles = new[]
{
SEBClientInfo.SebClientLogFile,
Path.Combine(SEBClientInfo.SebClientSettingsAppDataDirectory, SebWindowsConfigForm.SEB_CONFIG_LOG),
Path.Combine(SEBClientInfo.SebClientSettingsAppDataDirectory, "seb.log"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"SafeExamBrowser\SebWindowsServiceWCF\sebwindowsservice.log")
};
var existingFiles = logFiles.Where(f => File.Exists(f));
var missingFiles = logFiles.Except(existingFiles);
using (var stream = new FileStream(zipPath, FileMode.Create))
using (var zip = new ZipFile())
{
if (password != default(string))
{
zip.Password = password;
}
foreach (var file in existingFiles)
{
zip.AddFile(file, string.Empty);
}
zip.Save(stream);
}
if (missingFiles.Any())
{
var count = $"{existingFiles.Count()} of {logFiles.Count()}";
var missing = $"The following file(s) could not be found:\n\n{String.Join("\n\n", missingFiles)}";
MessageBox.Show(owner, $"{count} log files were collected and saved as '{zipPath}'.\n\n{missing}", "Status", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
MessageBox.Show(owner, $"The log files were successfully collected and saved as '{zipPath}'.", "Sucess", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
catch (Exception e)
{
MessageBox.Show(owner, $"Failed to collect the log files. Reason: {e.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.IO;
using System.Reflection;
namespace SebWindowsConfig.Utilities
{
public class Logger
{
private static readonly object Lock = new object();
private static string LogFilePath { get; set; }
public static void AddError(string message, object eventSource, Exception exception, string details = null)
{
Log(Severity.Error, message, eventSource, exception, details);
}
public static void AddWarning(string message, object eventSource = null, Exception exception = null, string details = null)
{
Log(Severity.Warning, message, eventSource, exception, details);
}
public static void AddInformation(string message, object eventSource = null, Exception exception = null, string details = null)
{
Log(Severity.Information, message, eventSource, exception, details);
}
public static void InitLogger(string logFileDirectory = null, string logFilePath = null)
{
try
{
if (String.IsNullOrEmpty(logFileDirectory))
{
logFileDirectory = SEBClientInfo.SebClientLogFileDirectory;
if (String.IsNullOrEmpty(logFileDirectory))
{
throw new DirectoryNotFoundException();
}
}
if (!Directory.Exists(logFileDirectory))
{
Directory.CreateDirectory(logFileDirectory);
}
if (String.IsNullOrEmpty(logFilePath))
{
logFilePath = SEBClientInfo.SebClientLogFile;
if (String.IsNullOrEmpty(logFilePath))
{
logFilePath = String.Format(@"{0}\{1}", logFileDirectory, SEBClientInfo.SEB_CLIENT_LOG);
}
}
LogFilePath = logFilePath;
}
catch (Exception)
{
LogFilePath = String.Format(@"{0}\{1}\{2}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), SEBClientInfo.MANUFACTURER_LOCAL, SEBClientInfo.SEB_CLIENT_LOG);
}
HandleFileSize();
AddInformation(String.Format("SEB version: {0}", Assembly.GetExecutingAssembly().GetName().Version));
}
private static void Log(Severity severity, string message, object eventSource, Exception exception, string details = null)
{
try
{
lock (Lock)
{
using (var file = new StreamWriter(LogFilePath, true))
{
string eventSourceString = eventSource == null ? "" : string.Format(" ({0})", eventSource);
string exceptionString = exception == null ? "" : string.Format("\n\n Exception: {0} - {1}\n{2}", exception, exception.Message, exception.StackTrace);
string detailsString = details == null || (exception != null && details == exception.Message) ? "" : string.Format("\n\n{0}", details);
file.WriteLine("{0} [{1}]: {2}{3}{4}{5}\n", DateTime.Now.ToLocalTime(), severity, message, eventSourceString, exceptionString, detailsString);
#if DEBUG
Console.WriteLine("{0} [{1}]: {2}{3}{4}{5}\n", DateTime.Now.ToLocalTime(), severity, message, eventSourceString, exceptionString, detailsString);
#endif
}
}
}
catch
{
}
}
private static void HandleFileSize()
{
const int TEN_MB = 10000000;
var logFile = new FileInfo(LogFilePath);
var timestamp = DateTime.Now.Ticks;
var fileName = Path.GetFileNameWithoutExtension(logFile.Name);
var backupFilePath = Path.Combine(logFile.DirectoryName, String.Format("{0}_Backup_{1}.log", fileName, timestamp));
if (logFile.Exists && logFile.Length > TEN_MB)
{
File.Move(LogFilePath, backupFilePath);
}
}
enum Severity
{
Error,
Warning,
Information
}
}
}

View File

@@ -0,0 +1,964 @@
//
// PlistCS Property List (plist) serialization and parsing library.
//
// https://github.com/animetrics/PlistCS
//
// Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
namespace SebWindowsConfig.Utilities
{
public static class Plist
{
private static readonly List<int> offsetTable = new List<int>();
private static List<byte> objectTable = new List<byte>();
private static int refCount;
private static int objRefSize;
private static int offsetByteSize;
private static long offsetTableOffset;
#region Public Functions
public static object readPlist(string path)
{
using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read))
{
return readPlist(f, plistType.Auto);
}
}
public static object readPlistSource(string source)
{
return readPlist(System.Text.Encoding.UTF8.GetBytes(source));
}
public static object readPlist(byte[] data)
{
return readPlist(new MemoryStream(data), plistType.Auto);
}
public static plistType getPlistType(Stream stream)
{
byte[] magicHeader = new byte[8];
stream.Read(magicHeader, 0, 8);
if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810)
{
return plistType.Binary;
}
else
{
return plistType.Xml;
}
}
public static object readPlist(Stream stream, plistType type)
{
if (type == plistType.Auto)
{
type = getPlistType(stream);
stream.Seek(0, SeekOrigin.Begin);
}
if (type == plistType.Binary)
{
using (BinaryReader reader = new BinaryReader(stream))
{
byte[] data = reader.ReadBytes((int) reader.BaseStream.Length);
return readBinary(data);
}
}
else
{
XmlDocument xml = new XmlDocument();
xml.XmlResolver = null;
xml.Load(stream);
return readXml(xml);
}
}
public static void writeXml(object value, string path)
{
using (StreamWriter writer = new StreamWriter(path))
{
writer.Write(writeXml(value));
}
}
public static void writeXml(object value, Stream stream)
{
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(writeXml(value));
}
}
public static string writeXml(object value)
{
using (MemoryStream ms = new MemoryStream())
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false);
xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document;
xmlWriterSettings.Indent = true;
using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings))
{
xmlWriter.WriteStartDocument();
xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "https://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
xmlWriter.WriteStartElement("plist");
xmlWriter.WriteAttributeString("version", "1.0");
compose(value, xmlWriter);
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
xmlWriter.Flush();
xmlWriter.Close();
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
}
}
}
public static void writeBinary(object value, string path)
{
using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create)))
{
writer.Write(writeBinary(value));
}
}
public static void writeBinary(object value, Stream stream)
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(writeBinary(value));
}
}
public static byte[] writeBinary(object value)
{
offsetTable.Clear();
objectTable.Clear();
refCount = 0;
objRefSize = 0;
offsetByteSize = 0;
offsetTableOffset = 0;
//Do not count the root node, subtract by 1
int totalRefs = countObject(value) - 1;
refCount = totalRefs;
objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length;
composeBinary(value);
writeBinaryString("bplist00", false);
offsetTableOffset = (long) objectTable.Count;
offsetTable.Add(objectTable.Count - 8);
offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count - 1])).Length;
List<byte> offsetBytes = new List<byte>();
offsetTable.Reverse();
for (int i = 0; i < offsetTable.Count; i++)
{
offsetTable[i] = objectTable.Count - offsetTable[i];
byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize);
Array.Reverse(buffer);
offsetBytes.AddRange(buffer);
}
objectTable.AddRange(offsetBytes);
objectTable.AddRange(new byte[6]);
objectTable.Add(Convert.ToByte(offsetByteSize));
objectTable.Add(Convert.ToByte(objRefSize));
var a = BitConverter.GetBytes((long) totalRefs + 1);
Array.Reverse(a);
objectTable.AddRange(a);
objectTable.AddRange(BitConverter.GetBytes((long) 0));
a = BitConverter.GetBytes(offsetTableOffset);
Array.Reverse(a);
objectTable.AddRange(a);
return objectTable.ToArray();
}
#endregion
#region Private Functions
private static object readXml(XmlDocument xml)
{
XmlNode rootNode = xml.DocumentElement.ChildNodes[0];
return (Dictionary<string, object>) parse(rootNode);
}
private static object readBinary(byte[] data)
{
offsetTable.Clear();
List<byte> offsetTableBytes = new List<byte>();
objectTable.Clear();
refCount = 0;
objRefSize = 0;
offsetByteSize = 0;
offsetTableOffset = 0;
List<byte> bList = new List<byte>(data);
List<byte> trailer = bList.GetRange(bList.Count - 32, 32);
parseTrailer(trailer);
objectTable = bList.GetRange(0, (int) offsetTableOffset);
offsetTableBytes = bList.GetRange((int) offsetTableOffset, bList.Count - (int) offsetTableOffset - 32);
parseOffsetTable(offsetTableBytes);
return parseBinary(0);
}
private static Dictionary<string, object> parseDictionary(XmlNode node)
{
XmlNodeList children = node.ChildNodes;
if (children.Count % 2 != 0)
{
throw new DataMisalignedException("Dictionary elements must have an even number of child nodes");
}
Dictionary<string, object> dict = new Dictionary<string, object>();
for (int i = 0; i < children.Count; i += 2)
{
XmlNode keynode = children[i];
XmlNode valnode = children[i + 1];
if (keynode.Name != "key")
{
throw new ApplicationException("expected a key node");
}
object result = parse(valnode);
if (result != null)
{
dict.Add(keynode.InnerText, result);
}
}
return dict;
}
private static List<object> parseArray(XmlNode node)
{
List<object> array = new List<object>();
foreach (XmlNode child in node.ChildNodes)
{
object result = parse(child);
if (result != null)
{
array.Add(result);
}
}
return array;
}
private static void composeArray(List<object> value, XmlWriter writer)
{
writer.WriteStartElement("array");
foreach (object obj in value)
{
compose(obj, writer);
}
writer.WriteEndElement();
}
private static object parse(XmlNode node)
{
switch (node.Name)
{
case "dict":
return parseDictionary(node);
case "array":
return parseArray(node);
case "string":
return node.InnerText;
case "integer":
// int result;
//int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result);
return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo);
case "real":
return Convert.ToDouble(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo);
case "false":
return false;
case "true":
return true;
case "null":
return null;
case "date":
return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc);
case "data":
return Convert.FromBase64String(node.InnerText);
}
throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name));
}
private static void compose(object value, XmlWriter writer)
{
if (value == null || value is string)
{
writer.WriteElementString("string", value as string);
}
else if (value is int || value is long)
{
writer.WriteElementString("integer", ((int) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
}
else if (value is System.Collections.Generic.Dictionary<string, object> ||
value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String"))
{
//Convert to Dictionary<string, object>
Dictionary<string, object> dic = value as Dictionary<string, object>;
if (dic == null)
{
dic = new Dictionary<string, object>();
IDictionary idic = (IDictionary) value;
foreach (var key in idic.Keys)
{
dic.Add(key.ToString(), idic[key]);
}
}
writeDictionaryValues(dic, writer);
}
else if (value is List<object>)
{
composeArray((List<object>) value, writer);
}
else if (value is byte[])
{
writer.WriteElementString("data", Convert.ToBase64String((Byte[]) value));
}
else if (value is float || value is double)
{
writer.WriteElementString("real", ((double) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
}
else if (value is DateTime)
{
DateTime time = (DateTime) value;
string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc);
writer.WriteElementString("date", theString);//, "yyyy-MM-ddTHH:mm:ssZ"));
}
else if (value is bool)
{
writer.WriteElementString(value.ToString().ToLower(), "");
}
else
{
throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString()));
}
}
private static void writeDictionaryValues(Dictionary<string, object> dictionary, XmlWriter writer)
{
writer.WriteStartElement("dict");
foreach (string key in dictionary.Keys)
{
object value = dictionary[key];
writer.WriteElementString("key", key);
compose(value, writer);
}
writer.WriteEndElement();
}
private static int countObject(object value)
{
int count = 0;
switch (value.GetType().ToString())
{
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
Dictionary<string, object> dict = (Dictionary<string, object>) value;
foreach (string key in dict.Keys)
{
count += countObject(dict[key]);
}
count += dict.Keys.Count;
count++;
break;
case "System.Collections.Generic.List`1[System.Object]":
List<object> list = (List<object>) value;
foreach (object obj in list)
{
count += countObject(obj);
}
count++;
break;
default:
count++;
break;
}
return count;
}
private static byte[] writeBinaryDictionary(Dictionary<string, object> dictionary)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
List<int> refs = new List<int>();
for (int i = dictionary.Count - 1; i >= 0; i--)
{
var o = new object[dictionary.Count];
dictionary.Values.CopyTo(o, 0);
composeBinary(o[i]);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
for (int i = dictionary.Count - 1; i >= 0; i--)
{
var o = new string[dictionary.Count];
dictionary.Keys.CopyTo(o, 0);
composeBinary(o[i]);//);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
if (dictionary.Count < 15)
{
header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count)));
}
else
{
header.Add(0xD0 | 0xf);
header.AddRange(writeBinaryInteger(dictionary.Count, false));
}
foreach (int val in refs)
{
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
Array.Reverse(refBuffer);
buffer.InsertRange(0, refBuffer);
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] composeBinaryArray(List<object> objects)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
List<int> refs = new List<int>();
for (int i = objects.Count - 1; i >= 0; i--)
{
composeBinary(objects[i]);
offsetTable.Add(objectTable.Count);
refs.Add(refCount);
refCount--;
}
if (objects.Count < 15)
{
header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count)));
}
else
{
header.Add(0xA0 | 0xf);
header.AddRange(writeBinaryInteger(objects.Count, false));
}
foreach (int val in refs)
{
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
Array.Reverse(refBuffer);
buffer.InsertRange(0, refBuffer);
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] composeBinary(object obj)
{
byte[] value;
switch (obj.GetType().ToString())
{
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
value = writeBinaryDictionary((Dictionary<string, object>) obj);
return value;
case "System.Collections.Generic.List`1[System.Object]":
value = composeBinaryArray((List<object>) obj);
return value;
case "System.Byte[]":
value = writeBinaryByteArray((byte[]) obj);
return value;
case "System.Double":
value = writeBinaryDouble((double) obj);
return value;
case "System.Int32":
value = writeBinaryInteger((int) obj, true);
return value;
case "System.String":
value = writeBinaryString((string) obj, true);
return value;
case "System.DateTime":
value = writeBinaryDate((DateTime) obj);
return value;
case "System.Boolean":
value = writeBinaryBool((bool) obj);
return value;
default:
return new byte[0];
}
}
public static byte[] writeBinaryDate(DateTime obj)
{
List<byte> buffer = new List<byte>(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), 8));
buffer.Reverse();
buffer.Insert(0, 0x33);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
public static byte[] writeBinaryBool(bool obj)
{
List<byte> buffer = new List<byte>(new byte[1] { (bool) obj ? (byte) 9 : (byte) 8 });
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryInteger(int value, bool write)
{
List<byte> buffer = new List<byte>(BitConverter.GetBytes((long) value));
buffer = new List<byte>(RegulateNullBytes(buffer.ToArray()));
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
buffer.Add(0);
int header = 0x10 | (int) (Math.Log(buffer.Count) / Math.Log(2));
buffer.Reverse();
buffer.Insert(0, Convert.ToByte(header));
if (write)
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryDouble(double value)
{
List<byte> buffer = new List<byte>(RegulateNullBytes(BitConverter.GetBytes(value), 4));
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
buffer.Add(0);
int header = 0x20 | (int) (Math.Log(buffer.Count) / Math.Log(2));
buffer.Reverse();
buffer.Insert(0, Convert.ToByte(header));
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryByteArray(byte[] value)
{
List<byte> buffer = new List<byte>(value);
List<byte> header = new List<byte>();
if (value.Length < 15)
{
header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length)));
}
else
{
header.Add(0x40 | 0xf);
header.AddRange(writeBinaryInteger(buffer.Count, false));
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] writeBinaryString(string value, bool head)
{
List<byte> buffer = new List<byte>();
List<byte> header = new List<byte>();
foreach (char chr in value.ToCharArray())
buffer.Add(Convert.ToByte(chr));
if (head)
{
if (value.Length < 15)
{
header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length)));
}
else
{
header.Add(0x50 | 0xf);
header.AddRange(writeBinaryInteger(buffer.Count, false));
}
}
buffer.InsertRange(0, header);
objectTable.InsertRange(0, buffer);
return buffer.ToArray();
}
private static byte[] RegulateNullBytes(byte[] value)
{
return RegulateNullBytes(value, 1);
}
private static byte[] RegulateNullBytes(byte[] value, int minBytes)
{
Array.Reverse(value);
List<byte> bytes = new List<byte>(value);
for (int i = 0; i < bytes.Count; i++)
{
if (bytes[i] == 0 && bytes.Count > minBytes)
{
bytes.Remove(bytes[i]);
i--;
}
else
break;
}
if (bytes.Count < minBytes)
{
int dist = minBytes - bytes.Count;
for (int i = 0; i < dist; i++)
bytes.Insert(0, 0);
}
value = bytes.ToArray();
Array.Reverse(value);
return value;
}
private static void parseTrailer(List<byte> trailer)
{
offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0);
objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0);
byte[] refCountBytes = trailer.GetRange(12, 4).ToArray();
Array.Reverse(refCountBytes);
refCount = BitConverter.ToInt32(refCountBytes, 0);
byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray();
Array.Reverse(offsetTableOffsetBytes);
offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0);
}
private static void parseOffsetTable(List<byte> offsetTableBytes)
{
for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize)
{
byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray();
Array.Reverse(buffer);
offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0));
}
}
private static object parseBinaryDictionary(int objRef)
{
Dictionary<string, object> buffer = new Dictionary<string, object>();
List<int> refs = new List<int>();
int refCount = 0;
byte dictByte = objectTable[offsetTable[objRef]];
int refStartPosition;
refCount = getCount(offsetTable[objRef], out refStartPosition);
if (refCount < 15)
refStartPosition = offsetTable[objRef] + 1;
else
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize)
{
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
Array.Reverse(refBuffer);
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
}
for (int i = 0; i < refCount; i++)
{
buffer.Add((string) parseBinary(refs[i]), parseBinary(refs[i + refCount]));
}
return buffer;
}
private static object parseBinaryArray(int objRef)
{
List<object> buffer = new List<object>();
List<int> refs = new List<int>();
int refCount = 0;
byte arrayByte = objectTable[offsetTable[objRef]];
int refStartPosition;
refCount = getCount(offsetTable[objRef], out refStartPosition);
if (refCount < 15)
refStartPosition = offsetTable[objRef] + 1;
else
//The following integer has a header aswell so we increase the refStartPosition by two to account for that.
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize)
{
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
Array.Reverse(refBuffer);
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
}
for (int i = 0; i < refCount; i++)
{
buffer.Add(parseBinary(refs[i]));
}
return buffer;
}
private static int getCount(int bytePosition, out int newBytePosition)
{
byte headerByte = objectTable[bytePosition];
byte headerByteTrail = Convert.ToByte(headerByte & 0xf);
int count;
if (headerByteTrail < 15)
{
count = headerByteTrail;
newBytePosition = bytePosition + 1;
}
else
count = (int) parseBinaryInt(bytePosition + 1, out newBytePosition);
return count;
}
private static object parseBinary(int objRef)
{
byte header = objectTable[offsetTable[objRef]];
switch (header & 0xF0)
{
case 0:
{
//If the byte is
//0 return null
//9 return true
//8 return false
return (objectTable[offsetTable[objRef]] == 0) ? (object) null : ((objectTable[offsetTable[objRef]] == 9) ? true : false);
}
case 0x10:
{
return parseBinaryInt(offsetTable[objRef]);
}
case 0x20:
{
return parseBinaryReal(offsetTable[objRef]);
}
case 0x30:
{
return parseBinaryDate(offsetTable[objRef]);
}
case 0x40:
{
return parseBinaryByteArray(offsetTable[objRef]);
}
case 0x50://String ASCII
{
return parseBinaryAsciiString(offsetTable[objRef]);
}
case 0x60://String Unicode
{
return parseBinaryUnicodeString(offsetTable[objRef]);
}
case 0xD0:
{
return parseBinaryDictionary(objRef);
}
case 0xA0:
{
return parseBinaryArray(objRef);
}
}
throw new Exception("This type is not supported");
}
public static object parseBinaryDate(int headerPosition)
{
byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray();
Array.Reverse(buffer);
double appleTime = BitConverter.ToDouble(buffer, 0);
DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime);
return result;
}
private static object parseBinaryInt(int headerPosition)
{
int output;
return parseBinaryInt(headerPosition, out output);
}
private static object parseBinaryInt(int headerPosition, out int newHeaderPosition)
{
byte header = objectTable[headerPosition];
int byteCount = (int) Math.Pow(2, header & 0xf);
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
Array.Reverse(buffer);
//Add one to account for the header byte
newHeaderPosition = headerPosition + byteCount + 1;
return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0);
}
private static object parseBinaryReal(int headerPosition)
{
byte header = objectTable[headerPosition];
int byteCount = (int) Math.Pow(2, header & 0xf);
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
Array.Reverse(buffer);
return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0);
}
private static object parseBinaryAsciiString(int headerPosition)
{
int charStartPosition;
int charCount = getCount(headerPosition, out charStartPosition);
var buffer = objectTable.GetRange(charStartPosition, charCount);
return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty;
}
private static object parseBinaryUnicodeString(int headerPosition)
{
int charStartPosition;
int charCount = getCount(headerPosition, out charStartPosition);
charCount = charCount * 2;
byte[] buffer = new byte[charCount];
byte one, two;
for (int i = 0; i < charCount; i += 2)
{
one = objectTable.GetRange(charStartPosition + i, 1)[0];
two = objectTable.GetRange(charStartPosition + i + 1, 1)[0];
if (BitConverter.IsLittleEndian)
{
buffer[i] = two;
buffer[i + 1] = one;
}
else
{
buffer[i] = one;
buffer[i + 1] = two;
}
}
return Encoding.Unicode.GetString(buffer);
}
private static object parseBinaryByteArray(int headerPosition)
{
int byteStartPosition;
int byteCount = getCount(headerPosition, out byteStartPosition);
return objectTable.GetRange(byteStartPosition, byteCount).ToArray();
}
#endregion
}
public enum plistType
{
Auto, Binary, Xml
}
public static class PlistDateConverter
{
public static long timeDifference = 978307200;
public static long GetAppleTime(long unixTime)
{
return unixTime - timeDifference;
}
public static long GetUnixTime(long appleTime)
{
return appleTime + timeDifference;
}
public static DateTime ConvertFromAppleTimeStamp(double timestamp)
{
DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
return origin.AddSeconds(timestamp);
}
public static double ConvertToAppleTimeStamp(DateTime date)
{
DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
TimeSpan diff = date - begin;
return Math.Floor(diff.TotalSeconds);
}
}
}

View File

@@ -0,0 +1,461 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
//
// SEBClientInfo.cs
// SafeExamBrowser
//
// Copyright (c) 2010-2020 Viktor Tomas, Dirk Bauer, Daniel R. Schneider, Pascal Wyss,
// ETH Zurich, IT Services,
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen
// Project concept: Thomas Piendl, Daniel R. Schneider,
// Dirk Bauer, Kai Reuter, Tobias Halbherr, Karsten Burger, Marco Lehre,
// Brigitte Schmucki, Oliver Rahs. French localization: Nicolas Dunand
//
// ``The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is Safe Exam Browser for Windows.
//
// The Initial Developers of the Original Code are Viktor Tomas,
// Dirk Bauer, Daniel R. Schneider, Pascal Wyss.
// Portions created by Viktor Tomas, Dirk Bauer, Daniel R. Schneider, Pascal Wyss
// are Copyright (c) 2010-2020 Viktor Tomas, Dirk Bauer, Daniel R. Schneider,
// Pascal Wyss, ETH Zurich, IT Services,
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen. All Rights Reserved.
//
// Contributor(s): ______________________________________.
//
namespace SebWindowsConfig.Utilities
{
public enum chooseFileToUploadPolicies
{
manuallyWithFileRequester = 0,
attemptUploadSameFileDownloadedBefore = 1,
onlyAllowUploadSameFileDownloadedBefore = 2
};
public enum newBrowserWindowPolicies
{
getGenerallyBlocked = 0,
openInSameWindow = 1,
openInNewWindow = 2
};
public enum sebServicePolicies
{
ignoreService = 0,
indicateMissingService = 1,
forceSebService = 2
};
public enum browserViewModes
{
browserViewModeWindow = 0,
browserViewModeFullscreen = 1
};
// MAC
public enum sebPurposePolicies
{
sebPurposePolicyStartingExam = 0,
sebPurposePolicyConfiguringClient = 1
};
public enum URLFilterRuleActions
{
block = 0,
allow = 1,
ignore = 2,
unknown = 3
};
public enum SEBMinMacOSVersion
{
SEBMinOSX10_7 = 0,
SEBMinOSX10_8 = 1,
SEBMinOSX10_9 = 2,
SEBMinOSX10_10 = 3,
SEBMinOSX10_11 = 4,
SEBMinMacOS10_12 = 5,
SEBMinMacOS10_13 = 6,
SEBMinMacOS10_14 = 7
};
public class SEBClientInfo
{
#region Imports
[DllImport("kernel32.Dll")]
public static extern short GetVersionEx(ref OSVERSIONINFO o);
#endregion
// Socket protocol
//static int ai_family = AF_INET;
//static int ai_socktype = SOCK_STREAM;
//static int ai_protocol = IPPROTO_TCP;
#region Constants
// Name and location of SEB configuration files and logfiles
public const string SEB_CLIENT_CONFIG = "SebClientSettings.seb";
public const string SEB_CLIENT_LOG = "SebClient.log";
private const string XUL_RUNNER_CONFIG = "config.json";
public const string SEB_SHORTNAME = "SEB";
public const string XUL_RUNNER = "firefox.exe";
private const string XUL_RUNNER_INI = "seb.ini";
// Application path contains [MANUFACTURER]\[PRODUCT_NAME]
// (see also "SebWindowsPackageSetup" Project in MS Visual Studio 10)
public const string MANUFACTURER_LOCAL = "SafeExamBrowser";
//private const string MANUFACTURER = "ETH Zuerich";
public const string PRODUCT_NAME = "SafeExamBrowser";
public const string SEB_SERVICE_DIRECTORY = "SebWindowsServiceWCF";
public const string SEB_BROWSER_DIRECTORY = "SebWindowsBrowser";
private const string XUL_RUNNER_DIRECTORY = "xulrunner";
private const string XUL_SEB_DIRECTORY = "xul_seb";
public const string FILENAME_SEB = "SafeExamBrowser.exe";
public const string FILENAME_SEBCONFIGTOOL = "SEBConfigTool.exe";
public const string FILENAME_SEBSERVICE = "SebWindowsServiceWCF.exe";
public const string FILENAME_DLL_FLECK = "Fleck.dll";
public const string FILENAME_DLL_ICONLIB = "IconLib.dll";
public const string FILENAME_DLL_IONICZIP = "Ionic.Zip.dll";
public const string FILENAME_DLL_METRO = "MetroFramework.dll";
public const string FILENAME_DLL_NAUDIO = "NAudio.dll";
public const string FILENAME_DLL_NEWTONSOFTJSON = "Newtonsoft.Json.dll";
public const string FILENAME_DLL_SERVICECONTRACTS = "SEBWindowsServiceContracts.dll";
public const string BROWSER_USERAGENT_DESKTOP = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0";
public const string BROWSER_USERAGENT_TOUCH = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0; Touch) Gecko/20100101 Firefox/52.0";
public const string BROWSER_USERAGENT_TOUCH_IPAD = "Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.3 Mobile/15E216 Safari/605.1.15";
public const string BROWSER_USERAGENT_SEB = "SEB";
public const string END_OF_STRING_KEYWORD = "---SEB---";
private const string DEFAULT_USERNAME = "";
private const string DEFAULT_HOSTNAME = "localhost";
private const string DEFAULT_HOST_IP_ADDRESS = "127.0.0.1";
private const int DEFAULT_PORTNUMBER = 57016;
public const string DEFAULT_KEY = "Di𝈭l𝈖Ch𝈒ah𝉇t𝈁a𝉈Hai1972";
private const int DEFAULT_SEND_INTERVAL = 100;
private const int DEFAULT_RECV_TIMEOUT = 100;
private const int DEFAULT_NUM_MESSAGES = 3;
public const string SEB_NEW_DESKTOP_NAME = "SEBDesktop";
public const string SEB_WINDOWS_SERVICE_NAME = "SebWindowsService";
#endregion
#region Public Properties
public static bool ExplorerShellWasKilled { get; set; }
public static bool IsNewOS { get; set; }
public static bool examMode = false;
// SEB Client Socket properties
public static char[] UserNameRegistryFlags { get; set; }
public static char[] RegistryFlags { get; set; }
public static string HostName { get; set; }
public static string HostIpAddress { get; set; }
public static string UserName { get; set; }
public static char[] UserSid { get; set; }
public static int PortNumber { get; set; }
public static int SendInterval { get; set; }
public static int RecvTimeout { get; set; }
public static int NumMessages { get; set; }
public static int MessageNr { get; set; }
public static string DesktopName { get; set; }
// SEB Client Directories properties
public static string ApplicationExecutableDirectory { get; set; }
public static string ProgramFilesX86Directory { get; set; }
public static bool LogFileDesiredMsgHook { get; set; }
public static bool LogFileDesiredSebClient { get; set; }
public static string SebClientLogFileDirectory { get; set; }
public static string SebClientDirectory { get; set; }
public static string SebClientLogFile { get; set; }
public static string SebClientSettingsProgramDataDirectory { get; set; }
public static string SebClientSettingsAppDataDirectory { get; set; }
public static string XulRunnerAdditionalDictionariesDirectory { get; set; }
public static string XulRunnerDirectory { get; set; }
public static string XulSebDirectory { get; set; }
public static string SebClientSettingsProgramDataFile;
public static string SebClientSettingsAppDataFile;
public static string XulRunnerConfigFileDirectory { get; set; }
public static string XulRunnerConfigFile;
public static string XulRunnerExePath;
public static string XulRunnerSebIniPath;
public static string XulRunnerParameter;
//public static string XulRunnerFlashContainerState { get; set; }
public static string ExamUrl { get; set; }
public static string QuitPassword { get; set; }
public static string QuitHashcode { get; set; }
//public static Dictionary<string, object> sebSettings = new Dictionary<string, object>();
public static string LoadingSettingsFileName = "";
public static float scaleFactor = 1;
public static int appChooserHeight = 132;
#endregion
#region Structures
/// <summary>
/// Stores windows version info.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct OSVERSIONINFO
{
public int dwOSVersionInfoSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
}
#endregion
//public static SEBClientConfig sebClientConfig;
public static Dictionary<string, object> getSebSetting(string key)
{
object sebSetting = null;
try
{
sebSetting = SEBSettings.settingsCurrent[key];
}
catch
{
sebSetting = null;
}
if (sebSetting != null)
return SEBSettings.settingsCurrent;
else
return SEBSettings.settingsDefault;
}
/// <summary>
/// Sets user, host info, send-recv interval, recv timeout, Logger and read SebClient configuration.
/// </summary>
/// <returns></returns>
public static bool SetSebClientConfiguration()
{
bool setSebClientConfiguration = false;
// Initialise socket properties
IsNewOS = false;
ExplorerShellWasKilled = false;
UserNameRegistryFlags = new char[100];
RegistryFlags = new char[50];
UserSid = new char[512];
UserName = DEFAULT_USERNAME;
HostName = DEFAULT_HOSTNAME;
HostIpAddress = DEFAULT_HOST_IP_ADDRESS;
PortNumber = DEFAULT_PORTNUMBER;
SendInterval = DEFAULT_SEND_INTERVAL;
RecvTimeout = DEFAULT_RECV_TIMEOUT;
NumMessages = DEFAULT_NUM_MESSAGES;
//Sets paths to files SEB has to save or read from the file system
SetSebPaths();
byte[] sebClientSettings = null;
// Create a string builder for a temporary log (until we can write it with the Logger)
StringBuilder tempLogStringBuilder = new StringBuilder();
// Try to read the SebClientSettigs.seb file from the program data directory
try
{
sebClientSettings = File.ReadAllBytes(SebClientSettingsProgramDataFile);
}
catch (Exception streamReadException)
{
// Write error into string with temporary log string builder
tempLogStringBuilder.Append("Could not load SebClientSettigs.seb from the Program Data directory").Append(streamReadException == null ? null : streamReadException.GetType().ToString()).Append(streamReadException.Message);
}
if (sebClientSettings == null)
{
// Try to read the SebClientSettigs.seb file from the local application data directory
try
{
sebClientSettings = File.ReadAllBytes(SebClientSettingsAppDataFile);
}
catch (Exception streamReadException)
{
// Write error into string with temporary log string builder
tempLogStringBuilder.Append("Could not load SebClientSettigs.seb from the Roaming Application Data directory. ").Append(streamReadException == null ? null : streamReadException.GetType().ToString()).Append(streamReadException.Message);
}
}
// Store the decrypted configuration settings.
if (!SEBSettings.StoreDecryptedSebClientSettings(sebClientSettings))
return false;
// Initialise Logger, if enabled
InitializeLogger();
// Save the temporary log string into the log
Logger.AddError(tempLogStringBuilder.ToString(), null, null);
// Set username
UserName = Environment.UserName;
setSebClientConfiguration = true;
// Write settings into log
StringBuilder userInfo =
new StringBuilder ("User Name: " ).Append(UserName)
.Append(" Host Name: " ).Append(HostName)
.Append(" Port Number: " ).Append(PortNumber)
.Append(" Send Interval: " ).Append(SendInterval)
.Append(" Recv Timeout: " ).Append(RecvTimeout)
.Append(" Num Messages: " ).Append(NumMessages)
.Append(" SebClientConfigFileDirectory: ").Append(SebClientSettingsAppDataDirectory)
.Append(" SebClientConfigFile: " ).Append(SebClientSettingsAppDataFile);
Logger.AddInformation(userInfo.ToString(), null, null);
return setSebClientConfiguration;
}
/// <summary>
/// Initialise Logger if it's enabled.
/// </summary>
public static void InitializeLogger()
{
if ((Boolean)getSebSetting(SEBSettings.KeyEnableLogging)[SEBSettings.KeyEnableLogging])
{
string logDirectory = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyLogDirectoryWin);
if (!String.IsNullOrEmpty(logDirectory))
{
// Expand environment variables in log file path
SebClientLogFileDirectory = Environment.ExpandEnvironmentVariables(logDirectory);
SebClientLogFile = String.Format(@"{0}\{1}", SebClientLogFileDirectory, SEB_CLIENT_LOG);
}
else
{
SEBClientInfo.SetDefaultClientLogFile();
}
Logger.InitLogger(SEBClientInfo.SebClientLogFileDirectory, null);
}
}
/// <summary>
/// Sets paths to files SEB has to save or read from the file system.
/// </summary>
public static void SetSebPaths()
{
// Get the path of the directory the application executable lies in
ApplicationExecutableDirectory = Path.GetDirectoryName(Application.ExecutablePath);
// Get the path of the "Program Files X86" directory.
ProgramFilesX86Directory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
// Get the path of the "Program Data" and "Local Application Data" directory.
string programDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); //GetEnvironmentVariable("PROGRAMMDATA");
string appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
/// Get paths for the two possible locations of the SebClientSettings.seb file
///
// In the program data directory (for managed systems, only an administrator can write in this directory):
// If there is a SebClientSettigs.seb file, then this has priority and is used by the SEB client, another
// SebClientSettigs.seb file in the local app data folder is ignored then and the SEB client cannot be
// reconfigured by opening a .seb file saved for configuring a client
StringBuilder sebClientSettingsProgramDataDirectoryBuilder = new StringBuilder(programDataDirectory).Append("\\").Append(MANUFACTURER_LOCAL).Append("\\"); //.Append(PRODUCT_NAME).Append("\\");
SebClientSettingsProgramDataDirectory = sebClientSettingsProgramDataDirectoryBuilder.ToString();
// In the local application data directory (for unmanaged systems like student computers, user can write in this directory):
// A SebClientSettigs.seb file in this directory can be created or replaced by opening a .seb file saved for configuring a client
StringBuilder sebClientSettingsAppDataDirectoryBuilder = new StringBuilder(appDataDirectory).Append("\\").Append(MANUFACTURER_LOCAL).Append("\\"); //.Append(PRODUCT_NAME).Append("\\");
SebClientSettingsAppDataDirectory = sebClientSettingsAppDataDirectoryBuilder.ToString();
// Set the location of the SebWindowsClientDirectory
StringBuilder sebClientDirectoryBuilder = new StringBuilder(ProgramFilesX86Directory).Append("\\").Append(PRODUCT_NAME).Append("\\");
SebClientDirectory = sebClientDirectoryBuilder.ToString();
// The directory into which additional dictionaries are extracted.
XulRunnerAdditionalDictionariesDirectory = Path.Combine(SebClientSettingsAppDataDirectory, "Dictionaries");
// Set the location of the XulRunnerDirectory
//StringBuilder xulRunnerDirectoryBuilder = new StringBuilder(SebClientDirectory).Append(XUL_RUNNER_DIRECTORY).Append("\\");
//XulRunnerDirectory = xulRunnerDirectoryBuilder.ToString();
StringBuilder xulRunnerDirectoryBuilder = new StringBuilder(SEB_BROWSER_DIRECTORY).Append("\\").Append(XUL_RUNNER_DIRECTORY).Append("\\");
XulRunnerDirectory = xulRunnerDirectoryBuilder.ToString();
// Set the location of the XulSebDirectory
//StringBuilder xulSebDirectoryBuilder = new StringBuilder(SebClientDirectory).Append(XUL_SEB_DIRECTORY).Append("\\");
//XulSebDirectory = xulSebDirectoryBuilder.ToString();
StringBuilder xulSebDirectoryBuilder = new StringBuilder(SEB_BROWSER_DIRECTORY).Append("\\").Append(XUL_SEB_DIRECTORY).Append("\\");
XulSebDirectory = xulSebDirectoryBuilder.ToString();
// Set the location of the XulRunnerExePath
//StringBuilder xulRunnerExePathBuilder = new StringBuilder("\"").Append(XulRunnerDirectory).Append(XUL_RUNNER).Append("\"");
//XulRunnerExePath = xulRunnerExePathBuilder.ToString();
StringBuilder xulRunnerExePathBuilder = new StringBuilder(XulRunnerDirectory).Append(XUL_RUNNER); //.Append("\"");
XulRunnerExePath = xulRunnerExePathBuilder.ToString();
// Set the location of the seb.ini
StringBuilder xulRunnerSebIniPathBuilder = new StringBuilder(XulSebDirectory).Append(XUL_RUNNER_INI); //.Append("\"");
XulRunnerSebIniPath = xulRunnerSebIniPathBuilder.ToString();
// Get the two possible paths of the SebClientSettings.seb file
StringBuilder sebClientSettingsProgramDataBuilder = new StringBuilder(SebClientSettingsProgramDataDirectory).Append(SEB_CLIENT_CONFIG);
SebClientSettingsProgramDataFile = sebClientSettingsProgramDataBuilder.ToString();
StringBuilder sebClientSettingsAppDataBuilder = new StringBuilder(SebClientSettingsAppDataDirectory).Append(SEB_CLIENT_CONFIG);
SebClientSettingsAppDataFile = sebClientSettingsAppDataBuilder.ToString();
// Set the default location of the SebClientLogFileDirectory
SetDefaultClientLogFile();
}
/// <summary>
/// Set the default location of the SebClientLogFileDirectory.
/// </summary>
public static void SetDefaultClientLogFile()
{
StringBuilder SebClientLogFileDirectoryBuilder = new StringBuilder(SebClientSettingsAppDataDirectory); //.Append("\\").Append(MANUFACTURER_LOCAL).Append("\\");
SebClientLogFileDirectory = SebClientLogFileDirectoryBuilder.ToString();
// Set the path of the SebClient.log file
StringBuilder sebClientLogFileBuilder = new StringBuilder(SebClientLogFileDirectory).Append(SEB_CLIENT_LOG);
SebClientLogFile = sebClientLogFileBuilder.ToString();
}
public static bool CreateNewDesktopOldValue { get; set; }
public static string ContractEnvironmentVariables(string path)
{
path = Path.GetFullPath(path);
DictionaryEntry currentEntry = new DictionaryEntry("", "");
foreach (object key in Environment.GetEnvironmentVariables().Keys)
{
string value = (string)Environment.GetEnvironmentVariables()[key];
if (path.ToUpperInvariant().Contains(value.ToUpperInvariant()) && value.Length > ((string)currentEntry.Value).Length)
{
currentEntry.Key = (string)key;
currentEntry.Value = value;
}
}
return path.Replace((string)currentEntry.Value, "%" + (string)currentEntry.Key + "%");
}
}
}

View File

@@ -0,0 +1,908 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
using DictObj = System.Collections.Generic.Dictionary<string, object>;
//
// SEBConfigFileManager.cs
// SafeExamBrowser
//
// Copyright (c) 2010-2020 Daniel R. Schneider,
// ETH Zurich, IT Services,
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen
// Project concept: Thomas Piendl, Daniel R. Schneider,
// Dirk Bauer, Kai Reuter, Tobias Halbherr, Karsten Burger, Marco Lehre,
// Brigitte Schmucki, Oliver Rahs. French localization: Nicolas Dunand
//
// ``The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is Safe Exam Browser for Windows.
//
// The Initial Developer of the Original Code is Daniel R. Schneider.
// Portions created by Daniel R. Schneider
// are Copyright (c) 2010-2020 Daniel R. Schneider,
// ETH Zurich, IT Services,
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen. All Rights Reserved.
//
// Contributor(s): ______________________________________.
//
namespace SebWindowsConfig.Utilities
{
public class SEBConfigFileManager
{
public static SebPasswordDialogForm sebPasswordDialogForm;
// Prefixes
private const int PREFIX_LENGTH = 4;
private const int MULTIPART_LENGTH = 8;
private const int CUSTOMHEADER_LENGTH = 4;
private const string PUBLIC_KEY_HASH_MODE = "pkhs";
private const string PUBLIC_SYMMETRIC_KEY_MODE = "phsk";
private const string PASSWORD_MODE = "pswd";
private const string PLAIN_DATA_MODE = "plnd";
private const string PASSWORD_CONFIGURING_CLIENT_MODE = "pwcc";
private const string UNENCRYPTED_MODE = "<?xm";
private const string MULTIPART_MODE = "mphd";
private const string CUSTOM_HEADER_MODE = "cmhd";
// Public key hash identifier length
private const int PUBLIC_KEY_HASH_LENGTH = 20;
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Decrypt and deserialize SEB settings
/// When forEditing = true, then the decrypting password the user entered and/or
/// certificate reference found in the .seb file is returned
/// </summary>
/// ----------------------------------------------------------------------------------------
public static DictObj DecryptSEBSettings(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash, ref X509Certificate2 sebFileCertificateRef, bool suppressFileFormatError = false)
{
// Ungzip the .seb (according to specification >= v14) source data
byte[] unzippedSebData = GZipByte.Decompress(sebData);
// if unzipped data is not null, then unzipping worked, we use unzipped data
// if unzipped data is null, then the source data may be an uncompressed .seb file, we proceed with it
if (unzippedSebData != null) sebData = unzippedSebData;
string prefixString;
// save the data including the first 4 bytes for the case that it's acutally an unencrypted XML plist
byte[] sebDataUnencrypted = sebData.Clone() as byte[];
// Get 4-char prefix
prefixString = GetPrefixStringFromData(ref sebData);
//// Check prefix identifying encryption modes
/// Check for new Multipart and Custom headers
// Multipart Config File: The first part containts the regular SEB key/value settings
// following parts can contain additional resources. An updated SEB version will be
// able to read and process those parts sequentially as a stream.
// Therefore potentially large additional resources won't have to be loaded into memory at once
if (prefixString.CompareTo(MULTIPART_MODE) == 0)
{
// Skip the Multipart Config File header
byte[] multipartConfigLengthData = GetPrefixDataFromData(ref sebData, MULTIPART_LENGTH);
long multipartConfigLength = BitConverter.ToInt64(multipartConfigLengthData, 0);
Logger.AddInformation("Multipart Config File, first part (settings) length: " + multipartConfigLength);
try
{
Logger.AddInformation("Cropping config file, as this SEB version cannot process additional parts of multipart config files.");
byte[] dataFirstPart = new byte[sebData.Length - multipartConfigLength];
Buffer.BlockCopy(sebData, 0, dataFirstPart, 0, dataFirstPart.Length);
sebData = dataFirstPart;
}
catch (Exception ex)
{
Logger.AddError("Error while cropping config file", null, ex, ex.Message);
}
}
// Custom Header: Containts a 32 bit value for the length of the header
// followed by the custom header information. After the header, regular
// SEB config file data follows
if (prefixString.CompareTo(CUSTOM_HEADER_MODE) == 0)
{
// Skip the Custom Header
byte[] customHeaderLengthData = GetPrefixDataFromData(ref sebData, CUSTOMHEADER_LENGTH);
int customHeaderLength = BitConverter.ToInt32(customHeaderLengthData, 0);
Logger.AddInformation("Custom Config File Header length: " + customHeaderLength);
try
{
Logger.AddInformation("Removing custom header from config file data. This SEB version cannot process this header type and will ignore it.");
byte[] customHeaderData = GetPrefixDataFromData(ref sebData, customHeaderLength);
Logger.AddInformation("Custom header data: " + customHeaderData);
}
catch (Exception ex)
{
Logger.AddError("Error while removing custom header from config file data", null, ex, ex.Message);
}
}
// Prefix = pksh ("Public-Symmetric Key Hash") ?
if (prefixString.CompareTo(PUBLIC_SYMMETRIC_KEY_MODE) == 0)
{
// Decrypt with cryptographic identity/private and symmetric key
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, true, forEditing, ref sebFileCertificateRef);
if (sebData == null)
{
return null;
}
// Get 4-char prefix again
// and remaining data without prefix, which is either plain or still encoded with password
prefixString = GetPrefixStringFromData(ref sebData);
}
// Prefix = pkhs ("Public Key Hash") ?
if (prefixString.CompareTo(PUBLIC_KEY_HASH_MODE) == 0)
{
// Decrypt with cryptographic identity/private key
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, false, forEditing, ref sebFileCertificateRef);
if (sebData == null)
{
return null;
}
// Get 4-char prefix again
// and remaining data without prefix, which is either plain or still encoded with password
prefixString = GetPrefixStringFromData(ref sebData);
}
// Prefix = pswd ("Password") ?
if (prefixString.CompareTo(PASSWORD_MODE) == 0)
{
// Decrypt with password
// if the user enters the right one
byte[] sebDataDecrypted = null;
string password;
// Allow up to 5 attempts for entering decoding password
string enterPasswordString = SEBUIStrings.enterPassword;
int i = 5;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings, enterPasswordString);
if (password == null) return null;
//error = nil;
sebDataDecrypted = SEBProtectionController.DecryptDataWithPassword(sebData, password);
enterPasswordString = SEBUIStrings.enterPasswordAgain;
// in case we get an error we allow the user to try it again
} while ((sebDataDecrypted == null) && i > 0);
if (sebDataDecrypted == null)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.decryptingSettingsFailedReason, SEBUIStrings.decryptingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
sebData = sebDataDecrypted;
// If these settings are being decrypted for editing, we return the decryption password
if (forEditing) sebFilePassword = password;
}
else
{
// Prefix = pwcc ("Password Configuring Client") ?
if (prefixString.CompareTo(PASSWORD_CONFIGURING_CLIENT_MODE) == 0)
{
// Decrypt with password and configure local client settings
// and quit afterwards, returning if reading the .seb file was successfull
DictObj sebSettings = DecryptDataWithPasswordForConfiguringClient(sebData, forEditing, ref sebFilePassword, ref passwordIsHash);
return sebSettings;
}
else
{
// Prefix = plnd ("Plain Data") ?
if (prefixString.CompareTo(PLAIN_DATA_MODE) != 0)
{
// No valid 4-char prefix was found in the .seb file
// Check if .seb file is unencrypted
if (prefixString.CompareTo(UNENCRYPTED_MODE) == 0)
{
// .seb file seems to be an unencrypted XML plist
// get the original data including the first 4 bytes
sebData = sebDataUnencrypted;
}
else
{
// No valid prefix and no unencrypted file with valid header
// cancel reading .seb file
if (!suppressFileFormatError)
{
MessageBox.Show(SEBUIStrings.settingsNotUsableReason, SEBUIStrings.settingsNotUsable, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return null;
}
}
}
}
// If we don't deal with an unencrypted seb file
// ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
if (prefixString.CompareTo(UNENCRYPTED_MODE) != 0)
{
sebData = GZipByte.Decompress(sebData);
}
// Get preferences dictionary from decrypted data
DictObj sebPreferencesDict = GetPreferencesDictFromConfigData(sebData, forEditing);
DictObj sebPreferencesDictOriginal = GetPreferencesDictFromConfigData(sebData, false);
// If we didn't get a preferences dict back, we abort reading settings
if (sebPreferencesDict == null) return null;
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method which decrypts the byte array using an empty password,
/// or the administrator password currently set in SEB
/// or asks for the password used for encrypting this SEB file
/// for configuring the client
/// </summary>
/// ----------------------------------------------------------------------------------------
private static DictObj DecryptDataWithPasswordForConfiguringClient(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash)
{
passwordIsHash = false;
string password;
// First try to decrypt with the current admin password
// get admin password hash
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
if (hashedAdminPassword == null)
{
hashedAdminPassword = "";
}
// We use always uppercase letters in the base16 hashed admin password used for encrypting
hashedAdminPassword = hashedAdminPassword.ToUpper();
DictObj sebPreferencesDict = null;
DictObj sebPreferencesDictOriginal = null;
byte[] decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedAdminPassword);
if (decryptedSebData == null)
{
// If decryption with admin password didn't work, try it with an empty password
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, "");
if (decryptedSebData == null)
{
// If decryption with empty and admin password didn't work, ask for the password the .seb file was encrypted with
// Allow up to 5 attempts for entering decoding password
int i = 5;
password = null;
string enterPasswordString = SEBUIStrings.enterEncryptionPassword;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
// If cancel was pressed, abort
if (password == null) return null;
string hashedPassword = SEBProtectionController.ComputePasswordHash(password);
// we try to decrypt with the hashed password
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedPassword);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterEncryptionPasswordAgain;
} while (decryptedSebData == null && i > 0);
if (decryptedSebData == null)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongPassword, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
else
{
// Decrypting with entered password worked: We save it for returning it later
if (forEditing) sebFilePassword = password;
}
}
}
else
{
//decrypting with hashedAdminPassword worked: we save it for returning as decryption password
sebFilePassword = hashedAdminPassword;
// identify that password as hash
passwordIsHash = true;
}
/// Decryption worked
// Ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
decryptedSebData = GZipByte.Decompress(decryptedSebData);
// Check if the openend reconfiguring seb file has the same admin password inside like the current one
try
{
sebPreferencesDict = (DictObj)Plist.readPlist(decryptedSebData);
sebPreferencesDictOriginal = (DictObj)Plist.readPlist(decryptedSebData);
}
catch (Exception readPlistException)
{
// Error when deserializing the decrypted configuration data
// We abort reading the new settings here
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine(readPlistException.Message);
return null;
}
// Get the admin password set in these settings
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
if (sebFileHashedAdminPassword == null)
{
sebFileHashedAdminPassword = "";
}
// Has the SEB config file the same admin password inside as the current settings have?
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
{
//No: The admin password inside the .seb file wasn't the same as the current one
if (forEditing)
{
// If the file is openend for editing (and not to reconfigure SEB)
// we have to ask the user for the admin password inside the file
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
{
// If the user didn't enter the right password we abort
return null;
}
}
else
{
// The file was actually opened for reconfiguring the SEB client:
// we have to ask for the current admin password and
// allow reconfiguring only if the user enters the right one
// We don't check this for the case the current admin password was used to encrypt the new settings
// In this case there can be a new admin pw defined in the new settings and users don't need to enter the old one
if (passwordIsHash == false && hashedAdminPassword.Length > 0)
{
// Allow up to 5 attempts for entering current admin password
int i = 5;
password = null;
string hashedPassword;
string enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguring;
bool passwordsMatch;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
// If cancel was pressed, abort
if (password == null) return null;
if (password.Length == 0)
{
hashedPassword = "";
}
else
{
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
}
passwordsMatch = (String.Compare(hashedPassword, hashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguringAgain;
} while (!passwordsMatch && i > 0);
if (!passwordsMatch)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongCurrentAdminPwd, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
}
}
}
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method: Get preferences dictionary from decrypted data.
/// In editing mode, users have to enter the right SEB administrator password
/// before they can access the settings contents
/// and returns the decrypted bytes
/// </summary>
/// ----------------------------------------------------------------------------------------
private static DictObj GetPreferencesDictFromConfigData(byte[] sebData, bool forEditing)
{
DictObj sebPreferencesDict = null;
try
{
// Get preferences dictionary from decrypted data
sebPreferencesDict = (DictObj)Plist.readPlist(sebData);
}
catch (Exception readPlistException)
{
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine(readPlistException.Message);
return null;
}
// In editing mode, the user has to enter the right SEB administrator password used in those settings before he can access their contents
if (forEditing)
{
// Get the admin password set in these settings
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
// If there was no or empty admin password set in these settings, the user can access them anyways
if (!String.IsNullOrEmpty(sebFileHashedAdminPassword))
{
// Get the current hashed admin password
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
if (hashedAdminPassword == null)
{
hashedAdminPassword = "";
}
// If the current hashed admin password is same as the hashed admin password from the settings file
// then the user is allowed to access the settings
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
{
// otherwise we have to ask for the SEB administrator password used in those settings and
// allow opening settings only if the user enters the right one
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
{
return null;
}
}
}
}
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Ask user to enter password and compare it to the passed (hashed) password string
/// Returns true if correct password was entered
/// </summary>
/// ----------------------------------------------------------------------------------------
private static bool askForPasswordAndCompareToHashedPassword(string sebFileHashedAdminPassword, bool forEditing)
{
// Check if there wasn't a hashed password (= empty password)
if (sebFileHashedAdminPassword.Length == 0) return true;
// We have to ask for the SEB administrator password used in the settings
// and allow opening settings only if the user enters the right one
// Allow up to 5 attempts for entering admin password
int i = 5;
string password = null;
string hashedPassword;
string enterPasswordString = SEBUIStrings.enterAdminPasswordRequired;
bool passwordsMatch;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings + (String.IsNullOrEmpty(SEBClientInfo.LoadingSettingsFileName) ? "" : ": " + SEBClientInfo.LoadingSettingsFileName), enterPasswordString);
// If cancel was pressed, abort
if (password == null) return false;
if (password.Length == 0)
{
hashedPassword = "";
}
else
{
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
}
passwordsMatch = (String.Compare(hashedPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterAdminPasswordRequiredAgain;
} while ((password == null || !passwordsMatch) && i > 0);
if (!passwordsMatch)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.loadingSettingsFailedWrongAdminPwd, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// Right password entered
return passwordsMatch;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method which fetches the public key hash from a byte array,
/// retrieves the according cryptographic identity from the certificate store
/// and returns the decrypted bytes
/// </summary>
/// ----------------------------------------------------------------------------------------
private static byte[] DecryptDataWithPublicKeyHashPrefix(byte[] sebData, bool usingSymmetricKey, bool forEditing, ref X509Certificate2 sebFileCertificateRef)
{
// Get 20 bytes public key hash prefix
// and remaining data with the prefix stripped
byte[] publicKeyHash = GetPrefixDataFromData(ref sebData, PUBLIC_KEY_HASH_LENGTH);
X509Certificate2 certificateRef = SEBProtectionController.GetCertificateFromStore(publicKeyHash);
if (certificateRef == null)
{
MessageBox.Show(SEBUIStrings.certificateNotFoundInStore, SEBUIStrings.errorDecryptingSettings, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
// If these settings are being decrypted for editing, we will return the decryption certificate reference
// in the variable which was passed as reference when calling this method
if (forEditing) sebFileCertificateRef = certificateRef;
// Are we using the new identity certificate decryption with a symmetric key?
if (usingSymmetricKey)
{
// Get length of the encrypted symmetric key
Int32 encryptedSymmetricKeyLength = BitConverter.ToInt32(GetPrefixDataFromData(ref sebData, sizeof(Int32)), 0);
// Get encrypted symmetric key
byte[] encryptedSymmetricKey = GetPrefixDataFromData(ref sebData, encryptedSymmetricKeyLength);
// Decrypt symmetric key
byte[] symmetricKey = SEBProtectionController.DecryptDataWithCertificate(encryptedSymmetricKey, certificateRef);
if (symmetricKey == null)
{
return null;
}
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
// Decrypt config file data using the symmetric key as password
sebData = SEBProtectionController.DecryptDataWithPassword(sebData, symmetricKeyString);
}
else
{
sebData = SEBProtectionController.DecryptDataWithCertificate(sebData, certificateRef);
}
return sebData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method for returning a prefix string (of PREFIX_LENGTH, currently 4 chars)
/// from a data byte array which is returned without the stripped prefix
/// </summary>
/// ----------------------------------------------------------------------------------------
public static string GetPrefixStringFromData(ref byte[] data)
{
string decryptedDataString = Encoding.UTF8.GetString(GetPrefixDataFromData(ref data, PREFIX_LENGTH));
return decryptedDataString;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method for stripping (and returning) a prefix byte array of prefixLength
/// from a data byte array which is returned without the stripped prefix
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] GetPrefixDataFromData(ref byte[] data, int prefixLength)
{
// Get prefix with indicated length
byte[] prefixData = new byte[prefixLength];
Buffer.BlockCopy(data, 0, prefixData, 0, prefixLength);
// Get data without the stripped prefix
byte[] dataStrippedKey = new byte[data.Length - prefixLength];
Buffer.BlockCopy(data, prefixLength, dataStrippedKey, 0, data.Length - prefixLength);
data = dataStrippedKey;
return prefixData;
}
///// ----------------------------------------------------------------------------------------
///// <summary>
///// Show SEB Password Dialog Form.
///// </summary>
///// ----------------------------------------------------------------------------------------
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
//{
// // Set the title of the dialog window
// sebPasswordDialogForm.Text = title;
// // Set the text of the dialog
// sebPasswordDialogForm.LabelText = passwordRequestText;
// sebPasswordDialogForm.txtSEBPassword.Focus();
// // If we are running in SebWindowsClient we need to activate it before showing the password dialog
// if (SEBClientInfo.SebWindowsClientForm != null) SebWindowsClientForm.SEBToForeground(); //SEBClientInfo.SebWindowsClientForm.Activate();
// // Show password dialog as a modal dialog and determine if DialogResult = OK.
// if (sebPasswordDialogForm.ShowDialog() == DialogResult.OK)
// {
// // Read the contents of testDialog's TextBox.
// string password = sebPasswordDialogForm.txtSEBPassword.Text;
// sebPasswordDialogForm.txtSEBPassword.Text = "";
// //sebPasswordDialogForm.txtSEBPassword.Focus();
// return password;
// }
// else
// {
// return null;
// }
//}
/// Generate Encrypted .seb Settings Data
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Read SEB settings from UserDefaults and encrypt them using provided security credentials
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] EncryptSEBSettingsWithCredentials(string settingsPassword, bool passwordIsHash, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption, SEBSettings.sebConfigPurposes configPurpose, bool forEditing)
{
// Get current settings dictionary and clean it from empty arrays and dictionaries
//DictObj cleanedCurrentSettings = SEBSettings.CleanSettingsDictionary();
SEBSettings.settingsCurrentOriginal = SEBSettings.settingsCurrent;
// Serialize preferences dictionary to an XML string
string sebXML = Plist.writeXml(SEBSettings.settingsCurrent);
string cleanedSebXML = sebXML.Replace("<array />", "<array></array>");
cleanedSebXML = cleanedSebXML.Replace("<dict />", "<dict></dict>");
cleanedSebXML = cleanedSebXML.Replace("<data />", "<data></data>");
byte[] encryptedSebData = Encoding.UTF8.GetBytes(cleanedSebXML);
string encryptingPassword = null;
// Check for special case: .seb configures client, empty password
if (String.IsNullOrEmpty(settingsPassword) && configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient)
{
encryptingPassword = "";
}
else
{
// in all other cases:
// Check if no password entered and no identity selected
if (String.IsNullOrEmpty(settingsPassword) && certificateRef == null)
{
if (MessageBox.Show(SEBUIStrings.noEncryptionChosenSaveUnencrypted, SEBUIStrings.noEncryptionChosen, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
// OK: save .seb config data unencrypted
return encryptedSebData;
}
else
{
return null;
}
}
}
// gzip the serialized XML data
encryptedSebData = GZipByte.Compress(encryptedSebData);
// Check if password for encryption is provided and use it then
if (!String.IsNullOrEmpty(settingsPassword))
{
encryptingPassword = settingsPassword;
}
// So if password is empty (special case) or provided
if (!(encryptingPassword == null))
{
// encrypt with password
encryptedSebData = EncryptDataUsingPassword(encryptedSebData, encryptingPassword, passwordIsHash, configPurpose);
}
else
{
// Create byte array large enough to hold prefix and data
byte[] encryptedData = new byte[encryptedSebData.Length + PREFIX_LENGTH];
// if no encryption with password: Add a 4-char prefix identifying plain data
string prefixString = PLAIN_DATA_MODE;
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedData, 0, PREFIX_LENGTH);
// append plain data
Buffer.BlockCopy(encryptedSebData, 0, encryptedData, PREFIX_LENGTH, encryptedSebData.Length);
encryptedSebData = (byte[])encryptedData.Clone();
}
// Check if cryptographic identity for encryption is selected
if (certificateRef != null)
{
// Encrypt preferences using a cryptographic identity
encryptedSebData = EncryptDataUsingIdentity(encryptedSebData, certificateRef, useAsymmetricOnlyEncryption);
}
// gzip the encrypted data
encryptedSebData = GZipByte.Compress(encryptedSebData);
return encryptedSebData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Encrypt preferences using a certificate
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] EncryptDataUsingIdentity(byte[] data, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption)
{
// Get public key hash from selected identity's certificate
string prefixString;
byte[] publicKeyHash = SEBProtectionController.GetPublicKeyHashFromCertificate(certificateRef);
byte[] encryptedData;
byte[] encryptedKeyLengthBytes = new byte[0];
byte[] encryptedKey = new byte[0];
byte[] encryptedSEBConfigData;
if (!useAsymmetricOnlyEncryption)
{
prefixString = PUBLIC_SYMMETRIC_KEY_MODE;
// For new asymmetric/symmetric encryption create a random symmetric key
byte[] symmetricKey = AESThenHMAC.NewKey();
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
// Encrypt the symmetric key using the identity certificate
encryptedKey = SEBProtectionController.EncryptDataWithCertificate(symmetricKey, certificateRef);
// Get length of the encrypted key
encryptedKeyLengthBytes = BitConverter.GetBytes(encryptedKey.Length);
//encrypt data using symmetric key
encryptedData = SEBProtectionController.EncryptDataWithPassword(data, symmetricKeyString);
}
else
{
prefixString = PUBLIC_KEY_HASH_MODE;
//encrypt data using public key
encryptedData = SEBProtectionController.EncryptDataWithCertificate(data, certificateRef);
}
// Create byte array large enough to hold prefix, public key hash, length of and encrypted symmetric key plus encrypted data
encryptedSEBConfigData = new byte[PREFIX_LENGTH + publicKeyHash.Length + encryptedKeyLengthBytes.Length + encryptedKey.Length + encryptedData.Length];
int destinationOffset = 0;
// Copy prefix indicating data has been encrypted with a public key identified by hash into out data
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSEBConfigData, destinationOffset, PREFIX_LENGTH);
destinationOffset += PREFIX_LENGTH;
// Copy public key hash to out data
Buffer.BlockCopy(publicKeyHash, 0, encryptedSEBConfigData, destinationOffset, publicKeyHash.Length);
destinationOffset += publicKeyHash.Length;
// Copy length of encrypted symmetric key to out data
Buffer.BlockCopy(encryptedKeyLengthBytes, 0, encryptedSEBConfigData, destinationOffset, encryptedKeyLengthBytes.Length);
destinationOffset += encryptedKeyLengthBytes.Length;
// Copy encrypted symmetric key to out data
Buffer.BlockCopy(encryptedKey, 0, encryptedSEBConfigData, destinationOffset, encryptedKey.Length);
destinationOffset += encryptedKey.Length;
// Copy encrypted data to out data
Buffer.BlockCopy(encryptedData, 0, encryptedSEBConfigData, destinationOffset, encryptedData.Length);
return encryptedSEBConfigData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Encrypt preferences using a password
/// </summary>
/// ----------------------------------------------------------------------------------------
// Encrypt preferences using a password
public static byte[] EncryptDataUsingPassword(byte[] data, string password, bool passwordIsHash, SEBSettings.sebConfigPurposes configPurpose)
{
string prefixString;
// Check if .seb file should start exam or configure client
if (configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam)
{
// prefix string for starting exam: normal password will be prompted
prefixString = PASSWORD_MODE;
}
else
{
// prefix string for configuring client: configuring password will either be hashed admin pw on client
// or if no admin pw on client set: empty pw
prefixString = PASSWORD_CONFIGURING_CLIENT_MODE;
if (!String.IsNullOrEmpty(password) && !passwordIsHash)
{
//empty password means no admin pw on clients and should not be hashed
//or we got already a hashed admin pw as settings pw, then we don't hash again
password = SEBProtectionController.ComputePasswordHash(password);
}
}
byte[] encryptedData = SEBProtectionController.EncryptDataWithPassword(data, password);
// Create byte array large enough to hold prefix and data
byte[] encryptedSebData = new byte[encryptedData.Length + PREFIX_LENGTH];
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSebData, 0, PREFIX_LENGTH);
Buffer.BlockCopy(encryptedData, 0, encryptedSebData, PREFIX_LENGTH, encryptedData.Length);
return encryptedSebData;
}
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Compressing and decompressing byte arrays using gzip
/// </summary>
/// ----------------------------------------------------------------------------------------
public static class GZipByte
{
public static byte[] Compress(byte[] input)
{
using (MemoryStream output = new MemoryStream())
{
using (GZipStream zip = new GZipStream(output, CompressionMode.Compress))
{
zip.Write(input, 0, input.Length);
}
return output.ToArray();
}
}
public static byte[] Decompress(byte[] input)
{
try
{
using (GZipStream stream = new GZipStream(new MemoryStream(input),
CompressionMode.Decompress))
{
const int size = 4096;
byte[] buffer = new byte[size];
using (MemoryStream output = new MemoryStream())
{
int count = 0;
do
{
count = stream.Read(buffer, 0, size);
if (count > 0)
{
output.Write(buffer, 0, count);
}
}
while (count > 0);
return output.ToArray();
}
}
}
catch (Exception)
{
return null;
}
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Show SEB Password Dialog Form.
/// </summary>
/// ----------------------------------------------------------------------------------------
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
//{
// Thread sf= new Thread(new ThreadStart(SebPasswordDialogForm.ShowPasswordDialogForm);
// sf.Start();
//}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,344 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
using DictObj = System.Collections.Generic.Dictionary<string, object>;
using ListObj = System.Collections.Generic.List<object>;
namespace SebWindowsConfig.Utilities
{
public class SEBURLFilter
{
public bool enableURLFilter;
public bool enableContentFilter;
public ListObj permittedList = new ListObj();
public ListObj prohibitedList = new ListObj();
// Updates filter rule arrays with current settings
public void UpdateFilterRules()
{
if (prohibitedList.Count != 0)
{
prohibitedList.Clear();
}
if (permittedList.Count != 0)
{
permittedList.Clear();
}
enableURLFilter = (bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnable];
enableContentFilter = (bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnableContentFilter];
// Add global URLFilterRules
ListObj URLFilterRules = (ListObj)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterRules];
ReadURLFilterRules(URLFilterRules);
// Add URLFilterRules from additional resources
ListObj additionalResources = (ListObj)SEBSettings.settingsCurrent[SEBSettings.KeyAdditionalResources];
ReadFilterRulesFromAdditionalResources(additionalResources);
// If URL filtering is enabled, then
// check if Start URL gets allowed by current filter rules and if not add a rule for the Start URL
string startURLString = (string)SEBSettings.settingsCurrent[SEBSettings.KeyStartURL];
if (enableURLFilter && Uri.TryCreate(startURLString, UriKind.Absolute, out Uri startURL))
{
if (TestURLAllowed(startURL) != URLFilterRuleActions.allow)
{
SEBURLFilterRegexExpression expression;
// If Start URL is not allowed: Create one using the full Start URL
try
{
expression = new SEBURLFilterRegexExpression(startURLString);
}
catch (Exception ex)
{
Logger.AddError("Could not create SEBURLFilterRegexExpression: ", this, ex, ex.Message);
prohibitedList.Clear();
permittedList.Clear();
// Convert these rules and add them to the XULRunner seb keys
CreateSebRuleLists();
return;
}
// Add this Start URL filter expression to the permitted filter list
permittedList.Add(expression);
}
}
// Convert these rules and add them to the XULRunner seb keys
CreateSebRuleLists();
}
public void ReadURLFilterRules(ListObj URLFilterRules)
{
foreach (DictObj URLFilterRule in URLFilterRules)
{
if (URLFilterRule.ContainsKey(SEBSettings.KeyURLFilterRuleRegex) && (bool)URLFilterRule[SEBSettings.KeyURLFilterRuleActive] == true)
{
string expressionString = (string)URLFilterRule[SEBSettings.KeyURLFilterRuleExpression];
if (!String.IsNullOrEmpty(expressionString))
{
Object expression;
bool regex = (bool)URLFilterRule[SEBSettings.KeyURLFilterRuleRegex];
try
{
if (regex)
{
expression = new Regex(expressionString, RegexOptions.IgnoreCase);
}
else
{
expression = new SEBURLFilterRegexExpression(expressionString);
}
}
catch (Exception ex)
{
Logger.AddError("Could not create SEBURLFilterRegexExpression: ", this, ex, ex.Message);
prohibitedList.Clear();
permittedList.Clear();
throw;
}
int action = (int)URLFilterRule[SEBSettings.KeyURLFilterRuleAction];
switch (action)
{
case (int)URLFilterRuleActions.block:
prohibitedList.Add(expression);
break;
case (int)URLFilterRuleActions.allow:
permittedList.Add(expression);
break;
}
}
}
}
}
// Read URLFilterRules from additionalResources
public void ReadFilterRulesFromAdditionalResources(ListObj additionalResources)
{
foreach (DictObj additionalResource in additionalResources)
{
if ((bool)additionalResource[SEBSettings.KeyAdditionalResourcesActive])
{
object URLFilterRules;
if (additionalResource.TryGetValue(SEBSettings.KeyURLFilterRules, out URLFilterRules))
{
ReadURLFilterRules((ListObj)URLFilterRules);
}
// Are there further additional resources in this additional resource?
if (additionalResource.TryGetValue(SEBSettings.KeyAdditionalResources, out object additionalSubResources))
{
if (((ListObj)additionalSubResources).Count != 0)
{
ReadFilterRulesFromAdditionalResources((ListObj)additionalSubResources);
}
}
}
}
}
// Convert these rules and add them to the XULRunner seb keys
public void CreateSebRuleLists()
{
// Set prohibited rules
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterBlacklist] = SebRuleStringForSEBURLFilterRuleList(prohibitedList);
// Set permitted rules
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterWhitelist] = SebRuleStringForSEBURLFilterRuleList(permittedList);
// All rules are regex
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterRulesAsRegex] = true;
// Set if content filter is enabled
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterTrustedContent] = !(bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnableContentFilter];
}
public string SebRuleStringForSEBURLFilterRuleList(ListObj filterRuleList)
{
if (filterRuleList.Count == 0)
{
// No rules defined
return "";
}
StringBuilder sebRuleString = new StringBuilder();
foreach (object expression in filterRuleList)
{
if (expression != null)
{
if (sebRuleString.Length == 0)
{
sebRuleString.Append(expression.ToString());
}
else
{
sebRuleString.AppendFormat(";{0}", expression.ToString());
}
}
}
return sebRuleString.ToString();
}
// Filter URL and return if it is allowed or blocked
public URLFilterRuleActions TestURLAllowed(Uri URLToFilter)
{
string URLToFilterString = URLToFilter.ToString();
// By default URLs are blocked
bool allowURL = false;
bool blockURL = false;
/// Apply current filter rules (expressions/actions) to URL
/// Apply prohibited filter expressions
foreach (object expression in prohibitedList)
{
if (expression.GetType().Equals(typeof(Regex)))
{
if (Regex.IsMatch(URLToFilterString, expression.ToString()))
{
blockURL = true;
break;
}
}
if (expression.GetType().Equals(typeof(SEBURLFilterRegexExpression)))
{
if (URLMatchesFilterExpression(URLToFilter, (SEBURLFilterRegexExpression)expression))
{
blockURL = true;
break;
}
}
}
if (blockURL == true)
{
return URLFilterRuleActions.block;
}
/// Apply permitted filter expressions
foreach (object expression in permittedList)
{
if (expression.GetType().Equals(typeof(Regex)))
{
if (Regex.IsMatch(URLToFilterString, expression.ToString()))
{
allowURL = true;
break;
}
}
if (expression.GetType().Equals(typeof(SEBURLFilterRegexExpression)))
{
if (URLMatchesFilterExpression(URLToFilter, (SEBURLFilterRegexExpression)expression))
{
allowURL = true;
break;
}
}
}
// Return URLFilterActionAllow if URL is allowed or
// URLFilterActionUnknown if it's unknown (= it will anyways be blocked)
return allowURL ? URLFilterRuleActions.allow : URLFilterRuleActions.unknown;
}
// Method comparing all components of a passed URL with the filter expression
// and returning YES (= allow or block) if it matches
public bool URLMatchesFilterExpression(Uri URLToFilter, SEBURLFilterRegexExpression filterExpression)
{
Regex filterComponent;
// If a scheme is indicated in the filter expression, it has to match
filterComponent = filterExpression.scheme;
UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
return false;
}
string userInfo = URLToFilter.UserInfo;
filterComponent = filterExpression.user;
if (filterComponent != null &&
!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
filterComponent = filterExpression.password;
if (filterComponent != null &&
!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
filterComponent = filterExpression.host;
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
if (filterExpression.port != null && URLToFilter.Port != filterExpression.port)
{
return false;
}
filterComponent = filterExpression.path;
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.AbsolutePath.TrimEnd(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
filterComponent = filterExpression.query;
if (filterComponent != null)
{
// If there's a query filter component, then we need to even filter empty URL query strings
// as the filter might either allow some specific queries or no query at all ("?." query filter)
if (urlQuery == null)
{
urlQuery = "";
}
if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
}
string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
filterComponent = filterExpression.fragment;
if (filterComponent != null &&
!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
// URL matches the filter expression
return true;
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
namespace SebWindowsConfig.Utilities
{
public class SEBURLFilterExpression
{
public string scheme;
public string user;
public string password;
public string host;
public int? port;
public string path;
public string query;
public string fragment;
public SEBURLFilterExpression(string filterExpressionString)
{
if (!string.IsNullOrEmpty(filterExpressionString))
{
/// Convert Uri to a SEBURLFilterExpression
string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
Regex splitURLRegex = new Regex(splitURLRegexPattern);
Match regexMatch = splitURLRegex.Match(filterExpressionString);
if (regexMatch.Success == false)
{
return;
}
this.scheme = regexMatch.Groups[1].Value;
this.user = regexMatch.Groups[2].Value;
this.password = regexMatch.Groups[3].Value;
this.host = regexMatch.Groups[4].Value;
// Treat a special case when a query or fragment is interpreted as part of the host address
if (this.host.Contains("?") || this.host.Contains("#"))
{
string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
Match regexMatch2 = splitURLRegex2.Match(this.host);
if (regexMatch.Success == false)
{
return;
}
this.host = regexMatch2.Groups[1].Value;
this.port = null;
this.path = "";
this.query = regexMatch2.Groups[2].Value;
this.fragment = regexMatch2.Groups[3].Value;
}
else
{
string portNumber = regexMatch.Groups[5].Value;
// We only want a port if the filter expression string explicitely defines one!
if (portNumber.Length == 0 || portNumber == "*")
{
this.port = null;
}
else
{
this.port = UInt16.Parse(portNumber);
}
this.path = regexMatch.Groups[6].Value.TrimEnd(new char[] { '/' });
this.query = regexMatch.Groups[7].Value;
this.fragment = regexMatch.Groups[8].Value;
}
}
}
public static string User(string userInfo)
{
string user = "";
if (!string.IsNullOrEmpty(userInfo))
{
int userPasswordSeparator = userInfo.IndexOf(":");
if (userPasswordSeparator == -1)
{
user = userInfo;
}
else
{
if (userPasswordSeparator != 0)
{
user = userInfo.Substring(0, userPasswordSeparator);
}
}
}
return user;
}
public static string Password(string userInfo)
{
string password = "";
if (!string.IsNullOrEmpty(userInfo))
{
int userPasswordSeparator = userInfo.IndexOf(":");
if (userPasswordSeparator != -1)
{
if (userPasswordSeparator < userInfo.Length - 1)
{
password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
}
}
}
return password;
}
public SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
{
this.scheme = scheme;
this.user = user;
this.password = password;
this.host = host;
this.port = port;
this.path = path;
this.query = query;
this.fragment = fragment;
}
public override string ToString()
{
StringBuilder expressionString = new StringBuilder();
if (!string.IsNullOrEmpty(this.scheme)) {
if (!string.IsNullOrEmpty(this.host)) {
expressionString.AppendFormat("{0}://", this.scheme);
} else {
expressionString.AppendFormat("{0}:", this.scheme);
}
}
if (!string.IsNullOrEmpty(this.user)) {
expressionString.Append(this.user);
if (!string.IsNullOrEmpty(this.password)) {
expressionString.AppendFormat(":{0}@", this.password);
} else {
expressionString.Append("@");
}
}
if (!string.IsNullOrEmpty(this.host)) {
expressionString.Append(this.host);
}
if (this.port != null && this.port > 0 && this.port <= 65535) {
expressionString.AppendFormat(":{0}", this.port);
}
if (!string.IsNullOrEmpty(this.path)) {
if (this.path.StartsWith("/")) {
expressionString.Append(this.path);
} else {
expressionString.AppendFormat("/{0}", this.path);
}
}
if (!string.IsNullOrEmpty(this.query)) {
expressionString.AppendFormat("?{0}", this.query);
}
if (!string.IsNullOrEmpty(this.fragment)) {
expressionString.AppendFormat("#{0}", this.fragment);
}
return expressionString.ToString();
}
}
}

View File

@@ -0,0 +1,298 @@
using System;
using System.Text;
using System.Text.RegularExpressions;
namespace SebWindowsConfig.Utilities
{
public class SEBURLFilterRegexExpression
{
public Regex scheme;
public Regex user;
public Regex password;
public Regex host;
public int? port;
public Regex path;
public Regex query;
public Regex fragment;
public SEBURLFilterRegexExpression(string filterExpressionString)
{
SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
try
{
this.scheme = RegexForFilterString(URLFromString.scheme);
this.user = RegexForFilterString(URLFromString.user);
this.password = RegexForFilterString(URLFromString.password);
this.host = RegexForHostFilterString(URLFromString.host);
this.port = URLFromString.port;
this.path = RegexForPathFilterString(URLFromString.path);
this.query = RegexForQueryFilterString(URLFromString.query);
this.fragment = RegexForFilterString(URLFromString.fragment);
}
catch (Exception)
{
throw;
}
}
public static Regex RegexForFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^{0}$", regexString);
try
{
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
}
public static Regex RegexForHostFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
try
{
// Check if host string has a dot "." prefix to disable subdomain matching
if (filterString.Length > 1 && filterString.StartsWith("."))
{
// Get host string without the "." prefix
filterString = filterString.Substring(1);
// Get regex for host <*://example.com> (without possible subdomains)
return RegexForFilterString(filterString);
}
// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
}
public static Regex RegexForPathFilterString(string filterString)
{
// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
filterString = filterString.TrimEnd(new char[] { '/' });
;
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
try
{
// Check if path string ends with a "/*" for matching contents of a directory
if (filterString.EndsWith("/*"))
{
// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
// Get path string without the "/*" suffix
string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
string regexStringDir = Regex.Escape(filterString);
regexStringDir = regexStringDir.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
else
{
return RegexForFilterString(filterString);
}
}
catch (Exception)
{
throw;
}
}
}
public static Regex RegexForQueryFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
if (filterString.Equals("."))
{
// Add regex command characters for matching at start and end of a line (part)
// and regex for no string allowed
string regexString = @"^$";
try
{
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
else
{
return RegexForFilterString(filterString);
}
}
}
public override string ToString()
{
StringBuilder expressionString = new StringBuilder();
string part;
expressionString.Append("^");
/// Scheme
if (this.scheme != null)
{
// If there is a regex filter for scheme
// get stripped regex pattern
part = StringForRegexFilter(this.scheme);
}
else
{
// otherwise use the regex wildcard pattern for scheme
part = @".*?";
}
expressionString.AppendFormat("{0}:\\/\\/", part);
/// User/Password
if (this.user != null)
{
part = StringForRegexFilter(this.user);
expressionString.Append(part);
if (this.password != null)
{
expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
}
else
{
expressionString.Append("@");
}
}
/// Host
string hostPort = "";
if (this.host != null)
{
hostPort = StringForRegexFilter(this.host);
} else
{
hostPort = ".*?";
}
/// Port
if (this.port != null && this.port > 0 && this.port <= 65535)
{
hostPort = string.Format("{0}:{1}", hostPort, this.port);
}
// When there is a host, but no path
if (this.host != null && this.path == null)
{
hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
}
expressionString.Append(hostPort);
/// Path
if (this.path != null)
{
string path = StringForRegexFilter(this.path);
if (path.StartsWith("\\/"))
{
expressionString.Append(path);
}
else
{
expressionString.AppendFormat("\\/{0}", path);
}
}
/// Query
if (this.query != null)
{
// Check for special case Query = "?." which means no query string is allowed
if (StringForRegexFilter(this.query).Equals("."))
{
expressionString.AppendFormat("[^\\?]");
} else
{
expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
}
} else
{
expressionString.AppendFormat("(()|(\\?.*?))");
}
/// Fragment
if (this.fragment != null)
{
expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
}
expressionString.Append("$");
return expressionString.ToString();
}
public string StringForRegexFilter(Regex regexFilter)
{
// Get pattern string from regular expression
string regexPattern = regexFilter.ToString();
if (regexPattern.Length <= 2)
{
return "";
}
// Remove the regex command characters for matching at start and end of a line
regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
return regexPattern;
}
}
}