/* 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 "admin/AdminClient.h" #include "benc/serialization/BencSerializer.h" #include "benc/serialization/standard/StandardBencSerializer.h" #include "interface/addressable/AddrInterface.h" #include "interface/addressable/UDPAddrInterface.h" #include "exception/Except.h" #include "io/ArrayReader.h" #include "io/ArrayWriter.h" #include "io/Reader.h" #include "io/Writer.h" #include "util/Bits.h" #include "util/platform/libc/strlen.h" #include "util/Endian.h" #include "util/Hex.h" #include "util/events/Timeout.h" #include "util/Identity.h" #include "wire/Message.h" #include #include #include struct Result { struct AdminClient_Result pub; struct Context* ctx; struct Allocator* alloc; }; struct Context { struct AdminClient pub; struct EventBase* eventBase; struct Sockaddr* targetAddr; struct Result* result; struct AddrInterface* addrIface; struct Log* logger; String* password; 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, 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 uint8_t buffer[AdminClient_MAX_MESSAGE_SIZE]; struct Writer* writer = ArrayWriter_new(buffer, AdminClient_MAX_MESSAGE_SIZE, alloc); if (StandardBencSerializer_get()->serializeDictionary(writer, message)) { return -1; } int length = writer->bytesWritten; // calculate the hash of the message with the password hash crypto_hash_sha256(hash, buffer, length); // 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 Context* ctx, enum AdminClient_Error err) { ctx->result->pub.err = err; EventBase_endLoop(ctx->eventBase); } static void timeout(void* vcontext) { done((struct Context*) vcontext, AdminClient_Error_TIMEOUT); } static void doCall(Dict* message, struct Result* res, bool getCookie) { String* cookie = NULL; if (!getCookie) { Dict gc = Dict_CONST(String_CONST("q"), String_OBJ(String_CONST("cookie")), NULL); doCall(&gc, res, true); if (res->pub.err != AdminClient_Error_NONE) { return; } cookie = Dict_getString(res->pub.responseDict, String_CONST("cookie")); if (!cookie) { res->pub.err = AdminClient_Error_NO_COOKIE; } } struct Writer* writer = ArrayWriter_new(res->pub.messageBytes, AdminClient_MAX_MESSAGE_SIZE, res->alloc); if (StandardBencSerializer_get()->serializeDictionary(writer, message)) { res->pub.err = AdminClient_Error_SERIALIZATION_FAILED; return; } if (!getCookie) { calculateAuth(message, res->ctx->password, cookie, res->alloc); writer = ArrayWriter_new(res->pub.messageBytes, AdminClient_MAX_MESSAGE_SIZE, res->alloc); if (StandardBencSerializer_get()->serializeDictionary(writer, message)) { res->pub.err = AdminClient_Error_SERIALIZATION_FAILED; return; } } struct Timeout* to = Timeout_setTimeout(timeout, res->ctx, res->ctx->pub.millisecondsToWait, res->ctx->eventBase, res->alloc); struct Message m = { .bytes = res->pub.messageBytes, .padding = AdminClient_Result_PADDING_SIZE, .length = writer->bytesWritten }; Message_push(&m, res->ctx->targetAddr, res->ctx->targetAddr->addrLen); struct Allocator* child = Allocator_child(res->alloc); struct Message* msg = Message_clone(&m, child); Interface_sendMessage(&res->ctx->addrIface->generic, msg); Allocator_free(child); EventBase_beginLoop(res->ctx->eventBase); Timeout_clearTimeout(to); } static uint8_t receiveMessage(struct Message* msg, struct Interface* iface) { struct Context* ctx = Identity_cast((struct Context*) iface->receiverContext); // Since this is a blocking api, one result per context. struct Result* res = ctx->result; struct Sockaddr_storage source; Message_pop(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, res->alloc), Sockaddr_print(ctx->targetAddr, res->alloc)); return 0; } struct Reader* reader = ArrayReader_new(msg->bytes, msg->length, res->alloc); Dict* d = Dict_new(res->alloc); if (StandardBencSerializer_get()->parseDictionary(reader, res->alloc, d)) { done(ctx, AdminClient_Error_DESERIALIZATION_FAILED); return 0; } res->pub.responseDict = d; int len = (msg->length > AdminClient_MAX_MESSAGE_SIZE) ? AdminClient_MAX_MESSAGE_SIZE : msg->length; Bits_memset(res->pub.messageBytes, 0, AdminClient_MAX_MESSAGE_SIZE); Bits_memcpy(res->pub.messageBytes, msg->bytes, len); done(ctx, AdminClient_Error_NONE); return 0; } struct AdminClient_Result* AdminClient_rpcCall(String* function, Dict* args, struct AdminClient* client, struct Allocator* alloc) { struct Context* ctx = Identity_cast((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 ))); struct Result* res = Allocator_clone(alloc, (&(struct Result) { .pub = { .err = AdminClient_Error_NONE }, .ctx = ctx, .alloc = alloc })); ctx->result = res; doCall(&message, res, false); return &res->pub; } 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(struct Sockaddr* connectToAddress, String* adminPassword, struct EventBase* eventBase, struct Log* logger, struct Allocator* alloc) { struct Context* context = Allocator_clone(alloc, (&(struct Context) { .eventBase = eventBase, .logger = logger, .password = adminPassword, .pub = { .millisecondsToWait = 5000, } })); 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_memcpyConst(addrBytes, &loopback, 4); } } Log_debug(logger, "Connecting to [%s]", Sockaddr_print(context->targetAddr, alloc)); context->addrIface = UDPAddrInterface_new(eventBase, NULL, alloc, NULL, logger); context->addrIface->generic.receiveMessage = receiveMessage; context->addrIface->generic.receiverContext = context; return &context->pub; }