/* 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;
}