示例#1
0
    def configure(self, **config):
        self.created = datetime.fromtimestamp(
            config['created']) if 'created' in config else datetime.now()

        if 'trigger_type' in config and valid_trigger_type(
                config['trigger_type']):
            self.trigger_type = config['trigger_type']

        if 'script' in config and valid_script(config['script']):
            self.script = config['script']

        if 'data' in config and isinstance(config['data'], dict):
            self.data = config['data']

        if 'multi' in config and config['multi'] in [True, False]:
            self.multi = config['multi']

        if 'status' in config and valid_status(config['status']):
            self.status = config['status']

        if 'reset' in config and config['reset'] is True:
            self.triggered = 0
            self.status = 'Active'

        elif 'triggered' in config and valid_amount(config['triggered']):
            self.triggered = config['triggered']

        if 'description' in config and valid_description(
                config['description']):
            self.description = config['description']

        if 'creator_name' in config and valid_creator(config['creator_name']):
            self.creator_name = config['creator_name']

        if 'creator_email' in config and valid_email(config['creator_email']):
            self.creator_email = config['creator_email']

        if 'youtube' in config and valid_youtube_id(config['youtube']):
            self.youtube = config['youtube']

        if 'visibility' in config and valid_visibility(config['visibility']):
            self.visibility = config['visibility']

        if 'actions' in config and valid_actions(config['actions']):
            self.actions = config['actions']
            configured_actions = get_actions()
            for action_id in self.actions:
                if action_id not in configured_actions:
                    LOG.warning('Trigger %s contains unknown action: %s' %
                                (self.id, action_id))

        if 'self_destruct' in config and valid_timestamp(
                config['self_destruct']):
            self.self_destruct = config['self_destruct']

        if 'destruct_actions' in config and config['destruct_actions'] in [
                True, False
        ]:
            self.destruct_actions = config['destruct_actions']
示例#2
0
    def push_tx(tx):
        # Must do import here to avoid circular import
        from data.data import get_explorer_api

        LOG.warning(
            'BTC.com api does not support broadcasting transactions, using Blockchain.info instead!'
        )
        blockchain_info_api = get_explorer_api('blockchain.info')
        return blockchain_info_api.push_tx(tx)
示例#3
0
    def get_transactions(self, address):
        LOG.warning(
            'DO NOT USE CHAIN.SO TO GET ADDRESS TRANSACTIONS!!!!!!!!!!!!!')
        url = '{api_url}/address/{network}/{address}'.format(
            api_url=self.url, network=self.network, address=address)
        try:
            LOG.info('GET %s' % url)
            r = requests.get(url)
            data = r.json()
        except Exception as ex:
            LOG.error(
                'Unable to get transactions of address %s from Chain.so: %s' %
                (address, ex))
            return {
                'error':
                'Unable to get transactions of address %s from Chain.so' %
                address
            }

        if 'data' not in data:
            LOG.error('Invalid response data from Chain.so: %s' % data)
            return {'error': 'Invalid response data from Chain.so: %s' % data}

        data = data['data']

        txids = [transaction['txid'] for transaction in data['txs']]
        txs = []

        # I know this is very ugly, but its the only way to get the necessary information from chain.so
        for txid in txids:
            transaction_data = self.get_transaction(txid=txid)
            if 'transaction' in transaction_data:
                txs.append(transaction_data['transaction'])
            sleep(
                10
            )  # try to avoid hitting the rate limits probably won't work, extremely slow!!!!!

        return {'transactions': txs}
def verify_signed_message(trigger_id, **data):
    if not all(key in data for key in ['address', 'message', 'signature']):
        return {
            'error':
            'Request data does not contain all required keys: address, message and signature'
        }

    triggers = get_triggers()
    if trigger_id not in triggers:
        return {'error': 'Unknown trigger id: %s' % trigger_id}

    trigger = get_trigger(trigger_id)
    if trigger.trigger_type != TriggerType.SIGNEDMESSAGE:
        return {
            'error':
            'Trigger %s is not a Signedmessage trigger' % trigger.trigger_type
        }

    if trigger.address is not None and trigger.address != data['address']:
        return {
            'error':
            'Trigger %s only listens to signed messages from address %s' %
            (trigger.id, trigger.address)
        }

    if verify_message(address=data['address'],
                      message=data['message'],
                      signature=data['signature']) is True:
        if trigger.status == 'Active':
            LOG.info('Trigger %s received a verified signed message' %
                     trigger_id)
            trigger.process_message(
                address=data['address'],
                message=data['message'],
                signature=data['signature'],
                data=data['data'] if 'data' in data else None,
                ipfs_object=data['ipfs_object']
                if 'ipfs_object' in data else None)
            return trigger.activate()
    else:
        LOG.warning('Trigger %s received a bad signed message' % trigger_id)
        LOG.warning('message: %s' % data['message'])
        LOG.warning('address: %s' % data['address'])
        LOG.warning('signature: %s' % data['signature'])
        return {'error': 'Signature is invalid!'}
