def apiPlaceOrderExtension(session, shopurl, bridgeid):
    print("# apiPlaceOrderExtension")

    url = "{0}/api/v1/public/tor_bridges/{1}/extend/".format(shopurl, bridgeid)
    try:
        response = session.post(url)
    except Exception as e:
        raise BlitzError("failed HTTP request", {'url': url}, e)
    if response.status_code == 420:
        raise BlitzError("forwarding this address was rejected",
                         {'status_code': response.status_code})
    if response.status_code != 200 and response.status_code != 201:
        raise BlitzError("failed HTTP code",
                         {'status_code': response.status_code})

    # parse & validate data
    print("# parse")
    try:
        jData = json.loads(response.content)
        if len(jData['po_id']) == 0:
            print("error='MISSING ID'")
            return
    except Exception as e:
        raise BlitzError("failed JSON parsing", {'content': response.content},
                         e)

    return jData['po_id']
def apiPlaceOrderNew(session, shopurl, hostid, toraddressWithPort):
    print("# apiPlaceOrderNew")

    url = "{0}/api/v1/public/order/".format(shopurl)
    postData = {
        'product': "tor_bridge",
        'host_id': hostid,
        'tos_accepted': True,
        'comment': 'RaspiBlitz',
        'target': toraddressWithPort,
        'public_key': ''
    }
    try:
        response = session.post(url, data=postData)
    except Exception as e:
        raise BlitzError("failed HTTP request", {'url': url}, e)
    if response.status_code == 420:
        raise BlitzError("forwarding this address was rejected", {'status_code': response.status_code})
    if response.status_code != 201:
        raise BlitzError("failed HTTP code", {'status_code': response.status_code})

    # parse & validate data
    try:
        jData = json.loads(response.content)
        if len(jData['id']) == 0:
            print("error='MISSING ID'")
            return
    except Exception as e:
        raise BlitzError("failed JSON parsing", {'status_code': response.status_code}, e)

    return jData['id']
def apiGetOrder(session, shopurl, orderid) -> dict:
    print("# apiGetOrder")

    # make HTTP request
    url = "{0}/api/v1/public/pos/{1}/".format(shopurl, orderid)
    try:
        response = session.get(url)
    except Exception as e:
        raise BlitzError("failed HTTP request", {'url': url}, e)
    if response.status_code != 200:
        raise BlitzError("failed HTTP code",
                         {'status_code': response.status_code})

    # parse & validate data
    try:
        jData = json.loads(response.content)
        if len(jData['item_details']) == 0:
            raise BlitzError("missing item", {'content': response.content})
        if len(jData['ln_invoices']) > 1:
            raise BlitzError("more than one invoice",
                             {'content': response.content})
    except Exception as e:
        raise BlitzError("failed JSON parsing", {'content': response.content},
                         e)

    return jData
