source: binary-improvements/MapRendering/Web/Web.cs@ 322

Last change on this file since 322 was 314, checked in by alloc, 7 years ago

Web: Fixed Steam OpenID server's SSL certificate validation

File size: 8.7 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.IO;
5using System.Net;
6using System.Net.Sockets;
7using System.Reflection;
8using System.Text;
9using System.Threading;
10using UnityEngine;
11
12using AllocsFixes.NetConnections.Servers.Web.Handlers;
13
14namespace AllocsFixes.NetConnections.Servers.Web
15{
16 public class Web : IConsoleServer {
17 private const int GUEST_PERMISSION_LEVEL = 2000;
18 private readonly HttpListener _listener = new HttpListener ();
19 private Dictionary<string, PathHandler> handlers = new Dictionary<string, PathHandler> ();
20 public static int handlingCount = 0;
21 public static int currentHandlers = 0;
22 public static long totalHandlingTime = 0;
23 private string dataFolder;
24 private bool useStaticCache = false;
25
26 public static bool isSslRedirected (HttpListenerRequest req) {
27 string proto = req.Headers ["X-Forwarded-Proto"];
28 if (!string.IsNullOrEmpty (proto)) {
29 return proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
30 }
31
32 return false;
33 }
34
35 public ConnectionHandler connectionHandler;
36
37 public Web () {
38 try {
39 int webPort = GamePrefs.GetInt (EnumGamePrefs.ControlPanelPort);
40 if (webPort < 1 || webPort > 65533) {
41 Log.Out ("Webserver not started (ControlPanelPort not within 1-65533)");
42 return;
43 }
44 if (!Directory.Exists (Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver")) {
45 Log.Out ("Webserver not started (folder \"webserver\" not found in WebInterface mod folder)");
46 return;
47 }
48
49 // TODO: Read from config
50 useStaticCache = false;
51
52 dataFolder = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location) + "/webserver";
53
54 if (!HttpListener.IsSupported) {
55 Log.Out ("Webserver not started (needs Windows XP SP2, Server 2003 or later or Mono)");
56 return;
57 }
58
59 handlers.Add (
60 "/index.htm",
61 new SimpleRedirectHandler ("/static/index.html"));
62 handlers.Add (
63 "/favicon.ico",
64 new SimpleRedirectHandler ("/static/favicon.ico"));
65 handlers.Add (
66 "/session/",
67 new SessionHandler (
68 "/session/",
69 dataFolder,
70 this)
71 );
72 handlers.Add (
73 "/userstatus",
74 new UserStatusHandler ()
75 );
76 if (useStaticCache) {
77 handlers.Add (
78 "/static/",
79 new StaticHandler (
80 "/static/",
81 dataFolder,
82 new AllocsFixes.FileCache.SimpleCache (),
83 false)
84 );
85 } else {
86 handlers.Add (
87 "/static/",
88 new StaticHandler (
89 "/static/",
90 dataFolder,
91 new AllocsFixes.FileCache.DirectAccess (),
92 false)
93 );
94 }
95
96 handlers.Add (
97 "/itemicons/",
98 new ItemIconHandler (
99 "/itemicons/",
100 true)
101 );
102
103 handlers.Add (
104 "/map/",
105 new StaticHandler (
106 "/map/",
107 GameUtils.GetSaveGameDir () + "/map",
108 MapRendering.MapRendering.GetTileCache (),
109 false,
110 "web.map")
111 );
112
113 handlers.Add (
114 "/api/",
115 new ApiHandler ("/api/")
116 );
117
118 connectionHandler = new ConnectionHandler (this);
119
120 _listener.Prefixes.Add (String.Format ("http://*:{0}/", webPort + 2));
121 _listener.Start ();
122
123 SdtdConsole.Instance.RegisterServer (this);
124
125 _listener.BeginGetContext (new AsyncCallback (HandleRequest), _listener);
126
127 Log.Out ("Started Webserver on " + (webPort + 2));
128 } catch (Exception e) {
129 Log.Out ("Error in Web.ctor: " + e);
130 }
131 }
132
133 private void HandleRequest (IAsyncResult result) {
134 if (_listener.IsListening) {
135 Interlocked.Increment (ref handlingCount);
136 Interlocked.Increment (ref currentHandlers);
137// MicroStopwatch msw = new MicroStopwatch ();
138 HttpListenerContext ctx = _listener.EndGetContext (result);
139 _listener.BeginGetContext (new AsyncCallback (HandleRequest), _listener);
140 try {
141 HttpListenerRequest request = ctx.Request;
142 HttpListenerResponse response = ctx.Response;
143 response.SendChunked = false;
144
145 response.ProtocolVersion = new Version ("1.1");
146
147 WebConnection conn;
148 int permissionLevel = DoAuthentication (request, out conn);
149
150
151 //Log.Out ("Login status: conn!=null: {0}, permissionlevel: {1}", conn != null, permissionLevel);
152
153
154 if (conn != null) {
155 Cookie cookie = new Cookie ("sid", conn.SessionID, "/");
156 cookie.Expired = false;
157 cookie.Expires = new DateTime (2020, 1, 1);
158 cookie.HttpOnly = true;
159 cookie.Secure = false;
160 response.AppendCookie (cookie);
161 }
162
163 // No game yet -> fail request
164 if (GameManager.Instance.World == null) {
165 response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
166 return;
167 }
168
169 if (request.Url.AbsolutePath.Length < 2) {
170 handlers ["/index.htm"].HandleRequest (request, response, conn, permissionLevel);
171 return;
172 } else {
173 foreach (KeyValuePair<string, PathHandler> kvp in handlers) {
174 if (request.Url.AbsolutePath.StartsWith (kvp.Key)) {
175 if (!kvp.Value.IsAuthorizedForHandler (conn, permissionLevel)) {
176 response.StatusCode = (int)HttpStatusCode.Forbidden;
177 if (conn != null) {
178 //Log.Out ("Web.HandleRequest: user '{0}' not allowed to access '{1}'", conn.SteamID, kvp.Value.ModuleName);
179 } else {
180 //Log.Out ("Web.HandleRequest: unidentified user from '{0}' not allowed to access '{1}'", request.RemoteEndPoint.Address, kvp.Value.ModuleName);
181 }
182 } else {
183 kvp.Value.HandleRequest (request, response, conn, permissionLevel);
184 }
185 return;
186 }
187 }
188 }
189
190 // Not really relevant for non-debugging purposes:
191 //Log.Out ("Error in Web.HandleRequest(): No handler found for path \"" + request.Url.AbsolutePath + "\"");
192 response.StatusCode = (int)HttpStatusCode.NotFound;
193 } catch (IOException e) {
194 if (e.InnerException is SocketException) {
195 Log.Out ("Error in Web.HandleRequest(): Remote host closed connection: " + e.InnerException.Message);
196 } else {
197 Log.Out ("Error (IO) in Web.HandleRequest(): " + e);
198 }
199 } catch (Exception e) {
200 Log.Out ("Error in Web.HandleRequest(): " + e);
201 } finally {
202 if (ctx != null && !ctx.Response.SendChunked) {
203 ctx.Response.Close ();
204 }
205// msw.Stop ();
206// totalHandlingTime += msw.ElapsedMicroseconds;
207// Log.Out ("Web.HandleRequest(): Took {0} µs", msw.ElapsedMicroseconds);
208 Interlocked.Decrement (ref currentHandlers);
209 }
210 }
211 }
212
213 private int DoAuthentication (HttpListenerRequest _req, out WebConnection _con) {
214 _con = null;
215
216 string sessionId = null;
217 if (_req.Cookies ["sid"] != null) {
218 sessionId = _req.Cookies ["sid"].Value;
219 }
220
221 if (!string.IsNullOrEmpty (sessionId)) {
222 WebConnection con = connectionHandler.IsLoggedIn (sessionId, _req.RemoteEndPoint.Address.ToString ());
223 if (con != null) {
224 _con = con;
225 return GameManager.Instance.adminTools.GetAdminToolsClientInfo (_con.SteamID.ToString ()).PermissionLevel;
226 }
227 }
228
229 if (_req.QueryString ["adminuser"] != null && _req.QueryString ["admintoken"] != null) {
230 WebPermissions.AdminToken admin = WebPermissions.Instance.GetWebAdmin (_req.QueryString ["adminuser"], _req.QueryString ["admintoken"]);
231 if (admin != null) {
232 return admin.permissionLevel;
233 } else {
234 Log.Warning ("Invalid Admintoken used from " + _req.RemoteEndPoint.ToString ());
235 }
236 }
237
238 if (_req.Url.AbsolutePath.StartsWith ("/session/verify")) {
239 try {
240 ulong id = OpenID.Validate (_req);
241 if (id > 0) {
242 WebConnection con = connectionHandler.LogIn (id, _req.RemoteEndPoint.Address.ToString ());
243 _con = con;
244 int level = GameManager.Instance.adminTools.GetAdminToolsClientInfo (id.ToString ()).PermissionLevel;
245 Log.Out ("Steam OpenID login from {0} with ID {1}, permission level {2}", _req.RemoteEndPoint.ToString (), con.SteamID, level);
246 return level;
247 } else {
248 Log.Out ("Steam OpenID login failed from {0}", _req.RemoteEndPoint.ToString ());
249 }
250 } catch (Exception e) {
251 Log.Error ("Error validating login:");
252 Log.Exception (e);
253 }
254 }
255
256 return GUEST_PERMISSION_LEVEL;
257 }
258
259 public void Disconnect () {
260 try {
261 _listener.Stop ();
262 _listener.Close ();
263 } catch (Exception e) {
264 Log.Out ("Error in Web.Disconnect: " + e);
265 }
266 }
267
268 public void SendLine (string line) {
269 connectionHandler.SendLine (line);
270 }
271
272 public void SendLog (string text, string trace, UnityEngine.LogType type) {
273 // Do nothing, handled by LogBuffer internally
274 }
275
276 public static void SetResponseTextContent (HttpListenerResponse resp, string text) {
277 byte[] buf = Encoding.UTF8.GetBytes (text);
278 resp.ContentLength64 = buf.Length;
279 resp.ContentType = "text/html";
280 resp.ContentEncoding = Encoding.UTF8;
281 resp.OutputStream.Write (buf, 0, buf.Length);
282 }
283
284 }
285}
Note: See TracBrowser for help on using the repository browser.