-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathGeoblocker.cs
297 lines (272 loc) · 12.4 KB
/
Geoblocker.cs
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#nullable disable
/* Geoblocker.cs
*
* Copyright (C) 2009 Triple IT. All Rights Reserved.
* Author: Frank Lippes, Modified for IIS 10 (.Net 4.6) by RvdH
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
namespace IISGeoIP2blockModule
{
/// <summary>
/// Checks whether an ip address has access to the IIS application
/// </summary>
public class Geoblocker
{
#if DEBUG
private readonly static string _my_name;
private readonly static string _my_version;
private Guid requestId;
static Geoblocker()
{
_my_name = Assembly.GetExecutingAssembly().GetName().Name.ToString();
_my_version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
#endif
//Note: Each request to the server is an entirely new instance of the module
// Keeping the GeoIP.dat file in a static memory stream is of no use.
/// <summary>
/// The path that points to the geo ip data file
/// </summary>
private string geoIpFilepath;
/// <summary>
/// The countrycodes to allow or deny access
/// </summary>
private string[] selectedCountryCodes;
/// <summary>
/// Indicates whether the selected countrycodes are allowed or denied access
/// </summary>
private bool allowedMode;
/// <summary>
/// The exception rules to check first
/// </summary>
private ExceptionRule[] exceptionRules;
/// <summary>
/// Indicates whether or not if any proxy in HTTP_X_FORWARDED_FOR should be ignored if previous checked ip matches
/// </summary>
private bool verifyAll;
/// <summary>
/// Creates a new Geoblocker instance
/// </summary>
/// <param name="geoIpFilepath">The path to the geo ip data file</param>
/// <param name="selectedCountryCodes">The countrycodes to look for</param>
/// <param name="allowedMode">Whether the selected country codes are allowed or denied access</param>
/// <param name="verifyAll">Indicates whether or not if any proxy in HTTP_X_FORWARDED_FOR should be ignored if previous checked ip matches</param>
public Geoblocker(string geoIpFilepath, string[] selectedCountryCodes, bool allowedMode, ExceptionRule[] exceptionRules, bool verifyAll)
{
this.geoIpFilepath = geoIpFilepath;
this.selectedCountryCodes = selectedCountryCodes;
this.allowedMode = allowedMode;
this.exceptionRules = exceptionRules;
this.verifyAll = verifyAll;
}
/// <summary>
/// Checks the country to which the ip's belong to and determines whether or not access is denied or allowed
/// </summary>
/// <param name="ipAddressesToCheck">The ip addresses to check</param>
/// <param name="resultMessage">An explanation of the result</param>
/// <returns>True if access is allowed. False otherwise</returns>
/// <remarks>All IP addresses must be allowed for the request to be allowed</remarks>
#if DEBUG
public bool Allowed(List<System.Net.IPAddress> ipAddressesToCheck, Guid guid, out string resultMessage)
{
requestId = guid;
#else
public bool Allowed(List<System.Net.IPAddress> ipAddressesToCheck, out string resultMessage)
{
#endif
if (!ipAddressesToCheck.Any())
{
resultMessage = "No valid IP found in request";
return false;
}
try
{
using (var reader = new MaxMind.GeoIP2.DatabaseReader(geoIpFilepath, MaxMind.Db.FileAccessMode.MemoryMapped))
{
//sanity check
if (reader == null)
{
resultMessage = "IP check failed";
return true;
}
#if DEBUG
this.DbgWrite("Module version: {0}, Database: {1}, {2}", _my_version, reader.Metadata.DatabaseType, reader.Metadata.BuildDate);
#endif
foreach (System.Net.IPAddress ipAddress in ipAddressesToCheck)
{
//first check if the IP is a private ip. It turns out that proxy servers put private ip addresses in the X_FORWARDED_FOR header.
//we won't base our geoblocking on private ip addresses
if (IPUtilities.IsPrivateIpAddress(ipAddress))
continue;
//next check the exception rules
bool matchedOnExceptionRule = false;
bool allowedByExceptionRule = false;
foreach (ExceptionRule exceptionRule in exceptionRules)
{
if (IpAddressMatchesExceptionRule(ipAddress, exceptionRule))
{
matchedOnExceptionRule = true;
if (exceptionRule.AllowedMode)
allowedByExceptionRule = true;
else
{
allowedByExceptionRule = false;
//one IP denied = deny access alltogether
break;
}
}
}
if (matchedOnExceptionRule)
{
if (allowedByExceptionRule)
{
#if DEBUG
this.DbgWrite("Allowed IP: [{0}] by Exception Rule", ipAddress.ToString());
#endif
//IP found that matches an allow exception rule, don't check the country
//We continue if verifyAll is specified, because another IP could be denied
if (verifyAll)
continue;
else
break;
}
else
{
#if DEBUG
this.DbgWrite("Blocked IP: [{0}] by Exception Rule", ipAddress.ToString());
#endif
//IP found that matches a deny exception rule, deny access immediately
resultMessage = string.Format("Blocked IP: [{0}]", ipAddress.ToString());
return false;
}
}
if (reader.Metadata.DatabaseType.ToLower().IndexOf("country") == -1)
throw new System.InvalidOperationException("This is not a GeoLite2-Country or GeoIP2-Country database");
string countryCode = string.Empty;
//not found in exception rule, so base access rights on the country
try
{
MaxMind.GeoIP2.Responses.CountryResponse countryResponse = reader.Country(ipAddress);
countryCode = !string.IsNullOrEmpty(countryResponse.Country.IsoCode) ? countryResponse.Country.IsoCode : !string.IsNullOrEmpty(countryResponse.RegisteredCountry.IsoCode) ? countryResponse.RegisteredCountry.IsoCode : string.Empty;
}
catch (MaxMind.GeoIP2.Exceptions.AddressNotFoundException e)
{
#if DEBUG
this.DbgWrite("Exception occurred: {0}", e.Message);
#endif
}
catch (MaxMind.GeoIP2.Exceptions.PermissionRequiredException e)
{
#if DEBUG
this.DbgWrite("Exception occurred: {0}", e.Message);
#endif
}
catch (MaxMind.GeoIP2.Exceptions.GeoIP2Exception e)
{
#if DEBUG
this.DbgWrite("Exception occurred: {0}", e.Message);
#endif
}
finally
{
if (string.IsNullOrEmpty(countryCode))
countryCode = "--";
}
bool selected = CountryCodeSelected(countryCode);
bool allowed = (selected == allowedMode);
/*
* allowedmode selected allowed
* 1 1 1
* 1 0 0
* 0 1 0
* 0 0 1
*/
if (!allowed)
{
resultMessage = string.Format("Blocked IP: [{0}] from [{1}]", ipAddress.ToString(), countryCode);
#if DEBUG
this.DbgWrite("Blocked IP: [{0}] from [{1}]", ipAddress.ToString(), countryCode);
#endif
return false;
}
else
{
#if DEBUG
this.DbgWrite("Allowed IP: [{0}] from [{1}]", ipAddress.ToString(), countryCode);
#endif
// If a proxy in HTTP_X_FORWARDED_FOR should be ignored if previous checked ip matches previous found country or exceptionRule
if (!verifyAll)
break;
}
}
}
}
catch (Exception e)
{
#if DEBUG
this.DbgWrite("Exception occurred: {0}", e.Message);
#endif
resultMessage = "IP check failed";
return true;
}
resultMessage = "None";
return true;
}
/// <summary>
/// Checks whether the country code is present in the selected country code array
/// </summary>
/// <param name="countryCode">The country code to look up</param>
/// <returns>True if the country code is present. False otherwise</returns>
private bool CountryCodeSelected(string countryCode)
{
foreach (string selectedCountryCode in selectedCountryCodes)
if (countryCode == selectedCountryCode)
return true;
return false;
}
/// <summary>
/// Checks whether an IP address matches to an exception rule
/// </summary>
/// <param name="ipAddress">The IP address to check</param>
/// <param name="exceptionRule">The exception rule to match it against</param>
/// <returns>True if the IP address matches to the exception rule. False otherwise</returns>
private bool IpAddressMatchesExceptionRule(IPAddress ipAddress, ExceptionRule exceptionRule)
{
if (String.IsNullOrEmpty(exceptionRule.Mask) && IPAddress.Parse(exceptionRule.IpAddress).Equals(ipAddress))
return true;
if (!String.IsNullOrEmpty(exceptionRule.Mask) && IPUtilities.IsInSameSubnet(ipAddress, exceptionRule.IpAddress, exceptionRule.Mask))
return true;
return false;
}
#if DEBUG
private void DbgWrite(string format, params object[] args)
{
try
{
string str = string.Format(format, args);
Trace.WriteLine(string.Format("[{0}]: {1} {2}", Geoblocker._my_name, requestId, str));
}
catch (Exception exception)
{
Trace.WriteLine(string.Format("DbgWrite::Error: {0}", exception.Message));
}
}
#endif
}
}