def apiGetHosts(session, shopurl):
    print("# apiGetHosts")
    hosts = []

    # make HTTP request
    url = "{0}/api/v1/public/hosts/?is_testnet={1}".format(
        shopurl, int(is_testnet))
    try:
        response = session.get(url)
    except Exception as e:
        raise BlitzError("failed HTTP request", {'url': url}, e)
    if response.status_code != 200:
        raise BlitzError("failed HTTP code",
                         {'status_code': response.status_code})

    # parse & validate data
    try:
        jData = json.loads(response.content)
    except Exception as e:
        raise BlitzError("failed JSON parsing", {'content': response.content},
                         e)
    if not isinstance(jData, list):
        raise BlitzError("hosts not list", {'content': response.content})
    for idx, hostEntry in enumerate(jData):
        try:
            # ignore if not offering tor bridge
            if not hostEntry['offers_tor_bridges']:
                continue
            # ignore if duration is less than an hour
            if hostEntry['tor_bridge_duration'] < 3600:
                continue
            # add duration per hour value
            hostEntry['tor_bridge_duration_hours'] = math.floor(
                hostEntry['tor_bridge_duration'] / 3600)
            # ignore if prices are negative or below one sat (maybe msats later)
            if hostEntry['tor_bridge_price_initial'] < 1000:
                continue
            if hostEntry['tor_bridge_price_extension'] < 1000:
                continue
            # add price in sats
            hostEntry['tor_bridge_price_initial_sats'] = math.ceil(
                hostEntry['tor_bridge_price_initial'] / 1000)
            hostEntry['tor_bridge_price_extension_sats'] = math.ceil(
                hostEntry['tor_bridge_price_extension'] / 1000)
            # ignore name is less then 3 chars
            if len(hostEntry['name']) < 3:
                continue
            # ignore id with zero value
            if len(hostEntry['id']) < 1:
                continue
            # shorten names to 20 chars max
            hostEntry['name'] = hostEntry['name'][:20]
        except Exception as e:
            raise BlitzError("failed host entry pasring", hostEntry, e)

        hosts.append(hostEntry)

    print("# found {0} valid torbridge hosts".format(len(hosts)))
    return hosts
예제 #5
0
def duckdns_update(domain, token, ip):
    print("# duckDNS update IP API call for {0}".format(domain))

    # make HTTP request
    url = "https://www.duckdns.org/update?domains={0}&token={1}&ip={2}".format(domain.split('.')[0], token, ip)
    print("# calling URL: {0}".format(url))
    try:
        response = session.get(url)
        if response.status_code != 200:
            raise BlitzError("failed HTTP code", str(response.status_code))
        print("# response-code: {0}".format(response.status_code))
    except Exception as e:
        raise BlitzError("failed HTTP request", url, e)

    return response.content
예제 #6
0
def subscription_new():
    # check parameters
    try:
        if len(sys.argv) <= 5:
            raise BlitzError("incorrect parameters", "")
    except Exception as e:
        handleException(e)

    ip = sys.argv[2]
    dnsservice_type = sys.argv[3]
    dnsservice_id = sys.argv[4]
    dnsservice_token = sys.argv[5]
    if len(sys.argv) <= 6:
        target = "ip&tor"
    else:
        target = sys.argv[6]

    # create the subscription
    try:
        subscription = subscriptions_new(ip, dnsservice_type, dnsservice_id, dnsservice_token, target)

        # output json ordered bridge
        print(json.dumps(subscription, indent=2))
        sys.exit()

    except Exception as e:
        handleException(e)
def shop_order():
    # check parameters
    try:
        if len(sys.argv) <= 8:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    shopurl = sys.argv[2]
    servicename = sys.argv[3]
    hostid = sys.argv[4]
    toraddress = sys.argv[5]
    duration = sys.argv[6]
    msatsFirst = sys.argv[7]
    msatsNext = sys.argv[8]
    if len(sys.argv) >= 10:
        description = sys.argv[9]
    else:
        description = ""

    # get data
    try:
        subscription = shopOrder(shopurl, hostid, servicename, toraddress, duration, msatsFirst, msatsNext, description)
        # output json ordered bridge
        print(json.dumps(subscription, indent=2))
        sys.exit()
    except Exception as e:
        handleException(e)
def subscription_cancel():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    subscriptionID = sys.argv[2]

    try:
        os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
        subs = toml.load(SUBSCRIPTIONS_FILE)
        newList = []
        for idx, sub in enumerate(subs['subscriptions_ip2tor']):
            if sub['id'] != subscriptionID:
                newList.append(sub)
        subs['subscriptions_ip2tor'] = newList

        # persist change
        with open(SUBSCRIPTIONS_FILE, 'w') as writer:
            writer.write(toml.dumps(subs))
            writer.close()

        print(json.dumps(subs, indent=2))

    except Exception as e:
        handleException(e)
