/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program 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 . */ #include "interface/RainflyClient.h" #include "benc/String.h" #include "benc/serialization/BencSerializer.h" #include "benc/serialization/json/JsonBencSerializer.h" #include "crypto/random/Random.h" #include "io/ArrayReader.h" #include "util/events/Timeout.h" #include "util/Assert.h" #include "wire/Message.h" #include "wire/Error.h" #include struct RainflyClient_Key { uint8_t bytes[32]; }; Assert_compileTime(sizeof(struct RainflyClient_Key) == 32); struct RainflyClient_Sig { uint8_t bytes[64]; }; Assert_compileTime(sizeof(struct RainflyClient_Sig) == 64); struct RainflyClient_Lookup_pvt; struct RainflyClient_Lookup_pvt { struct RainflyClient_Lookup pub; uint32_t cookie; int server; int triedServers; /** The index in hotKeys of the first key pushed to the request. */ int firstKey; struct RainflyClient_pvt* ctx; struct Timeout* tryNextTimeout; struct RainflyClient_Lookup_pvt* next; Identity }; struct RainflyClient_pvt { struct RainflyClient pub; struct Interface* iface; struct Log* logger; struct EventBase* eventBase; struct Allocator* alloc; struct Random* rand; /** My address. */ struct Sockaddr* addr; int serverCount; struct Sockaddr** servers; int keyCount; struct RainflyClient_Key* keys; /** Bitmap of available hot keys. */ uint64_t keyMap; struct RainflyClient_Key* hotKeys; struct Timeout* hotKeysTimeout; struct RainflyClient_Lookup_pvt* lookups; Identity }; enum RequestType { RequestType_PING = 0x00, RequestType_HOT_KEYS = 0x01, RequestType_LOOKUP = 0x02 }; static String** readNames(struct Message* msg, int count, struct Allocator* alloc) { String** out = Allocator_calloc(alloc, sizeof(uintptr_t), count+1); int totalLen = 0; for (int i = 0; i < count; i++) { if (msg->length < 1) { return NULL; } int len = Message_pop8(msg, NULL); out[i] = String_newBinary(NULL, len, alloc); Message_pop(msg, out[i]->bytes, len, NULL); totalLen += len + 1; } while (totalLen++ % 8) { if (Message_pop8(msg, NULL)) { return NULL; } } return out; } static void writeNames(struct Message* msg, String** names) { int totalLength = 0; int i; for (i = 0; names && names[i]; ++i) { totalLength += names[i]->len + 1; } while (totalLength++ % 8) { Message_push8(msg, 0, NULL); } while (--i >= 0) { Message_push(msg, names[i]->bytes, names[i]->len, NULL); Message_push8(msg, names[i]->len, NULL); } } static int compare(String* a, String* b) { if (a->len != b->len) { return (a->len > b->len) ? 1 : -1; } for (int i = 0; i < (int)a->len; i++) { if (a->bytes[i] != b->bytes[i]) { return ((uint8_t)a->bytes[i]) > ((uint8_t)b->bytes[i]) ? 1 : -1; } } return 0; } static void handleLookupReply(struct Message* msg, struct RainflyClient_pvt* ctx) { Log_debug(ctx->logger, "lookup reply!"); if (msg->length < 10) { Log_debug(ctx->logger, "runt"); return; } uint32_t cookie = Message_pop8(msg, NULL); cookie <<= 16; cookie |= Message_pop16(msg, NULL); uint32_t height = Message_pop32(msg, NULL); String** namesAndValue = readNames(msg, 3, msg->alloc); if (!namesAndValue) { Log_debug(ctx->logger, "runt"); return; } int count = msg->length / 64; if (msg->length != (count * 64)) { Log_debug(ctx->logger, "invalid message size"); return; } struct RainflyClient_Sig* sigs = Allocator_malloc(msg->alloc, msg->length); for (int i = count-1; i >= 0; i--) { Message_pop(msg, sigs[i].bytes, 64, NULL); } Message_shift(msg, -msg->length, NULL); writeNames(msg, namesAndValue); Message_push32(msg, height, NULL); // find the matching lookup struct RainflyClient_Lookup_pvt* lookup = ctx->lookups; while (lookup && lookup->cookie != cookie) { lookup = lookup->next; } if (!lookup) { Log_debug(ctx->logger, "Response to unasked question"); return; } enum RainflyClient_ResponseCode code = RainflyClient_ResponseCode_NO_ERROR; String* domain = lookup->pub.domain; if (!String_equals(namesAndValue[0], domain)) { if (compare(namesAndValue[0], namesAndValue[1]) == 1) { // they returned the last entry in the set // it must fall outside of range if (compare(domain, namesAndValue[0]) != 1 && compare(namesAndValue[1], domain) != 1) { Log_debug(ctx->logger, "Invalid NXDOMAIN with last entry [%s] -> [%s] req: [%s]", namesAndValue[0]->bytes, namesAndValue[1]->bytes, domain->bytes); return; } } else if (compare(domain, namesAndValue[0]) != 1 || compare(namesAndValue[1], domain) != 1) { Log_debug(ctx->logger, "Invalid NXDOMAIN, domain not in range [%s] -> [%s] req: [%s]", namesAndValue[0]->bytes, namesAndValue[1]->bytes, domain->bytes); return; } Log_debug(ctx->logger, "Domain [%s] Nonexistant [%s] -> [%s]", domain->bytes, namesAndValue[0]->bytes, namesAndValue[1]->bytes); code = RainflyClient_ResponseCode_NXDOMAIN; } int validSigs = 0; uint8_t* scratch = Allocator_malloc(msg->alloc, msg->length + 64); for (int i = 0; i < count; i++) { unsigned long long x = 32; Message_push(msg, sigs[i].bytes, 64, NULL); uint8_t* key = ctx->hotKeys[(lookup->firstKey + i) % ctx->keyCount].bytes; if (!crypto_sign_ed25519_open(scratch, &x, msg->bytes, msg->length, key) && (int)x == msg->length - 64) { Log_debug(ctx->logger, "valid signature [%d] on [%s]", i, namesAndValue[0]->bytes); validSigs++; } else if (Bits_isZero(sigs[i].bytes, 64)) { Log_debug(ctx->logger, "unknown signature [%d] on [%s]", i, namesAndValue[0]->bytes); } else { Log_debug(ctx->logger, "invalid signature [%d] on [%s]", i, namesAndValue[0]->bytes); } Message_shift(msg, -64, NULL); } if (validSigs < ctx->pub.minSignatures) { Log_debug(ctx->logger, "Not enough signatures"); return; } struct Reader* r = ArrayReader_new(namesAndValue[2]->bytes, namesAndValue[2]->len, msg->alloc); Dict d; if (JsonBencSerializer_get()->parseDictionary(r, msg->alloc, &d)) { Log_debug(ctx->logger, "Error parsing json"); if (!code) { code = RainflyClient_ResponseCode_SERVER_ERROR; } } if (lookup->pub.onReply) { lookup->pub.onReply(&lookup->pub, &d, code); } Allocator_free(lookup->pub.alloc); } static void handleHotKeysReply(struct Message* msg, struct RainflyClient_pvt* ctx) { Log_debug(ctx->logger, "hotkeys reply"); if (msg->length < 7) { Log_debug(ctx->logger, "runt"); return; } uint8_t keyCount = Message_pop8(msg, NULL); uint16_t start = Message_pop16(msg, NULL); if (keyCount > ctx->keyCount) { Log_debug(ctx->logger, "keycount too high"); return; } if (start > keyCount) { Log_debug(ctx->logger, "start too high"); return; } uint32_t bitField = Message_pop32(msg, NULL); if ((Bits_popCountx32(bitField) * (64+32)) != msg->length) { Log_debug(ctx->logger, "incorrect message size"); return; } uint32_t i = start; do { i = (i - 1) % keyCount; if (!(bitField & 1)) { Log_debug(ctx->logger, "missing signature [%d]", i); bitField >>= 1; continue; } bitField >>= 1; // This needs to be 96 bytes otherwise crypto_sign_ed25519_open() will buffer overflow. // We only use the first 32 uint8_t hotKey[96]; uint8_t sig[96]; Message_pop(msg, sig, 96, NULL); unsigned long long x = 32; if (!crypto_sign_ed25519_open(hotKey, &x, sig, 96, ctx->keys[i].bytes) && x == 32) { Log_debug(ctx->logger, "valid signature [%d]", i); Bits_memcpyConst(ctx->hotKeys[i].bytes, hotKey, 32); ctx->keyMap |= (1 << i); } else { Log_debug(ctx->logger, "invalid signature [%d]", i); } } while (i != start); } static uint8_t receiveMessage(struct Message* msg, struct Interface* iface) { struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)iface->receiverContext); if (msg->length < ctx->addr->addrLen + 4) { Log_debug(ctx->logger, "runt"); return Error_NONE; } Message_shift(msg, -ctx->addr->addrLen, NULL); uint8_t operation = Message_pop8(msg, NULL); if (!(operation & (1<<7))) { Log_debug(ctx->logger, "query"); } operation &= ((1<<7)-1); Log_debug(ctx->logger, "Got a reply [%d]", operation); switch (operation) { case RequestType_PING: return Error_NONE; case RequestType_HOT_KEYS: handleHotKeysReply(msg, ctx); return Error_NONE; case RequestType_LOOKUP: handleLookupReply(msg, ctx); return Error_NONE; default: Log_debug(ctx->logger, "Got a message with unrecognized type [%d]", operation); } return Error_NONE; } static void sendHotKeysRequest(void* vRainflyClient) { struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)vRainflyClient); int time; if (Bits_popCountx32(ctx->keyMap) < ctx->pub.minSignatures && ctx->keyCount) { time = Random_uint32(ctx->rand) % 5000; } else { time = (Random_uint32(ctx->rand) % 600000) + 120000; } Timeout_resetTimeout(ctx->hotKeysTimeout, time); if (!ctx->keyCount) { Log_debug(ctx->logger, "No keys to try"); return; } if (!ctx->serverCount) { Log_debug(ctx->logger, "No servers to try"); return; } struct Allocator* alloc = Allocator_child(ctx->alloc); struct Message* msg = Message_new(0, 1536, alloc); int start = Random_uint32(ctx->rand) % ctx->keyCount; int j = 0; int i = start; do { Message_push(msg, ctx->keys[i].bytes, 32, NULL); i = (i + 1) % ctx->keyCount; } while (i != start && ++j < 10); // only 24 bits of space to put some information so put the beginning key and the current // number of keys so we can reconstruct which keys we sent when we get the response. Message_push16(msg, start, NULL); Message_push8(msg, ctx->keyCount, NULL); Message_push8(msg, RequestType_HOT_KEYS, NULL); int server = Random_uint32(ctx->rand) % ctx->serverCount; Message_push(msg, ctx->servers[server], ctx->addr->addrLen, NULL); Log_debug(ctx->logger, "Sending hotkeys request to [%s]", Sockaddr_print(ctx->servers[server], alloc)); Interface_sendMessage(ctx->iface, msg); Allocator_free(alloc); } static int lookupOnFree(struct Allocator_OnFreeJob* onFree) { struct RainflyClient_Lookup_pvt* lookup = Identity_check((struct RainflyClient_Lookup_pvt*)onFree->userData); struct RainflyClient_Lookup_pvt* l = lookup->ctx->lookups; if (l == lookup) { lookup->ctx->lookups = lookup->next; return 0; } while (l->next != lookup) { l = l->next; } l->next = lookup->next; return 0; } static void tryNextServer(void* vLookup) { struct RainflyClient_Lookup_pvt* lookup = Identity_check((struct RainflyClient_Lookup_pvt*)vLookup); struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)lookup->ctx); if (lookup->triedServers++ > ctx->pub.maxTries) { if (lookup->pub.onReply) { lookup->pub.onReply(&lookup->pub, NULL, RainflyClient_ResponseCode_SERVER_ERROR); } Allocator_free(lookup->pub.alloc); return; } struct Allocator* alloc = Allocator_child(lookup->pub.alloc); struct Message* msg = Message_new(0, 1536, alloc); int start = lookup->firstKey = Random_uint32(ctx->rand) % ctx->keyCount; int j = 0; int i = start; do { Message_push(msg, ctx->hotKeys[i].bytes, 32, NULL); i = (i + 1) % ctx->keyCount; } while (i != start && ++j < 10); Log_debug(ctx->logger, "looking up [%s]", lookup->pub.domain->bytes); writeNames(msg, (String*[]) { lookup->pub.domain, NULL }); uint32_t typeAndIdent = lookup->cookie | (RequestType_LOOKUP << 24); Message_push32(msg, typeAndIdent, NULL); int server = lookup->server++ % ctx->serverCount; Message_push(msg, ctx->servers[server], ctx->addr->addrLen, NULL); Log_debug(ctx->logger, "Sending lookup request to [%s]", Sockaddr_print(ctx->servers[server], alloc)); Interface_sendMessage(ctx->iface, msg); Allocator_free(alloc); } //////////////////// struct RainflyClient_Lookup* RainflyClient_lookup(struct RainflyClient* client, String* domain) { struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)client); if (!ctx->keyCount || ctx->keyCount < ctx->pub.minSignatures) { Log_debug(ctx->logger, "Not enough keys to get required signatues"); return NULL; } if (!ctx->serverCount) { Log_debug(ctx->logger, "No servers to try"); return NULL; } struct Allocator* alloc = Allocator_child(ctx->alloc); struct RainflyClient_Lookup_pvt* out = Allocator_calloc(alloc, sizeof(struct RainflyClient_Lookup_pvt), 1); out->pub.alloc = alloc; out->next = ctx->lookups; if (out->next) { out->cookie = out->next->cookie++ & 0x00ffffff; } ctx->lookups = out; out->ctx = ctx; Identity_set(out); out->pub.domain = String_newBinary(NULL, domain->len+2, alloc); Bits_memcpy(&out->pub.domain->bytes[2], domain->bytes, domain->len); out->pub.domain->bytes[0] = 'h'; out->pub.domain->bytes[1] = '/'; out->server = Random_uint32(ctx->rand) % ctx->serverCount; out->tryNextTimeout = Timeout_setTimeout(tryNextServer, out, 3000, ctx->eventBase, alloc); Allocator_onFree(alloc, lookupOnFree, out); tryNextServer(out); return &out->pub; } int RainflyClient_addKey(struct RainflyClient* client, uint8_t key[32]) { struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)client); if (ctx->keyCount > 63) { return RainflyClient_addKey_TOO_MANY_KEYS; } ctx->keyCount++; ctx->keys = Allocator_realloc(ctx->alloc, ctx->keys, ctx->keyCount * 32); ctx->hotKeys = Allocator_realloc(ctx->alloc, ctx->hotKeys, ctx->keyCount * 32); Bits_memcpyConst(&ctx->keys[ctx->keyCount-1], key, 32); return 0; } int RainflyClient_addServer(struct RainflyClient* client, struct Sockaddr* addr) { struct RainflyClient_pvt* ctx = Identity_check((struct RainflyClient_pvt*)client); if (addr->addrLen != ctx->addr->addrLen || Sockaddr_getFamily(addr) != Sockaddr_getFamily(ctx->addr)) { return RainflyClient_addServer_WRONG_ADDRESS_TYPE; } ctx->serverCount++; ctx->servers = Allocator_realloc(ctx->alloc, ctx->servers, ctx->serverCount * sizeof(uintptr_t)); ctx->servers[ctx->serverCount-1] = Sockaddr_clone(addr, ctx->alloc); return 0; } struct RainflyClient* RainflyClient_new(struct AddrInterface* iface, struct EventBase* base, struct Random* rand, struct Log* logger) { struct RainflyClient_pvt* ctx = Allocator_clone(iface->generic.allocator, (&(struct RainflyClient_pvt) { .pub = { .minSignatures = RainflyClient_DEFAULT_MIN_SIGNATURES, .maxTries = RainflyClient_DEFAULT_MAX_TRIES, }, .iface = &iface->generic, .logger = logger, .addr = iface->addr, .alloc = iface->generic.allocator, .rand = rand, .eventBase = base, })); Identity_set(ctx); ctx->hotKeysTimeout = Timeout_setInterval(sendHotKeysRequest, ctx, 1000, base, ctx->alloc); iface->generic.receiveMessage = receiveMessage; iface->generic.receiverContext = ctx; return &ctx->pub; }