示例#5
0
    def __init__(self):
        super(SpellbookRESTAPI, self).__init__()

        # Initialize variables
        self.host = get_host()
        self.port = get_port()

        # Log the requests to the REST API in a separate file by installing a custom LoggingPlugin
        self.install(self.log_to_logger)

        # Make sure that an api_keys.json file is present, the first time the server is started
        # a new random api key and secret pair will be generated
        if not os.path.isfile('json/private/api_keys.json'):
            LOG.info('Generating new API keys')
            initialize_api_keys_file()

        LOG.info('Starting Bitcoin Spellbook')

        try:
            get_hot_wallet()
        except Exception as ex:
            LOG.error('Unable to decrypt hot wallet: %s' % ex)
            sys.exit(1)

        LOG.info(
            'To make the server run in the background: use Control-Z, then use command: bg %1'
        )

        # Initialize the routes for the REST API
        self.route(
            '/', method='GET', callback=self.index
        )  # on linux this gets requested every minute or so, but not on windows
        self.route('/favicon.ico', method='GET', callback=self.get_favicon)

        # Route for ping, to test if server is online
        self.route('/spellbook/ping', method='GET', callback=self.ping)

        # Routes for managing blockexplorers
        self.route('/spellbook/explorers',
                   method='GET',
                   callback=self.get_explorers)
        self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>',
                   method='POST',
                   callback=self.save_explorer)
        self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>',
                   method='GET',
                   callback=self.get_explorer_config)
        self.route('/spellbook/explorers/<explorer_id:re:[a-zA-Z0-9_\-.]+>',
                   method='DELETE',
                   callback=self.delete_explorer)

        # Routes for retrieving data from the blockchain
        self.route('/spellbook/blocks/latest',
                   method='GET',
                   callback=self.get_latest_block)
        self.route('/spellbook/blocks/<height:int>',
                   method='GET',
                   callback=self.get_block_by_height)
        self.route('/spellbook/blocks/<block_hash:re:[a-f0-9]+>',
                   method='GET',
                   callback=self.get_block_by_hash)

        self.route('/spellbook/transactions/<txid:re:[a-f0-9]+>/prime_input',
                   method='GET',
                   callback=self.get_prime_input_address)
        self.route('/spellbook/transactions/<txid:re:[a-f0-9]+>',
                   method='GET',
                   callback=self.get_transaction)
        self.route(
            '/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/transactions',
            method='GET',
            callback=self.get_transactions)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/balance',
                   method='GET',
                   callback=self.get_balance)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/utxos',
                   method='GET',
                   callback=self.get_utxos)

        # Routes for Simplified Inputs List (SIL)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/SIL',
                   method='GET',
                   callback=self.get_sil)

        # Routes for Profile
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/profile',
                   method='GET',
                   callback=self.get_profile)

        # Routes for Simplified UTXO List (SUL)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/SUL',
                   method='GET',
                   callback=self.get_sul)

        # Routes for Linked Lists
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LAL',
                   method='GET',
                   callback=self.get_lal)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LBL',
                   method='GET',
                   callback=self.get_lbl)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LRL',
                   method='GET',
                   callback=self.get_lrl)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/LSL',
                   method='GET',
                   callback=self.get_lsl)

        # Routes for Random Address
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/SIL',
                   method='GET',
                   callback=self.get_random_address_from_sil)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LBL',
                   method='GET',
                   callback=self.get_random_address_from_lbl)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LRL',
                   method='GET',
                   callback=self.get_random_address_from_lrl)
        self.route('/spellbook/addresses/<address:re:[a-zA-Z1-9]+>/random/LSL',
                   method='GET',
                   callback=self.get_random_address_from_lsl)

        # Routes for Triggers
        self.route('/spellbook/triggers',
                   method='GET',
                   callback=self.get_triggers)
        self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='GET',
                   callback=self.get_trigger)
        self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='POST',
                   callback=self.save_trigger)
        self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='DELETE',
                   callback=self.delete_trigger)
        self.route(
            '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/activate',
            method='GET',
            callback=self.activate_trigger)
        self.route(
            '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/message',
            method='POST',
            callback=self.verify_signed_message)
        self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/get',
                   method='GET',
                   callback=self.http_get_request)
        self.route('/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/post',
                   method='POST',
                   callback=self.http_post_request)
        self.route(
            '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/delete',
            method='DELETE',
            callback=self.http_delete_request)
        self.route(
            '/spellbook/triggers/<trigger_id:re:[a-zA-Z0-9_\-.]+>/check',
            method='GET',
            callback=self.check_trigger)
        self.route('/spellbook/check_triggers',
                   method='GET',
                   callback=self.check_all_triggers)

        # Additional routes for Rest API endpoints
        self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='GET',
                   callback=self.http_get_request)
        self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='OPTIONS',
                   callback=self.http_get_request)
        self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='POST',
                   callback=self.http_post_request)
        self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='DELETE',
                   callback=self.http_delete_request)
        self.route('/html/<trigger_id:re:[a-zA-Z0-9_\-.]+>',
                   method='GET',
                   callback=self.html_request)
        self.route('/api/<trigger_id:re:[a-zA-Z0-9_\-.]+>/message',
                   method='POST',
                   callback=self.verify_signed_message)

        self.route('/api/sign_message',
                   method='POST',
                   callback=self.sign_message)

        # Routes for QR image generation
        self.route('/api/qr', method='GET', callback=self.qr)

        # Routes for Actions
        self.route('/spellbook/actions',
                   method='GET',
                   callback=self.get_actions)
        self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>',
                   method='GET',
                   callback=self.get_action)
        self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>',
                   method='POST',
                   callback=self.save_action)
        self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>',
                   method='DELETE',
                   callback=self.delete_action)
        self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>/run',
                   method='GET',
                   callback=self.run_action)

        # Routes for retrieving log messages
        self.route('/spellbook/logs/<filter_string>',
                   method='GET',
                   callback=self.get_logs)

        # Routes for RevealSecret actions
        self.route('/spellbook/actions/<action_id:re:[a-zA-Z0-9_\-.]+>/reveal',
                   method='GET',
                   callback=self.get_reveal)

        # Check if there are explorers configured, this will also initialize the default explorers on first startup
        if len(get_explorers()) == 0:
            LOG.warning('No block explorers configured!')

        try:
            # start the webserver for the REST API
            if get_enable_ssl() is True:
                self.run(host=self.host,
                         port=self.port,
                         debug=False,
                         server='sslwebserver')
            else:
                self.run(host=self.host,
                         port=self.port,
                         debug=True,
                         server='cheroot')

        except Exception as ex:
            LOG.error('An exception occurred in the main loop: %s' % ex)
            error_traceback = traceback.format_exc()
            for line in error_traceback.split('\n'):
                LOG.error(line)

            if get_mail_on_exception() is True:
                variables = {'HOST': get_host(), 'TRACEBACK': error_traceback}
                body_template = os.path.join('server_exception')
                sendmail(recipients=get_notification_email(),
                         subject='Main loop Exception occurred @ %s' %
                         get_host(),
                         body_template=body_template,
                         variables=variables)