예제 #9
0
def subscription_by_service():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    service_name = sys.argv[2]

    try:
        if os.path.isfile(SUBSCRIPTIONS_FILE):
            os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
            subs = toml.load(SUBSCRIPTIONS_FILE)
            for idx, sub in enumerate(subs['subscriptions_ip2tor']):
                if sub['active'] and sub['name'] == service_name:
                    print("type='{0}'".format(sub['type']))
                    print("ip='{0}'".format(sub['ip']))
                    print("port='{0}'".format(sub['port']))
                    print("tor='{0}'".format(sub['tor']))
                    sys.exit(0)

        print("error='not found'")

    except Exception as e:
        handleException(e)
        sys.exit(1)
def ip_by_tor():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    onion = sys.argv[2]

    try:
        if os.path.isfile(SUBSCRIPTIONS_FILE):
            subs = toml.load(SUBSCRIPTIONS_FILE)
            for idx, sub in enumerate(subs['subscriptions_ip2tor']):
                if sub['active'] and (sub['tor'] == onion or sub['tor'].split(":")[0] == onion):
                    print("id='{0}'".format(sub['id']))
                    print("type='{0}'".format(sub['type']))
                    print("ip='{0}'".format(sub['ip']))
                    print("port='{0}'".format(sub['port']))
                    print("tor='{0}'".format(sub['tor']))
                    sys.exit(0)

        print("error='not found'")

    except Exception as e:
        handleException(e)
        sys.exit(1)
예제 #11
0
def subscriptions_cancel(s_id):
    os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
    subs = toml.load(SUBSCRIPTIONS_FILE)
    new_list = []
    removed_cert = None
    for idx, sub in enumerate(subs['subscriptions_letsencrypt']):
        if sub['id'] != s_id:
            new_list.append(sub)
        else:
            removed_cert = sub
    subs['subscriptions_letsencrypt'] = new_list

    # run the ACME script to remove cert
    if removed_cert:
        acme_result = subprocess.Popen(
            ["/home/admin/config.scripts/bonus.letsencrypt.sh", "remove-cert", removed_cert['id'],
             removed_cert['target']], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8')
        out, err = acme_result.communicate()
        if out.find("error=") > -1:
            time.sleep(6)
            raise BlitzError("letsencrypt acme failed", out)

    # persist change
    with open(SUBSCRIPTIONS_FILE, 'w') as writer:
        writer.write(toml.dumps(subs))
        writer.close()

    print(json.dumps(subs, indent=2))
예제 #12
0
def get_domain_by_ip(ip):
    # does subscriptin file exists
    if Path(SUBSCRIPTIONS_FILE).is_file():
        subs = toml.load(SUBSCRIPTIONS_FILE)
    else:
        raise BlitzError("no match")
    # section with letsencrypt subs exists
    if "subscriptions_letsencrypt" not in subs:
        raise BlitzError("no match")
    # go thru subscription and check of a match
    for idx, sub in enumerate(subs['subscriptions_letsencrypt']):
        # if IP is a direct match
        if sub['ip'] == ip:
            return sub['id']
        # if IP is a dynamicIP - check with the publicIP from the config
        if sub['ip'] == "dyndns":
            if cfg.public_ip == ip:
                return sub['id']
    raise BlitzError("no match")
def apiGetBridgeStatus(session, shopurl, bridgeid):
    print("# apiGetBridgeStatus")

    # make HTTP request
    url = "{0}/api/v1/public/tor_bridges/{1}/".format(shopurl, bridgeid)
    try:
        response = session.get(url)
    except Exception as e:
        raise BlitzError("failed HTTP request", {'url': url}, e)
    if response.status_code != 200:
        raise BlitzError("failed HTTP code", {'status_code': response.status_code})
    # parse & validate data
    try:
        jData = json.loads(response.content)
        if len(jData['id']) == 0:
            raise BlitzError("missing id", {'content': response.content})
    except Exception as e:
        raise BlitzError("failed JSON parsing", {'content': response.content}, e)

    return jData
예제 #14
0
def subscription_cancel():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters", "")
    except Exception as e:
        handleException(e)

    subscription_id = sys.argv[2]
    try:
        subscriptions_cancel(subscription_id)
    except Exception as e:
        handleException(e)
def lndPayInvoice(lnInvoiceString):
    try:
        # call LND GRPC API
        macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex')
        os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
        cert = open(LND_TLS_PATH, 'rb').read()
        ssl_creds = grpc.ssl_channel_credentials(cert)
        channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds)
        stub = rpcstub.LightningStub(channel)
        request = lnrpc.SendRequest(
            payment_request=lnInvoiceString,
        )
        response = stub.SendPaymentSync(request, metadata=[('macaroon', macaroon)])

        # validate results
        if len(response.payment_error) > 0:
            raise BlitzError(response.payment_error, {'invoice': lnInvoiceString})

    except Exception as e:
        raise BlitzError("payment failed", {'invoice': lnInvoiceString}, e)

    return response
