summaryrefslogtreecommitdiffstats
path: root/src/main.lib/Plugins/ValidationPlugins/Http/SelfHosting/SelfHosting.cs
blob: 6e165afe94bf4c8b7daec407046f4769de135026 (plain)
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);
            }
        }
    }
}