mirror of https://gitlab.com/bashrc2/epicyon
Merge
commit
98300fb5f9
|
|
@ -3,6 +3,6 @@ image: debian:testing
|
||||||
test:
|
test:
|
||||||
script:
|
script:
|
||||||
- apt-get update
|
- apt-get update
|
||||||
- apt-get install -y python3-crypto python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-pycryptodome python3-requests python3-socks python3-setuptools python3-pyqrcode
|
- apt-get install -y python3-cryptography python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-requests python3-socks python3-setuptools python3-pyqrcode
|
||||||
- python3 epicyon.py --tests
|
- python3 epicyon.py --tests
|
||||||
- python3 epicyon.py --testsnetwork
|
- python3 epicyon.py --testsnetwork
|
||||||
|
|
|
||||||
119
blocking.py
119
blocking.py
|
|
@ -7,6 +7,9 @@ __email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from utils import fileLastModified
|
||||||
|
from utils import setConfigParam
|
||||||
from utils import hasUsersPath
|
from utils import hasUsersPath
|
||||||
from utils import getFullDomain
|
from utils import getFullDomain
|
||||||
from utils import removeIdEnding
|
from utils import removeIdEnding
|
||||||
|
|
@ -175,15 +178,27 @@ def isBlockedDomain(baseDir: str, domain: str) -> bool:
|
||||||
if noOfSections > 2:
|
if noOfSections > 2:
|
||||||
shortDomain = domain[noOfSections-2] + '.' + domain[noOfSections-1]
|
shortDomain = domain[noOfSections-2] + '.' + domain[noOfSections-1]
|
||||||
|
|
||||||
globalBlockingFilename = baseDir + '/accounts/blocking.txt'
|
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||||
if os.path.isfile(globalBlockingFilename):
|
if not os.path.isfile(allowFilename):
|
||||||
with open(globalBlockingFilename, 'r') as fpBlocked:
|
# instance block list
|
||||||
blockedStr = fpBlocked.read()
|
globalBlockingFilename = baseDir + '/accounts/blocking.txt'
|
||||||
if '*@' + domain in blockedStr:
|
if os.path.isfile(globalBlockingFilename):
|
||||||
return True
|
with open(globalBlockingFilename, 'r') as fpBlocked:
|
||||||
if shortDomain:
|
blockedStr = fpBlocked.read()
|
||||||
if '*@' + shortDomain in blockedStr:
|
if '*@' + domain in blockedStr:
|
||||||
return True
|
return True
|
||||||
|
if shortDomain:
|
||||||
|
if '*@' + shortDomain in blockedStr:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# instance allow list
|
||||||
|
if not shortDomain:
|
||||||
|
if domain not in open(allowFilename).read():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if shortDomain not in open(allowFilename).read():
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -344,3 +359,91 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str,
|
||||||
nicknameBlocked, domainBlockedFull)
|
nicknameBlocked, domainBlockedFull)
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: post undo blocked via c2s - ' + postFilename)
|
print('DEBUG: post undo blocked via c2s - ' + postFilename)
|
||||||
|
|
||||||
|
|
||||||
|
def setBrochMode(baseDir: str, domainFull: str, enabled: bool) -> None:
|
||||||
|
"""Broch mode can be used to lock down the instance during
|
||||||
|
a period of time when it is temporarily under attack.
|
||||||
|
For example, where an adversary is constantly spinning up new
|
||||||
|
instances.
|
||||||
|
It surveys the following lists of all accounts and uses that
|
||||||
|
to construct an instance level allow list. Anything arriving
|
||||||
|
which is then not from one of the allowed domains will be dropped
|
||||||
|
"""
|
||||||
|
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||||
|
|
||||||
|
if not enabled:
|
||||||
|
# remove instance allow list
|
||||||
|
if os.path.isfile(allowFilename):
|
||||||
|
os.remove(allowFilename)
|
||||||
|
print('Broch mode turned off')
|
||||||
|
else:
|
||||||
|
if os.path.isfile(allowFilename):
|
||||||
|
lastModified = fileLastModified(allowFilename)
|
||||||
|
print('Broch mode already activated ' + lastModified)
|
||||||
|
return
|
||||||
|
# generate instance allow list
|
||||||
|
allowedDomains = [domainFull]
|
||||||
|
followFiles = ('following.txt', 'followers.txt')
|
||||||
|
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||||
|
for acct in dirs:
|
||||||
|
if '@' not in acct:
|
||||||
|
continue
|
||||||
|
if 'inbox@' in acct or 'news@' in acct:
|
||||||
|
continue
|
||||||
|
accountDir = os.path.join(baseDir + '/accounts', acct)
|
||||||
|
for followFileType in followFiles:
|
||||||
|
followingFilename = accountDir + '/' + followFileType
|
||||||
|
if not os.path.isfile(followingFilename):
|
||||||
|
continue
|
||||||
|
with open(followingFilename, "r") as f:
|
||||||
|
followList = f.readlines()
|
||||||
|
for handle in followList:
|
||||||
|
if '@' not in handle:
|
||||||
|
continue
|
||||||
|
handle = handle.replace('\n', '')
|
||||||
|
handleDomain = handle.split('@')[1]
|
||||||
|
if handleDomain not in allowedDomains:
|
||||||
|
allowedDomains.append(handleDomain)
|
||||||
|
break
|
||||||
|
|
||||||
|
# write the allow file
|
||||||
|
allowFile = open(allowFilename, "w+")
|
||||||
|
if allowFile:
|
||||||
|
allowFile.write(domainFull + '\n')
|
||||||
|
for d in allowedDomains:
|
||||||
|
allowFile.write(d + '\n')
|
||||||
|
allowFile.close()
|
||||||
|
print('Broch mode enabled')
|
||||||
|
|
||||||
|
setConfigParam(baseDir, "brochMode", enabled)
|
||||||
|
|
||||||
|
|
||||||
|
def brochModeLapses(baseDir: str, lapseDays=7) -> bool:
|
||||||
|
"""After broch mode is enabled it automatically
|
||||||
|
elapses after a period of time
|
||||||
|
"""
|
||||||
|
allowFilename = baseDir + '/accounts/allowedinstances.txt'
|
||||||
|
if not os.path.isfile(allowFilename):
|
||||||
|
return False
|
||||||
|
lastModified = fileLastModified(allowFilename)
|
||||||
|
modifiedDate = None
|
||||||
|
brochMode = True
|
||||||
|
try:
|
||||||
|
modifiedDate = \
|
||||||
|
datetime.strptime(lastModified, "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
except BaseException:
|
||||||
|
return brochMode
|
||||||
|
if not modifiedDate:
|
||||||
|
return brochMode
|
||||||
|
currTime = datetime.datetime.utcnow()
|
||||||
|
daysSinceBroch = (currTime - modifiedDate).days
|
||||||
|
if daysSinceBroch >= lapseDays:
|
||||||
|
try:
|
||||||
|
os.remove(allowFilename)
|
||||||
|
brochMode = False
|
||||||
|
setConfigParam(baseDir, "brochMode", brochMode)
|
||||||
|
print('Broch mode has elapsed')
|
||||||
|
except BaseException:
|
||||||
|
pass
|
||||||
|
return brochMode
|
||||||
|
|
|
||||||
78
daemon.py
78
daemon.py
|
|
@ -109,6 +109,7 @@ from threads import threadWithTrace
|
||||||
from threads import removeDormantThreads
|
from threads import removeDormantThreads
|
||||||
from media import replaceYouTube
|
from media import replaceYouTube
|
||||||
from media import attachMedia
|
from media import attachMedia
|
||||||
|
from blocking import setBrochMode
|
||||||
from blocking import addBlock
|
from blocking import addBlock
|
||||||
from blocking import removeBlock
|
from blocking import removeBlock
|
||||||
from blocking import addGlobalBlock
|
from blocking import addGlobalBlock
|
||||||
|
|
@ -185,6 +186,7 @@ from shares import addShare
|
||||||
from shares import removeShare
|
from shares import removeShare
|
||||||
from shares import expireShares
|
from shares import expireShares
|
||||||
from categories import setHashtagCategory
|
from categories import setHashtagCategory
|
||||||
|
from utils import getLocalNetworkAddresses
|
||||||
from utils import decodedHost
|
from utils import decodedHost
|
||||||
from utils import isPublicPost
|
from utils import isPublicPost
|
||||||
from utils import getLockedAccount
|
from utils import getLockedAccount
|
||||||
|
|
@ -478,6 +480,10 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if 'text/html' not in self.headers['Accept']:
|
if 'text/html' not in self.headers['Accept']:
|
||||||
return False
|
return False
|
||||||
if self.headers['Accept'].startswith('*'):
|
if self.headers['Accept'].startswith('*'):
|
||||||
|
if self.headers.get('User-Agent'):
|
||||||
|
if 'ELinks' in self.headers['User-Agent'] or \
|
||||||
|
'Lynx' in self.headers['User-Agent']:
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
if 'json' in self.headers['Accept']:
|
if 'json' in self.headers['Accept']:
|
||||||
return False
|
return False
|
||||||
|
|
@ -1153,20 +1159,46 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
# check for blocked domains so that they can be rejected early
|
# check for blocked domains so that they can be rejected early
|
||||||
messageDomain = None
|
messageDomain = None
|
||||||
if messageJson.get('actor'):
|
if not messageJson.get('actor'):
|
||||||
messageDomain, messagePort = \
|
|
||||||
getDomainFromActor(messageJson['actor'])
|
|
||||||
if isBlockedDomain(self.server.baseDir, messageDomain):
|
|
||||||
print('POST from blocked domain ' + messageDomain)
|
|
||||||
self._400()
|
|
||||||
self.server.POSTbusy = False
|
|
||||||
return 3
|
|
||||||
else:
|
|
||||||
print('Message arriving at inbox queue has no actor')
|
print('Message arriving at inbox queue has no actor')
|
||||||
self._400()
|
self._400()
|
||||||
self.server.POSTbusy = False
|
self.server.POSTbusy = False
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
# actor should be a string
|
||||||
|
if not isinstance(messageJson['actor'], str):
|
||||||
|
self._400()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return 3
|
||||||
|
|
||||||
|
# actor should look like a url
|
||||||
|
if '://' not in messageJson['actor'] or \
|
||||||
|
'.' not in messageJson['actor']:
|
||||||
|
print('POST actor does not look like a url ' +
|
||||||
|
messageJson['actor'])
|
||||||
|
self._400()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return 3
|
||||||
|
|
||||||
|
# sent by an actor on a local network address?
|
||||||
|
if not self.server.allowLocalNetworkAccess:
|
||||||
|
localNetworkPatternList = getLocalNetworkAddresses()
|
||||||
|
for localNetworkPattern in localNetworkPatternList:
|
||||||
|
if localNetworkPattern in messageJson['actor']:
|
||||||
|
print('POST actor contains local network address ' +
|
||||||
|
messageJson['actor'])
|
||||||
|
self._400()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return 3
|
||||||
|
|
||||||
|
messageDomain, messagePort = \
|
||||||
|
getDomainFromActor(messageJson['actor'])
|
||||||
|
if isBlockedDomain(self.server.baseDir, messageDomain):
|
||||||
|
print('POST from blocked domain ' + messageDomain)
|
||||||
|
self._400()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return 3
|
||||||
|
|
||||||
# if the inbox queue is full then return a busy code
|
# if the inbox queue is full then return a busy code
|
||||||
if len(self.server.inboxQueue) >= self.server.maxQueueLength:
|
if len(self.server.inboxQueue) >= self.server.maxQueueLength:
|
||||||
if messageDomain:
|
if messageDomain:
|
||||||
|
|
@ -4512,6 +4544,18 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
setConfigParam(baseDir, "verifyAllSignatures",
|
setConfigParam(baseDir, "verifyAllSignatures",
|
||||||
verifyAllSignatures)
|
verifyAllSignatures)
|
||||||
|
|
||||||
|
brochMode = False
|
||||||
|
if fields.get('brochMode'):
|
||||||
|
if fields['brochMode'] == 'on':
|
||||||
|
brochMode = True
|
||||||
|
currBrochMode = \
|
||||||
|
getConfigParam(baseDir, "brochMode")
|
||||||
|
if brochMode != currBrochMode:
|
||||||
|
setBrochMode(self.server.baseDir,
|
||||||
|
self.server.domainFull,
|
||||||
|
brochMode)
|
||||||
|
setConfigParam(baseDir, "brochMode", brochMode)
|
||||||
|
|
||||||
# change moderators list
|
# change moderators list
|
||||||
if fields.get('moderators'):
|
if fields.get('moderators'):
|
||||||
if path.startswith('/users/' +
|
if path.startswith('/users/' +
|
||||||
|
|
@ -10099,9 +10143,13 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
# manifest for progressive web apps
|
# manifest for progressive web apps
|
||||||
if '/manifest.json' in self.path:
|
if '/manifest.json' in self.path:
|
||||||
self._progressiveWebAppManifest(callingDomain,
|
if self._hasAccept(callingDomain):
|
||||||
GETstartTime, GETtimings)
|
if not self._requestHTTP():
|
||||||
return
|
self._progressiveWebAppManifest(callingDomain,
|
||||||
|
GETstartTime, GETtimings)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.path = '/'
|
||||||
|
|
||||||
# default newswire favicon, for links to sites which
|
# default newswire favicon, for links to sites which
|
||||||
# have no favicon
|
# have no favicon
|
||||||
|
|
@ -13889,7 +13937,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def runDaemon(verifyAllSignatures: bool,
|
def runDaemon(brochMode: bool,
|
||||||
|
verifyAllSignatures: bool,
|
||||||
sendThreadsTimeoutMins: int,
|
sendThreadsTimeoutMins: int,
|
||||||
dormantMonths: int,
|
dormantMonths: int,
|
||||||
maxNewswirePosts: int,
|
maxNewswirePosts: int,
|
||||||
|
|
@ -14142,6 +14191,9 @@ def runDaemon(verifyAllSignatures: bool,
|
||||||
# cache to store css files
|
# cache to store css files
|
||||||
httpd.cssCache = {}
|
httpd.cssCache = {}
|
||||||
|
|
||||||
|
# whether to enable broch mode, which locks down the instance
|
||||||
|
setBrochMode(baseDir, httpd.domainFull, brochMode)
|
||||||
|
|
||||||
if not os.path.isdir(baseDir + '/accounts/inbox@' + domain):
|
if not os.path.isdir(baseDir + '/accounts/inbox@' + domain):
|
||||||
print('Creating shared inbox: inbox@' + domain)
|
print('Creating shared inbox: inbox@' + domain)
|
||||||
createSharedInbox(baseDir, 'inbox', domain, port, httpPrefix)
|
createSharedInbox(baseDir, 'inbox', domain, port, httpPrefix)
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,11 @@
|
||||||
--column-left-icons-margin: 0;
|
--column-left-icons-margin: 0;
|
||||||
--column-right-border-width: 0;
|
--column-right-border-width: 0;
|
||||||
--column-left-border-color: black;
|
--column-left-border-color: black;
|
||||||
--column-left-icon-size: 20%;
|
--column-left-icon-size: 2.1vw;
|
||||||
--column-left-icon-size-mobile: 10%;
|
--column-left-icon-size-mobile: 10%;
|
||||||
--column-left-image-width-mobile: 40vw;
|
--column-left-image-width-mobile: 40vw;
|
||||||
--column-right-image-width-mobile: 100vw;
|
--column-right-image-width-mobile: 100vw;
|
||||||
--column-right-icon-size: 20%;
|
--column-right-icon-size: 2.1vw;
|
||||||
--column-right-icon-size-mobile: 10%;
|
--column-right-icon-size-mobile: 10%;
|
||||||
--newswire-date-color: white;
|
--newswire-date-color: white;
|
||||||
--newswire-voted-background-color: black;
|
--newswire-voted-background-color: black;
|
||||||
|
|
|
||||||
13
epicyon.py
13
epicyon.py
|
|
@ -279,6 +279,11 @@ parser.add_argument("--verifyAllSignatures",
|
||||||
const=True, default=False,
|
const=True, default=False,
|
||||||
help="Whether to require that all incoming " +
|
help="Whether to require that all incoming " +
|
||||||
"posts have valid jsonld signatures")
|
"posts have valid jsonld signatures")
|
||||||
|
parser.add_argument("--brochMode",
|
||||||
|
dest='brochMode',
|
||||||
|
type=str2bool, nargs='?',
|
||||||
|
const=True, default=False,
|
||||||
|
help="Enable broch mode")
|
||||||
parser.add_argument("--noapproval", type=str2bool, nargs='?',
|
parser.add_argument("--noapproval", type=str2bool, nargs='?',
|
||||||
const=True, default=False,
|
const=True, default=False,
|
||||||
help="Allow followers without approval")
|
help="Allow followers without approval")
|
||||||
|
|
@ -2292,6 +2297,11 @@ verifyAllSignatures = \
|
||||||
if verifyAllSignatures is not None:
|
if verifyAllSignatures is not None:
|
||||||
args.verifyAllSignatures = bool(verifyAllSignatures)
|
args.verifyAllSignatures = bool(verifyAllSignatures)
|
||||||
|
|
||||||
|
brochMode = \
|
||||||
|
getConfigParam(baseDir, 'brochMode')
|
||||||
|
if brochMode is not None:
|
||||||
|
args.brochMode = bool(brochMode)
|
||||||
|
|
||||||
YTDomain = getConfigParam(baseDir, 'youtubedomain')
|
YTDomain = getConfigParam(baseDir, 'youtubedomain')
|
||||||
if YTDomain:
|
if YTDomain:
|
||||||
if '://' in YTDomain:
|
if '://' in YTDomain:
|
||||||
|
|
@ -2305,7 +2315,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess):
|
||||||
print('Theme set to ' + themeName)
|
print('Theme set to ' + themeName)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
runDaemon(args.verifyAllSignatures,
|
runDaemon(args.brochMode,
|
||||||
|
args.verifyAllSignatures,
|
||||||
args.sendThreadsTimeoutMins,
|
args.sendThreadsTimeoutMins,
|
||||||
args.dormantMonths,
|
args.dormantMonths,
|
||||||
args.maxNewswirePosts,
|
args.maxNewswirePosts,
|
||||||
|
|
|
||||||
3
inbox.py
3
inbox.py
|
|
@ -51,6 +51,7 @@ from bookmarks import updateBookmarksCollection
|
||||||
from bookmarks import undoBookmarksCollectionEntry
|
from bookmarks import undoBookmarksCollectionEntry
|
||||||
from blocking import isBlocked
|
from blocking import isBlocked
|
||||||
from blocking import isBlockedDomain
|
from blocking import isBlockedDomain
|
||||||
|
from blocking import brochModeLapses
|
||||||
from filters import isFiltered
|
from filters import isFiltered
|
||||||
from utils import updateAnnounceCollection
|
from utils import updateAnnounceCollection
|
||||||
from utils import undoAnnounceCollectionEntry
|
from utils import undoAnnounceCollectionEntry
|
||||||
|
|
@ -2518,6 +2519,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
# heartbeat to monitor whether the inbox queue is running
|
# heartbeat to monitor whether the inbox queue is running
|
||||||
heartBeatCtr += 5
|
heartBeatCtr += 5
|
||||||
if heartBeatCtr >= 10:
|
if heartBeatCtr >= 10:
|
||||||
|
# turn off broch mode after it has timed out
|
||||||
|
brochModeLapses(baseDir)
|
||||||
print('>>> Heartbeat Q:' + str(len(queue)) + ' ' +
|
print('>>> Heartbeat Q:' + str(len(queue)) + ' ' +
|
||||||
'{:%F %T}'.format(datetime.datetime.now()))
|
'{:%F %T}'.format(datetime.datetime.now()))
|
||||||
heartBeatCtr = 0
|
heartBeatCtr = 0
|
||||||
|
|
|
||||||
18
outbox.py
18
outbox.py
|
|
@ -14,6 +14,7 @@ from posts import outboxMessageCreateWrap
|
||||||
from posts import savePostToBox
|
from posts import savePostToBox
|
||||||
from posts import sendToFollowersThread
|
from posts import sendToFollowersThread
|
||||||
from posts import sendToNamedAddresses
|
from posts import sendToNamedAddresses
|
||||||
|
from utils import getLocalNetworkAddresses
|
||||||
from utils import getFullDomain
|
from utils import getFullDomain
|
||||||
from utils import removeIdEnding
|
from utils import removeIdEnding
|
||||||
from utils import getDomainFromActor
|
from utils import getDomainFromActor
|
||||||
|
|
@ -114,6 +115,23 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str,
|
||||||
'Create does not have the "to" parameter ' +
|
'Create does not have the "to" parameter ' +
|
||||||
str(messageJson))
|
str(messageJson))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# actor should be a string
|
||||||
|
if not isinstance(messageJson['actor'], str):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# actor should look like a url
|
||||||
|
if '://' not in messageJson['actor'] or \
|
||||||
|
'.' not in messageJson['actor']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# sent by an actor on a local network address?
|
||||||
|
if not allowLocalNetworkAccess:
|
||||||
|
localNetworkPatternList = getLocalNetworkAddresses()
|
||||||
|
for localNetworkPattern in localNetworkPatternList:
|
||||||
|
if localNetworkPattern in messageJson['actor']:
|
||||||
|
return False
|
||||||
|
|
||||||
testDomain, testPort = getDomainFromActor(messageJson['actor'])
|
testDomain, testPort = getDomainFromActor(messageJson['actor'])
|
||||||
testDomain = getFullDomain(testDomain, testPort)
|
testDomain = getFullDomain(testDomain, testPort)
|
||||||
if isBlockedDomain(baseDir, testDomain):
|
if isBlockedDomain(baseDir, testDomain):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setuptools.setup()
|
||||||
12
tests.py
12
tests.py
|
|
@ -325,8 +325,10 @@ def createServerAlice(path: str, domain: str, port: int,
|
||||||
sendThreadsTimeoutMins = 30
|
sendThreadsTimeoutMins = 30
|
||||||
maxFollowers = 10
|
maxFollowers = 10
|
||||||
verifyAllSignatures = True
|
verifyAllSignatures = True
|
||||||
|
brochMode = False
|
||||||
print('Server running: Alice')
|
print('Server running: Alice')
|
||||||
runDaemon(verifyAllSignatures,
|
runDaemon(brochMode,
|
||||||
|
verifyAllSignatures,
|
||||||
sendThreadsTimeoutMins,
|
sendThreadsTimeoutMins,
|
||||||
dormantMonths, maxNewswirePosts,
|
dormantMonths, maxNewswirePosts,
|
||||||
allowLocalNetworkAccess,
|
allowLocalNetworkAccess,
|
||||||
|
|
@ -420,8 +422,10 @@ def createServerBob(path: str, domain: str, port: int,
|
||||||
sendThreadsTimeoutMins = 30
|
sendThreadsTimeoutMins = 30
|
||||||
maxFollowers = 10
|
maxFollowers = 10
|
||||||
verifyAllSignatures = True
|
verifyAllSignatures = True
|
||||||
|
brochMode = False
|
||||||
print('Server running: Bob')
|
print('Server running: Bob')
|
||||||
runDaemon(verifyAllSignatures,
|
runDaemon(brochMode,
|
||||||
|
verifyAllSignatures,
|
||||||
sendThreadsTimeoutMins,
|
sendThreadsTimeoutMins,
|
||||||
dormantMonths, maxNewswirePosts,
|
dormantMonths, maxNewswirePosts,
|
||||||
allowLocalNetworkAccess,
|
allowLocalNetworkAccess,
|
||||||
|
|
@ -469,8 +473,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
|
||||||
sendThreadsTimeoutMins = 30
|
sendThreadsTimeoutMins = 30
|
||||||
maxFollowers = 10
|
maxFollowers = 10
|
||||||
verifyAllSignatures = True
|
verifyAllSignatures = True
|
||||||
|
brochMode = False
|
||||||
print('Server running: Eve')
|
print('Server running: Eve')
|
||||||
runDaemon(verifyAllSignatures,
|
runDaemon(brochMode,
|
||||||
|
verifyAllSignatures,
|
||||||
sendThreadsTimeoutMins,
|
sendThreadsTimeoutMins,
|
||||||
dormantMonths, maxNewswirePosts,
|
dormantMonths, maxNewswirePosts,
|
||||||
allowLocalNetworkAccess,
|
allowLocalNetworkAccess,
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "انتقل إلى Newswire",
|
"Skip to Newswire": "انتقل إلى Newswire",
|
||||||
"Skip to Links": "تخطي إلى روابط الويب",
|
"Skip to Links": "تخطي إلى روابط الويب",
|
||||||
"Publish a blog article": "نشر مقال بلوق",
|
"Publish a blog article": "نشر مقال بلوق",
|
||||||
"Featured writer": "كاتب متميز"
|
"Featured writer": "كاتب متميز",
|
||||||
|
"Broch mode": "وضع الكتيب"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Vés a Newswire",
|
"Skip to Newswire": "Vés a Newswire",
|
||||||
"Skip to Links": "Vés als enllaços web",
|
"Skip to Links": "Vés als enllaços web",
|
||||||
"Publish a blog article": "Publicar un article del bloc",
|
"Publish a blog article": "Publicar un article del bloc",
|
||||||
"Featured writer": "Escriptor destacat"
|
"Featured writer": "Escriptor destacat",
|
||||||
|
"Broch mode": "Mode Broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Neidio i Newswire",
|
"Skip to Newswire": "Neidio i Newswire",
|
||||||
"Skip to Links": "Neidio i Dolenni Gwe",
|
"Skip to Links": "Neidio i Dolenni Gwe",
|
||||||
"Publish a blog article": "Cyhoeddi erthygl blog",
|
"Publish a blog article": "Cyhoeddi erthygl blog",
|
||||||
"Featured writer": "Awdur dan sylw"
|
"Featured writer": "Awdur dan sylw",
|
||||||
|
"Broch mode": "Modd Broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Springe zu Newswire",
|
"Skip to Newswire": "Springe zu Newswire",
|
||||||
"Skip to Links": "Springe zu Weblinks",
|
"Skip to Links": "Springe zu Weblinks",
|
||||||
"Publish a blog article": "Veröffentlichen Sie einen Blog-Artikel",
|
"Publish a blog article": "Veröffentlichen Sie einen Blog-Artikel",
|
||||||
"Featured writer": "Ausgewählter Schriftsteller"
|
"Featured writer": "Ausgewählter Schriftsteller",
|
||||||
|
"Broch mode": "Broch-Modus"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Skip to Newswire",
|
"Skip to Newswire": "Skip to Newswire",
|
||||||
"Skip to Links": "Skip to Links",
|
"Skip to Links": "Skip to Links",
|
||||||
"Publish a blog article": "Publish a blog article",
|
"Publish a blog article": "Publish a blog article",
|
||||||
"Featured writer": "Featured writer"
|
"Featured writer": "Featured writer",
|
||||||
|
"Broch mode": "Broch mode"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Saltar a Newswire",
|
"Skip to Newswire": "Saltar a Newswire",
|
||||||
"Skip to Links": "Saltar a enlaces web",
|
"Skip to Links": "Saltar a enlaces web",
|
||||||
"Publish a blog article": "Publica un artículo de blog",
|
"Publish a blog article": "Publica un artículo de blog",
|
||||||
"Featured writer": "Escritora destacada"
|
"Featured writer": "Escritora destacada",
|
||||||
|
"Broch mode": "Modo broche"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Passer à Newswire",
|
"Skip to Newswire": "Passer à Newswire",
|
||||||
"Skip to Links": "Passer aux liens Web",
|
"Skip to Links": "Passer aux liens Web",
|
||||||
"Publish a blog article": "Publier un article de blog",
|
"Publish a blog article": "Publier un article de blog",
|
||||||
"Featured writer": "Écrivain en vedette"
|
"Featured writer": "Écrivain en vedette",
|
||||||
|
"Broch mode": "Mode Broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Scipeáil chuig Newswire",
|
"Skip to Newswire": "Scipeáil chuig Newswire",
|
||||||
"Skip to Links": "Scipeáil chuig Naisc Ghréasáin",
|
"Skip to Links": "Scipeáil chuig Naisc Ghréasáin",
|
||||||
"Publish a blog article": "Foilsigh alt blagála",
|
"Publish a blog article": "Foilsigh alt blagála",
|
||||||
"Featured writer": "Scríbhneoir mór le rá"
|
"Featured writer": "Scríbhneoir mór le rá",
|
||||||
|
"Broch mode": "Modh broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Newswire पर जाएं",
|
"Skip to Newswire": "Newswire पर जाएं",
|
||||||
"Skip to Links": "वेब लिंक पर जाएं",
|
"Skip to Links": "वेब लिंक पर जाएं",
|
||||||
"Publish a blog article": "एक ब्लॉग लेख प्रकाशित करें",
|
"Publish a blog article": "एक ब्लॉग लेख प्रकाशित करें",
|
||||||
"Featured writer": "फीचर्ड लेखक"
|
"Featured writer": "फीचर्ड लेखक",
|
||||||
|
"Broch mode": "ब्रोच मोड"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Passa a Newswire",
|
"Skip to Newswire": "Passa a Newswire",
|
||||||
"Skip to Links": "Passa a collegamenti Web",
|
"Skip to Links": "Passa a collegamenti Web",
|
||||||
"Publish a blog article": "Pubblica un articolo sul blog",
|
"Publish a blog article": "Pubblica un articolo sul blog",
|
||||||
"Featured writer": "Scrittore in primo piano"
|
"Featured writer": "Scrittore in primo piano",
|
||||||
|
"Broch mode": "Modalità Broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Newswireにスキップ",
|
"Skip to Newswire": "Newswireにスキップ",
|
||||||
"Skip to Links": "Webリンクにスキップ",
|
"Skip to Links": "Webリンクにスキップ",
|
||||||
"Publish a blog article": "ブログ記事を公開する",
|
"Publish a blog article": "ブログ記事を公開する",
|
||||||
"Featured writer": "注目の作家"
|
"Featured writer": "注目の作家",
|
||||||
|
"Broch mode": "ブロッホモード"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,5 +364,6 @@
|
||||||
"Skip to Newswire": "Skip to Newswire",
|
"Skip to Newswire": "Skip to Newswire",
|
||||||
"Skip to Links": "Skip to Links",
|
"Skip to Links": "Skip to Links",
|
||||||
"Publish a blog article": "Publish a blog article",
|
"Publish a blog article": "Publish a blog article",
|
||||||
"Featured writer": "Featured writer"
|
"Featured writer": "Featured writer",
|
||||||
|
"Broch mode": "Broch mode"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Pular para Newswire",
|
"Skip to Newswire": "Pular para Newswire",
|
||||||
"Skip to Links": "Pular para links da web",
|
"Skip to Links": "Pular para links da web",
|
||||||
"Publish a blog article": "Publique um artigo de blog",
|
"Publish a blog article": "Publique um artigo de blog",
|
||||||
"Featured writer": "Escritor em destaque"
|
"Featured writer": "Escritor em destaque",
|
||||||
|
"Broch mode": "Modo broch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "Перейти к ленте новостей",
|
"Skip to Newswire": "Перейти к ленте новостей",
|
||||||
"Skip to Links": "Перейти к веб-ссылкам",
|
"Skip to Links": "Перейти к веб-ссылкам",
|
||||||
"Publish a blog article": "Опубликовать статью в блоге",
|
"Publish a blog article": "Опубликовать статью в блоге",
|
||||||
"Featured writer": "Избранный писатель"
|
"Featured writer": "Избранный писатель",
|
||||||
|
"Broch mode": "Брош режим"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,5 +368,6 @@
|
||||||
"Skip to Newswire": "跳到新闻专线",
|
"Skip to Newswire": "跳到新闻专线",
|
||||||
"Skip to Links": "跳到网页链接",
|
"Skip to Links": "跳到网页链接",
|
||||||
"Publish a blog article": "发布博客文章",
|
"Publish a blog article": "发布博客文章",
|
||||||
"Featured writer": "特色作家"
|
"Featured writer": "特色作家",
|
||||||
|
"Broch mode": "断点模式"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
utils.py
8
utils.py
|
|
@ -605,6 +605,12 @@ def urlPermitted(url: str, federationList: []):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def getLocalNetworkAddresses() -> []:
|
||||||
|
"""Returns patterns for local network address detection
|
||||||
|
"""
|
||||||
|
return ('localhost', '127.0.', '192.168', '10.0.')
|
||||||
|
|
||||||
|
|
||||||
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
||||||
"""Returns true if the given content contains dangerous html markup
|
"""Returns true if the given content contains dangerous html markup
|
||||||
"""
|
"""
|
||||||
|
|
@ -615,7 +621,7 @@ def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
|
||||||
contentSections = content.split('<')
|
contentSections = content.split('<')
|
||||||
invalidPartials = ()
|
invalidPartials = ()
|
||||||
if not allowLocalNetworkAccess:
|
if not allowLocalNetworkAccess:
|
||||||
invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
|
invalidPartials = getLocalNetworkAddresses()
|
||||||
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
||||||
'frame', 'iframe', 'html', 'body',
|
'frame', 'iframe', 'html', 'body',
|
||||||
'hr', 'allow-popups', 'allow-scripts')
|
'hr', 'allow-popups', 'allow-scripts')
|
||||||
|
|
|
||||||
|
|
@ -1278,6 +1278,16 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
|
||||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
'name="verifyallsignatures"> ' + \
|
'name="verifyallsignatures"> ' + \
|
||||||
translate['Verify all signatures'] + '<br>\n'
|
translate['Verify all signatures'] + '<br>\n'
|
||||||
|
if getConfigParam(baseDir, "brochMode"):
|
||||||
|
instanceStr += \
|
||||||
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
|
'name="brochMode" checked> ' + \
|
||||||
|
translate['Broch mode'] + '<br>\n'
|
||||||
|
else:
|
||||||
|
instanceStr += \
|
||||||
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
|
'name="brochMode"> ' + \
|
||||||
|
translate['Broch mode'] + '<br>\n'
|
||||||
instanceStr += '</div>'
|
instanceStr += '</div>'
|
||||||
|
|
||||||
moderators = ''
|
moderators = ''
|
||||||
|
|
|
||||||
|
|
@ -416,19 +416,19 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
translate['Mod']
|
translate['Mod']
|
||||||
navLinks = {
|
navLinks = {
|
||||||
menuProfile: '/users/' + nickname,
|
menuProfile: '/users/' + nickname,
|
||||||
menuInbox: usersPath + '/inbox#timeline',
|
menuInbox: usersPath + '/inbox#timelineposts',
|
||||||
menuSearch: usersPath + '/search',
|
menuSearch: usersPath + '/search',
|
||||||
menuNewPost: usersPath + '/newpost',
|
menuNewPost: usersPath + '/newpost',
|
||||||
menuCalendar: usersPath + '/calendar',
|
menuCalendar: usersPath + '/calendar',
|
||||||
menuDM: usersPath + '/dm#timeline',
|
menuDM: usersPath + '/dm#timelineposts',
|
||||||
menuReplies: usersPath + '/tlreplies#timeline',
|
menuReplies: usersPath + '/tlreplies#timelineposts',
|
||||||
menuOutbox: usersPath + '/inbox#timeline',
|
menuOutbox: usersPath + '/inbox#timelineposts',
|
||||||
menuBookmarks: usersPath + '/tlbookmarks#timeline',
|
menuBookmarks: usersPath + '/tlbookmarks#timelineposts',
|
||||||
menuShares: usersPath + '/tlshares#timeline',
|
menuShares: usersPath + '/tlshares#timelineposts',
|
||||||
menuBlogs: usersPath + '/tlblogs#timeline',
|
menuBlogs: usersPath + '/tlblogs#timelineposts',
|
||||||
# menuEvents: usersPath + '/tlevents#timeline',
|
# menuEvents: usersPath + '/tlevents#timelineposts',
|
||||||
menuNewswire: '#newswire',
|
menuNewswire: usersPath + '/newswiremobile',
|
||||||
menuLinks: '#links'
|
menuLinks: usersPath + '/linksmobile'
|
||||||
}
|
}
|
||||||
if moderator:
|
if moderator:
|
||||||
navLinks[menuModeration] = usersPath + '/moderation#modtimeline'
|
navLinks[menuModeration] = usersPath + '/moderation#modtimeline'
|
||||||
|
|
@ -502,7 +502,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
calendarImage, followApprovals,
|
calendarImage, followApprovals,
|
||||||
iconsAsButtons)
|
iconsAsButtons)
|
||||||
|
|
||||||
tlStr += ' <div id="timeline" class="timeline-posts">\n'
|
tlStr += ' <div id="timelineposts" class="timeline-posts">\n'
|
||||||
|
|
||||||
# second row of buttons for moderator actions
|
# second row of buttons for moderator actions
|
||||||
if moderator and boxName == 'moderation':
|
if moderator and boxName == 'moderation':
|
||||||
|
|
|
||||||
|
|
@ -892,26 +892,26 @@ def htmlKeyboardNavigation(banner: str, links: {},
|
||||||
followApprovals=False) -> str:
|
followApprovals=False) -> str:
|
||||||
"""Given a set of links return the html for keyboard navigation
|
"""Given a set of links return the html for keyboard navigation
|
||||||
"""
|
"""
|
||||||
htmlStr = '<div class="transparent"><ul>'
|
htmlStr = '<div class="transparent"><ul>\n'
|
||||||
|
|
||||||
if banner:
|
if banner:
|
||||||
htmlStr += '<pre aria-label="">' + banner + '<br><br></pre>'
|
htmlStr += '<pre aria-label="">\n' + banner + '\n<br><br></pre>\n'
|
||||||
|
|
||||||
if subHeading:
|
if subHeading:
|
||||||
htmlStr += '<strong><label class="transparent">' + \
|
htmlStr += '<strong><label class="transparent">' + \
|
||||||
subHeading + '</label></strong><br>'
|
subHeading + '</label></strong><br>\n'
|
||||||
|
|
||||||
# show new follower approvals
|
# show new follower approvals
|
||||||
if usersPath and translate and followApprovals:
|
if usersPath and translate and followApprovals:
|
||||||
htmlStr += '<strong><label class="transparent">' + \
|
htmlStr += '<strong><label class="transparent">' + \
|
||||||
'<a href="' + usersPath + '/followers#timeline">' + \
|
'<a href="' + usersPath + '/followers#timeline">' + \
|
||||||
translate['Approve follow requests'] + '</a>' + \
|
translate['Approve follow requests'] + '</a>' + \
|
||||||
'</label></strong><br><br>'
|
'</label></strong><br><br>\n'
|
||||||
|
|
||||||
# show the list of links
|
# show the list of links
|
||||||
for title, url in links.items():
|
for title, url in links.items():
|
||||||
htmlStr += '<li><label class="transparent">' + \
|
htmlStr += '<li><label class="transparent">' + \
|
||||||
'<a href="' + str(url) + '">' + \
|
'<a href="' + str(url) + '">' + \
|
||||||
str(title) + '</a></label></li>'
|
str(title) + '</a></label></li>\n'
|
||||||
htmlStr += '</ul></div>'
|
htmlStr += '</ul></div>\n'
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="ActivityPub server written in Python, HTML and CSS, and suitable for self-hosting on single board computers">
|
||||||
|
<meta name="keywords" content="ActivityPub, Fediverse, Python, HTML, CSS">
|
||||||
|
<meta name="author" content="Bob Mottram">
|
||||||
<style>
|
<style>
|
||||||
@charset "UTF-8";
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
|
@ -1141,6 +1144,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
<head>
|
||||||
|
<title>Epicyon ActivityPub server</title>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="timeline-banner"></div>
|
<div class="timeline-banner"></div>
|
||||||
<center>
|
<center>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue