PyBitmessage/src/bitmessagemain.py

307 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/python2.7
2016-05-01 08:34:04 +02:00
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2016 The Bitmessage developers
2012-11-19 20:45:05 +01:00
# Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Right now, PyBitmessage only support connecting to stream 1. It doesn't
# yet contain logic to expand into further streams.
2012-11-19 20:45:05 +01:00
# The software version variable is now held in shared.py
import depends
depends.check_dependencies()
import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
# The next 3 are used for the API
import singleton
2013-06-25 22:26:12 +02:00
import os
import socket
import ctypes
from struct import pack
import sys
from subprocess import call
import time
2013-05-01 22:06:55 +02:00
from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer
2014-01-20 21:25:02 +01:00
from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections
import shared
from helper_sql import sqlQuery
import threading
# Classes
from class_sqlThread import sqlThread
from class_singleCleaner import singleCleaner
from class_objectProcessor import objectProcessor
from class_outgoingSynSender import outgoingSynSender
from class_singleListener import singleListener
from class_singleWorker import singleWorker
from class_addressGenerator import addressGenerator
from class_smtpDeliver import smtpDeliver
from class_smtpServer import smtpServer
2013-08-25 22:23:28 +02:00
from debug import logger
# Helper Functions
import helper_bootstrap
import helper_generic
from helper_threading import *
2015-11-15 15:08:48 +01:00
2013-05-01 22:06:55 +02:00
def connectToStream(streamNumber):
shared.streamsInWhichIAmParticipating[streamNumber] = 'no data'
2013-05-01 22:06:55 +02:00
selfInitiatedConnections[streamNumber] = {}
2013-09-07 00:55:12 +02:00
2014-01-20 21:25:02 +01:00
if isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
# Some XP and Vista systems can only have 10 outgoing connections at a time.
maximumNumberOfHalfOpenConnections = 9
else:
2014-01-20 21:25:02 +01:00
maximumNumberOfHalfOpenConnections = 64
try:
# don't overload Tor
if shared.config.get('bitmessagesettings', 'socksproxytype') != 'none':
maximumNumberOfHalfOpenConnections = 4
except:
pass
for i in range(maximumNumberOfHalfOpenConnections):
2013-05-01 22:06:55 +02:00
a = outgoingSynSender()
a.setup(streamNumber, selfInitiatedConnections)
2013-05-01 22:06:55 +02:00
a.start()
def _fixWinsock():
if not ('win32' in sys.platform) and not ('win64' in sys.platform):
return
# Python 2 on Windows doesn't define a wrapper for
# socket.inet_ntop but we can make one ourselves using ctypes
if not hasattr(socket, 'inet_ntop'):
addressToString = ctypes.windll.ws2_32.WSAAddressToStringA
def inet_ntop(family, host):
if family == socket.AF_INET:
if len(host) != 4:
raise ValueError("invalid IPv4 host")
host = pack("hH4s8s", socket.AF_INET, 0, host, "\0" * 8)
elif family == socket.AF_INET6:
if len(host) != 16:
raise ValueError("invalid IPv6 host")
host = pack("hHL16sL", socket.AF_INET6, 0, 0, host, 0)
else:
raise ValueError("invalid address family")
buf = "\0" * 64
lengthBuf = pack("I", len(buf))
addressToString(host, len(host), None, buf, lengthBuf)
return buf[0:buf.index("\0")]
socket.inet_ntop = inet_ntop
# Same for inet_pton
if not hasattr(socket, 'inet_pton'):
stringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA
def inet_pton(family, host):
buf = "\0" * 28
lengthBuf = pack("I", len(buf))
if stringToAddress(str(host),
int(family),
None,
buf,
lengthBuf) != 0:
raise socket.error("illegal IP address passed to inet_pton")
if family == socket.AF_INET:
return buf[4:8]
elif family == socket.AF_INET6:
return buf[8:24]
else:
raise ValueError("invalid address family")
socket.inet_pton = inet_pton
# These sockopts are needed on for IPv6 support
if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27
# This thread, of which there is only one, runs the API.
class singleAPI(threading.Thread, StoppableThread):
2013-05-01 22:06:55 +02:00
def __init__(self):
threading.Thread.__init__(self, name="singleAPI")
self.initStop()
def stopThread(self):
super(singleAPI, self).stopThread()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((shared.config.get('bitmessagesettings', 'apiinterface'), shared.config.getint(
'bitmessagesettings', 'apiport')))
s.shutdown(socket.SHUT_RDWR)
s.close()
except:
pass
2013-03-19 18:32:37 +01:00
def run(self):
se = StoppableXMLRPCServer((shared.config.get('bitmessagesettings', 'apiinterface'), shared.config.getint(
'bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True)
2013-03-19 18:32:37 +01:00
se.register_introspection_functions()
se.serve_forever()
# This is a list of current connections (the thread pointers at least)
selfInitiatedConnections = {}
if shared.useVeryEasyProofOfWorkForTesting:
shared.networkDefaultProofOfWorkNonceTrialsPerByte = int(
2014-08-27 09:14:32 +02:00
shared.networkDefaultProofOfWorkNonceTrialsPerByte / 100)
shared.networkDefaultPayloadLengthExtraBytes = int(
2014-08-27 09:14:32 +02:00
shared.networkDefaultPayloadLengthExtraBytes / 100)
2013-01-18 23:38:09 +01:00
class Main:
def start(self, daemon=False):
_fixWinsock()
2013-09-05 02:14:25 +02:00
shared.daemon = daemon
# get curses flag
shared.curses = False
if '-c' in sys.argv:
shared.curses = True
# is the application already running? If yes then exit.
shared.thisapp = singleton.singleinstance("", daemon)
if daemon:
with shared.printLock:
print('Running as a daemon. Send TERM signal to end.')
self.daemonize()
self.setSignalHandler()
helper_bootstrap.knownNodes()
# Start the address generation thread
addressGeneratorThread = addressGenerator()
addressGeneratorThread.daemon = True # close the main program even if there are threads left
addressGeneratorThread.start()
# Start the thread that calculates POWs
singleWorkerThread = singleWorker()
singleWorkerThread.daemon = True # close the main program even if there are threads left
singleWorkerThread.start()
# Start the SQL thread
sqlLookup = sqlThread()
sqlLookup.daemon = False # DON'T close the main program even if there are threads left. The closeEvent should command this thread to exit gracefully.
sqlLookup.start()
# SMTP delivery thread
if daemon and shared.safeConfigGet("bitmessagesettings", "smtpdeliver", '') != '':
smtpDeliveryThread = smtpDeliver()
smtpDeliveryThread.start()
# SMTP daemon thread
if daemon and shared.safeConfigGetBoolean("bitmessagesettings", "smtpd"):
smtpServerThread = smtpServer()
smtpServerThread.start()
# Start the thread that calculates POWs
objectProcessorThread = objectProcessor()
objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object.
objectProcessorThread.start()
# Start the cleanerThread
singleCleanerThread = singleCleaner()
singleCleanerThread.daemon = True # close the main program even if there are threads left
singleCleanerThread.start()
shared.reloadMyAddressHashes()
shared.reloadBroadcastSendersForWhichImWatching()
if shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'):
try:
apiNotifyPath = shared.config.get(
'bitmessagesettings', 'apinotifypath')
except:
apiNotifyPath = ''
if apiNotifyPath != '':
with shared.printLock:
print('Trying to call', apiNotifyPath)
2013-05-02 17:53:54 +02:00
call([apiNotifyPath, "startingUp"])
singleAPIThread = singleAPI()
singleAPIThread.daemon = True # close the main program even if there are threads left
singleAPIThread.start()
connectToStream(1)
2013-05-01 22:06:55 +02:00
singleListenerThread = singleListener()
singleListenerThread.setup(selfInitiatedConnections)
singleListenerThread.daemon = True # close the main program even if there are threads left
singleListenerThread.start()
if shared.safeConfigGetBoolean('bitmessagesettings','upnp'):
import upnp
upnpThread = upnp.uPnPThread()
upnpThread.start()
2013-05-01 22:06:55 +02:00
if daemon == False and shared.safeConfigGetBoolean('bitmessagesettings', 'daemon') == False:
if shared.curses == False:
if not depends.check_pyqt():
print('PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon')
print('You can also run PyBitmessage with the new curses interface by providing \'-c\' as a commandline argument.')
sys.exit()
import bitmessageqt
bitmessageqt.run()
else:
if True:
# if depends.check_curses():
print('Running with curses')
import bitmessagecurses
bitmessagecurses.runwrapper()
else:
shared.config.remove_option('bitmessagesettings', 'dontconnect')
2013-05-02 22:55:13 +02:00
while True:
time.sleep(20)
def daemonize(self):
if os.fork():
exit(0)
shared.thisapp.lock() # relock
os.umask(0)
os.setsid()
if os.fork():
exit(0)
shared.thisapp.lock() # relock
shared.thisapp.lockPid = None # indicate we're the final child
sys.stdout.flush()
sys.stderr.flush()
si = file('/dev/null', 'r')
so = file('/dev/null', 'a+')
se = file('/dev/null', 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
def setSignalHandler(self):
signal.signal(signal.SIGINT, helper_generic.signal_handler)
signal.signal(signal.SIGTERM, helper_generic.signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL)
2013-09-28 14:09:15 +02:00
def stop(self):
with shared.printLock:
print('Stopping Bitmessage Deamon.')
shared.doCleanShutdown()
2013-09-28 14:09:15 +02:00
#TODO: nice function but no one is using this
def getApiAddress(self):
if not shared.safeConfigGetBoolean('bitmessagesettings', 'apienabled'):
return None
address = shared.config.get('bitmessagesettings', 'apiinterface')
port = shared.config.getint('bitmessagesettings', 'apiport')
return {'address':address,'port':port}
2013-09-28 14:09:15 +02:00
2013-08-05 22:29:06 +02:00
if __name__ == "__main__":
mainprogram = Main()
mainprogram.start(shared.safeConfigGetBoolean('bitmessagesettings', 'daemon'))
2013-09-28 14:09:15 +02:00
# So far, the creation of and management of the Bitmessage protocol and this
# client is a one-man operation. Bitcoin tips are quite appreciated.
# 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u