def lndDecodeInvoice(lnInvoiceString):
    try:
        # call LND GRPC API
        macaroon = codecs.encode(open(LND_ADMIN_MACAROON_PATH, 'rb').read(), 'hex')
        os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
        cert = open(LND_TLS_PATH, 'rb').read()
        ssl_creds = grpc.ssl_channel_credentials(cert)
        channel = grpc.secure_channel("{0}:10009".format(LND_IP), ssl_creds)
        stub = rpcstub.LightningStub(channel)
        request = lnrpc.PayReqString(
            pay_req=lnInvoiceString,
        )
        response = stub.DecodePayReq(request, metadata=[('macaroon', macaroon)])

        # validate results
        if response.num_msat <= 0:
            raise BlitzError("zero invoice not allowed", {'invoice': lnInvoiceString})

    except Exception as e:
        raise BlitzError("failed LND invoice decoding", {'invoice': lnInvoiceString}, e)

    return response
def create_ssh_dialog():
    # check parameters
    try:
        if len(sys.argv) <= 4:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    servicename = sys.argv[2]
    toraddress = sys.argv[3]
    port = sys.argv[4]

    menuMakeSubscription(servicename, toraddress, port)

    sys.exit()
예제 #18
0
def domain_by_ip():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters", "")

    except Exception as e:
        handleException(e)

    ip = sys.argv[2]
    try:

        domain = get_domain_by_ip(ip)
        print("domain='{0}'".format(domain))

    except Exception as e:
        handleException(e)
def shop_list():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters")
    except Exception as e:
        handleException(e)

    shopurl = sys.argv[2]

    try:
        # get data
        hosts = shopList(shopurl)
        # output is json list of hosts
        print(json.dumps(hosts, indent=2))
    except Exception as e:
        handleException(e)

    sys.exit(0)
예제 #20
0
def subscription_detail():
    # check parameters
    try:
        if len(sys.argv) <= 2:
            raise BlitzError("incorrect parameters", "")
    except Exception as e:
        handleException(e)

    subscription_id = sys.argv[2]
    httpsTestport = ""
    if len(sys.argv) > 3:
        httpsTestport = sys.argv[3]
    try:
        sub = get_subscription(subscription_id)

        # use unix 'getent' to resolve DNS to IP
        dns_result = subprocess.Popen(["getent", "hosts", subscription_id],
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.STDOUT,
                                      encoding='utf8')
        out, err = dns_result.communicate()
        sub['dns_response'] = "unknown"
        if subscription_id in out:
            sub['dns_response'] = out.split(" ")[0]
            if sub['dns_response'] != sub['ip'] and len(sub['warning']) == 0:
                sub['warning'] = "Domain resolves not to target IP yet."

        # when https testport is set - check if you we get a https response
        sub['https_response'] = -1
        if len(httpsTestport) > 0:
            url = "https://{0}:{1}".format(subscription_id, httpsTestport)
            try:
                response = session.get(url)
                sub['https_response'] = response.status_code
            except Exception as e:
                sub['https_response'] = 0
            if sub['https_response'] != 200 and len(sub['warning']) == 0:
                sub['warning'] = "Not able to get HTTPS response."

        print(json.dumps(sub, indent=2))

    except Exception as e:
        handleException(e)
