From 61ba3727dfb102e8d04f5892662d3c6c770e1348 Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Mon, 6 Jun 2022 17:18:22 +0200 Subject: [PATCH] JSON-RPC: Use proper error response with error codes according to the official specification (one of JSON_RPC_ERROR_*). Add proper rpc_error() and rpc_error_fmt() Don't steal reference in rpc_response(). --- include/h.h | 6 ++-- include/modules.h | 1 + include/struct.h | 7 ++++ src/api-efunctions.c | 4 ++- src/misc.c | 6 +++- src/modules/rpc/rpc.c | 75 ++++++++++++++++++++++++++++++++++++------- 6 files changed, 84 insertions(+), 15 deletions(-) diff --git a/include/h.h b/include/h.h index 8717c6464..26653c235 100644 --- a/include/h.h +++ b/include/h.h @@ -839,7 +839,8 @@ extern MODVAR int (*unreal_match_iplist)(Client *client, NameList *l); extern MODVAR void (*webserver_send_response)(Client *client, int status, char *msg); extern MODVAR void (*webserver_close_client)(Client *client); extern MODVAR void (*rpc_response)(Client *client, json_t *request, json_t *result); -extern MODVAR void (*rpc_error)(Client *client, json_t *request, const char *msg); +extern MODVAR void (*rpc_error)(Client *client, json_t *request, int error_code, const char *error_message); +extern MODVAR void (*rpc_error_fmt)(Client *client, json_t *request, int error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5))); /* /Efuncs */ /* TLS functions */ @@ -879,7 +880,8 @@ extern int make_oper_default_handler(Client *client, const char *operblock_name, extern void webserver_send_response_default_handler(Client *client, int status, char *msg); extern void webserver_close_client_default_handler(Client *client); extern void rpc_response_default_handler(Client *client, json_t *request, json_t *result); -extern void rpc_error_default_handler(Client *client, json_t *request, const char *msg); +extern void rpc_error_default_handler(Client *client, json_t *request, int error_code, const char *error_message); +extern void rpc_error_fmt_default_handler(Client *client, json_t *request, int error_code, const char *fmt, ...); /* End of default handlers for efunctions */ extern MODVAR MOTDFile opermotd, svsmotd, motd, botmotd, smotd, rules; diff --git a/include/modules.h b/include/modules.h index 027d22083..0e663800f 100644 --- a/include/modules.h +++ b/include/modules.h @@ -2481,6 +2481,7 @@ enum EfunctionType { EFUNC_WEBSERVER_CLOSE_CLIENT, EFUNC_RPC_RESPONSE, EFUNC_RPC_ERROR, + EFUNC_RPC_ERROR_FMT, }; /* Module flags */ diff --git a/include/struct.h b/include/struct.h index bde1dd702..7bed6992a 100644 --- a/include/struct.h +++ b/include/struct.h @@ -2300,6 +2300,13 @@ typedef enum WhoisConfigDetails { #define UNRL_STRIP_LOW_ASCII 0x1 /**< Strip all ASCII < 32 (control codes) */ #define UNRL_STRIP_KEEP_LF 0x2 /**< Do not strip LF (line feed, \n) */ +/* JSON RPC API Errors, according to jsonrpc.org spec */ +#define JSON_RPC_ERROR_PARSE_ERROR -32700 +#define JSON_RPC_ERROR_INVALID_REQUEST -32600 +#define JSON_RPC_ERROR_METHOD_NOT_FOUND -32601 +#define JSON_RPC_ERROR_INVALID_PARAMS -32602 +#define JSON_RPC_ERROR_INTERNAL_ERROR -32603 + #endif /* __struct_include__ */ #include "dynconf.h" diff --git a/src/api-efunctions.c b/src/api-efunctions.c index 6adb5d48a..fcb2b4816 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -140,7 +140,8 @@ int (*unreal_match_iplist)(Client *client, NameList *l); void (*webserver_send_response)(Client *client, int status, char *msg); void (*webserver_close_client)(Client *client); void (*rpc_response)(Client *client, json_t *request, json_t *result); -void (*rpc_error)(Client *client, json_t *request, const char *msg); +void (*rpc_error)(Client *client, json_t *request, int error_code, const char *error_message); +void (*rpc_error_fmt)(Client *client, json_t *request, int error_code, const char *fmt, ...); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)()) { @@ -417,4 +418,5 @@ void efunctions_init(void) efunc_init_function(EFUNC_WEBSERVER_CLOSE_CLIENT, webserver_close_client, webserver_close_client_default_handler); efunc_init_function(EFUNC_RPC_RESPONSE, rpc_response, rpc_response_default_handler); efunc_init_function(EFUNC_RPC_ERROR, rpc_error, rpc_error_default_handler); + efunc_init_function(EFUNC_RPC_ERROR_FMT, rpc_error_fmt, rpc_error_fmt_default_handler); } diff --git a/src/misc.c b/src/misc.c index 2aec49853..067a5dc1e 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1407,7 +1407,11 @@ void rpc_response_default_handler(Client *client, json_t *request, json_t *resul { } -void rpc_error_default_handler(Client *client, json_t *request, const char *msg) +void rpc_error_default_handler(Client *client, json_t *request, int error_code, const char *error_message) +{ +} + +void rpc_error_fmt_default_handler(Client *client, json_t *request, int error_code, const char *fmt, ...) { } diff --git a/src/modules/rpc/rpc.c b/src/modules/rpc/rpc.c index 1ddfb3eda..947c4a23b 100644 --- a/src/modules/rpc/rpc.c +++ b/src/modules/rpc/rpc.c @@ -27,7 +27,8 @@ int rpc_packet_in(Client *client, const char *readbuf, int *length); void rpc_call_text(Client *client, const char *buf, int len); void rpc_call(Client *client, json_t *request); void _rpc_response(Client *client, json_t *request, json_t *result); -void _rpc_error(Client *client, json_t *request, const char *msg); +void _rpc_error(Client *client, json_t *request, int error_code, const char *error_message); +void _rpc_error_fmt(Client *client, json_t *request, int error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5))); /* Structs */ typedef struct RPCUser RPCUser; @@ -48,6 +49,7 @@ MOD_TEST() HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rpc_config_test); EfunctionAddVoid(modinfo->handle, EFUNC_RPC_RESPONSE, _rpc_response); EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR, _rpc_error); + EfunctionAddVoid(modinfo->handle, EFUNC_RPC_ERROR_FMT, TO_VOIDFUNC(_rpc_error_fmt)); return MOD_SUCCESS; } @@ -238,7 +240,7 @@ void rpc_call_text(Client *client, const char *readbuf, int len) unreal_log(ULOG_INFO, "log", "RPC_INVALID_JSON", client, "Received unparsable JSON request from $client", log_data_string("json_incoming", buf)); - rpc_error(client, NULL, "Unparsable JSON data"); + rpc_error(client, NULL, JSON_RPC_ERROR_PARSE_ERROR, "Unparsable JSON data"); /* This is a fatal error */ rpc_close(client); return; @@ -247,25 +249,73 @@ void rpc_call_text(Client *client, const char *readbuf, int len) json_decref(request); } -void _rpc_error(Client *client, json_t *request, const char *msg) +void _rpc_error(Client *client, json_t *request, int error_code, const char *error_message) { - // FIXME - //json_t *response = json_object; - sendto_one(client, NULL, "{ ERROR: %s }", msg); + /* Careful, we are in the "error" routine, so everything can be NULL */ + const char *method = NULL; + json_t *id = NULL; + char *json_serialized; + json_t *error; + + /* Start a new object for the error response */ + json_t *j = json_object(); + + if (request) + { + method = json_object_get_string(request, "method"); + id = json_object_get(request, "id"); + } + + json_object_set_new(j, "jsonrpc", json_string_unreal("2.0")); + if (method) + json_object_set_new(j, "method", json_string_unreal(method)); + if (id) + json_object_set_new(j, "id", id); + + error = json_object(); + json_object_set_new(j, "error", error); + json_object_set_new(error, "code", json_integer(error_code)); + json_object_set_new(error, "message", json_string_unreal(error_message)); + + json_serialized = json_dumps(j, 0); + if (!json_serialized) + { + unreal_log(ULOG_WARNING, "rpc", "BUG_RPC_ERROR_SERIALIZE_FAILED", NULL, + "[BUG] rpc_error() failed to serialize response " + "for request from $client ($method)", + log_data_string("method", method)); + json_decref(j); + return; + } + dbuf_put(&client->local->sendQ, json_serialized, strlen(json_serialized)); + dbuf_put(&client->local->sendQ, "\n", 1); + json_decref(j); + safe_free(json_serialized); +} + +void _rpc_error_fmt(Client *client, json_t *request, int error_code, const char *fmt, ...) +{ + char buf[512]; + + va_list vl; + va_start(vl, fmt); + vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + rpc_error(client, request, error_code, buf); } void _rpc_response(Client *client, json_t *request, json_t *result) { const char *method = json_object_get_string(request, "method"); json_t *id = json_object_get(request, "id"); - const char *json_serialized; + char *json_serialized; json_t *j = json_object(); json_object_set_new(j, "jsonrpc", json_string_unreal("2.0")); json_object_set_new(j, "method", json_string_unreal(method)); if (id) json_object_set_new(j, "id", id); /* 'id' is optional */ - json_object_set_new(j, "response", result); + json_object_set(j, "response", result); json_serialized = json_dumps(j, 0); if (!json_serialized) @@ -274,10 +324,13 @@ void _rpc_response(Client *client, json_t *request, json_t *result) "[BUG] rpc_response() failed to serialize response " "for request from $client ($method)", log_data_string("method", method)); + json_decref(j); return; } dbuf_put(&client->local->sendQ, json_serialized, strlen(json_serialized)); dbuf_put(&client->local->sendQ, "\n", 1); + json_decref(j); + safe_free(json_serialized); } @@ -293,13 +346,13 @@ void rpc_call(Client *client, json_t *request) jsonrpc = json_object_get_string(request, "jsonrpc"); if (!jsonrpc || strcasecmp(jsonrpc, "2.0")) { - rpc_error(client, request, "Only JSON-RPC version 2.0 is supported"); + rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Only JSON-RPC version 2.0 is supported"); return; } method = json_object_get_string(request, "method"); if (!method) { - rpc_error(client, request, "Missing 'method' to call"); + rpc_error(client, request, JSON_RPC_ERROR_INVALID_REQUEST, "Missing 'method' to call"); return; } params = json_object_get(request, "params"); @@ -314,7 +367,7 @@ void rpc_call(Client *client, json_t *request) handler = RPCHandlerFind(method); if (!handler) { - rpc_error(client, request, "Unsupported method"); + rpc_error(client, request, JSON_RPC_ERROR_METHOD_NOT_FOUND, "Unsupported method"); return; } handler->call(client, request, params);