123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- /* 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 <https://www.gnu.org/licenses/>.
- */
- #include "client/AdminClient.h"
- #include "benc/serialization/standard/BencMessageReader.h"
- #include "benc/serialization/standard/BencMessageWriter.h"
- #include "benc/serialization/cloner/Cloner.h"
- #include "exception/Except.h"
- #include "util/Bits.h"
- #include "util/Endian.h"
- #include "util/Hex.h"
- #include "util/events/Timeout.h"
- #include "util/Identity.h"
- #include "wire/Message.h"
- #include "wire/Error.h"
- #include <sodium/crypto_hash_sha256.h>
- #include <stdio.h>
- #include <stdlib.h>
- struct Request;
- typedef void (* AdminClient_RespHandler)(struct Request* req);
- struct Request
- {
- struct AdminClient_Result res;
- struct AdminClient_Promise* promise;
- AdminClient_RespHandler callback;
- struct Context* ctx;
- struct Allocator* alloc;
- /** Need a special allocator for the timeout so it can be axed before the request is complete */
- struct Allocator* timeoutAlloc;
- struct Timeout* timeout;
- Dict* requestMessage;
- /** the handle in the ctx->outstandingRequests map */
- uint32_t handle;
- Identity
- };
- #define Map_NAME OfRequestByHandle
- #define Map_ENABLE_HANDLES
- #define Map_VALUE_TYPE struct Request*
- #include "util/Map.h"
- struct Context
- {
- struct AdminClient pub;
- EventBase_t* eventBase;
- struct Iface addrIface;
- struct Sockaddr* targetAddr;
- struct Log* logger;
- String* password;
- struct Map_OfRequestByHandle outstandingRequests;
- struct Allocator* alloc;
- Identity
- };
- static int calculateAuth(Dict* message,
- String* password,
- String* cookieStr,
- struct Allocator* alloc)
- {
- // Calculate the hash of the password.
- String* hashHex = String_newBinary(NULL, 64, alloc);
- uint8_t passAndCookie[64];
- uint32_t cookie = (cookieStr != NULL) ? strtoll(cookieStr->bytes, NULL, 10) : 0;
- snprintf((char*) passAndCookie, 64, "%s%u", password->bytes, cookie);
- uint8_t hash[32];
- crypto_hash_sha256(hash, passAndCookie, CString_strlen((char*) passAndCookie));
- Hex_encode((uint8_t*)hashHex->bytes, 64, hash, 32);
- Dict_putString(message, String_new("hash", alloc), hashHex, alloc);
- Dict_putString(message, String_new("cookie", alloc), cookieStr, alloc);
- // serialize the message with the password hash
- Message_t* msg = Message_new(0, AdminClient_MAX_MESSAGE_SIZE, alloc);
- Er_assert(BencMessageWriter_write(message, msg));
- // calculate the hash of the message with the password hash
- crypto_hash_sha256(hash, Message_bytes(msg), Message_getLength(msg));
- // swap the hash of the message with the password hash into the location
- // where the password hash was.
- Hex_encode((uint8_t*)hashHex->bytes, 64, hash, 32);
- return 0;
- }
- static void done(struct Request* req, enum AdminClient_Error err)
- {
- req->res.err = err;
- Allocator_t* ra = req->timeoutAlloc;
- req->callback(req);
- if (ra == req->timeoutAlloc) { Allocator_free(req->timeoutAlloc); }
- }
- static void timeout(void* vreq)
- {
- done((struct Request*) vreq, AdminClient_Error_TIMEOUT);
- }
- static Iface_DEFUN receiveMessage(Message_t* msg, struct Iface* addrIface)
- {
- struct Context* ctx = Identity_containerOf(addrIface, struct Context, addrIface);
- struct Sockaddr_storage source;
- Er_assert(Message_epop(msg, &source, ctx->targetAddr->addrLen));
- if (Bits_memcmp(&source, ctx->targetAddr, ctx->targetAddr->addrLen)) {
- Log_info(ctx->logger, "Got spurious message from [%s], expecting messages from [%s]",
- Sockaddr_print(&source.addr, Message_getAlloc(msg)),
- Sockaddr_print(ctx->targetAddr, Message_getAlloc(msg)));
- // The UDP interface can't make use of an error but we'll inform anyway
- return Error(msg, "INVALID source addr");
- }
- // we don't yet know with which message this data belongs,
- // the message alloc lives the length of the message reception.
- struct Allocator* alloc = Allocator_child(Message_getAlloc(msg));
- int origLen = Message_getLength(msg);
- Dict* d = NULL;
- const char* err = BencMessageReader_readNoExcept(msg, alloc, &d);
- if (err) { return Error(msg, "Error decoding benc: %s", err); }
- Er_assert(Message_eshift(msg, origLen));
- String* txid = Dict_getStringC(d, "txid");
- if (!txid || txid->len != 8) { return Error(msg, "INVALID missing or wrong size txid"); }
- // look up the result
- uint32_t handle = ~0u;
- Hex_decode((uint8_t*)&handle, 4, txid->bytes, 8);
- int idx = Map_OfRequestByHandle_indexForHandle(handle, &ctx->outstandingRequests);
- if (idx < 0) { return Error(msg, "INVALID no such handle"); }
- struct Request* req = ctx->outstandingRequests.values[idx];
- // now this data will outlive the life of the message.
- Allocator_adopt(req->promise->alloc, alloc);
- req->res.responseDict = d;
- int len = (Message_getLength(msg) > AdminClient_MAX_MESSAGE_SIZE)
- ? AdminClient_MAX_MESSAGE_SIZE
- : Message_getLength(msg);
- Bits_memset(req->res.messageBytes, 0, AdminClient_MAX_MESSAGE_SIZE);
- Bits_memcpy(req->res.messageBytes, Message_bytes(msg), len);
- done(req, AdminClient_Error_NONE);
- return NULL;
- }
- static void requestOnFree(struct Allocator_OnFreeJob* job)
- {
- struct Request* req = Identity_check((struct Request*) job->userData);
- int idx = Map_OfRequestByHandle_indexForHandle(req->handle, &req->ctx->outstandingRequests);
- if (idx > -1) {
- Map_OfRequestByHandle_remove(idx, &req->ctx->outstandingRequests);
- }
- }
- static struct Request* sendRaw(Dict* messageDict,
- struct AdminClient_Promise* promise,
- struct Context* ctx,
- String* cookie,
- AdminClient_RespHandler callback)
- {
- struct Allocator* reqAlloc = Allocator_child(promise->alloc);
- struct Request* req = Allocator_clone(reqAlloc, (&(struct Request) {
- .alloc = reqAlloc,
- .ctx = ctx,
- .promise = promise
- }));
- Identity_set(req);
- int idx = Map_OfRequestByHandle_put(&req, &ctx->outstandingRequests);
- req->handle = ctx->outstandingRequests.handles[idx];
- String* id = String_newBinary(NULL, 8, req->alloc);
- Hex_encode(id->bytes, 8, (int8_t*) &req->handle, 4);
- Dict_putStringC(messageDict, "txid", id, req->alloc);
- if (cookie) {
- Assert_true(!calculateAuth(messageDict, ctx->password, cookie, req->alloc));
- }
- struct Allocator* child = Allocator_child(req->alloc);
- Message_t* msg = Message_new(0, AdminClient_MAX_MESSAGE_SIZE + 256, child);
- Er_assert(BencMessageWriter_write(messageDict, msg));
- req->timeoutAlloc = Allocator_child(req->alloc);
- req->timeout = Timeout_setTimeout(timeout,
- req,
- ctx->pub.millisecondsToWait,
- ctx->eventBase,
- req->timeoutAlloc);
- Allocator_onFree(req->timeoutAlloc, requestOnFree, req);
- req->callback = callback;
- Er_assert(Message_epush(msg, ctx->targetAddr, ctx->targetAddr->addrLen));
- Iface_send(&ctx->addrIface, msg);
- Allocator_free(child);
- return req;
- }
- static void requestCallback(struct Request* req)
- {
- if (req->promise->callback) {
- req->promise->callback(req->promise, &req->res);
- }
- Allocator_free(req->promise->alloc);
- }
- static void cookieCallback(struct Request* req)
- {
- if (req->res.err) {
- requestCallback(req);
- return;
- }
- String* cookie = Dict_getStringC(req->res.responseDict, "cookie");
- if (!cookie) {
- req->res.err = AdminClient_Error_NO_COOKIE;
- requestCallback(req);
- return;
- }
- Dict* message = req->requestMessage;
- sendRaw(message, req->promise, req->ctx, cookie, requestCallback);
- Allocator_free(req->alloc);
- }
- static struct AdminClient_Promise* doCall(Dict* message,
- struct Context* ctx,
- struct Allocator* alloc)
- {
- struct Allocator* promiseAlloc = Allocator_child(alloc);
- struct AdminClient_Promise* promise =
- Allocator_calloc(promiseAlloc, sizeof(struct AdminClient_Promise), 1);
- promise->alloc = promiseAlloc;
- Dict gc = Dict_CONST(String_CONST("q"), String_OBJ(String_CONST("cookie")), NULL);
- struct Request* req = sendRaw(&gc, promise, ctx, NULL, cookieCallback);
- req->requestMessage = Cloner_cloneDict(message, promiseAlloc);
- return promise;
- }
- struct AdminClient_Promise* AdminClient_rpcCall(String* function,
- Dict* args,
- struct AdminClient* client,
- struct Allocator* alloc)
- {
- struct Context* ctx = Identity_check((struct Context*) client);
- Dict a = (args) ? *args : NULL;
- Dict message = Dict_CONST(
- String_CONST("q"), String_OBJ(String_CONST("auth")), Dict_CONST(
- String_CONST("aq"), String_OBJ(function), Dict_CONST(
- String_CONST("args"), Dict_OBJ(&a), NULL
- )));
- return doCall(&message, ctx, alloc);
- }
- char* AdminClient_errorString(enum AdminClient_Error err)
- {
- switch (err) {
- case AdminClient_Error_NONE:
- return "Success";
- case AdminClient_Error_OVERLONG_RESPONSE:
- return "Overlong resonse message";
- case AdminClient_Error_ERROR_READING_FROM_SOCKET:
- return "Error reading from socket, check errno.";
- case AdminClient_Error_SOCKET_NOT_READY:
- return "Socket not ready for reading";
- case AdminClient_Error_DESERIALIZATION_FAILED:
- return "Failed to deserialize response";
- case AdminClient_Error_SERIALIZATION_FAILED:
- return "Failed to serialize request";
- case AdminClient_Error_TIMEOUT:
- return "Timed out waiting for a response";
- case AdminClient_Error_NO_COOKIE:
- return "Cookie request returned with no cookie";
- default:
- return "Internal error";
- };
- }
- struct AdminClient* AdminClient_new(AddrIface_t* ai,
- struct Sockaddr* connectToAddress,
- String* adminPassword,
- EventBase_t* eventBase,
- struct Log* logger,
- struct Allocator* alloc)
- {
- struct Context* context = Allocator_clone(alloc, (&(struct Context) {
- .eventBase = eventBase,
- .logger = logger,
- .password = adminPassword,
- .pub = {
- .millisecondsToWait = 5000,
- },
- .outstandingRequests = {
- .allocator = alloc
- },
- .alloc = alloc
- }));
- context->addrIface.send = receiveMessage;
- Identity_set(context);
- context->targetAddr = Sockaddr_clone(connectToAddress, alloc);
- if (Sockaddr_getFamily(context->targetAddr) == Sockaddr_AF_INET) {
- uint8_t* addrBytes;
- int len = Sockaddr_getAddress(context->targetAddr, &addrBytes);
- if (Bits_isZero(addrBytes, len)) {
- // 127.0.0.1
- uint32_t loopback = Endian_hostToBigEndian32(0x7f000001);
- Bits_memcpy(addrBytes, &loopback, 4);
- }
- }
- Log_debug(logger, "Connecting to [%s]", Sockaddr_print(context->targetAddr, alloc));
- Iface_plumb(ai->iface, &context->addrIface);
- return &context->pub;
- }
|