def shopOrder(shopUrl, hostid, servicename, torTarget, duration, msatsFirst, msatsNext, description=""):
    print("#### Placeing order ...")
    shopUrl = normalizeShopUrl(shopUrl)
    orderid = apiPlaceOrderNew(session, shopUrl, hostid, torTarget)

    print("#### Waiting until invoice is available ...")
    loopCount = 0
    while True:
        time.sleep(2)
        loopCount += 1
        print("# Loop {0}".format(loopCount))
        order = apiGetOrder(session, shopUrl, orderid)
        if order['status'] == "R":
            raise BlitzError("Subscription Rejected", order)
        if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None:
            break
        if loopCount > 60:
            raise BlitzError("timeout on getting invoice", order)

    # get data from now complete order
    paymentRequestStr = order['ln_invoices'][0]['payment_request']
    bridge_id = order['item_details'][0]['product']['id']
    bridge_ip = order['item_details'][0]['product']['host']['ip']
    bridge_port = order['item_details'][0]['product']['port']

    print("#### Decoding invoice and checking ..)")
    print("# invoice: {0}".format(paymentRequestStr))
    paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
    if paymentRequestDecoded is None: sys.exit()
    print("# amount as advertised: {0} milliSats".format(msatsFirst))
    print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat))
    if int(msatsFirst) < int(paymentRequestDecoded.num_msat):
        raise BlitzError("invoice bigger amount than advertised",
                         "advertised({0}) invoice({1})".format(msatsFirst, paymentRequestDecoded.num_msat))

    print("#### Paying invoice ...")
    payedInvoice = lndPayInvoice(paymentRequestStr)
    print('# OK PAYMENT SENT')

    print("#### Waiting until bridge is ready ...")
    loopCount = 0
    while True:
        time.sleep(3)
        loopCount += 1
        print("## Loop {0}".format(loopCount))
        bridge = apiGetBridgeStatus(session, shopUrl, bridge_id)
        if bridge['status'] == "A":
            break
        if bridge['status'] == "R":
            break
        if loopCount > 120:
            raise BlitzError("timeout bridge not getting ready", bridge)

    print("#### Check if port is valid ...")
    try:
        bridge_port = int(bridge['port'])
    except KeyError:
        raise BlitzError("invalid port (key not found)", bridge)
    except ValueError:
        raise BlitzError("invalid port (not a number)", bridge)
    if bridge_port < 1 or bridge_port > 65535:
        raise BlitzError("invalid port (not a valid tcp port)", bridge)        

    print("#### Check if duration delivered is as advertised ...")
    contract_breached = False
    warning_text = ""
    secondsDelivered = secondsLeft(parseDate(bridge['suspend_after']))
    print("# delivered({0}) promised({1})".format(secondsDelivered, duration))
    if (secondsDelivered + 600) < int(duration):
        contract_breached = True
        warning_text = "delivered duration shorter than advertised"
    if bridge['status'] == "R":
        contract_breached = True
        try:
            warningTXT = "rejected: {0}".format(bridge['message'])
        except Exception as e:
            warningTXT = "rejected: n/a"

    # create subscription data for storage
    subscription = dict()
    subscription['type'] = "ip2tor-v1"
    subscription['id'] = bridge['id']
    subscription['name'] = servicename
    subscription['shop'] = shopUrl
    subscription['active'] = not contract_breached
    subscription['ip'] = bridge_ip
    subscription['port'] = bridge_port
    subscription['duration'] = int(duration)
    subscription['price_initial'] = int(msatsFirst)
    subscription['price_extension'] = int(msatsNext)
    subscription['price_total'] = int(paymentRequestDecoded.num_msat)
    subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
    subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
    subscription['suspend_after'] = bridge['suspend_after']
    subscription['description'] = str(description)
    subscription['contract_breached'] = contract_breached
    subscription['warning'] = warning_text
    subscription['tor'] = torTarget

    # load, add and store subscriptions
    try:
        os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
        if Path(SUBSCRIPTIONS_FILE).is_file():
            print("# load toml file")
            subscriptions = toml.load(SUBSCRIPTIONS_FILE)
        else:
            print("# new toml file")
            subscriptions = {}
        if "subscriptions_ip2tor" not in subscriptions:
            subscriptions['subscriptions_ip2tor'] = []
        subscriptions['subscriptions_ip2tor'].append(subscription)
        subscriptions['shop_ip2tor'] = shopUrl
        with open(SUBSCRIPTIONS_FILE, 'w') as writer:
            writer.write(toml.dumps(subscriptions))
            writer.close()

    except Exception as e:
        eprint(e)
        raise BlitzError("fail on subscription storage", subscription, e)

    print("# OK - BRIDGE READY: {0}:{1} -> {2}".format(bridge_ip, bridge_port, torTarget))
    return subscription
