2019-06-28 18:55:29 +00:00
__filename__ = " epicyon.py "
__author__ = " Bob Mottram "
__license__ = " AGPL3+ "
__version__ = " 0.0.1 "
__maintainer__ = " Bob Mottram "
__email__ = " bob@freedombone.net "
__status__ = " Production "
from person import createPerson
2019-07-05 11:27:18 +00:00
from person import createSharedInbox
2019-07-05 21:24:16 +00:00
from person import createCapabilitiesInbox
2019-07-03 09:40:27 +00:00
from person import setPreferredNickname
2019-06-28 20:00:25 +00:00
from person import setBio
2019-07-05 21:27:49 +00:00
from person import validNickname
2019-06-28 18:55:29 +00:00
from webfinger import webfingerHandle
2019-07-02 09:25:29 +00:00
from posts import getPosts
2019-06-29 10:08:59 +00:00
from posts import createPublicPost
2019-06-29 11:47:33 +00:00
from posts import deleteAllPosts
2019-06-29 13:17:02 +00:00
from posts import createOutbox
2019-06-29 13:44:21 +00:00
from posts import archivePosts
2019-06-30 10:14:02 +00:00
from posts import sendPost
2019-07-03 10:31:02 +00:00
from posts import getPublicPostsOfPerson
2019-07-05 15:53:26 +00:00
from posts import getUserUrl
2019-06-28 18:55:29 +00:00
from session import createSession
2019-06-29 16:47:37 +00:00
from session import getJson
2019-06-28 18:55:29 +00:00
import json
2019-06-30 22:56:37 +00:00
import os
2019-07-04 22:51:40 +00:00
import shutil
2019-06-28 18:55:29 +00:00
import sys
import requests
from pprint import pprint
2019-06-30 20:14:03 +00:00
from tests import testHttpsig
2019-06-28 18:55:29 +00:00
from daemon import runDaemon
2019-06-28 19:52:35 +00:00
import socket
2019-06-29 18:23:13 +00:00
from follow import clearFollows
2019-06-29 20:21:37 +00:00
from follow import clearFollowers
2019-07-06 19:24:52 +00:00
from utils import followPerson
2019-06-29 18:23:13 +00:00
from follow import followerOfPerson
from follow import unfollowPerson
from follow import unfollowerOfPerson
2019-07-05 12:35:29 +00:00
from follow import getFollowersOfPerson
2019-06-30 21:20:02 +00:00
from tests import testPostMessageBetweenServers
2019-07-06 13:49:25 +00:00
from tests import testFollowBetweenServers
2019-06-30 21:20:02 +00:00
from tests import runAllTests
2019-07-05 09:20:54 +00:00
from config import setConfigParam
from config import getConfigParam
2019-07-05 09:44:15 +00:00
from auth import storeBasicCredentials
2019-07-05 09:49:57 +00:00
from auth import removePassword
2019-07-05 11:27:18 +00:00
from auth import createPassword
2019-07-09 15:51:31 +00:00
from utils import getDomainFromActor
from utils import getNicknameFromActor
2019-07-03 09:24:55 +00:00
import argparse
2019-06-30 21:20:02 +00:00
2019-07-03 09:24:55 +00:00
def str2bool ( v ) :
if isinstance ( v , bool ) :
return v
if v . lower ( ) in ( ' yes ' , ' true ' , ' t ' , ' y ' , ' 1 ' ) :
return True
elif v . lower ( ) in ( ' no ' , ' false ' , ' f ' , ' n ' , ' 0 ' ) :
return False
else :
raise argparse . ArgumentTypeError ( ' Boolean value expected. ' )
2019-07-05 09:20:54 +00:00
2019-07-03 09:24:55 +00:00
parser = argparse . ArgumentParser ( description = ' ActivityPub Server ' )
2019-07-09 15:51:31 +00:00
parser . add_argument ( ' -n ' , ' --nickname ' , dest = ' nickname ' , type = str , default = None , \
help = ' Nickname of the account to use ' )
parser . add_argument ( ' --fol ' , ' --follow ' , dest = ' follow ' , type = str , default = None , \
help = ' Handle of account to follow. eg. nickname@domain ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' -d ' , ' --domain ' , dest = ' domain ' , type = str , default = None , \
2019-07-03 09:24:55 +00:00
help = ' Domain name of the server ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' -p ' , ' --port ' , dest = ' port ' , type = int , default = None , \
2019-07-03 09:24:55 +00:00
help = ' Port number to run on ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --path ' , dest = ' baseDir ' , \
type = str , default = os . getcwd ( ) , \
2019-07-03 09:24:55 +00:00
help = ' Directory in which to store posts ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' -a ' , ' --addaccount ' , dest = ' addaccount ' , \
type = str , default = None , \
2019-07-04 22:44:32 +00:00
help = ' Adds a new account ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' -r ' , ' --rmaccount ' , dest = ' rmaccount ' , \
type = str , default = None , \
2019-07-04 22:50:40 +00:00
help = ' Remove an account ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --pass ' , ' --password ' , dest = ' password ' , \
type = str , default = None , \
2019-07-04 22:44:32 +00:00
help = ' Set a password for an account ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --chpass ' , ' --changepassword ' , \
nargs = ' + ' , dest = ' changepassword ' , \
2019-07-05 09:44:15 +00:00
help = ' Change the password for an account ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --actor ' , dest = ' actor ' , type = str , default = None , \
2019-07-05 15:53:26 +00:00
help = ' Show the json actor the given handle ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --posts ' , dest = ' posts ' , type = str , default = None , \
2019-07-03 10:31:02 +00:00
help = ' Show posts for the given handle ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' --postsraw ' , dest = ' postsraw ' , type = str , default = None , \
2019-07-03 11:24:38 +00:00
help = ' Show raw json of posts for the given handle ' )
2019-07-12 10:53:49 +00:00
parser . add_argument ( ' --json ' , dest = ' json ' , type = str , default = None , \
help = ' Show the json for a given activitypub url ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( ' -f ' , ' --federate ' , nargs = ' + ' , dest = ' federationList ' , \
2019-07-03 09:24:55 +00:00
help = ' Specify federation list separated by spaces ' )
2019-07-06 20:19:49 +00:00
parser . add_argument ( " --debug " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Show debug messages " )
parser . add_argument ( " --http " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Use http only " )
parser . add_argument ( " --dat " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Use dat protocol only " )
parser . add_argument ( " --tor " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Route via Tor " )
parser . add_argument ( " --tests " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Run unit tests " )
parser . add_argument ( " --testsnetwork " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Run network unit tests " )
parser . add_argument ( " --testdata " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Generate some data for testing purposes " )
2019-07-07 17:47:37 +00:00
parser . add_argument ( " --ocap " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Always strictly enforce object capabilities " )
2019-07-09 17:54:08 +00:00
parser . add_argument ( " --noreply " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Default capabilities don ' t allow replies on posts " )
parser . add_argument ( " --nolike " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Default capabilities don ' t allow likes/favourites on posts " )
2019-07-09 18:11:23 +00:00
parser . add_argument ( " --nopics " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Default capabilities don ' t allow attached pictures " )
parser . add_argument ( " --noannounce " , " --norepeat " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Default capabilities don ' t allow announce/repeat " )
parser . add_argument ( " --cw " , type = str2bool , nargs = ' ? ' , \
const = True , default = False , \
help = " Default capabilities don ' t allow posts without content warnings " )
2019-07-03 09:24:55 +00:00
args = parser . parse_args ( )
2019-07-03 10:31:02 +00:00
2019-07-03 16:14:45 +00:00
debug = False
if args . debug :
debug = True
2019-07-03 09:24:55 +00:00
if args . tests :
runAllTests ( )
sys . exit ( )
2019-07-03 10:31:02 +00:00
if args . testsnetwork :
print ( ' Network Tests ' )
2019-07-06 19:36:27 +00:00
testPostMessageBetweenServers ( )
2019-07-06 13:49:25 +00:00
testFollowBetweenServers ( )
2019-07-03 10:31:02 +00:00
sys . exit ( )
2019-07-05 15:53:26 +00:00
2019-07-03 10:33:55 +00:00
if args . posts :
2019-07-05 15:53:26 +00:00
if ' @ ' not in args . posts :
print ( ' Syntax: --posts nickname@domain ' )
sys . exit ( )
2019-07-03 10:33:55 +00:00
nickname = args . posts . split ( ' @ ' ) [ 0 ]
domain = args . posts . split ( ' @ ' ) [ 1 ]
2019-07-03 11:24:38 +00:00
getPublicPostsOfPerson ( nickname , domain , False , True )
sys . exit ( )
if args . postsraw :
2019-07-06 21:58:56 +00:00
if ' @ ' not in args . postsraw :
2019-07-05 15:53:26 +00:00
print ( ' Syntax: --postsraw nickname@domain ' )
sys . exit ( )
2019-07-03 11:24:38 +00:00
nickname = args . postsraw . split ( ' @ ' ) [ 0 ]
domain = args . postsraw . split ( ' @ ' ) [ 1 ]
getPublicPostsOfPerson ( nickname , domain , True , False )
2019-07-03 10:33:55 +00:00
sys . exit ( )
2019-07-04 22:44:32 +00:00
baseDir = args . baseDir
if baseDir . endswith ( ' / ' ) :
print ( " --path option should not end with ' / ' " )
2019-07-03 10:31:02 +00:00
sys . exit ( )
2019-07-05 09:20:54 +00:00
# get domain name from configuration
configDomain = getConfigParam ( baseDir , ' domain ' )
if configDomain :
domain = configDomain
else :
domain = ' localhost '
# get port number from configuration
configPort = getConfigParam ( baseDir , ' port ' )
if configPort :
port = configPort
else :
port = 8085
2019-07-09 15:51:31 +00:00
nickname = None
if args . nickname :
nickname = nickname
httpPrefix = ' https '
if args . http :
httpPrefix = ' http '
federationList = [ ]
if args . federationList :
if len ( args . federationList ) == 1 :
if not ( args . federationList [ 0 ] . lower ( ) == ' any ' or \
args . federationList [ 0 ] . lower ( ) == ' all ' or \
args . federationList [ 0 ] . lower ( ) == ' * ' ) :
for federationDomain in args . federationList :
if ' @ ' in federationDomain :
print ( federationDomain + ' : Federate with domains, not individual accounts ' )
sys . exit ( )
federationList = args . federationList . copy ( )
setConfigParam ( baseDir , ' federationList ' , federationList )
else :
configFederationList = getConfigParam ( baseDir , ' federationList ' )
if configFederationList :
federationList = configFederationList
2019-07-09 15:58:51 +00:00
useTor = args . tor
if domain . endswith ( ' .onion ' ) :
useTor = True
2019-07-09 15:51:31 +00:00
if args . follow and nickname :
if not os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
print ( nickname + ' is not an account on the system. use --addaccount if necessary. ' )
sys . exit ( )
if ' . ' not in args . follow :
print ( " This doesn ' t look like a fediverse handle " )
sys . exit ( )
followNickname = getNicknameFromActor ( args . follow )
followDomain , followPort = getDomainFromActor ( args . follow )
if os . path . isfile ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /following.txt ' ) :
if followNickname + ' @ ' + followDomain in open ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /following.txt ' ) . read ( ) :
print ( nickname + ' @ ' + domain + ' is already following ' + followNickname + ' @ ' + followDomain )
sys . exit ( )
2019-07-09 15:58:51 +00:00
session = createSession ( domain , port , useTor )
2019-07-09 15:51:31 +00:00
personCache = { }
cachedWebfingers = { }
sendThreads = [ ]
sendThreads = [ ]
postLog = [ ]
followHttpPrefix = httpPrefix
if args . follow . startswith ( ' https ' ) :
followHttpPrefix = ' https '
sendFollowRequest ( session , baseDir , \
nickname , domain , port , httpPrefix , \
followNickname , followDomain , followPort , \
followHttpPrefix , \
False , federationList , \
sendThreads , postLog , \
cachedWebfingers , personCache , debug )
for t in range ( 30 ) :
time . sleep ( 1 )
if os . path . isfile ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /following.txt ' ) :
if followNickname + ' @ ' + followDomain in open ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /following.txt ' ) . read ( ) :
print ( ' Ok ' )
sys . exit ( )
print ( ' Follow attempt failed ' )
sys . exit ( )
2019-07-03 09:40:27 +00:00
nickname = ' admin '
2019-07-05 09:20:54 +00:00
if args . domain :
domain = args . domain
setConfigParam ( baseDir , ' domain ' , domain )
if args . port :
port = args . port
setConfigParam ( baseDir , ' port ' , port )
2019-07-07 17:47:37 +00:00
ocapAlways = False
if args . ocap :
ocapAlways = args . ocap
2019-07-03 19:00:03 +00:00
if args . dat :
httpPrefix = ' dat '
2019-07-04 22:44:32 +00:00
2019-07-05 15:53:26 +00:00
if args . actor :
if ' @ ' not in args . actor :
print ( ' Syntax: --actor nickname@domain ' )
sys . exit ( )
nickname = args . actor . split ( ' @ ' ) [ 0 ]
domain = args . actor . split ( ' @ ' ) [ 1 ] . replace ( ' \n ' , ' ' )
wfCache = { }
if domain . endswith ( ' .onion ' ) :
httpPrefix = ' http '
port = 80
else :
httpPrefix = ' https '
port = 443
session = createSession ( domain , port , useTor )
wfRequest = webfingerHandle ( session , nickname + ' @ ' + domain , httpPrefix , wfCache )
if not wfRequest :
print ( ' Unable to webfinger ' + nickname + ' @ ' + domain )
sys . exit ( )
asHeader = { ' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' }
personUrl = getUserUrl ( wfRequest )
personJson = getJson ( session , personUrl , asHeader , None )
if personJson :
pprint ( personJson )
else :
print ( ' Failed to get ' + personUrl )
sys . exit ( )
2019-07-12 10:53:49 +00:00
if args . json :
2019-07-12 10:58:08 +00:00
session = createSession ( domain , port , True )
2019-07-12 10:53:49 +00:00
asHeader = { ' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' }
testJson = getJson ( session , args . json , asHeader , None )
pprint ( testJson )
sys . exit ( )
2019-07-04 22:44:32 +00:00
if args . addaccount :
if ' @ ' in args . addaccount :
2019-07-04 22:50:40 +00:00
nickname = args . addaccount . split ( ' @ ' ) [ 0 ]
domain = args . addaccount . split ( ' @ ' ) [ 1 ]
2019-07-04 22:44:32 +00:00
else :
2019-07-04 22:50:40 +00:00
nickname = args . addaccount
2019-07-05 09:20:54 +00:00
if not args . domain or not getConfigParam ( baseDir , ' domain ' ) :
2019-07-04 22:44:32 +00:00
print ( ' Use the --domain option to set the domain name ' )
sys . exit ( )
2019-07-05 21:27:49 +00:00
if not validNickname ( nickname ) :
print ( nickname + ' is a reserved name. Use something different. ' )
sys . exit ( )
2019-07-05 09:20:54 +00:00
if not args . password :
print ( ' Use the --password option to set the password for ' + nickname )
sys . exit ( )
if len ( args . password . strip ( ) ) < 8 :
print ( ' Password should be at least 8 characters ' )
sys . exit ( )
2019-07-04 22:50:40 +00:00
if os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
print ( ' Account already exists ' )
sys . exit ( )
2019-07-05 09:20:54 +00:00
createPerson ( baseDir , nickname , domain , port , httpPrefix , True , args . password . strip ( ) )
2019-07-04 22:44:32 +00:00
if os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
print ( ' Account created for ' + nickname + ' @ ' + domain )
2019-07-05 09:20:54 +00:00
else :
print ( ' Account creation failed ' )
2019-07-04 22:44:32 +00:00
sys . exit ( )
2019-07-04 22:50:40 +00:00
if args . rmaccount :
if ' @ ' in args . rmaccount :
nickname = args . rmaccount . split ( ' @ ' ) [ 0 ]
domain = args . rmaccount . split ( ' @ ' ) [ 1 ]
else :
nickname = args . rmaccount
2019-07-05 09:20:54 +00:00
if not args . domain or not getConfigParam ( baseDir , ' domain ' ) :
2019-07-04 22:50:40 +00:00
print ( ' Use the --domain option to set the domain name ' )
sys . exit ( )
2019-07-05 09:20:54 +00:00
handle = nickname + ' @ ' + domain
accountRemoved = False
2019-07-05 09:49:57 +00:00
removePassword ( baseDir , nickname )
2019-07-05 09:20:54 +00:00
if os . path . isdir ( baseDir + ' /accounts/ ' + handle ) :
shutil . rmtree ( baseDir + ' /accounts/ ' + handle )
accountRemoved = True
if os . path . isfile ( baseDir + ' /accounts/ ' + handle + ' .json ' ) :
os . remove ( baseDir + ' /accounts/ ' + handle + ' .json ' )
accountRemoved = True
if os . path . isfile ( baseDir + ' /wfendpoints/ ' + handle + ' .json ' ) :
os . remove ( baseDir + ' /wfendpoints/ ' + handle + ' .json ' )
accountRemoved = True
if os . path . isfile ( baseDir + ' /keys/private/ ' + handle + ' .key ' ) :
os . remove ( baseDir + ' /keys/private/ ' + handle + ' .key ' )
accountRemoved = True
2019-07-05 09:22:06 +00:00
if os . path . isfile ( baseDir + ' /keys/public/ ' + handle + ' .pem ' ) :
2019-07-05 09:20:54 +00:00
os . remove ( baseDir + ' /keys/public/ ' + handle + ' .pem ' )
accountRemoved = True
if accountRemoved :
print ( ' Account for ' + handle + ' was removed ' )
2019-07-04 22:50:40 +00:00
sys . exit ( )
2019-07-05 09:44:15 +00:00
if args . changepassword :
if len ( args . changepassword ) != 2 :
print ( ' --changepassword [nickname] [new password] ' )
sys . exit ( )
if ' @ ' in args . changepassword [ 0 ] :
nickname = args . changepassword [ 0 ] . split ( ' @ ' ) [ 0 ]
domain = args . changepassword [ 0 ] . split ( ' @ ' ) [ 1 ]
else :
nickname = args . changepassword [ 0 ]
if not args . domain or not getConfigParam ( baseDir , ' domain ' ) :
print ( ' Use the --domain option to set the domain name ' )
sys . exit ( )
newPassword = args . changepassword [ 1 ]
if len ( newPassword ) < 8 :
print ( ' Password should be at least 8 characters ' )
sys . exit ( )
2019-07-05 09:58:58 +00:00
if not os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
print ( ' Account ' + nickname + ' @ ' + domain + ' not found ' )
sys . exit ( )
2019-07-05 09:44:15 +00:00
passwordFile = baseDir + ' /accounts/passwords '
if os . path . isfile ( passwordFile ) :
if nickname + ' : ' in open ( passwordFile ) . read ( ) :
storeBasicCredentials ( baseDir , nickname , newPassword )
print ( ' Password for ' + nickname + ' was changed ' )
else :
print ( nickname + ' is not in the passwords file ' )
else :
print ( ' Passwords file not found ' )
sys . exit ( )
2019-07-05 10:03:25 +00:00
if not args . domain and not domain :
2019-07-04 22:44:32 +00:00
print ( ' Specify a domain with --domain [name] ' )
2019-07-03 09:24:55 +00:00
sys . exit ( )
2019-07-05 09:44:15 +00:00
2019-07-05 09:20:54 +00:00
if federationList :
2019-07-03 16:16:36 +00:00
print ( ' Federating with: ' + str ( federationList ) )
2019-06-28 18:55:29 +00:00
2019-07-03 12:24:54 +00:00
if not os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
2019-07-03 12:25:42 +00:00
print ( ' Creating default admin account ' + nickname + ' @ ' + domain )
2019-07-05 11:27:18 +00:00
print ( ' See config.json for the password. You can remove the password from config.json after moving it elsewhere. ' )
adminPassword = createPassword ( 10 )
setConfigParam ( baseDir , ' adminPassword ' , adminPassword )
createPerson ( baseDir , nickname , domain , port , httpPrefix , True , adminPassword )
2019-07-03 12:24:54 +00:00
2019-07-06 20:19:49 +00:00
if args . testdata :
2019-07-06 21:45:09 +00:00
nickname = ' testuser567 '
2019-07-06 20:19:49 +00:00
print ( ' Generating some test data for user: ' + nickname )
createPerson ( baseDir , nickname , domain , port , httpPrefix , True , ' likewhateveryouwantscoob ' )
deleteAllPosts ( baseDir , nickname , domain , ' inbox ' )
deleteAllPosts ( baseDir , nickname , domain , ' outbox ' )
followPerson ( baseDir , nickname , domain , ' admin ' , domain , federationList , True )
followerOfPerson ( baseDir , nickname , domain , ' admin ' , domain , federationList , True )
2019-07-09 14:20:23 +00:00
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " like, this is totally just a test, man " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " Zoiks!!! " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " Hey scoob we need like a hundred more milkshakes " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " Getting kinda spooky around here " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " And they would have gotten away with it too if it wasn ' t for those pesky hackers " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " man, these centralized sites are, like, the worst! " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " another mystery solved hey " , False , True , False )
createPublicPost ( baseDir , nickname , domain , port , httpPrefix , " let ' s go bowling " , False , True , False )
2019-07-12 10:53:49 +00:00
2019-07-09 18:11:23 +00:00
runDaemon ( baseDir , domain , port , httpPrefix , federationList , \
args . noreply , args . nolike , args . nopics , \
args . noannounce , args . cw , ocapAlways , useTor , debug )