示例#6
0
def sendmail(recipients,
             subject,
             body_template,
             variables=None,
             images=None,
             attachments=None):
    """
    Send an email using the smtp settings in the spellbook.conf file

    :param recipients: Email address(es) of the recipient(s) separated by comma
    :param subject: The subject for the email
    :param body_template: The filename of the body template for the email without the extension (both txt and html versions will be searched for)
    :param variables: A dict containing the variables that will be replaced in the email body template
                      The body template can contain variables like $MYVARIABLE$, if the dict contains a key MYVARIABLE (without $), then it will be replaced by the value of that key
    :param images: A dict containing the filename of the images that need to be embedded in the html email
    :param attachments: A dict containing the filename and path for each attachment that needs to be added to the email
    :return: True upon success, False upon failure
    """
    if get_enable_smtp() is False:
        LOG.warning(
            'SMTP is disabled, mail will not be sent! see spellbook configuration file'
        )
        return True  # Return true here so everything continues as normal

    # Load the smtp settings
    load_smtp_settings()

    if variables is None:
        variables = {}

    if images is None:
        images = {}

    if attachments is None:
        attachments = {}

    LOG.info('Creating new email with template %s' % body_template)

    # Create message container - the correct MIME type is multipart/alternative.
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = FROM_ADDRESS
    msg['To'] = recipients.replace(',', ', ')

    html_template_filename = None
    txt_template_filename = None
    # Search the 'email-templates' and 'apps' directory for the template
    if os.path.isfile(
            os.path.join(TEMPLATE_DIR, '%s.html' % body_template)
    ):  # First see if a html template is found in the main template directory
        html_template_filename = os.path.join(TEMPLATE_DIR,
                                              '%s.html' % body_template)
    elif os.path.isfile(os.path.join(
            APPS_DIR, '%s.html' %
            body_template)):  # Check the app directory for a html template
        html_template_filename = os.path.join(APPS_DIR,
                                              '%s.html' % body_template)

    if os.path.isfile(
            os.path.join(TEMPLATE_DIR, '%s.txt' % body_template)
    ):  # Then check if a txt template if found in the main template directory
        txt_template_filename = os.path.join(TEMPLATE_DIR,
                                             '%s.txt' % body_template)
    elif os.path.isfile(
            os.path.join(APPS_DIR, '%s.txt' % body_template)
    ):  # Lastly, check the app directory for a txt template
        txt_template_filename = os.path.join(APPS_DIR,
                                             '%s.txt' % body_template)

    if html_template_filename is None and txt_template_filename is None:
        LOG.error('Template %s for email not found!' % body_template)
        return False

    html_body = ''
    if html_template_filename is not None:
        try:
            with open(html_template_filename, 'r') as input_file:
                html_body = input_file.read()
        except Exception as ex:
            LOG.error('Unable to read template %s: %s' %
                      (html_template_filename, ex))
            return False

    txt_body = ''
    if txt_template_filename is not None:
        try:
            with open(txt_template_filename, 'r') as input_file:
                txt_body = input_file.read()
        except Exception as ex:
            LOG.error('Unable to read template %s: %s' %
                      (txt_template_filename, ex))
            return False

    # Replace all placeholder values in the body like $myvariable$ with the correct value
    for variable, value in variables.items():
        html_body = html_body.replace('$%s$' % str(variable), str(value))
        txt_body = txt_body.replace('$%s$' % str(variable), str(value))

    # Record the MIME types of both parts - text/plain and text/html.
    part1 = MIMEText(txt_body, 'plain')
    part2 = MIMEText(html_body, 'html')

    # Attach parts into message container.
    # According to RFC 2046, the last part of a multipart message, in this case
    # the HTML message, is best and preferred.
    if txt_body != '':
        msg.attach(part1)

    if html_body != '':
        msg.attach(part2)

    # Attach all images that are referenced in the html email template
    for image_name, image_file in images.items():
        LOG.info('adding image %s' % image_file)
        try:
            fp = open(image_file, 'rb')
            mime_image = MIMEImage(fp.read())
            fp.close()

            # Define the image's ID as referenced in the template
            mime_image.add_header('Content-ID', '<%s>' % image_name)
            mime_image.add_header('content-disposition',
                                  'attachment',
                                  filename=image_name)
            msg.attach(mime_image)
        except Exception as ex:
            LOG.error('Unable to add image %s to email: %s' % (image_name, ex))

    # Attach all attachments
    for attachment_name, attachment_file in attachments.items():
        LOG.info('adding attachment %s' % attachment_file)
        try:
            fp = open(attachment_file, 'rb')
            mime_file = MIMEBase('application', "octet-stream")
            mime_file.set_payload(fp.read())
            Encoders.encode_base64(mime_file)

            fp.close()

            # Define the image's ID as referenced in the template
            mime_file.add_header('content-disposition',
                                 'attachment',
                                 filename=attachment_name)
            msg.attach(mime_file)
        except Exception as ex:
            LOG.error('Unable to add attachment %s to email: %s' %
                      (attachment_name, ex))

    # Attempt to connect to the smtp server and send the message.
    try:
        # Start the smtp session
        session = smtplib.SMTP(HOST, PORT)
        session.ehlo()
        session.starttls()
        session.ehlo()
        session.login(USER, PASSWORD)

        # Send the message and quit
        session.sendmail(FROM_ADDRESS, recipients.split(','), msg.as_string())
        session.quit()

        LOG.info('Email sent to %s : %s (template: %s)' %
                 (recipients.split(','), subject, body_template))
        return True

    except Exception as ex:
        LOG.error('Failed sending mail: %s' % ex)
        return False