2017-12-26 07:25:29 +00:00
#!/usr/bin/env python3
'''
2018-01-26 07:22:48 +00:00
Onionr - P2P Microblogging Platform & Social network .
2018-01-14 00:07:13 +00:00
Onionr is the name for both the protocol and the original / reference software .
Run with ' help ' for usage .
'''
'''
2017-12-26 07:25:29 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < https : / / www . gnu . org / licenses / > .
'''
2018-02-23 01:58:36 +00:00
import sys , os , base64 , random , getpass , shutil , subprocess , requests , time , platform
2018-04-18 06:55:44 +00:00
import api , core , config , logger , onionrplugins as plugins
2018-01-09 22:58:12 +00:00
from onionrutils import OnionrUtils
2018-01-19 09:16:38 +00:00
from netcontroller import NetController
2017-12-28 19:30:15 +00:00
2018-01-28 01:53:24 +00:00
try :
from urllib3 . contrib . socks import SOCKSProxyManager
except ImportError :
raise Exception ( " You need the PySocks module (for use with socks5 proxy to use Tor) " )
2018-04-18 06:55:44 +00:00
try :
import gui
except ImportError :
logger . error ( ' You need python3 tkinter and tk installed to use Onionr. ' )
sys . exit ( 1 )
2018-04-19 01:17:47 +00:00
ONIONR_TAGLINE = ' Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech '
2018-02-04 01:11:35 +00:00
ONIONR_VERSION = ' 0.0.0 ' # for debugging and stuff
API_VERSION = ' 1 ' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes knows how to communicate without learning too much information about you.
2017-12-26 07:25:29 +00:00
class Onionr :
2018-03-03 07:00:43 +00:00
cmds = { }
cmdhelp = { }
2017-12-26 07:25:29 +00:00
def __init__ ( self ) :
2018-02-04 01:11:35 +00:00
'''
Main Onionr class . This is for the CLI program , and does not handle much of the logic .
In general , external programs and plugins should not use this class .
2018-01-14 00:07:13 +00:00
'''
2018-02-23 01:58:36 +00:00
2018-01-15 08:52:45 +00:00
try :
os . chdir ( sys . path [ 0 ] )
except FileNotFoundError :
pass
2018-02-04 01:11:35 +00:00
2018-02-23 01:58:36 +00:00
# Load global configuration data
exists = os . path . exists ( config . get_config_file ( ) )
2018-03-03 07:26:02 +00:00
config . set_config ( { ' devmode ' : True , ' log ' : { ' file ' : { ' output ' : True , ' path ' : ' data/output.log ' } , ' console ' : { ' output ' : True , ' color ' : True } } } ) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
2018-02-23 01:58:36 +00:00
config . reload ( ) # this will read the configuration file into memory
2018-02-23 02:53:49 +00:00
settings = 0b000
2018-03-03 07:26:02 +00:00
if config . get ( ' log ' , { ' console ' : { ' color ' : True } } ) [ ' console ' ] [ ' color ' ] :
2018-02-23 02:53:49 +00:00
settings = settings | logger . USE_ANSI
2018-03-03 07:26:02 +00:00
if config . get ( ' log ' , { ' console ' : { ' output ' : True } } ) [ ' console ' ] [ ' output ' ] :
2018-02-23 02:53:49 +00:00
settings = settings | logger . OUTPUT_TO_CONSOLE
2018-03-03 07:26:02 +00:00
if config . get ( ' log ' , { ' file ' : { ' output ' : True } } ) [ ' file ' ] [ ' output ' ] :
2018-02-23 02:53:49 +00:00
settings = settings | logger . OUTPUT_TO_FILE
2018-03-03 07:26:02 +00:00
logger . set_file ( config . get ( ' log ' , { ' file ' : { ' path ' : ' data/output.log ' } } ) [ ' file ' ] [ ' path ' ] )
2018-02-23 02:53:49 +00:00
logger . set_settings ( settings )
2018-02-23 01:58:36 +00:00
if config . get ( ' devmode ' , True ) :
2018-01-10 03:50:38 +00:00
self . _developmentMode = True
2018-01-26 07:22:48 +00:00
logger . set_level ( logger . LEVEL_DEBUG )
2018-01-10 03:50:38 +00:00
else :
self . _developmentMode = False
2018-01-26 07:22:48 +00:00
logger . set_level ( logger . LEVEL_INFO )
2017-12-27 01:13:19 +00:00
2018-01-14 08:48:23 +00:00
self . onionrCore = core . Core ( )
2018-01-26 06:28:11 +00:00
self . onionrUtils = OnionrUtils ( self . onionrCore )
2018-01-09 22:58:12 +00:00
2018-02-23 01:58:36 +00:00
# Handle commands
2018-01-26 07:22:48 +00:00
2018-01-02 08:43:29 +00:00
self . debug = False # Whole application debugging
2017-12-27 01:13:19 +00:00
2018-01-09 22:58:12 +00:00
if os . path . exists ( ' data-encrypted.dat ' ) :
while True :
print ( ' Enter password to decrypt: ' )
password = getpass . getpass ( )
2018-01-14 08:48:23 +00:00
result = self . onionrCore . dataDirDecrypt ( password )
2018-01-09 22:58:12 +00:00
if os . path . exists ( ' data/ ' ) :
break
else :
2018-04-19 02:25:16 +00:00
logger . error ( ' Failed to decrypt: ' + result [ 1 ] , timestamp = False )
2018-01-09 22:58:12 +00:00
else :
2018-01-10 03:50:38 +00:00
if not os . path . exists ( ' data/ ' ) :
os . mkdir ( ' data/ ' )
2018-01-26 06:28:11 +00:00
os . mkdir ( ' data/blocks/ ' )
2018-01-26 07:22:48 +00:00
2018-02-22 09:33:30 +00:00
if not os . path . exists ( self . onionrCore . peerDB ) :
2018-01-14 08:48:23 +00:00
self . onionrCore . createPeerDB ( )
2018-01-10 03:50:38 +00:00
pass
2018-02-22 09:33:30 +00:00
if not os . path . exists ( self . onionrCore . addressDB ) :
self . onionrCore . createAddressDB ( )
2017-12-28 00:18:00 +00:00
2017-12-27 05:00:02 +00:00
# Get configuration
2018-02-23 01:58:36 +00:00
if not exists :
2017-12-27 05:00:02 +00:00
# Generate default config
2017-12-27 01:13:19 +00:00
# Hostname should only be set if different from 127.x.x.x. Important for DNS rebinding attack prevention.
2017-12-28 00:18:00 +00:00
if self . debug :
2017-12-27 01:13:19 +00:00
randomPort = 8080
else :
2018-01-20 07:23:09 +00:00
while True :
randomPort = random . randint ( 1024 , 65535 )
if self . onionrUtils . checkPort ( randomPort ) :
break
2018-04-16 02:22:19 +00:00
config . set ( ' client ' , { ' participate ' : ' true ' , ' client_hmac ' : base64 . b16encode ( os . urandom ( 32 ) ) . decode ( ' utf-8 ' ) , ' port ' : randomPort , ' api_version ' : API_VERSION } , True )
2018-02-04 01:11:35 +00:00
2018-03-03 07:00:43 +00:00
self . cmds = {
2018-03-03 04:19:01 +00:00
' ' : self . showHelpSuggestion ,
2018-02-23 02:25:05 +00:00
' help ' : self . showHelp ,
' version ' : self . version ,
' config ' : self . configure ,
2018-02-04 01:11:35 +00:00
' start ' : self . start ,
' stop ' : self . killDaemon ,
2018-02-23 02:25:05 +00:00
' stats ' : self . showStats ,
2018-03-03 04:19:01 +00:00
' enable-plugin ' : self . enablePlugin ,
' enplugin ' : self . enablePlugin ,
' enableplugin ' : self . enablePlugin ,
' enmod ' : self . enablePlugin ,
' disable-plugin ' : self . disablePlugin ,
' displugin ' : self . disablePlugin ,
' disableplugin ' : self . disablePlugin ,
' dismod ' : self . disablePlugin ,
' reload-plugin ' : self . reloadPlugin ,
' reloadplugin ' : self . reloadPlugin ,
' reload-plugins ' : self . reloadPlugin ,
' reloadplugins ' : self . reloadPlugin ,
2018-04-18 03:43:33 +00:00
' listkeys ' : self . listKeys ,
' list-keys ' : self . listKeys ,
2018-03-03 04:19:01 +00:00
2018-02-04 01:11:35 +00:00
' addmsg ' : self . addMessage ,
' addmessage ' : self . addMessage ,
' add-msg ' : self . addMessage ,
' add-message ' : self . addMessage ,
2018-02-08 09:14:32 +00:00
' pm ' : self . sendEncrypt ,
2018-04-19 02:56:25 +00:00
' introduce ' : self . onionrCore . introduceNode ,
2018-03-03 04:19:01 +00:00
2018-04-18 03:43:33 +00:00
' getpms ' : self . getPMs ,
' get-pms ' : self . getPMs ,
2018-02-04 01:11:35 +00:00
' gui ' : self . openGUI ,
2018-03-03 04:19:01 +00:00
2018-02-04 01:11:35 +00:00
' addpeer ' : self . addPeer ,
2018-02-27 21:23:49 +00:00
' add-peer ' : self . addPeer ,
' add-address ' : self . addAddress ,
2018-04-19 01:47:35 +00:00
' add-addr ' : self . addAddress ,
' addaddr ' : self . addAddress ,
2018-03-03 04:19:01 +00:00
' addaddress ' : self . addAddress ,
' connect ' : self . addAddress
2018-02-04 01:11:35 +00:00
}
2018-03-03 07:00:43 +00:00
self . cmdhelp = {
2018-02-22 07:24:25 +00:00
' help ' : ' Displays this Onionr help menu ' ,
' version ' : ' Displays the Onionr version ' ,
2018-04-19 02:56:25 +00:00
' introduce ' : ' Introduce your node to the public Onionr network. (DAEMON MUST BE RUNNING) ' ,
2018-02-23 02:25:05 +00:00
' config ' : ' Configures something and adds it to the file ' ,
2018-02-22 07:24:25 +00:00
' start ' : ' Starts the Onionr daemon ' ,
' stop ' : ' Stops the Onionr daemon ' ,
' stats ' : ' Displays node statistics ' ,
2018-03-03 04:19:01 +00:00
' enable-plugin ' : ' Enables and starts a plugin ' ,
' disable-plugin ' : ' Disables and stops a plugin ' ,
' reload-plugin ' : ' Reloads a plugin ' ,
2018-02-22 07:24:25 +00:00
' add-peer ' : ' Adds a peer (?) ' ,
2018-04-19 01:47:35 +00:00
' list-peers ' : ' Displays a list of peers ' ,
2018-02-22 07:24:25 +00:00
' add-msg ' : ' Broadcasts a message to the Onionr network ' ,
2018-04-19 02:56:25 +00:00
' pm ' : ' Sends a private message to a user as an Onionr block ' ,
2018-04-19 01:47:35 +00:00
' get-pms ' : ' Shows private messages sent to you ' ,
' gui ' : ' Opens a graphical interface for Onionr '
2018-02-22 07:24:25 +00:00
}
2018-03-03 07:00:43 +00:00
command = ' '
try :
command = sys . argv [ 1 ] . lower ( )
except IndexError :
command = ' '
finally :
self . execute ( command )
if not self . _developmentMode :
encryptionPassword = self . onionrUtils . getPassword ( ' Enter password to encrypt directory: ' )
self . onionrCore . dataDirEncrypt ( encryptionPassword )
shutil . rmtree ( ' data/ ' )
return
'''
THIS SECTION HANDLES THE COMMANDS
'''
def getCommands ( self ) :
return self . cmds
def getHelp ( self ) :
return self . cmdhelp
def addCommand ( self , command , function ) :
cmds [ str ( command ) . lower ( ) ] = function
def addHelp ( self , command , description ) :
cmdhelp [ str ( command ) . lower ( ) ] = str ( description )
2018-02-23 02:25:05 +00:00
def configure ( self ) :
'''
Displays something from the configuration file , or sets it
'''
if len ( sys . argv ) > = 4 :
config . reload ( )
config . set ( sys . argv [ 2 ] , sys . argv [ 3 ] , True )
logger . debug ( ' Configuration file updated. ' )
elif len ( sys . argv ) > = 3 :
config . reload ( )
logger . info ( logger . colors . bold + sys . argv [ 2 ] + ' : ' + logger . colors . reset + str ( config . get ( sys . argv [ 2 ] , logger . colors . fg . red + ' Not set. ' ) ) )
else :
logger . info ( logger . colors . bold + ' Get a value: ' + logger . colors . reset + sys . argv [ 0 ] + ' ' + sys . argv [ 1 ] + ' <key> ' )
logger . info ( logger . colors . bold + ' Set a value: ' + logger . colors . reset + sys . argv [ 0 ] + ' ' + sys . argv [ 1 ] + ' <key> <value> ' )
2018-02-04 01:11:35 +00:00
def execute ( self , argument ) :
2018-02-22 07:24:25 +00:00
'''
Executes a command
'''
2018-02-04 01:11:35 +00:00
argument = argument [ argument . startswith ( ' -- ' ) and len ( ' -- ' ) : ] # remove -- if it starts with it
# define commands
commands = self . getCommands ( )
command = commands . get ( argument , self . notFound )
command ( )
'''
THIS SECTION DEFINES THE COMMANDS
'''
2018-02-22 07:24:25 +00:00
def version ( self , verbosity = 5 ) :
'''
Displays the Onionr version
'''
logger . info ( ' Onionr ' + ONIONR_VERSION + ' ( ' + platform . machine ( ) + ' ) - API v ' + API_VERSION )
if verbosity > = 1 :
2018-03-03 07:10:27 +00:00
logger . info ( ONIONR_TAGLINE )
2018-02-22 07:24:25 +00:00
if verbosity > = 2 :
logger . info ( ' Running on ' + platform . platform ( ) + ' ' + platform . release ( ) )
2018-02-04 01:11:35 +00:00
2018-02-08 09:14:32 +00:00
def sendEncrypt ( self ) :
2018-02-22 07:24:25 +00:00
'''
Create a private message and send it
'''
2018-04-15 08:46:50 +00:00
invalidID = True
while invalidID :
2018-02-28 09:06:02 +00:00
try :
peer = logger . readline ( ' Peer to send to: ' )
except KeyboardInterrupt :
2018-02-08 09:14:32 +00:00
break
else :
2018-04-04 00:34:15 +00:00
if self . onionrUtils . validatePubKey ( peer ) :
2018-04-15 08:46:50 +00:00
invalidID = False
2018-02-28 09:06:02 +00:00
else :
logger . error ( ' Invalid peer ID ' )
else :
try :
message = logger . readline ( " Enter a message: " )
except KeyboardInterrupt :
pass
else :
logger . info ( " Sending message to " + peer )
self . onionrUtils . sendPM ( peer , message )
2018-02-08 09:14:32 +00:00
2018-02-04 01:11:35 +00:00
def openGUI ( self ) :
2018-02-22 07:24:25 +00:00
'''
Opens a graphical interface for Onionr
'''
2018-02-04 01:11:35 +00:00
gui . OnionrGUI ( self . onionrCore )
2018-04-18 03:43:33 +00:00
def listKeys ( self ) :
2018-02-22 07:24:25 +00:00
'''
2018-04-18 03:43:33 +00:00
Displays a list of keys ( used to be called peers ) ( ? )
2018-02-22 07:24:25 +00:00
'''
2018-04-18 03:43:33 +00:00
logger . info ( ' Public keys in database: \n ' )
2018-02-04 01:11:35 +00:00
for i in self . onionrCore . listPeers ( ) :
logger . info ( i )
def addPeer ( self ) :
2018-02-22 07:24:25 +00:00
'''
Adds a peer ( ? )
'''
2018-04-19 01:47:35 +00:00
2018-02-04 01:11:35 +00:00
try :
newPeer = sys . argv [ 2 ]
except :
pass
else :
logger . info ( " Adding peer: " + logger . colors . underline + newPeer )
self . onionrCore . addPeer ( newPeer )
2018-03-03 04:19:01 +00:00
return
2018-02-27 21:23:49 +00:00
def addAddress ( self ) :
2018-04-19 01:47:35 +00:00
'''
Adds a Onionr node address
'''
2018-02-27 21:23:49 +00:00
try :
newAddress = sys . argv [ 2 ]
except :
pass
else :
logger . info ( " Adding address: " + logger . colors . underline + newAddress )
if self . onionrCore . addAddress ( newAddress ) :
2018-04-19 01:47:35 +00:00
logger . info ( " Successfully added address. " )
2018-02-27 21:23:49 +00:00
else :
2018-04-19 01:47:35 +00:00
logger . warn ( " Unable to add address. " )
2018-02-04 01:11:35 +00:00
2018-03-03 04:19:01 +00:00
return
2018-04-16 02:22:19 +00:00
def addMessage ( self , header = " txt " ) :
2018-02-22 07:24:25 +00:00
'''
Broadcasts a message to the Onionr network
'''
2018-02-04 01:11:35 +00:00
while True :
2018-04-16 02:22:19 +00:00
2018-02-04 01:11:35 +00:00
messageToAdd = ' -txt- ' + logger . readline ( ' Broadcast message to network: ' )
2018-04-16 02:22:19 +00:00
if len ( messageToAdd ) - 5 > = 1 :
2018-02-04 01:11:35 +00:00
break
2018-02-22 07:24:25 +00:00
2018-02-04 01:11:35 +00:00
addedHash = self . onionrCore . setData ( messageToAdd )
self . onionrCore . addToBlockDB ( addedHash , selfInsert = True )
self . onionrCore . setBlockType ( addedHash , ' txt ' )
2018-03-03 04:19:01 +00:00
return
2018-04-19 01:47:35 +00:00
2018-04-18 03:43:33 +00:00
def getPMs ( self ) :
'''
display PMs sent to us
'''
self . onionrUtils . loadPMs ( )
2018-03-03 04:19:01 +00:00
def enablePlugin ( self ) :
'''
Enables and starts the given plugin
'''
if len ( sys . argv ) > = 3 :
plugin_name = sys . argv [ 2 ]
logger . info ( ' Enabling plugin \" ' + plugin_name + ' \" ... ' )
plugins . enable ( plugin_name )
else :
logger . info ( sys . argv [ 0 ] + ' ' + sys . argv [ 1 ] + ' <plugin> ' )
return
def disablePlugin ( self ) :
'''
Disables and stops the given plugin
'''
if len ( sys . argv ) > = 3 :
plugin_name = sys . argv [ 2 ]
logger . info ( ' Disabling plugin \" ' + plugin_name + ' \" ... ' )
plugins . disable ( plugin_name )
else :
logger . info ( sys . argv [ 0 ] + ' ' + sys . argv [ 1 ] + ' <plugin> ' )
return
def reloadPlugin ( self ) :
'''
Reloads ( stops and starts ) all plugins , or the given plugin
'''
if len ( sys . argv ) > = 3 :
plugin_name = sys . argv [ 2 ]
logger . info ( ' Reloading plugin \" ' + plugin_name + ' \" ... ' )
plugins . stop ( plugin_name )
plugins . start ( plugin_name )
else :
logger . info ( ' Reloading all plugins... ' )
plugins . reload ( )
return
2018-02-04 01:11:35 +00:00
def notFound ( self ) :
2018-02-22 07:24:25 +00:00
'''
Displays a " command not found " message
'''
2018-04-19 02:25:16 +00:00
logger . error ( ' Command not found. ' , timestamp = False )
2018-02-04 01:11:35 +00:00
def showHelpSuggestion ( self ) :
2018-02-22 07:24:25 +00:00
'''
Displays a message suggesting help
'''
2018-02-04 01:11:35 +00:00
logger . info ( ' Do ' + logger . colors . bold + sys . argv [ 0 ] + ' --help ' + logger . colors . reset + logger . colors . fg . green + ' for Onionr help. ' )
def start ( self ) :
2018-02-22 07:24:25 +00:00
'''
Starts the Onionr daemon
'''
2018-02-04 01:11:35 +00:00
if os . path . exists ( ' .onionr-lock ' ) :
logger . fatal ( ' Cannot start. Daemon is already running, or it did not exit cleanly. \n (if you are sure that there is not a daemon running, delete .onionr-lock & try again). ' )
else :
if not self . debug and not self . _developmentMode :
lockFile = open ( ' .onionr-lock ' , ' w ' )
lockFile . write ( ' ' )
lockFile . close ( )
self . daemon ( )
if not self . debug and not self . _developmentMode :
os . remove ( ' .onionr-lock ' )
2017-12-26 07:25:29 +00:00
def daemon ( self ) :
2018-02-22 07:24:25 +00:00
'''
Starts the Onionr communication daemon
'''
2018-03-03 04:19:01 +00:00
2018-01-13 09:03:51 +00:00
if not os . environ . get ( " WERKZEUG_RUN_MAIN " ) == " true " :
2018-02-21 02:44:56 +00:00
if self . _developmentMode :
logger . warn ( ' DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!) ' )
2018-03-03 07:26:02 +00:00
net = NetController ( config . get ( ' client ' ) [ ' port ' ] )
2018-01-26 07:22:48 +00:00
logger . info ( ' Tor is starting... ' )
2018-01-28 01:53:24 +00:00
if not net . startTor ( ) :
sys . exit ( 1 )
2018-01-26 07:22:48 +00:00
logger . info ( ' Started Tor .onion service: ' + logger . colors . underline + net . myID )
2018-02-21 02:44:56 +00:00
logger . info ( ' Our Public key: ' + self . onionrCore . _crypto . pubKey )
2018-01-19 21:28:34 +00:00
time . sleep ( 1 )
2018-01-26 06:28:11 +00:00
subprocess . Popen ( [ " ./communicator.py " , " run " , str ( net . socksPort ) ] )
2018-01-26 07:22:48 +00:00
logger . debug ( ' Started communicator ' )
2018-02-23 01:58:36 +00:00
api . API ( self . debug )
2018-02-04 01:11:35 +00:00
2017-12-26 07:25:29 +00:00
return
2018-02-04 01:11:35 +00:00
2017-12-26 07:25:29 +00:00
def killDaemon ( self ) :
2018-02-22 07:24:25 +00:00
'''
Shutdown the Onionr daemon
'''
2018-02-04 01:11:35 +00:00
2018-01-26 07:22:48 +00:00
logger . warn ( ' Killing the running daemon ' )
2018-03-03 07:26:02 +00:00
net = NetController ( config . get ( ' client ' ) [ ' port ' ] )
2018-01-14 08:48:23 +00:00
try :
self . onionrUtils . localCommand ( ' shutdown ' )
except requests . exceptions . ConnectionError :
pass
2018-01-27 01:24:38 +00:00
self . onionrCore . daemonQueueAdd ( ' shutdown ' )
2018-01-27 08:43:36 +00:00
net . killTor ( )
2018-02-04 01:11:35 +00:00
2017-12-26 07:25:29 +00:00
return
2018-02-04 01:11:35 +00:00
2017-12-26 07:25:29 +00:00
def showStats ( self ) :
2018-02-22 07:24:25 +00:00
'''
Displays statistics and exits
'''
2018-02-04 01:11:35 +00:00
2017-12-26 07:25:29 +00:00
return
2018-02-04 01:11:35 +00:00
2018-02-22 07:24:25 +00:00
def showHelp ( self , command = None ) :
'''
Show help for Onionr
'''
helpmenu = self . getHelp ( )
2018-02-04 01:11:35 +00:00
2018-02-22 07:24:25 +00:00
if command is None and len ( sys . argv ) > = 3 :
for cmd in sys . argv [ 2 : ] :
self . showHelp ( cmd )
elif not command is None :
if command . lower ( ) in helpmenu :
logger . info ( logger . colors . bold + command + logger . colors . reset + logger . colors . fg . blue + ' : ' + logger . colors . reset + helpmenu [ command . lower ( ) ] )
else :
logger . warn ( logger . colors . bold + command + logger . colors . reset + logger . colors . fg . blue + ' : ' + logger . colors . reset + ' No help menu entry was found ' )
else :
self . version ( 0 )
for command , helpmessage in helpmenu . items ( ) :
self . showHelp ( command )
2017-12-26 07:25:29 +00:00
return
2018-02-04 01:11:35 +00:00
2018-01-29 06:01:36 +00:00
Onionr ( )