From 78f77d7dd36cf6928115df3b30ed7c6384bc6f5e Mon Sep 17 00:00:00 2001 From: william-stacken Date: Fri, 6 Aug 2021 13:12:18 +0200 Subject: [PATCH] log file for exceptions, delete all logs, disable stdout --- .../TiriryaraiMitm/Properties/AssemblyInfo.cs | 2 +- .../TuxEverywhere/Properties/AssemblyInfo.cs | 2 +- README.md | 2 + Tiriryarai/Http/HttpMessage.cs | 2 +- Tiriryarai/Program.cs | 28 ++--- Tiriryarai/Server/HttpsMitmProxy.cs | 98 +++++++++++++---- Tiriryarai/Util/HttpsMitmProxyCache.cs | 5 +- Tiriryarai/Util/HttpsMitmProxyConfig.cs | 30 +++-- Tiriryarai/Util/Logger.cs | 104 ++++++++---------- Tiriryarai/Util/Resources.cs | 13 ++- 10 files changed, 171 insertions(+), 115 deletions(-) diff --git a/Plugins/TiriryaraiMitm/Properties/AssemblyInfo.cs b/Plugins/TiriryaraiMitm/Properties/AssemblyInfo.cs index c1f1a0a..42d8bcc 100644 --- a/Plugins/TiriryaraiMitm/Properties/AssemblyInfo.cs +++ b/Plugins/TiriryaraiMitm/Properties/AssemblyInfo.cs @@ -39,7 +39,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.*")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/Plugins/TuxEverywhere/Properties/AssemblyInfo.cs b/Plugins/TuxEverywhere/Properties/AssemblyInfo.cs index 0a9a9b8..318a832 100644 --- a/Plugins/TuxEverywhere/Properties/AssemblyInfo.cs +++ b/Plugins/TuxEverywhere/Properties/AssemblyInfo.cs @@ -38,7 +38,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.*")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/README.md b/README.md index 994bcf4..217dd30 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ The program creates the following folders and files: on an array of "log entries", where each log entry has a 16 byte initialization vector field, a 4 byte "length" field, followed by a "length" byte encrypted HTML representation of a HTTP message. Custom objects can also be logged by the custom plugins. + - `-Debug-.tirlog`: Log file that mainly contains stack traces and other useful information + for troubleshooting. - `-RootCA-.pfx`: PKCS12 file containing the Root CA certificate that will be used to sign certificates generated by Tiriryarai. The Root CA certificate needs to be installed in your client, refer to [6. How To Use](#6-how-to-use) for details. diff --git a/Tiriryarai/Http/HttpMessage.cs b/Tiriryarai/Http/HttpMessage.cs index 3f62d25..bb0166f 100644 --- a/Tiriryarai/Http/HttpMessage.cs +++ b/Tiriryarai/Http/HttpMessage.cs @@ -552,7 +552,7 @@ protected Dictionary ParseUrlEncoded(string urlEncoded) else if (i > 0) { key = keyVal.Substring(0, i).ToLower(); - val = keyVal.Substring(i + 1); + val = HttpUtility.UrlDecode(keyVal.Substring(i + 1)); } else { diff --git a/Tiriryarai/Program.cs b/Tiriryarai/Program.cs index 03e7a86..cd5ab4d 100644 --- a/Tiriryarai/Program.cs +++ b/Tiriryarai/Program.cs @@ -45,6 +45,7 @@ static void Main(string[] args) HttpsMitmProxy proxy = null; List extraOpts = null; Dictionary props = new Dictionary(); + Logger logger = Logger.GetSingleton(); try { HttpsMitmProxyConfig conf = HttpsMitmProxyConfig.GetSingleton(); @@ -96,34 +97,33 @@ static void Main(string[] args) Environment.Exit(-1); } - PrintStartup(); + if (!conf.DisableStdout) + PrintStartup(); if (!conf.Authenticate) { - Console.WriteLine("NOTICE: Authentication for accessing admin pages is disabled."); - Console.WriteLine("Hosting Tiriryarai on the public internet or an untrusted network is strongly discouraged."); - Console.WriteLine("If this was unintentional, see the help by using the \"-h\" flag."); - Console.WriteLine(); + logger.WriteStdout("NOTICE: Authentication for accessing admin pages is disabled."); + logger.WriteStdout("Hosting Tiriryarai on the public internet or an untrusted network is strongly discouraged."); + logger.WriteStdout("If this was unintentional, see the help by using the \"-h\" flag.\n"); } - Console.Write("Starting server and generating certificates... "); + logger.WriteStdout("Starting server and generating certificates... "); proxy = new HttpsMitmProxy(conf); - Console.WriteLine("Done"); - Console.WriteLine(); - Console.WriteLine("Tiriryarai has started!"); - Console.WriteLine("Configure your client to use host " + conf.Hostname + " and port " + conf.Port + " as a HTTP proxy."); - Console.WriteLine("Then open http://" + Resources.HOSTNAME + " for more information."); + logger.WriteStdout("Done\n"); + logger.WriteStdout("Tiriryarai has started!"); + logger.WriteStdout("Configure your client to use host " + conf.Hostname + " and port " + conf.Port + " as a HTTP proxy."); + logger.WriteStdout("Then open http://" + Resources.HOSTNAME + " for more information."); } catch (Exception e) { if (e is TargetInvocationException t) e = t.InnerException; - Console.WriteLine("\nFailed to initialize server:"); - Console.WriteLine(e.Message); + logger.WriteStdout("Failed to initialize server:"); + logger.WriteStdout(e.Message); Environment.Exit(-2); } proxy.Start(); - Console.WriteLine("Tiriryarai shut down..."); + logger.WriteStdout("Tiriryarai shut down..."); } private static void PrintStartup() diff --git a/Tiriryarai/Server/HttpsMitmProxy.cs b/Tiriryarai/Server/HttpsMitmProxy.cs index e1612db..00b440e 100755 --- a/Tiriryarai/Server/HttpsMitmProxy.cs +++ b/Tiriryarai/Server/HttpsMitmProxy.cs @@ -107,6 +107,11 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) } }}, {"favicon.ico", (req, resp) => { + if (!string.Empty.Equals(req.SubPath(1))) + { + DefaultNotFound(resp, req); + return; + } switch (req.Method) { case Method.HEAD: @@ -129,6 +134,11 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) logger.Log(15, req.Host, "OUTGOING INTERNAL RESPONSE", resp); }}, {"cert", (req, resp) => { + if (!string.Empty.Equals(req.SubPath(1))) + { + DefaultNotFound(resp, req); + return; + } switch (req.Method) { case Method.HEAD: @@ -154,6 +164,11 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) }}, {Resources.CA_ISSUER_PATH, (req, resp) => { + if (!string.Empty.Equals(req.SubPath(1))) + { + DefaultNotFound(resp, req); + return; + } logger.Log(8, req.Host, "INCOMMING ISSUER REQUEST", req); switch (req.Method) { @@ -179,6 +194,11 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) }}, {Resources.OCSP_PATH, (req, resp) => { + if (!string.Empty.Equals(req.SubPath(1))) + { + DefaultNotFound(resp, req); + return; + } logger.Log(8, req.Host, "INCOMMING OCSP REQUEST", req); switch (req.Method) { @@ -196,7 +216,8 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) } catch (Exception e) { - logger.LogException(e, req); + logger.LogDebug(10, e); + logger.LogDebug(10, req); } X509OCSPResponse ocspResp = ocspReq != null ? cache.GetOCSPResponse(ocspReq) : @@ -223,6 +244,11 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) }}, {Resources.CRL_PATH, (req, resp) => { + if (!string.Empty.Equals(req.SubPath(1))) + { + DefaultNotFound(resp, req); + return; + } logger.Log(8, req.Host, "INCOMMING CRL REQUEST", req); switch (req.Method) { @@ -255,7 +281,7 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) DateTime? ifModified = req.GetDateHeader("If-Modified-Since"); string logFile = req.SubPath(1); - if ("".Equals(logFile)) + if (string.Empty.Equals(logFile)) { // Request to log directory switch (req.Method) @@ -281,15 +307,35 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) string.Format(Resources.LOG_PAGE, entryBuilder) ), false, req); return; + case Method.POST: + if (!"application/x-www-form-urlencoded".Equals(req.ContentTypeWithoutCharset)) + { + DefaultBadMediaType(resp, req); + return; + } + else if ("on".Equals(req.GetBodyParam("sure")) && "Delete All".Equals(req.GetBodyParam("deleteall"))) + { + foreach (string log in logger.LogNames) + logger.DeleteLog(log); + } + break; + case Method.DELETE: + foreach (string log in logger.LogNames) + logger.DeleteLog(log); + break; case Method.OPTIONS: - DefaultOptions(resp, req); + DefaultOptions(resp, req, Method.POST, Method.DELETE); return; default: DefaultUnsupported(resp, req); return; } + resp.Status = 303; + resp.SetHeader("Location", "/logs"); + resp.ContentLength = 0; + return; } - else if (logger.Exists(logFile) && "".Equals(req.SubPath(2))) // Only one path level allowed + else if (logger.Exists(logFile) && string.Empty.Equals(req.SubPath(2))) // Only one path level allowed { switch (req.Method) { @@ -339,7 +385,7 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) DefaultNotFound(resp, req); }}, {"config", (req, resp) => { - if (conf.Configuration) + if (conf.Configuration && string.Empty.Equals(req.SubPath(1))) { logger.Log(8, req.Host, "INCOMMING CONFIG REQUEST", req); string value, description; @@ -413,7 +459,7 @@ public HttpsMitmProxy(HttpsMitmProxyConfig conf) } catch (Exception e) { - logger.LogException(e); + logger.LogDebug(5, e); success = false; } if (clearCache) @@ -521,14 +567,6 @@ private void ProcessClient(TcpClient client) } catch (Exception e) { - if (e is IOException || - e is ObjectDisposedException || - e.InnerException is IOException) - { - // Connection has become inactive or was closed by the remote - keepAlive = false; - break; - } resp = DefaultHttpResponse(400); resp.ToStream(stream); throw e; @@ -582,14 +620,17 @@ e is ObjectDisposedException || catch (Exception e) { if (e is IOException || + e is SocketException || e is ObjectDisposedException || - e.InnerException is IOException) + e.InnerException is IOException || + e is AggregateException) { // Connection has become inactive or was closed by the remote + logger.LogDebug(12, e); keepAlive = false; break; } - logger.LogException(e); + logger.LogDebug(8, e); resp = DefaultHttpResponse(400); } resp.ToStream(sslStream); @@ -599,7 +640,7 @@ e is ObjectDisposedException || } catch (Exception e) { - logger.LogException(e); + logger.LogDebug(13, e); } finally { @@ -638,7 +679,18 @@ e is ObjectDisposedException || } catch (Exception e) { - logger.LogException(e); + if (e is IOException || + e is SocketException || + e is ObjectDisposedException || + e.InnerException is IOException) + { + // Connection was probably closed by the remote + logger.LogDebug(15, e); + } + else + { + logger.LogDebug(8, e); + } } finally { @@ -652,8 +704,8 @@ private HttpResponse HandleRequest(HttpRequest req, HttpsClient destination, boo HttpMessage http; try { - Console.WriteLine("\n--------------------\n" + - req.Method + (tls ? " https://" : " http://") + destination.HostnameWithPort + req.Path); + logger.WriteStdout("\n--------------------\n" + + req.Method + (tls ? " https://" : " http://") + destination.HostnameWithPort); if (!conf.MitM.Block(destination.Hostname)) { logger.Log(3, destination.Hostname, "RECEIVED REQUEST", req); @@ -673,7 +725,7 @@ private HttpResponse HandleRequest(HttpRequest req, HttpsClient destination, boo if (e is IOException || e is SocketException) return DefaultHttpResponse(504); - logger.LogException(e); + logger.LogDebug(6, e); return DefaultHttpResponse(502); } if (modified.HeaderContains("Connection", "close") || resp.HeaderContains("Connection", "close")) @@ -702,7 +754,7 @@ private HttpResponse HandleRequest(HttpRequest req, HttpsClient destination, boo } catch (Exception e) { - logger.LogException(e); + logger.LogDebug(2, e); resp = DefaultHttpResponse(500, req); } return resp; @@ -799,7 +851,7 @@ private HttpResponse HomePage(HttpRequest req, string host, IPAddress client, bo } catch (Exception e) { - logger.LogException(e); + logger.LogDebug(2, e); resp = DefaultHttpResponse(500, req); } return resp; diff --git a/Tiriryarai/Util/HttpsMitmProxyCache.cs b/Tiriryarai/Util/HttpsMitmProxyCache.cs index 79fd47f..d84bb90 100644 --- a/Tiriryarai/Util/HttpsMitmProxyCache.cs +++ b/Tiriryarai/Util/HttpsMitmProxyCache.cs @@ -204,7 +204,7 @@ public X509Certificate2 GetRootCA() return AddOrGetExisting(rootCA, certPath => { if (!(certPath is string path)) throw new ArgumentException("certPath must be a string"); - Console.WriteLine("\n--------------------\nNOTICE: The root CA certificate has expired and will be replaced." + + logger.LogDebug(1, "NOTICE: The root CA certificate has expired and will be replaced." + "Please install the new certificate and remove the old one."); // TODO Clear the cache somehow since essentially everything in the cache is now invalid. File.Delete(path); @@ -558,7 +558,8 @@ private X509OCSPResponse CreateOCSPResponse(object id) } catch (Exception e) { - logger.LogException(e, certId); + logger.LogDebug(10, e); + logger.LogDebug(10, certId); ocsp = new X509OCSPResponse(X509OCSPResponse.ResponseStatus.MalformedRequest); } return new X509OCSPResponse(ocsp.Sign(ca)); diff --git a/Tiriryarai/Util/HttpsMitmProxyConfig.cs b/Tiriryarai/Util/HttpsMitmProxyConfig.cs index 9cd48b1..b6874ae 100644 --- a/Tiriryarai/Util/HttpsMitmProxyConfig.cs +++ b/Tiriryarai/Util/HttpsMitmProxyConfig.cs @@ -164,10 +164,10 @@ public byte[] PassKey [HttpsMitmProxy(HttpsMitmProxyProperty.Password | HttpsMitmProxyProperty.Authentication | HttpsMitmProxyProperty.Cache, "password", "w|password=")] [Description("The password required for accessing the admin pages if one should be required. " + - "It will be sent securely using HTTPS only. NOTICE: If this password is changed " + - "Tiriryarai will be unable to read any existing logs. If that is a concern, please back up the logs " + - "first. Furthermore, if updated at runtime, the cache will be cleared, meaning that you must re-install " + - "the root certificate.")] + "It will be sent securely using HTTPS only and can only be set if the username is set. " + + "NOTICE: If this password is changed, Tiriryarai will be unable to read any existing " + + "logs. If that is a concern, please back up the logs first. Furthermore, if updated at runtime, the " + + "cache will be cleared, meaning that you must re-install the root certificate.")] public string Password { set @@ -196,8 +196,9 @@ public byte[] ProxyPassKey } [HttpsMitmProxy(HttpsMitmProxyProperty.Password | HttpsMitmProxyProperty.Standard, "password", "x|proxy-pass=")] - [Description("The password required for using the proxy if one should be required. " + - "It will be sent insecurely using HTTP and SHOULD NOT be the same as the admin password.")] + [Description("The password required for using the proxy if one should be required. It will be sent " + + "insecurely using HTTP and can only be set if the username is set. Also, it SHOULD NOT " + + "be the same as the admin password.")] public string ProxyPassword { set @@ -326,11 +327,7 @@ public bool Configuration } private bool certignore; - /// - /// Gets or sets a value indicating whether invalid certificates should be ignored - /// by the HttpsClient. - /// - /// true if certificates should be ignored; otherwise, false. + [HttpsMitmProxy(HttpsMitmProxyProperty.Standard, "checkbox", "i|ignore-certs")] [Description("Ignore invalid certificates when sending HTTPS requests.")] public bool IgnoreCertificates @@ -455,6 +452,17 @@ public uint CachePollingInterval } } + private bool disablestdout; + + [HttpsMitmProxy(HttpsMitmProxyProperty.Standard, "checkbox", "q|quiet")] + [Description("Do not print anything to standard out, such as incomming requests " + + "or status information.")] + public bool DisableStdout + { + get { return disablestdout; } + set { disablestdout = value; LastModifiedTime = DateTime.UtcNow; } + } + [HttpsMitmProxy(HttpsMitmProxyProperty.None, null, null)] [Description("A flag indicating that Tiriryarai is undergoing maintenance. This could for " + "example happen due to the cache being cleared or another resource heavy operation.")] diff --git a/Tiriryarai/Util/Logger.cs b/Tiriryarai/Util/Logger.cs index f20d3f1..7932fa1 100644 --- a/Tiriryarai/Util/Logger.cs +++ b/Tiriryarai/Util/Logger.cs @@ -375,6 +375,33 @@ public void Log(uint level, string logname, string head, object obj) Task.Run(() => LogInternal(level, logname, head, obj)); } + /// + /// Logs an object to the debug log. + /// + /// The log level to use for the object. + /// Information object to log. + public void LogDebug(uint level, object info) + { + if (level < 1 || conf.LogVerbosity < level) + return; + string logname = "-Debug-"; + string head = "DEBUG"; + ThrowIfInvalid(level, logname, head, info); + + Task.Run(() => LogInternal(level, logname, head, info)); + } + + /// + /// Prints an object to standard out in case it is allowed by the + /// configuration. + /// + /// object to print. + public void WriteStdout(object o) + { + if (!conf.DisableStdout) + Console.WriteLine(o); + } + private void LogInternal(uint level, string logname, string head, object obj) { int attempts = 5; @@ -420,49 +447,9 @@ private void LogInternal(uint level, string logname, string head, object obj) } } } - catch (Exception e) - { - LogException(e); - } - } - - /// - /// Logs an exception to stdout. - /// - /// The exception. - public void LogException(Exception e) - { - LogException(e, null); - } - - /// - /// Logs an exception to stdout. - /// - /// The exception. - /// Optional information object to log. - public void LogException(Exception e, object info) - { - switch (conf.LogVerbosity) + catch { - case 0: - case 1: - case 2: - case 3: - break; - case 4: - case 5: - case 6: - case 7: - Console.WriteLine(e.Message); - if (info != null) - Console.WriteLine(info); - break; - default: - Console.WriteLine(e); - if (info != null) - Console.WriteLine(info); - break; - + WriteStdout("\n--------------------\nWARNING: Request to log timed out."); } } @@ -476,7 +463,7 @@ public string[] LogNames files[i] = Path.GetFileName(files[i]); files[i] = files[i].Substring(0, files[i].Length - LOG_SUFFIX.Length); } - Array.Sort(files, StringComparer.InvariantCulture); + Array.Sort(files, StringComparer.Ordinal); return files; } } @@ -555,27 +542,26 @@ public byte[] ReadLog(string logname) { for (int i = 0; i < attempts; i++) { - try + using (MemoryStream ms = new MemoryStream()) { - using (MemoryStream ms = new MemoryStream()) + FileStream s = null; + try { - using (var s = new FileStream(path, FileMode.Open)) - { - using (var logStream = new LogStream(s, conf.PassKey, mode: true)) - { - logStream.CopyTo(ms); - return ms.ToArray(); - } - } + s = new FileStream(path, FileMode.Open); + } + catch + { + Thread.Sleep(100); + continue; + } + using (var logStream = new LogStream(s, conf.PassKey, mode: true)) + { + logStream.CopyTo(ms); + return ms.ToArray(); + // s will be closed here since leaveOpen in LogStream is false by default } - } - catch (Exception e) - { - LogException(e); - Thread.Sleep(100); } } - throw new IOException("Failed to read the log"); } throw new FileNotFoundException(logname); } diff --git a/Tiriryarai/Util/Resources.cs b/Tiriryarai/Util/Resources.cs index 92ea0e7..ea8aa93 100644 --- a/Tiriryarai/Util/Resources.cs +++ b/Tiriryarai/Util/Resources.cs @@ -185,7 +185,14 @@ public static Version Version "Actions" + "" + "{0}" + - "" + "" + + "
" + + "
" + + "" + + "" + + "" + + "
" + + "
" ); public static string LOG_ENTRY = @@ -209,10 +216,10 @@ public static Version Version "be reset to their default value. Note that when the form is submitted, invalid " + "fields will be ignored, but valid fields will still update the configuration." + "

" + - "

" + + "

" + "Much of the configuration CANNOT be reverted once updated. Please read through the " + "information carefully before making changes and make sure you know what you are doing." + - "

" + + "

" + "
" + "" + "" +