blocking.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. __filename__ = "blocking.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.0.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=baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
  133. if os.path.isfile(allowFilename):
  134. if blockDomain not in open(allowFilename).read():
  135. return True
  136. blockingFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
  137. if os.path.isfile(blockingFilename):
  138. if '*@'+blockDomain in open(blockingFilename).read():
  139. return True
  140. blockHandle=blockNickname+'@'+blockDomain
  141. if blockHandle in open(blockingFilename).read():
  142. return True
  143. return False
  144. def sendBlockViaServer(baseDir: str,session, \
  145. fromNickname: str,password: str, \
  146. fromDomain: str,fromPort: int, \
  147. httpPrefix: str,blockedUrl: str, \
  148. cachedWebfingers: {},personCache: {}, \
  149. debug: bool,projectVersion: str) -> {}:
  150. """Creates a block via c2s
  151. """
  152. if not session:
  153. print('WARN: No session for sendBlockViaServer')
  154. return 6
  155. fromDomainFull=fromDomain
  156. if fromPort:
  157. if fromPort!=80 and fromPort!=443:
  158. if ':' not in fromDomain:
  159. fromDomainFull=fromDomain+':'+str(fromPort)
  160. toUrl = 'https://www.w3.org/ns/activitystreams#Public'
  161. ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  162. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  163. newBlockJson = {
  164. "@context": "https://www.w3.org/ns/activitystreams",
  165. 'type': 'Block',
  166. 'actor': blockActor,
  167. 'object': blockedUrl,
  168. 'to': [toUrl],
  169. 'cc': [ccUrl]
  170. }
  171. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  172. # lookup the inbox for the To handle
  173. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  174. fromDomain,projectVersion)
  175. if not wfRequest:
  176. if debug:
  177. print('DEBUG: announce webfinger failed for '+handle)
  178. return 1
  179. postToBox='outbox'
  180. # get the actor inbox for the To handle
  181. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  182. getPersonBox(baseDir,session,wfRequest,personCache, \
  183. projectVersion,httpPrefix,fromDomain,postToBox)
  184. if not inboxUrl:
  185. if debug:
  186. print('DEBUG: No '+postToBox+' was found for '+handle)
  187. return 3
  188. if not fromPersonId:
  189. if debug:
  190. print('DEBUG: No actor was found for '+handle)
  191. return 4
  192. authHeader=createBasicAuthHeader(fromNickname,password)
  193. headers = {'host': fromDomain, \
  194. 'Content-type': 'application/json', \
  195. 'Authorization': authHeader}
  196. postResult = \
  197. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  198. #if not postResult:
  199. # if debug:
  200. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  201. # return 5
  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 = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
  223. blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
  224. newBlockJson = {
  225. "@context": "https://www.w3.org/ns/activitystreams",
  226. 'type': 'Undo',
  227. 'actor': blockActor,
  228. 'object': {
  229. 'type': 'Block',
  230. 'actor': blockActor,
  231. 'object': blockedUrl,
  232. 'to': [toUrl],
  233. 'cc': [ccUrl]
  234. }
  235. }
  236. handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
  237. # lookup the inbox for the To handle
  238. wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
  239. fromDomain,projectVersion)
  240. if not wfRequest:
  241. if debug:
  242. print('DEBUG: announce webfinger failed for '+handle)
  243. return 1
  244. postToBox='outbox'
  245. # get the actor inbox for the To handle
  246. inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
  247. getPersonBox(baseDir,session,wfRequest,personCache, \
  248. projectVersion,httpPrefix,fromDomain,postToBox)
  249. if not inboxUrl:
  250. if debug:
  251. print('DEBUG: No '+postToBox+' was found for '+handle)
  252. return 3
  253. if not fromPersonId:
  254. if debug:
  255. print('DEBUG: No actor was found for '+handle)
  256. return 4
  257. authHeader=createBasicAuthHeader(fromNickname,password)
  258. headers = {'host': fromDomain, \
  259. 'Content-type': 'application/json', \
  260. 'Authorization': authHeader}
  261. postResult = \
  262. postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
  263. #if not postResult:
  264. # if debug:
  265. # print('DEBUG: POST announce failed for c2s to '+inboxUrl)
  266. # return 5
  267. if debug:
  268. print('DEBUG: c2s POST block success')
  269. return newBlockJson
  270. def outboxBlock(baseDir: str,httpPrefix: str, \
  271. nickname: str,domain: str,port: int, \
  272. messageJson: {},debug: bool) -> None:
  273. """ When a block request is received by the outbox from c2s
  274. """
  275. if not messageJson.get('type'):
  276. if debug:
  277. print('DEBUG: block - no type')
  278. return
  279. if not messageJson['type']=='Block':
  280. if debug:
  281. print('DEBUG: not a block')
  282. return
  283. if not messageJson.get('object'):
  284. if debug:
  285. print('DEBUG: no object in block')
  286. return
  287. if not isinstance(messageJson['object'], str):
  288. if debug:
  289. print('DEBUG: block object is not string')
  290. return
  291. if debug:
  292. print('DEBUG: c2s block request arrived in outbox')
  293. messageId=messageJson['object'].replace('/activity','')
  294. if '/statuses/' not in messageId:
  295. if debug:
  296. print('DEBUG: c2s block object is not a status')
  297. return
  298. if '/users/' not in messageId and '/profile/' not in messageId:
  299. if debug:
  300. print('DEBUG: c2s block object has no nickname')
  301. return
  302. if ':' in domain:
  303. domain=domain.split(':')[0]
  304. postFilename=locatePost(baseDir,nickname,domain,messageId)
  305. if not postFilename:
  306. if debug:
  307. print('DEBUG: c2s block post not found in inbox or outbox')
  308. print(messageId)
  309. return
  310. nicknameBlocked=getNicknameFromActor(messageJson['object'])
  311. if not nicknameBlocked:
  312. print('WARN: unable to find nickname in '+messageJson['object'])
  313. return
  314. domainBlocked,portBlocked=getDomainFromActor(messageJson['object'])
  315. domainBlockedFull=domainBlocked
  316. if portBlocked:
  317. if portBlocked!=80 and portBlocked!=443:
  318. if ':' not in domainBlocked:
  319. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  320. addBlock(baseDir,nickname,domain, \
  321. nicknameBlocked,domainBlockedFull)
  322. if debug:
  323. print('DEBUG: post blocked via c2s - '+postFilename)
  324. def outboxUndoBlock(baseDir: str,httpPrefix: str, \
  325. nickname: str,domain: str,port: int, \
  326. messageJson: {},debug: bool) -> None:
  327. """ When an undo block request is received by the outbox from c2s
  328. """
  329. if not messageJson.get('type'):
  330. if debug:
  331. print('DEBUG: undo block - no type')
  332. return
  333. if not messageJson['type']=='Undo':
  334. if debug:
  335. print('DEBUG: not an undo block')
  336. return
  337. if not messageJson.get('object'):
  338. if debug:
  339. print('DEBUG: no object in undo block')
  340. return
  341. if not isinstance(messageJson['object'], dict):
  342. if debug:
  343. print('DEBUG: undo block object is not string')
  344. return
  345. if not messageJson['object'].get('type'):
  346. if debug:
  347. print('DEBUG: undo block - no type')
  348. return
  349. if not messageJson['object']['type']=='Block':
  350. if debug:
  351. print('DEBUG: not an undo block')
  352. return
  353. if not messageJson['object'].get('object'):
  354. if debug:
  355. print('DEBUG: no object in undo block')
  356. return
  357. if not isinstance(messageJson['object']['object'], str):
  358. if debug:
  359. print('DEBUG: undo block object is not string')
  360. return
  361. if debug:
  362. print('DEBUG: c2s undo block request arrived in outbox')
  363. messageId=messageJson['object']['object'].replace('/activity','')
  364. if '/statuses/' not in messageId:
  365. if debug:
  366. print('DEBUG: c2s undo block object is not a status')
  367. return
  368. if '/users/' not in messageId and '/profile/' not in messageId:
  369. if debug:
  370. print('DEBUG: c2s undo block object has no nickname')
  371. return
  372. if ':' in domain:
  373. domain=domain.split(':')[0]
  374. postFilename=locatePost(baseDir,nickname,domain,messageId)
  375. if not postFilename:
  376. if debug:
  377. print('DEBUG: c2s undo block post not found in inbox or outbox')
  378. print(messageId)
  379. return
  380. nicknameBlocked=getNicknameFromActor(messageJson['object']['object'])
  381. if not nicknameBlocked:
  382. print('WARN: unable to find nickname in '+messageJson['object']['object'])
  383. return
  384. domainBlocked,portBlocked=getDomainFromActor(messageJson['object']['object'])
  385. domainBlockedFull=domainBlocked
  386. if portBlocked:
  387. if portBlocked!=80 and portBlocked!=443:
  388. if ':' not in domainBlocked:
  389. domainBlockedFull=domainBlocked+':'+str(portBlocked)
  390. removeBlock(baseDir,nickname,domain, \
  391. nicknameBlocked,domainBlockedFull)
  392. if debug:
  393. print('DEBUG: post undo blocked via c2s - '+postFilename)