예제 #22
0
def dynu_update(domain, token, ip):

    print("# dynu update IP API call for {0}".format(domain))

    # split token to oAuth username and password
    try:
        print("Splitting oAuth user & pass:"******":")[0]
        password = token.split(":")[1]
        print(username)
        print(password)
    except Exception as e:
        raise BlitzError("failed to split token", token, e)

    # get API token from oAuth data
    url="https://api.dynu.com/v2/oauth2/token"
    headers = {'accept': 'application/json'}
    print("# calling URL: {0}".format(url))
    print("# headers: {0}".format(headers))
    try:
        response = session.get(url, headers=headers, auth=(username, password))
        if response.status_code != 200:
            raise BlitzError("failed HTTP request", url + str(response.status_code))
        print("# response-code: {0}".format(response.status_code))
    except Exception as e:
        raise BlitzError("failed HTTP request", url, e)
    
    # parse data
    apitoken=""
    try:
        print(response.content)
        data = json.loads(response.content)
        apitoken = data["access_token"];
    except Exception as e:
        raise BlitzError("failed parsing data", response.content, e)
    if len(apitoken) == 0:
        raise BlitzError("access_token not found", response.content)

    # get id for domain
    url = "https://api.dynu.com/v2/dns"
    headers = {'accept': 'application/json', 'Authorization': "Bearer {0}".format(apitoken)}
    print("# calling URL: {0}".format(url))
    print("# headers: {0}".format(headers))
    try:
        response = session.get(url, headers=headers)
        if response.status_code != 200:
            raise BlitzError("failed HTTP request", url + str(response.status_code))
        print("# response-code: {0}".format(response.status_code))
    except Exception as e:
        print(e)
        time.sleep(4)
        raise BlitzError("failed HTTP request", url, e)

    # parse data
    id_for_domain=0
    try:
        print(response.content)
        data = json.loads(response.content)
        for entry in data["domains"]:   
            if entry['name'] == domain:
                id_for_domain = entry['id']
                break
    except Exception as e:
        print(e)
        time.sleep(4)
        raise BlitzError("failed parsing data", response.content, e)
    if id_for_domain == 0:
        raise BlitzError("domain not found", response.content)

    # update ip address
    url = "https://api.dynu.com/v2/dns/{0}".format(id_for_domain)
    print("# calling URL: {0}".format(url))
    headers = {'accept': 'application/json', 'Authorization': "Bearer {0}".format(apitoken)}
    print("# headers: {0}".format(headers))
    data = {
        "name": domain,
        "ipv4Address": ip,
        "ipv4": True,
        "ipv6": False
    }
    data = json.dumps(data)
    print("# post data: {0}".format(data))
    try:
        response = session.post(url, headers=headers, data=data)
        if response.status_code != 200:
            raise BlitzError("failed HTTP request", url + str(response.status_code))
        print("# response-code: {0}".format(response.status_code))
    except Exception as e:
        print(e)
        time.sleep(4)
        raise BlitzError("failed HTTP request", url, e)

    return response.content    
