using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Shared; using Shared.Engine; using Shared.Models; using System; using System.Collections.Concurrent; using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Lampac.Engine.Middlewares { public class Accsdb { static readonly Regex rexJac = new Regex("^/(api/v2.0/indexers|api/v1.0/|toloka|rutracker|rutor|torrentby|nnmclub|kinozal|bitru|selezen|megapeer|animelayer|anilibria|anifilm|toloka|lostfilm|bigfangroup|mazepa)", RegexOptions.Compiled | RegexOptions.IgnoreCase); static readonly Regex rexStaticAssets = new Regex("\\.(js|css|ico|png|svg|jpe?g|woff|webmanifest)", RegexOptions.Compiled | RegexOptions.IgnoreCase); static readonly Regex rexProxyPath = new Regex("/(proxy|proxyimg([^/]+)?)/", RegexOptions.Compiled | RegexOptions.IgnoreCase); static readonly Regex rexTmdbPath = new Regex("^/tmdb/[^/]+/", RegexOptions.Compiled | RegexOptions.IgnoreCase); static readonly Regex rexLockBypass = new Regex("^/(testaccsdb|proxy/|proxyimg|lifeevents|externalids|sisi/(bookmarks|historys)|(ts|transcoding|dlna|storage|bookmark|tmdb|cub)/|timecode)", RegexOptions.Compiled | RegexOptions.IgnoreCase); static Accsdb() { Directory.CreateDirectory("cache/logs/accsdb"); } private readonly RequestDelegate _next; static bool manifestInitial = false; IMemoryCache memoryCache; public Accsdb(RequestDelegate next, IMemoryCache mem) { _next = next; memoryCache = mem; } public Task Invoke(HttpContext httpContext) { var requestInfo = httpContext.Features.Get(); #region manifest/install if (!manifestInitial) { if (!File.Exists("module/manifest.json")) { if (httpContext.Request.Path.Value.StartsWith("/admin/manifest/install", StringComparison.OrdinalIgnoreCase)) return _next(httpContext); httpContext.Response.Redirect("/admin/manifest/install"); return Task.CompletedTask; } else { manifestInitial = true; } } #endregion #region admin if (httpContext.Request.Path.Value.StartsWith("/admin", StringComparison.OrdinalIgnoreCase)) { if (httpContext.Request.Cookies.TryGetValue("passwd", out string passwd)) { string ipKey = $"Accsdb:auth:IP:{requestInfo.IP}"; if (!memoryCache.TryGetValue(ipKey, out ConcurrentDictionary passwds)) { passwds = new ConcurrentDictionary(); memoryCache.Set(ipKey, passwds, DateTime.Today.AddDays(1)); } passwds.TryAdd(passwd, 0); if (passwds.Count > 10) return httpContext.Response.WriteAsync("Too many attempts, try again tomorrow.", httpContext.RequestAborted); if (passwd == AppInit.rootPasswd) return _next(httpContext); } if (httpContext.Request.Path.Value.StartsWith("/admin/auth", StringComparison.OrdinalIgnoreCase)) return _next(httpContext); httpContext.Response.Redirect("/admin/auth"); return Task.CompletedTask; } #endregion if (requestInfo.IsLocalRequest || requestInfo.IsAnonymousRequest) return _next(httpContext); #region jacred if (rexJac.IsMatch(httpContext.Request.Path.Value)) { if (!string.IsNullOrEmpty(AppInit.conf.apikey)) { if (AppInit.conf.apikey != httpContext.Request.Query["apikey"]) return Task.CompletedTask; } return _next(httpContext); } #endregion if (AppInit.conf.accsdb.enable || (!requestInfo.IsLocalIp && !AppInit.conf.WAF.allowExternalIpAccess)) { var accsdb = AppInit.conf.accsdb; if (httpContext.Request.Path.Value.StartsWith("/testaccsdb", StringComparison.OrdinalIgnoreCase) && accsdb.shared_passwd != null && requestInfo.user_uid == accsdb.shared_passwd) { requestInfo.IsLocalRequest = true; return _next(httpContext); } if (!string.IsNullOrEmpty(accsdb.premium_pattern) && !Regex.IsMatch(httpContext.Request.Path.Value, accsdb.premium_pattern, RegexOptions.IgnoreCase)) return _next(httpContext); if (!string.IsNullOrEmpty(accsdb.whitepattern) && Regex.IsMatch(httpContext.Request.Path.Value, accsdb.whitepattern, RegexOptions.IgnoreCase)) { requestInfo.IsAnonymousRequest = true; return _next(httpContext); } bool limitip = false; var user = requestInfo.user; if (requestInfo.user_uid != null && accsdb.white_uids != null && accsdb.white_uids.Contains(requestInfo.user_uid)) return _next(httpContext); string uri = httpContext.Request.Path.Value + httpContext.Request.QueryString.Value; if (IsLockHostOrUser(memoryCache, requestInfo.user_uid, requestInfo.IP, uri, out limitip) || user == null || user.ban || DateTime.UtcNow > user.expires) { if (httpContext.Request.Path.Value.StartsWith("/proxy/", StringComparison.OrdinalIgnoreCase) || httpContext.Request.Path.Value.StartsWith("/proxyimg", StringComparison.OrdinalIgnoreCase)) { string hash = rexProxyPath.Replace(httpContext.Request.Path.Value, ""); if (AppInit.conf.serverproxy.encrypt || ProxyLink.Decrypt(hash, requestInfo.IP)?.uri != null) return _next(httpContext); } if (uri.StartsWith("/tmdb/api.themoviedb.org/", StringComparison.OrdinalIgnoreCase) || uri.StartsWith("/tmdb/api/", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.Redirect("https://api.themoviedb.org/" + rexTmdbPath.Replace(httpContext.Request.Path.Value, "")); return Task.CompletedTask; } if (rexStaticAssets.IsMatch(httpContext.Request.Path.Value)) { if (uri.StartsWith("/tmdb/image.tmdb.org/", StringComparison.OrdinalIgnoreCase) || uri.StartsWith("/tmdb/img/", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.Redirect("https://image.tmdb.org/" + rexTmdbPath.Replace(httpContext.Request.Path.Value, "")); return Task.CompletedTask; } httpContext.Response.StatusCode = 404; httpContext.Response.ContentType = "application/octet-stream"; return Task.CompletedTask; } #region msg string msg = limitip ? $"Превышено допустимое количество ip/запросов на аккаунт." : string.IsNullOrEmpty(requestInfo.user_uid) ? accsdb.authMesage : accsdb.denyMesage.Replace("{account_email}", requestInfo.user_uid).Replace("{user_uid}", requestInfo.user_uid).Replace("{host}", httpContext.Request.Host.Value); if (user != null) { if (user.ban) msg = user.ban_msg ?? "Вы заблокированы"; else if (DateTime.UtcNow > user.expires) { msg = accsdb.expiresMesage .Replace("{account_email}", requestInfo.user_uid) .Replace("{user_uid}", requestInfo.user_uid) .Replace("{expires}", user.expires.ToString("dd.MM.yyyy")); } } #endregion #region denymsg string denymsg = limitip ? $"Превышено допустимое количество ip/запросов на аккаунт." : null; if (user != null) { if (user.ban) denymsg = user.ban_msg ?? "Вы заблокированы"; else if (DateTime.UtcNow > user.expires) { denymsg = accsdb.expiresMesage .Replace("{account_email}", requestInfo.user_uid) .Replace("{user_uid}", requestInfo.user_uid) .Replace("{expires}", user.expires.ToString("dd.MM.yyyy")); } } #endregion return httpContext.Response.WriteAsJsonAsync(new { accsdb = true, msg, denymsg, user }, httpContext.RequestAborted); } } return _next(httpContext); } #region IsLock static bool IsLockHostOrUser(IMemoryCache memoryCache, string account_email, string userip, string uri, out bool islock) { if (string.IsNullOrEmpty(account_email)) { islock = false; return islock; } if (rexLockBypass.IsMatch(uri)) { islock = false; return islock; } if (IsLockIpHour(memoryCache, account_email, userip, out islock, out ConcurrentDictionary ips) | IsLockReqHour(memoryCache, account_email, uri, out islock, out ConcurrentDictionary urls)) { setLogs("lock_hour", account_email); countlock_day(memoryCache, true, account_email); File.WriteAllLines($"cache/logs/accsdb/{CrypTo.md5(account_email)}.ips.log", ips.Keys); File.WriteAllLines($"cache/logs/accsdb/{CrypTo.md5(account_email)}.urls.log", urls.Keys); return islock; } if (countlock_day(memoryCache, false, account_email) > AppInit.conf.accsdb.maxlock_day) { if (AppInit.conf.accsdb.blocked_hour != -1) memoryCache.Set($"Accsdb:blocked_hour:{account_email}", 0, DateTime.Now.AddHours(AppInit.conf.accsdb.blocked_hour)); setLogs("lock_day", account_email); islock = true; return islock; } if (memoryCache.TryGetValue($"Accsdb:blocked_hour:{account_email}", out _)) { setLogs("blocked", account_email); islock = true; return islock; } islock = false; return islock; } static bool IsLockIpHour(IMemoryCache memoryCache, string account_email, string userip, out bool islock, out ConcurrentDictionary ips) { ips = memoryCache.GetOrCreate($"Accsdb:IsLockIpHour:{account_email}:{DateTime.Now.Hour}", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); return new ConcurrentDictionary(); }); ips.TryAdd(userip, 0); if (ips.Count > AppInit.conf.accsdb.maxip_hour) { islock = true; return islock; } islock = false; return islock; } static bool IsLockReqHour(IMemoryCache memoryCache, string account_email, string uri, out bool islock, out ConcurrentDictionary urls) { urls = memoryCache.GetOrCreate($"Accsdb:IsLockReqHour:{account_email}:{DateTime.Now.Hour}", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); return new ConcurrentDictionary(); }); urls.TryAdd(uri, 0); if (urls.Count > AppInit.conf.accsdb.maxrequest_hour) { islock = true; return islock; } islock = false; return islock; } #endregion #region setLogs static string logsLock = string.Empty; static void setLogs(string name, string account_email) { string logFile = $"cache/logs/accsdb/{DateTime.Now:dd-MM-yyyy}.lock.txt"; if (logsLock != string.Empty && !File.Exists(logFile)) logsLock = string.Empty; string line = $"{name} / {account_email} / {CrypTo.md5(account_email)}.*.log"; if (!logsLock.Contains(line)) { logsLock += $"{DateTime.Now}: {line}\n"; File.WriteAllText(logFile, logsLock); } } #endregion #region countlock_day static int countlock_day(IMemoryCache memoryCache, bool update, string account_email) { var lockhour = memoryCache.GetOrCreate($"Accsdb:lock_day:{account_email}:{DateTime.Now.Day}", entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); return new ConcurrentDictionary(); }); if (update) lockhour.TryAdd(DateTime.Now.Hour, 0); return lockhour.Count; } #endregion } }