1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
using ACMESharp.Authorizations;
using PKISharp.WACS.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace PKISharp.WACS.Plugins.ValidationPlugins.Http
{
internal class SelfHosting : Validation<Http01ChallengeValidationDetails>
{
internal const int DefaultHttpValidationPort = 80;
internal const int DefaultHttpsValidationPort = 443;
private HttpListener? _listener;
private readonly Dictionary<string, string> _files;
private readonly SelfHostingOptions _options;
private readonly ILogService _log;
private readonly IUserRoleService _userRoleService;
private bool HasListener => _listener != null;
private HttpListener Listener
{
get
{
if (_listener == null)
{
throw new InvalidOperationException();
}
return _listener;
}
set => _listener = value;
}
public SelfHosting(ILogService log, SelfHostingOptions options, IUserRoleService userRoleService)
{
_log = log;
_options = options;
_files = new Dictionary<string, string>();
_userRoleService = userRoleService;
}
public async Task ReceiveRequests()
{
while (Listener.IsListening)
{
var ctx = await Listener.GetContextAsync();
var path = ctx.Request.Url.LocalPath;
if (_files.TryGetValue(path, out var response))
{
_log.Verbose("SelfHosting plugin serving file {name}", path);
using var writer = new StreamWriter(ctx.Response.OutputStream);
writer.Write(response);
}
else
{
_log.Warning("SelfHosting plugin couldn't serve file {name}", path);
ctx.Response.StatusCode = 404;
}
}
}
public override Task CleanUp()
{
if (HasListener)
{
try
{
Listener.Stop();
Listener.Close();
}
catch
{
}
}
return Task.CompletedTask;
}
public override Task PrepareChallenge()
{
_files.Add("/" + Challenge.HttpResourcePath, Challenge.HttpResourceValue);
var protocol = _options.Https == true ? "https" : "http";
var port = _options.Port ?? (_options.Https == true ?
DefaultHttpsValidationPort :
DefaultHttpValidationPort);
var prefix = $"{protocol}://+:{port}/.well-known/acme-challenge/";
try
{
Listener = new HttpListener();
Listener.Prefixes.Add(prefix);
Listener.Start();
Task.Run(ReceiveRequests);
}
catch
{
_log.Error("Unable to activate listener, this may be because of insufficient rights or a non-Microsoft webserver using port {port}", port);
throw;
}
return Task.CompletedTask;
}
public override (bool, string?) Disabled => IsDisabled(_userRoleService);
internal static (bool, string?) IsDisabled(IUserRoleService userRoleService)
{
if (!userRoleService.IsAdmin)
{
return (true, "Run as administrator to allow use of the built-in web listener.");
}
else
{
return (false, null);
}
}
}
}
|