def subscriptionExtend(shopUrl, bridgeid, durationAdvertised, msatsNext, bridge_suspendafter):
    warningTXT = ""
    contract_breached = False

    print("#### Placing extension order ...")
    shopUrl = normalizeShopUrl(shopUrl)
    orderid = apiPlaceOrderExtension(session, shopUrl, bridgeid)

    print("#### Waiting until invoice is available ...")
    loopCount = 0
    while True:
        time.sleep(2)
        loopCount += 1
        print("## Loop {0}".format(loopCount))
        order = apiGetOrder(session, shopUrl, orderid)
        if len(order['ln_invoices']) > 0 and order['ln_invoices'][0]['payment_request'] is not None:
            break
        if loopCount > 120:
            raise BlitzError("timeout on getting invoice", order)

    paymentRequestStr = order['ln_invoices'][0]['payment_request']

    print("#### Decoding invoice and checking ..)")
    print("# invoice: {0}".format(paymentRequestStr))
    paymentRequestDecoded = lndDecodeInvoice(paymentRequestStr)
    if paymentRequestDecoded is None: sys.exit()
    print("# amount as advertised: {0} milliSats".format(msatsNext))
    print("# amount in invoice is: {0} milliSats".format(paymentRequestDecoded.num_msat))
    if int(msatsNext) < int(paymentRequestDecoded.num_msat):
        raise BlitzError("invoice bigger amount than advertised",
                         "advertised({0}) invoice({1})".format(msatsNext, paymentRequestDecoded.num_msat))

    print("#### Paying invoice ...")
    payedInvoice = lndPayInvoice(paymentRequestStr)

    print("#### Check if bridge was extended ...")
    bridge = None
    loopCount = 0
    while True:
        time.sleep(3)
        loopCount += 1
        print("## Loop {0}".format(loopCount))
        try:
            bridge = apiGetBridgeStatus(session, shopUrl, bridgeid)
            if bridge['status'] == "R":
                contract_breached = True
                try:
                    warningTXT = "rejected: {0}".format(bridge['message'])
                except Exception as e:
                    warningTXT = "rejected: n/a"
                break
            if bridge['suspend_after'] != bridge_suspendafter:
                break
        except Exception as e:
            eprint(e)
            print("# EXCEPTION on apiGetBridgeStatus")
        if loopCount > 240:
            warningTXT = "timeout on last payed extension"
            contract_breached = True
            break

    if bridge and not contract_breached:
        print("#### Check if extension duration is as advertised ...")
        secondsLeftOld = secondsLeft(parseDate(bridge_suspendafter))
        secondsLeftNew = secondsLeft(parseDate(bridge['suspend_after']))
        secondsExtended = secondsLeftNew - secondsLeftOld
        print("# secondsExtended({0}) promised({1})".format(secondsExtended, durationAdvertised))
        if secondsExtended < int(durationAdvertised):
            contract_breached = True
            warningTXT = "delivered duration shorter than advertised"

    # load, update and store subscriptions
    try:
        print("# load toml file")
        os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
        subscriptions = toml.load(SUBSCRIPTIONS_FILE)
        for idx, subscription in enumerate(subscriptions['subscriptions_ip2tor']):
            if subscription['id'] == bridgeid:
                subscription['suspend_after'] = str(bridge['suspend_after'])
                subscription['time_lastupdate'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
                subscription['price_total'] += int(paymentRequestDecoded.num_msat)
                subscription['contract_breached'] = contract_breached
                subscription['warning'] = warningTXT
                if contract_breached:
                    subscription['active'] = False
                with open(SUBSCRIPTIONS_FILE, 'w') as writer:
                    writer.write(toml.dumps(subscriptions))
                    writer.close()
                break

    except Exception as e:
        eprint(e)
        raise BlitzError("fail on subscription storage", org=e)

    print("# BRIDGE GOT EXTENDED: {0} -> {1}".format(bridge_suspendafter, bridge['suspend_after']))
예제 #24
0
def subscriptions_new(ip, dnsservice, domain, token, target):
    # domain needs to be the full domain name
    if domain.find(".") == -1:
        raise BlitzError("not a fully qualified domain name", domain)

    # check if domain already exists
    if len(get_subscription(domain)) > 0:
        raise BlitzError("domain already exists", domain)

    # make sure lets encrypt client is installed
    os.system("/home/admin/config.scripts/bonus.letsencrypt.sh on")

    # dyndns
    real_ip = ip
    if ip == "dyndns":
        update_url = ""
        if dnsservice == "duckdns":
            update_url = "https://www.duckdns.org/update?domains={0}&token={1}".format(domain, token, ip)
        subprocess.run(['/home/admin/config.scripts/internet.dyndomain.sh', 'on', domain, update_url],
                       stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
        real_ip = cfg.public_ip
        if dnsservice == "dynu":
            raise BlitzError("not implemented", "dynamic ip updating for dynu.com not implemented yet ", e)
            sys.exit(0)

    # update DNS with actual IP
    if dnsservice == "duckdns":
        print("# dnsservice=duckdns --> update {0}".format(domain))
        duckdns_update(domain, token, real_ip)
    if dnsservice == "dynu":
        print("# dnsservice=dynu --> update {0}".format(domain))
        dynu_update(domain, token, real_ip)

    # create subscription data for storage
    subscription = dict()
    subscription['type'] = "letsencrypt-v1"
    subscription['id'] = domain
    subscription['active'] = True
    subscription['name'] = "{0} for {1}".format(dnsservice, domain)
    subscription['dnsservice_type'] = dnsservice
    subscription['dnsservice_token'] = token
    subscription['ip'] = ip
    subscription['target'] = target
    subscription['description'] = "For {0}".format(target)
    subscription['time_created'] = str(datetime.now().strftime("%Y-%m-%d %H:%M"))
    subscription['warning'] = ""

    # load, add and store subscriptions
    try:
        os.system("sudo chown admin:admin {0}".format(SUBSCRIPTIONS_FILE))
        if Path(SUBSCRIPTIONS_FILE).is_file():
            print("# load toml file")
            subscriptions = toml.load(SUBSCRIPTIONS_FILE)
        else:
            print("# new toml file")
            subscriptions = {}
        if "subscriptions_letsencrypt" not in subscriptions:
            subscriptions['subscriptions_letsencrypt'] = []
        subscriptions['subscriptions_letsencrypt'].append(subscription)
        with open(SUBSCRIPTIONS_FILE, 'w') as writer:
            writer.write(toml.dumps(subscriptions))
            writer.close()

    except Exception as e:
        eprint(e)
        raise BlitzError("fail on subscription storage", str(subscription), e)

    # run the ACME script
    print("# Running letsencrypt ACME script ...")
    acme_result = subprocess.Popen(
        ["/home/admin/config.scripts/bonus.letsencrypt.sh", "issue-cert", dnsservice, domain, token, target],
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf8')
    out, err = acme_result.communicate()
    eprint(str(out))
    eprint(str(err))
    if out.find("error=") > -1:
        time.sleep(6)
        raise BlitzError("letsancrypt acme failed", out)

    print("# OK - LETSENCRYPT DOMAIN IS READY")
    return subscription