mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon
commit
55cd454907
|
|
@ -82,7 +82,7 @@ Type=simple
|
|||
User=epicyon
|
||||
Group=epicyon
|
||||
WorkingDirectory=/opt/epicyon
|
||||
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open
|
||||
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --logLoginFailures
|
||||
Environment=USER=epicyon
|
||||
Environment=PYTHONUNBUFFERED=true
|
||||
Restart=always
|
||||
|
|
@ -208,6 +208,8 @@ And restart the web server:
|
|||
systemctl restart nginx
|
||||
```
|
||||
|
||||
If you need to use **fail2ban** then failed login attempts can be found in *accounts/loginfailures.log*.
|
||||
|
||||
If you are using the [Caddy web server](https://caddyserver.com) then see *caddy.example.conf*
|
||||
|
||||
## Running Static Analysis
|
||||
|
|
|
|||
45
auth.py
45
auth.py
|
|
@ -11,6 +11,7 @@ import hashlib
|
|||
import binascii
|
||||
import os
|
||||
import secrets
|
||||
import datetime
|
||||
from utils import isSystemAccount
|
||||
from utils import hasUsersPath
|
||||
|
||||
|
|
@ -204,3 +205,47 @@ def createPassword(length=10):
|
|||
validChars = 'abcdefghijklmnopqrstuvwxyz' + \
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
return ''.join((secrets.choice(validChars) for i in range(length)))
|
||||
|
||||
|
||||
def recordLoginFailure(baseDir: str, ipAddress: str,
|
||||
countDict: {}, failTime: int,
|
||||
logToFile: bool) -> None:
|
||||
"""Keeps ip addresses and the number of times login failures
|
||||
occured for them in a dict
|
||||
"""
|
||||
if not countDict.get(ipAddress):
|
||||
while len(countDict.items()) > 100:
|
||||
oldestTime = 0
|
||||
oldestIP = None
|
||||
for ipAddr, ipItem in countDict.items():
|
||||
if oldestTime == 0 or ipItem['time'] < oldestTime:
|
||||
oldestTime = ipItem['time']
|
||||
oldestIP = ipAddr
|
||||
if oldestIP:
|
||||
del countDict[oldestIP]
|
||||
countDict[ipAddress] = {
|
||||
"count": 1,
|
||||
"time": failTime
|
||||
}
|
||||
else:
|
||||
countDict[ipAddress]['count'] += 1
|
||||
countDict[ipAddress]['time'] = failTime
|
||||
failCount = countDict[ipAddress]['count']
|
||||
if failCount > 4:
|
||||
print('WARN: ' + str(ipAddress) + ' failed to log in ' +
|
||||
str(failCount) + ' times')
|
||||
|
||||
if not logToFile:
|
||||
return
|
||||
|
||||
failureLog = baseDir + '/accounts/loginfailures.log'
|
||||
writeType = 'a+'
|
||||
if not os.path.isfile(failureLog):
|
||||
writeType = 'w+'
|
||||
currTime = datetime.datetime.utcnow()
|
||||
try:
|
||||
with open(failureLog, writeType) as fp:
|
||||
fp.write(currTime.strftime("%Y-%m-%d %H:%M:%SZ") +
|
||||
' ' + ipAddress + '\n')
|
||||
except BaseException:
|
||||
pass
|
||||
|
|
|
|||
27
daemon.py
27
daemon.py
|
|
@ -101,6 +101,7 @@ from skills import noOfActorSkills
|
|||
from skills import actorHasSkill
|
||||
from skills import actorSkillValue
|
||||
from skills import setActorSkillLevel
|
||||
from auth import recordLoginFailure
|
||||
from auth import authorize
|
||||
from auth import createPassword
|
||||
from auth import createBasicAuthHeader
|
||||
|
|
@ -206,6 +207,7 @@ from shares import addShare
|
|||
from shares import removeShare
|
||||
from shares import expireShares
|
||||
from categories import setHashtagCategory
|
||||
from utils import isLocalNetworkAddress
|
||||
from utils import permittedDir
|
||||
from utils import isAccountDir
|
||||
from utils import getOccupationSkills
|
||||
|
|
@ -1437,15 +1439,33 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return
|
||||
authHeader = \
|
||||
createBasicAuthHeader(loginNickname, loginPassword)
|
||||
if self.headers.get('X-Forward-For'):
|
||||
ipAddress = self.headers['X-Forward-For']
|
||||
elif self.headers.get('X-Forwarded-For'):
|
||||
ipAddress = self.headers['X-Forwarded-For']
|
||||
else:
|
||||
ipAddress = self.client_address[0]
|
||||
if not domain.endswith('.onion'):
|
||||
if not isLocalNetworkAddress(ipAddress):
|
||||
print('Login attempt from IP: ' + str(ipAddress))
|
||||
if not authorizeBasic(baseDir, '/users/' +
|
||||
loginNickname + '/outbox',
|
||||
authHeader, False):
|
||||
print('Login failed: ' + loginNickname)
|
||||
self._clearLoginDetails(loginNickname, callingDomain)
|
||||
self.server.lastLoginFailure = int(time.time())
|
||||
failTime = int(time.time())
|
||||
self.server.lastLoginFailure = failTime
|
||||
if not domain.endswith('.onion'):
|
||||
if not isLocalNetworkAddress(ipAddress):
|
||||
recordLoginFailure(baseDir, ipAddress,
|
||||
self.server.loginFailureCount,
|
||||
failTime,
|
||||
self.server.logLoginFailures)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
else:
|
||||
if self.server.loginFailureCount.get(ipAddress):
|
||||
del self.server.loginFailureCount[ipAddress]
|
||||
if isSuspended(baseDir, loginNickname):
|
||||
msg = \
|
||||
htmlSuspended(self.server.cssCache,
|
||||
|
|
@ -14829,7 +14849,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
|
|||
break
|
||||
|
||||
|
||||
def runDaemon(city: str,
|
||||
def runDaemon(logLoginFailures: bool,
|
||||
city: str,
|
||||
showNodeInfoAccounts: bool,
|
||||
showNodeInfoVersion: bool,
|
||||
brochMode: bool,
|
||||
|
|
@ -15097,6 +15118,8 @@ def runDaemon(city: str,
|
|||
httpd.allowDeletion = allowDeletion
|
||||
httpd.lastLoginTime = 0
|
||||
httpd.lastLoginFailure = 0
|
||||
httpd.loginFailureCount = {}
|
||||
httpd.logLoginFailures = logLoginFailures
|
||||
httpd.maxReplies = maxReplies
|
||||
httpd.tokens = {}
|
||||
httpd.tokensLookup = {}
|
||||
|
|
|
|||
13
epicyon.py
13
epicyon.py
|
|
@ -291,6 +291,11 @@ parser.add_argument("--iconsAsButtons",
|
|||
type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Show header icons as buttons")
|
||||
parser.add_argument("--logLoginFailures",
|
||||
dest='logLoginFailures',
|
||||
type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Whether to log longin failures")
|
||||
parser.add_argument("--rssIconAtTop",
|
||||
dest='rssIconAtTop',
|
||||
type=str2bool, nargs='?',
|
||||
|
|
@ -2510,6 +2515,11 @@ brochMode = \
|
|||
if brochMode is not None:
|
||||
args.brochMode = bool(brochMode)
|
||||
|
||||
logLoginFailures = \
|
||||
getConfigParam(baseDir, 'logLoginFailures')
|
||||
if logLoginFailures is not None:
|
||||
args.logLoginFailures = bool(logLoginFailures)
|
||||
|
||||
showNodeInfoAccounts = \
|
||||
getConfigParam(baseDir, 'showNodeInfoAccounts')
|
||||
if showNodeInfoAccounts is not None:
|
||||
|
|
@ -2539,7 +2549,8 @@ if setTheme(baseDir, themeName, domain,
|
|||
print('Theme set to ' + themeName)
|
||||
|
||||
if __name__ == "__main__":
|
||||
runDaemon(args.city,
|
||||
runDaemon(args.logLoginFailures,
|
||||
args.city,
|
||||
args.showNodeInfoAccounts,
|
||||
args.showNodeInfoVersion,
|
||||
args.brochMode,
|
||||
|
|
|
|||
9
tests.py
9
tests.py
|
|
@ -518,8 +518,9 @@ def createServerAlice(path: str, domain: str, port: int,
|
|||
showNodeInfoAccounts = True
|
||||
showNodeInfoVersion = True
|
||||
city = 'London, England'
|
||||
logLoginFailures = False
|
||||
print('Server running: Alice')
|
||||
runDaemon(city,
|
||||
runDaemon(logLoginFailures, city,
|
||||
showNodeInfoAccounts,
|
||||
showNodeInfoVersion,
|
||||
brochMode,
|
||||
|
|
@ -620,8 +621,9 @@ def createServerBob(path: str, domain: str, port: int,
|
|||
showNodeInfoAccounts = True
|
||||
showNodeInfoVersion = True
|
||||
city = 'London, England'
|
||||
logLoginFailures = False
|
||||
print('Server running: Bob')
|
||||
runDaemon(city,
|
||||
runDaemon(logLoginFailures, city,
|
||||
showNodeInfoAccounts,
|
||||
showNodeInfoVersion,
|
||||
brochMode,
|
||||
|
|
@ -677,8 +679,9 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
|
|||
showNodeInfoAccounts = True
|
||||
showNodeInfoVersion = True
|
||||
city = 'London, England'
|
||||
logLoginFailures = False
|
||||
print('Server running: Eve')
|
||||
runDaemon(city,
|
||||
runDaemon(logLoginFailures, city,
|
||||
showNodeInfoAccounts,
|
||||
showNodeInfoVersion,
|
||||
brochMode,
|
||||
|
|
|
|||
10
utils.py
10
utils.py
|
|
@ -669,6 +669,16 @@ def getLocalNetworkAddresses() -> []:
|
|||
return ('localhost', '127.0.', '192.168', '10.0.')
|
||||
|
||||
|
||||
def isLocalNetworkAddress(ipAddress: str) -> bool:
|
||||
"""
|
||||
"""
|
||||
localIPs = getLocalNetworkAddresses()
|
||||
for ipAddr in localIPs:
|
||||
if ipAddress.startswith(ipAddr):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
||||
"""Returns true if the given content contains dangerous html markup
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1343,7 +1343,7 @@
|
|||
User=epicyon<br>
|
||||
Group=epicyon<br>
|
||||
WorkingDirectory=/opt/epicyon<br>
|
||||
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --debug<br>
|
||||
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --debug --logLoginFailures<br>
|
||||
Environment=USER=epicyon<br>
|
||||
Environment=PYTHONUNBUFFERED=true<br>
|
||||
Restart=always<br>
|
||||
|
|
@ -1492,6 +1492,9 @@
|
|||
<div class="shell">
|
||||
systemctl restart nginx
|
||||
</div>
|
||||
<p class="intro">
|
||||
If you need to use <a href="https://www.fail2ban.org">fail2ban</a> then failed login attempts can be found in <b>accounts/loginfailures.log</b>.
|
||||
</p>
|
||||
<p class="intro">
|
||||
If you are using the <a href="https://caddyserver.com">Caddy web server</a> then see <a href="https://code.freedombone.net/bashrc/epicyon/raw/main/caddy.example.conf">caddy.example.conf</a>
|
||||
</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue