blocking.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. __filename__ = "blocking.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.1.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import os
  9. from utils import isEvil
  10. def addGlobalBlock(baseDir: str, \
  11. blockNickname: str,blockDomain: str) -> bool:
  12. """Global block which applies to all accounts
  13. """
  14. blockingFilename=baseDir+'/accounts/blocking.txt'
  15. if not blockNickname.startswith('#'):
  16. blockHandle=blockNickname+'@'+blockDomain
  17. if os.path.isfile(blockingFilename):
  18. if blockHandle in open(blockingFilename).read():
  19. return False
  20. blockFile=open(blockingFilename, "a+")
  21. blockFile.write(blockHandle+'\n')
  22. blockFile.close()
  23. else:
  24. blockHashtag=blockNickname
  25. if os.path.isfile(blockingFilename):
  26. if blockHashtag+'\n' in open(blockingFilename).read():
  27. return False
  28. blockFile=open(blockingFilename, "a+")
  29. blockFile.write(blockHashtag+'\n')
  30. blockFile.close()
  31. return True
  32. def addBlock(baseDir: str,nickname: str,domain: str, \
  33. blockNickname: str,blockDomain: str) -> bool:
  34. """Block the given account
  35. """
  36. if ':' in domain:
  37. domain=domain.split(':')[0]
  38. blockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  39. blockHandle=blockNickname+'@'+blockDomain
  40. if os.path.isfile(blockingFilename):
  41. if blockHandle in open(blockingFilename).read():
  42. return False
  43. blockFile=open(blockingFilename, "a+")
  44. blockFile.write(blockHandle+'\n')
  45. blockFile.close()
  46. return True
  47. def removeGlobalBlock(baseDir: str, \
  48. unblockNickname: str, \
  49. unblockDomain: str) -> bool:
  50. """Unblock the given global block
  51. """
  52. unblockingFilename=baseDir+'/accounts/blocking.txt'
  53. if not unblockNickname.startswith('#'):
  54. unblockHandle=unblockNickname+'@'+unblockDomain
  55. if os.path.isfile(unblockingFilename):
  56. if unblockHandle in open(unblockingFilename).read():
  57. with open(unblockingFilename, 'r') as fp:
  58. with open(unblockingFilename+'.new', 'w') as fpnew:
  59. for line in fp:
  60. handle=line.replace('\n','')
  61. if unblockHandle not in line:
  62. fpnew.write(handle+'\n')
  63. if os.path.isfile(unblockingFilename+'.new'):
  64. os.rename(unblockingFilename+'.new',unblockingFilename)
  65. return True
  66. else:
  67. unblockHashtag=unblockNickname
  68. if os.path.isfile(unblockingFilename):
  69. if unblockHashtag+'\n' in open(unblockingFilename).read():
  70. with open(unblockingFilename, 'r') as fp:
  71. with open(unblockingFilename+'.new', 'w') as fpnew:
  72. for line in fp:
  73. blockLine=line.replace('\n','')
  74. if unblockHashtag not in line:
  75. fpnew.write(blockLine+'\n')
  76. if os.path.isfile(unblockingFilename+'.new'):
  77. os.rename(unblockingFilename+'.new',unblockingFilename)
  78. return True
  79. return False
  80. def removeBlock(baseDir: str,nickname: str,domain: str, \
  81. unblockNickname: str,unblockDomain: str) -> bool:
  82. """Unblock the given account
  83. """
  84. if ':' in domain:
  85. domain=domain.split(':')[0]
  86. unblockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  87. unblockHandle=unblockNickname+'@'+unblockDomain
  88. if os.path.isfile(unblockingFilename):
  89. if unblockHandle in open(unblockingFilename).read():
  90. with open(unblockingFilename, 'r') as fp:
  91. with open(unblockingFilename+'.new', 'w') as fpnew:
  92. for line in fp:
  93. handle=line.replace('\n','')
  94. if unblockHandle not in line:
  95. fpnew.write(handle+'\n')
  96. if os.path.isfile(unblockingFilename+'.new'):
  97. os.rename(unblockingFilename+'.new',unblockingFilename)
  98. return True
  99. return False
  100. def isBlockedHashtag(baseDir: str,hashtag: str) -> bool:
  101. """Is the given hashtag blocked?
  102. """
  103. globalBlockingFilename=baseDir+'/accounts/blocking.txt'
  104. if os.path.isfile(globalBlockingFilename):
  105. hashtag=hashtag.strip('\n')
  106. if hashtag+'\n' in open(globalBlockingFilename).read():
  107. return True
  108. return False
  109. def isBlockedDomain(baseDir: str,domain: str) -> bool:
  110. """Is the given domain blocked?
  111. """
  112. if isEvil(domain):
  113. return True
  114. globalBlockingFilename=baseDir+'/accounts/blocking.txt'
  115. if os.path.isfile(globalBlockingFilename):
  116. if '*@'+domain in open(globalBlockingFilename).read():
  117. return True
  118. return False
  119. def isBlocked(baseDir: str,nickname: str,domain: str, \
  120. blockNickname: str,blockDomain: str) -> bool:
  121. """Is the given nickname blocked?
  122. """
  123. if isEvil(blockDomain):
  124. return True
  125. globalBlockingFilename=baseDir+'/accounts/blocking.txt'
  126. if os.path.isfile(globalBlockingFilename):
  127. if '*@'+blockDomain in open(globalBlockingFilename).read():
  128. return True
  129. blockHandle=blockNickname+'@'+blockDomain
  130. if blockHandle in open(globalBlockingFilename).read():
  131. return True
  132. allowFilename= \
  133. baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
  134. if os.path.isfile(allowFilename):
  135. if blockDomain not in open(allowFilename).read():
  136. return True
  137. blockingFilename= \
  138. baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  139. if os.path.isfile(blockingFilename):
  140. if '*@'+blockDomain in open(blockingFilename).read():
  141. return True
  142. blockHandle=blockNickname+'@'+blockDomain
  143. if blockHandle in open(blockingFilename).read():
  144. return True
  145. return False
  146. def sendBlockViaServer(baseDir: str,session, \
  147. fromNickname: str,password: str, \
  148. fromDomain: str,fromPort: int, \
  149. httpPrefix: str,blockedUrl: str, \
  150. cachedWebfingers: {},personCache: {}, \
  151. debug: bool,projectVersion: str) -> {}:
  152. """Creates a block via c2s
  153. """
  154. if not session:
  155. print('WARN: No session for sendBlockViaServer')
  156. return 6
  157. fromDomainFull=fromDomain
  158. if fromPort:
  159. if fromPort!=80 and fromPort!=443:
  160. if ':' not in fromDomain:
  161. fromDomainFull=fromDomain+':'+str(fromPort)
  162. toUrl= 'https://www.w3.org/ns/activitystreams#Public'
  163. ccUrl= \
  164. httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  165. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  166. newBlockJson = {
  167. "@context": "https://www.w3.org/ns/activitystreams",
  168. 'type': 'Block',
  169. 'actor': blockActor,
  170. 'object': blockedUrl,
  171. 'to': [toUrl],
  172. 'cc': [ccUrl]
  173. }
  174. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  175. # lookup the inbox for the To handle
  176. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  177. fromDomain,projectVersion)
  178. if not wfRequest:
  179. if debug:
  180. print('DEBUG: announce webfinger failed for '+handle)
  181. return 1
  182. postToBox='outbox'
  183. # get the actor inbox for the To handle
  184. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  185. getPersonBox(baseDir,session,wfRequest,personCache, \
  186. projectVersion,httpPrefix,fromNickname, \
  187. fromDomain,postToBox)
  188. if not inboxUrl:
  189. if debug:
  190. print('DEBUG: No '+postToBox+' was found for '+handle)
  191. return 3
  192. if not fromPersonId:
  193. if debug:
  194. print('DEBUG: No actor was found for '+handle)
  195. return 4
  196. authHeader=createBasicAuthHeader(fromNickname,password)
  197. headers = {'host': fromDomain, \
  198. 'Content-type': 'application/json', \
  199. 'Authorization': authHeader}
  200. postResult = \
  201. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  202. if debug:
  203. print('DEBUG: c2s POST block success')
  204. return newBlockJson
  205. def sendUndoBlockViaServer(baseDir: str,session, \
  206. fromNickname: str,password: str, \
  207. fromDomain: str,fromPort: int, \
  208. httpPrefix: str,blockedUrl: str, \
  209. cachedWebfingers: {},personCache: {}, \
  210. debug: bool,projectVersion: str) -> {}:
  211. """Creates a block via c2s
  212. """
  213. if not session:
  214. print('WARN: No session for sendBlockViaServer')
  215. return 6
  216. fromDomainFull=fromDomain
  217. if fromPort:
  218. if fromPort!=80 and fromPort!=443:
  219. if ':' not in fromDomain:
  220. fromDomainFull=fromDomain+':'+str(fromPort)
  221. toUrl= 'https://www.w3.org/ns/activitystreams#Public'
  222. ccUrl= \
  223. httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  224. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  225. newBlockJson = {
  226. "@context": "https://www.w3.org/ns/activitystreams",
  227. 'type': 'Undo',
  228. 'actor': blockActor,
  229. 'object': {
  230. 'type': 'Block',
  231. 'actor': blockActor,
  232. 'object': blockedUrl,
  233. 'to': [toUrl],
  234. 'cc': [ccUrl]
  235. }
  236. }
  237. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  238. # lookup the inbox for the To handle
  239. wfRequest= \
  240. webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  241. fromDomain,projectVersion)
  242. if not wfRequest:
  243. if debug:
  244. print('DEBUG: announce webfinger failed for '+handle)
  245. return 1
  246. postToBox='outbox'
  247. # get the actor inbox for the To handle
  248. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  249. getPersonBox(baseDir,session,wfRequest,personCache, \
  250. projectVersion,httpPrefix,fromNickname, \
  251. fromDomain,postToBox)
  252. if not inboxUrl:
  253. if debug:
  254. print('DEBUG: No '+postToBox+' was found for '+handle)
  255. return 3
  256. if not fromPersonId:
  257. if debug:
  258. print('DEBUG: No actor was found for '+handle)
  259. return 4
  260. authHeader=createBasicAuthHeader(fromNickname,password)
  261. headers = {'host': fromDomain, \
  262. 'Content-type': 'application/json', \
  263. 'Authorization': authHeader}
  264. postResult = \
  265. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  266. if debug:
  267. print('DEBUG: c2s POST block success')
  268. return newBlockJson
  269. def outboxBlock(baseDir: str,httpPrefix: str, \
  270. nickname: str,domain: str,port: int, \
  271. messageJson: {},debug: bool) -> None:
  272. """ When a block request is received by the outbox from c2s
  273. """
  274. if not messageJson.get('type'):
  275. if debug:
  276. print('DEBUG: block - no type')
  277. return
  278. if not messageJson['type']=='Block':
  279. if debug:
  280. print('DEBUG: not a block')
  281. return
  282. if not messageJson.get('object'):
  283. if debug:
  284. print('DEBUG: no object in block')
  285. return
  286. if not isinstance(messageJson['object'], str):
  287. if debug:
  288. print('DEBUG: block object is not string')
  289. return
  290. if debug:
  291. print('DEBUG: c2s block request arrived in outbox')
  292. messageId=messageJson['object'].replace('/activity','')
  293. if '/statuses/' not in messageId:
  294. if debug:
  295. print('DEBUG: c2s block object is not a status')
  296. return
  297. if '/users/' not in messageId and \
  298. '/channel/' not in messageId and \
  299. '/profile/' not in messageId:
  300. if debug:
  301. print('DEBUG: c2s block object has no nickname')
  302. return
  303. if ':' in domain:
  304. domain=domain.split(':')[0]
  305. postFilename=locatePost(baseDir,nickname,domain,messageId)
  306. if not postFilename:
  307. if debug:
  308. print('DEBUG: c2s block post not found in inbox or outbox')
  309. print(messageId)
  310. return
  311. nicknameBlocked=getNicknameFromActor(messageJson['object'])
  312. if not nicknameBlocked:
  313. print('WARN: unable to find nickname in '+messageJson['object'])
  314. return
  315. domainBlocked,portBlocked=getDomainFromActor(messageJson['object'])
  316. domainBlockedFull=domainBlocked
  317. if portBlocked:
  318. if portBlocked!=80 and portBlocked!=443:
  319. if ':' not in domainBlocked:
  320. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  321. addBlock(baseDir,nickname,domain, \
  322. nicknameBlocked,domainBlockedFull)
  323. if debug:
  324. print('DEBUG: post blocked via c2s - '+postFilename)
  325. def outboxUndoBlock(baseDir: str,httpPrefix: str, \
  326. nickname: str,domain: str,port: int, \
  327. messageJson: {},debug: bool) -> None:
  328. """ When an undo block request is received by the outbox from c2s
  329. """
  330. if not messageJson.get('type'):
  331. if debug:
  332. print('DEBUG: undo block - no type')
  333. return
  334. if not messageJson['type']=='Undo':
  335. if debug:
  336. print('DEBUG: not an undo block')
  337. return
  338. if not messageJson.get('object'):
  339. if debug:
  340. print('DEBUG: no object in undo block')
  341. return
  342. if not isinstance(messageJson['object'], dict):
  343. if debug:
  344. print('DEBUG: undo block object is not string')
  345. return
  346. if not messageJson['object'].get('type'):
  347. if debug:
  348. print('DEBUG: undo block - no type')
  349. return
  350. if not messageJson['object']['type']=='Block':
  351. if debug:
  352. print('DEBUG: not an undo block')
  353. return
  354. if not messageJson['object'].get('object'):
  355. if debug:
  356. print('DEBUG: no object in undo block')
  357. return
  358. if not isinstance(messageJson['object']['object'], str):
  359. if debug:
  360. print('DEBUG: undo block object is not string')
  361. return
  362. if debug:
  363. print('DEBUG: c2s undo block request arrived in outbox')
  364. messageId=messageJson['object']['object'].replace('/activity','')
  365. if '/statuses/' not in messageId:
  366. if debug:
  367. print('DEBUG: c2s undo block object is not a status')
  368. return
  369. if '/users/' not in messageId and \
  370. '/channel/' not in messageId and \
  371. '/profile/' not in messageId:
  372. if debug:
  373. print('DEBUG: c2s undo block object has no nickname')
  374. return
  375. if ':' in domain:
  376. domain=domain.split(':')[0]
  377. postFilename=locatePost(baseDir,nickname,domain,messageId)
  378. if not postFilename:
  379. if debug:
  380. print('DEBUG: c2s undo block post not found in inbox or outbox')
  381. print(messageId)
  382. return
  383. nicknameBlocked=getNicknameFromActor(messageJson['object']['object'])
  384. if not nicknameBlocked:
  385. print('WARN: unable to find nickname in '+messageJson['object']['object'])
  386. return
  387. domainBlocked,portBlocked=getDomainFromActor(messageJson['object']['object'])
  388. domainBlockedFull=domainBlocked
  389. if portBlocked:
  390. if portBlocked!=80 and portBlocked!=443:
  391. if ':' not in domainBlocked:
  392. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  393. removeBlock(baseDir,nickname,domain, \
  394. nicknameBlocked,domainBlockedFull)
  395. if debug:
  396. print('DEBUG: post undo blocked via c2s - '+postFilename)