1 |
/** |
2 |
* jsonrpc.C: JSON-RPC for Ermyth |
3 |
* |
4 |
* Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team |
5 |
* Rights to this code are as documented in COPYING. |
6 |
* |
7 |
* |
8 |
* Portions of this file were derived from sources bearing the following license: |
9 |
* Copyright © 2005-2006 Jilles Tjoelker et al. |
10 |
* Rights to this code are as documented in doc/pod/license.pod. |
11 |
* |
12 |
* $Id: jsonrpc.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $ |
13 |
*/ |
14 |
|
15 |
#include "atheme.h" |
16 |
#include <libermyth.h> |
17 |
#include <ermyth/module.h> |
18 |
#include <account/myuser.h> |
19 |
#include "datastream.h" |
20 |
#include "authcookie.h" |
21 |
#include "connection.h" |
22 |
#include "confparse.h" |
23 |
|
24 |
#include "httpd.h" |
25 |
|
26 |
#include "json/json.h" |
27 |
#include "json/rpc.h" |
28 |
|
29 |
using namespace json; |
30 |
|
31 |
static char const rcsid[] = "$Id: jsonrpc.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $"; |
32 |
|
33 |
static void on_config_ready (void); |
34 |
|
35 |
#define JSONRPC_BUFSIZE 2048 |
36 |
#define MAX_CMD_PARC 20 |
37 |
|
38 |
REGISTER_MODULE ("rpc/jsonrpc", false, "The Ermyth Team <http://ermyth.one09.net>"); |
39 |
|
40 |
static void handle_request (connection_t *cptr, void *requestbuf); |
41 |
|
42 |
Value current_response; // XXX: Hack |
43 |
|
44 |
E pathvec pathhandlers; |
45 |
|
46 |
static void jsonrpc_command_fail (sourceinfo_t *si, fault::code code, char const *message); |
47 |
static void jsonrpc_command_success_nodata (sourceinfo_t *si, char const *message); |
48 |
static void jsonrpc_command_success_string (sourceinfo_t *si, char const *result, char const *message); |
49 |
|
50 |
static void jsonrpcmethod_login (void *conn, Value &request, Value &response); |
51 |
static void jsonrpcmethod_logout (void *conn, Value &request, Value &response); |
52 |
static void jsonrpcmethod_command (void *conn, Value &request, Value &response); |
53 |
|
54 |
/* Configuration */ |
55 |
ConfTable::list_type conf_jsonrpc_table; |
56 |
|
57 |
path_handler_t handle_jsonrpc = |
58 |
{ |
59 |
NULL, |
60 |
handle_request |
61 |
}; |
62 |
|
63 |
static int |
64 |
conf_jsonrpc_path (config_entry_t *ce) |
65 |
{ |
66 |
if (!ce->valid ()) |
67 |
return -1; |
68 |
|
69 |
if (handle_jsonrpc.path != NULL) |
70 |
{ |
71 |
if (!strcmp (handle_jsonrpc.path, ce->vardata<char *> ())) |
72 |
return 0; |
73 |
sfree (handle_jsonrpc.path); |
74 |
} |
75 |
handle_jsonrpc.path = sstrdup (ce->vardata<char *> ()); |
76 |
|
77 |
return 0; |
78 |
} |
79 |
|
80 |
static int |
81 |
conf_jsonrpc (config_entry_t *ce) |
82 |
{ |
83 |
subblock_handler (ce, conf_jsonrpc_table); |
84 |
return 0; |
85 |
} |
86 |
|
87 |
struct sourceinfo_vtable jsonrpc_vtable = { |
88 |
"jsonrpc", |
89 |
jsonrpc_command_fail, |
90 |
jsonrpc_command_success_nodata, |
91 |
jsonrpc_command_success_string |
92 |
}; |
93 |
|
94 |
static char * |
95 |
dump_buffer (connection_t *cptr, char *buf, int length) |
96 |
{ |
97 |
struct httpddata *hd; |
98 |
char buf1[300]; |
99 |
|
100 |
hd = static_cast<httpddata *> (cptr->userdata); |
101 |
snprintf (buf1, sizeof buf1, "HTTP/1.1 200 OK\r\n" |
102 |
"%s" // connection_close? |
103 |
"Server: " PACKAGE_NAME "/%s\r\n" |
104 |
"Content-Type: application/json\r\n" |
105 |
"Content-Length: %d\r\n\r\n", |
106 |
hd->connection_close ? "Connection: close\r\n" : "", version, length); |
107 |
sendq_add (cptr, buf1, strlen (buf1)); |
108 |
sendq_add (cptr, buf, length); |
109 |
if (hd->connection_close) |
110 |
sendq_add_eof (cptr); |
111 |
return buf; |
112 |
} |
113 |
|
114 |
void |
115 |
jsonrpc_generic_error (Value &response, int code, char const *string) |
116 |
{ |
117 |
Value error (objectValue); |
118 |
|
119 |
error["name"] = "JSONRPCError"; |
120 |
error["code"] = Value (code); |
121 |
error["message"] = Value (string); |
122 |
|
123 |
response["version"] = "1.1"; |
124 |
response["error"] = error; |
125 |
} |
126 |
|
127 |
static void |
128 |
handle_request (connection_t *cptr, void *requestbuf) |
129 |
{ |
130 |
char response[JSONRPC_BUFSIZE]; |
131 |
jsonrpc_process (&cptr, response, static_cast<char const *> (requestbuf), JSONRPC_BUFSIZE); |
132 |
dump_buffer (cptr, response, strlen (response)); |
133 |
current_response = Value (nullValue); |
134 |
} |
135 |
|
136 |
static void |
137 |
on_config_ready (void) |
138 |
{ |
139 |
if (handle_jsonrpc.handler != NULL) |
140 |
{ |
141 |
if (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path)) |
142 |
!= pathhandlers.end ()) |
143 |
{ |
144 |
slog (LG_INFO, "jsonrpc/main.C: handler already in the list"); |
145 |
return; |
146 |
} |
147 |
|
148 |
pathhandlers.push_back (handle_jsonrpc); |
149 |
} |
150 |
else |
151 |
slog (LG_ERROR, "on_config_ready (): jsonrpc { } block missing or invalid"); |
152 |
} |
153 |
|
154 |
static void |
155 |
jsonrpc_generic_reply (Value &response, char const *message) |
156 |
{ |
157 |
Value entry (objectValue); |
158 |
|
159 |
entry["result"] = Value (message); |
160 |
response["result"] = entry; |
161 |
} |
162 |
|
163 |
static void |
164 |
jsonrpc_command_fail (sourceinfo_t *si, fault::code code, char const *message) |
165 |
{ |
166 |
connection_t *cptr; |
167 |
struct httpddata *hd; |
168 |
|
169 |
cptr = si->connection; |
170 |
hd = static_cast<httpddata *> (cptr->userdata); |
171 |
|
172 |
if (hd->sent_reply) |
173 |
return; |
174 |
|
175 |
jsonrpc_generic_error (current_response, code, message); |
176 |
hd->sent_reply = true; |
177 |
} |
178 |
|
179 |
static void |
180 |
jsonrpc_command_success_nodata (sourceinfo_t *si, char const *message) |
181 |
{ |
182 |
char *p; |
183 |
char const *q; |
184 |
size_t msglen; |
185 |
connection_t *cptr; |
186 |
httpddata *hd; |
187 |
|
188 |
cptr = si->connection; |
189 |
hd = static_cast<httpddata *> (cptr->userdata); |
190 |
|
191 |
msglen = strlen (message); |
192 |
|
193 |
if (hd->sent_reply) |
194 |
return; |
195 |
|
196 |
if (hd->replybuf == NULL) |
197 |
{ |
198 |
hd->replybuf = salloc<char> (msglen + 1); |
199 |
p = hd->replybuf; |
200 |
} |
201 |
else |
202 |
{ |
203 |
size_t replybuf_len = strlen (hd->replybuf); |
204 |
hd->replybuf = salloc (replybuf_len + msglen + 2, hd->replybuf); |
205 |
p = hd->replybuf + replybuf_len; |
206 |
*p++ = '\n'; |
207 |
} |
208 |
|
209 |
q = message; |
210 |
|
211 |
/* What this code does is the following: |
212 |
* - set *p to *q |
213 |
* - increment q (go to the next character in q |
214 |
* - if *p is below 0x1f (the space character ' '), then |
215 |
* do not increment p |
216 |
* if *p is above or the same as the space character, then |
217 |
* do increment p |
218 |
* |
219 |
* This incrementing/not incrementing effectively skips any character below the |
220 |
* space character, as by not incrementing p, *p gets overwritten in the next cycle. |
221 |
*/ |
222 |
while (msglen--) |
223 |
{ |
224 |
*p = *q++; |
225 |
p += !!(*p & ~0x1f); |
226 |
} |
227 |
|
228 |
*p = '\0'; |
229 |
} |
230 |
|
231 |
static void |
232 |
jsonrpc_command_success_string (sourceinfo_t *si, char const *result, char const *message) |
233 |
{ |
234 |
connection_t *cptr; |
235 |
struct httpddata *hd; |
236 |
|
237 |
cptr = si->connection; |
238 |
hd = static_cast<httpddata *> (cptr->userdata); |
239 |
if (hd->sent_reply) |
240 |
return; |
241 |
jsonrpc_generic_reply (current_response, result); |
242 |
hd->sent_reply = true; |
243 |
} |
244 |
|
245 |
/* These taken from the old modules/jsonrpc/account.c */ |
246 |
/* |
247 |
* ermyth.login |
248 |
* |
249 |
* JSON Inputs: |
250 |
* account name, password, source ip (optional) |
251 |
* |
252 |
* JSON Outputs: |
253 |
* fault 1 - insufficient parameters |
254 |
* fault 3 - account is not registered |
255 |
* fault 5 - invalid username and password |
256 |
* fault 6 - account is frozen |
257 |
* default - success (authcookie) |
258 |
* |
259 |
* Side Effects: |
260 |
* an authcookie ticket is created for the myuser_t. |
261 |
* the user's lastlogin is updated |
262 |
*/ |
263 |
static void |
264 |
jsonrpcmethod_login (void *conn, Value &request, Value &response) |
265 |
{ |
266 |
myuser_t *mu; |
267 |
authcookie_t *ac; |
268 |
char const *sourceip; |
269 |
|
270 |
Value params = request["params"]; |
271 |
int parc, i; |
272 |
char **parv = salloc<char *> (MAX_CMD_PARC); |
273 |
connection_t *cptr = static_cast<connection_t *> (conn); |
274 |
|
275 |
parc = params.size (); |
276 |
|
277 |
if (parc < 2) |
278 |
{ |
279 |
jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters."); |
280 |
sfree (parv); |
281 |
return; |
282 |
} |
283 |
|
284 |
for (i = 0; i < parc; i++) |
285 |
parv[i] = const_cast<char *> (static_cast<char const *> ((params[i]))); |
286 |
|
287 |
sourceip = parc >= 3 && *parv[2] != '\0' ? parv[2] : NULL; |
288 |
|
289 |
if (! (mu = myuser_t::find (parv[0]))) |
290 |
{ |
291 |
jsonrpc_generic_error (response, fault::nosuch_source, "The account is not registered."); |
292 |
sfree (parv); |
293 |
return; |
294 |
} |
295 |
|
296 |
if (mu->find_metadata ("private:freeze:freezer") != NULL) |
297 |
{ |
298 |
logcommand_external (nicksvs.me, "jsonrpc", cptr, sourceip, NULL, CMDLOG_LOGIN, "failed LOGIN to %s (frozen)", mu->name); |
299 |
jsonrpc_generic_error (response, fault::noprivs, "The account has been frozen."); |
300 |
sfree (parv); |
301 |
return; |
302 |
} |
303 |
|
304 |
if (!mu->verify_password (parv[1])) |
305 |
{ |
306 |
logcommand_external (nicksvs.me, "jsonrpc", cptr, sourceip, NULL, CMDLOG_LOGIN, "failed LOGIN to %s (bad password)", mu->name); |
307 |
jsonrpc_generic_error (response, fault::authfail, "The password is not valid for this account."); |
308 |
sfree (parv); |
309 |
return; |
310 |
} |
311 |
|
312 |
mu->lastlogin = NOW; |
313 |
|
314 |
ac = authcookie_create (mu); |
315 |
|
316 |
logcommand_external (nicksvs.me, "jsonrpc", cptr, sourceip, mu, CMDLOG_LOGIN, "LOGIN"); |
317 |
|
318 |
jsonrpc_generic_reply (response, ac->ticket); |
319 |
|
320 |
sfree (parv); |
321 |
} |
322 |
|
323 |
/* |
324 |
* ermyth.logout |
325 |
* |
326 |
* JSON inputs: |
327 |
* authcookie, and account name. |
328 |
* |
329 |
* JSON outputs: |
330 |
* fault 1 - insufficient parameters |
331 |
* fault 3 - unknown user |
332 |
* fault 5 - validation failed |
333 |
* default - success message |
334 |
* |
335 |
* Side Effects: |
336 |
* an authcookie ticket is destroyed. |
337 |
*/ |
338 |
static void |
339 |
jsonrpcmethod_logout (void *conn, Value &request, Value &response) |
340 |
{ |
341 |
authcookie_t *ac; |
342 |
myuser_t *mu; |
343 |
|
344 |
Value params = request["params"]; |
345 |
int parc, i; |
346 |
char **parv = salloc<char *> (MAX_CMD_PARC); |
347 |
connection_t *cptr = static_cast<connection_t *> (conn); |
348 |
|
349 |
parc = params.size (); |
350 |
|
351 |
if (parc < 2) |
352 |
{ |
353 |
jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters."); |
354 |
sfree (parv); |
355 |
return; |
356 |
} |
357 |
|
358 |
for (i = 0; i < parc; i++) |
359 |
parv[i] = const_cast<char *> (static_cast<char const *> ((params[i]))); |
360 |
|
361 |
if ( (mu = myuser_t::find (parv[1])) == NULL) |
362 |
{ |
363 |
jsonrpc_generic_error (response, fault::nosuch_source, "Unknown user."); |
364 |
sfree (parv); |
365 |
return; |
366 |
} |
367 |
|
368 |
if (authcookie_validate (parv[0], mu) == false) |
369 |
{ |
370 |
jsonrpc_generic_error (response, fault::authfail, "Invalid authcookie for this account."); |
371 |
sfree (parv); |
372 |
return; |
373 |
} |
374 |
|
375 |
logcommand_external (nicksvs.me, "jsonrpc", cptr, NULL, mu, CMDLOG_LOGIN, "LOGOUT"); |
376 |
|
377 |
ac = authcookie_find (parv[0], mu); |
378 |
authcookie_destroy (ac); |
379 |
|
380 |
jsonrpc_generic_reply (response, "You are now logged out."); |
381 |
|
382 |
sfree (parv); |
383 |
} |
384 |
|
385 |
/* |
386 |
* ermyth.command |
387 |
* |
388 |
* JSON inputs: |
389 |
* authcookie, account name, source ip, service name, command name, |
390 |
* parameters. |
391 |
* |
392 |
* JSON outputs: |
393 |
* depends on command |
394 |
* |
395 |
* Side Effects: |
396 |
* command is executed |
397 |
*/ |
398 |
static void |
399 |
jsonrpcmethod_command (void *conn, Value &request, Value &response) |
400 |
{ |
401 |
myuser_t *mu; |
402 |
service_t *svs; |
403 |
command_t const *cmd; |
404 |
sourceinfo_t si; |
405 |
int newparc; |
406 |
char *newparv[MAX_CMD_PARC]; |
407 |
connection_t *cptr = static_cast<connection_t *> (conn); |
408 |
struct httpddata *hd = static_cast<httpddata *> (cptr->userdata); |
409 |
|
410 |
Value params = request["params"]; |
411 |
int parc, i; |
412 |
char **parv = salloc<char *> (MAX_CMD_PARC); |
413 |
|
414 |
parc = params.size (); |
415 |
|
416 |
if (parc < 5) |
417 |
{ |
418 |
jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters."); |
419 |
sfree (parv); |
420 |
return; |
421 |
} |
422 |
|
423 |
for (i = 0; i < parc; i++) |
424 |
parv[i] = const_cast<char *> (static_cast<char const *> (params[i])); |
425 |
|
426 |
for (i = 0; i < parc; i++) |
427 |
{ |
428 |
if (strchr (parv[i], '\r') || strchr (parv[i], '\n')) |
429 |
{ |
430 |
jsonrpc_generic_error (response, fault::badparams, "Invalid parameters."); |
431 |
return; |
432 |
} |
433 |
} |
434 |
|
435 |
if (*parv[1] != '\0' && strlen (parv[0]) > 1) |
436 |
{ |
437 |
if ( (mu = myuser_t::find (parv[1])) == NULL) |
438 |
{ |
439 |
jsonrpc_generic_error (response, fault::nosuch_source, "Unknown user."); |
440 |
sfree (parv); |
441 |
return; |
442 |
} |
443 |
|
444 |
if (authcookie_validate (parv[0], mu) == false) |
445 |
{ |
446 |
jsonrpc_generic_error (response, fault::authfail, "Invalid authcookie for this account."); |
447 |
sfree (parv); |
448 |
return; |
449 |
} |
450 |
} |
451 |
else |
452 |
mu = NULL; |
453 |
|
454 |
svs = find_service (parv[3]); |
455 |
if (svs == NULL || svs->cmdtree == NULL) |
456 |
{ |
457 |
slog (LG_DEBUG, "jsonrpcmethod_command (): invalid service %s", parv[3]); |
458 |
jsonrpc_generic_error (response, fault::nosuch_source, "Invalid service name."); |
459 |
sfree (parv); |
460 |
return; |
461 |
} |
462 |
|
463 |
cmd = svs->cmdtree->find (parv[4]); |
464 |
if (cmd == NULL) |
465 |
{ |
466 |
jsonrpc_generic_error (response, fault::nosuch_source, "Invalid command name."); |
467 |
sfree (parv); |
468 |
return; |
469 |
} |
470 |
|
471 |
memset (newparv, '\0', sizeof newparv); |
472 |
newparc = parc - 5; |
473 |
if (newparc > MAX_CMD_PARC) |
474 |
newparc = MAX_CMD_PARC; |
475 |
if (newparc > 0) |
476 |
memcpy (newparv, parv + 5, newparc * sizeof (parv[0])); |
477 |
memset (&si, '\0', sizeof si); |
478 |
si.smu = mu; |
479 |
si.service = svs; |
480 |
si.sourcedesc = parv[2][0] != '\0' ? parv[2] : NULL; |
481 |
si.connection = cptr; |
482 |
si.v = &jsonrpc_vtable; |
483 |
cmd->exec (svs, &si, newparc, newparv); |
484 |
|
485 |
response = current_response; |
486 |
|
487 |
if (!hd->sent_reply) |
488 |
{ |
489 |
if (hd->replybuf != NULL) |
490 |
jsonrpc_generic_reply (response, hd->replybuf); |
491 |
else |
492 |
jsonrpc_generic_error (response, fault::unimplemented, "Command did not return a result."); |
493 |
} |
494 |
|
495 |
sfree (parv); |
496 |
} |
497 |
|
498 |
bool |
499 |
_modinit (module *m) |
500 |
{ |
501 |
ConfTable::callback.ready.attach (on_config_ready); |
502 |
|
503 |
add_top_conf ("JSONRPC", conf_jsonrpc); |
504 |
add_conf_item ("PATH", conf_jsonrpc_table, conf_jsonrpc_path); |
505 |
|
506 |
jsonrpc_add_method (PACKAGE_NAME ".login", jsonrpcmethod_login); |
507 |
jsonrpc_add_method (PACKAGE_NAME ".logout", jsonrpcmethod_logout); |
508 |
jsonrpc_add_method (PACKAGE_NAME ".command", jsonrpcmethod_command); |
509 |
|
510 |
return true; |
511 |
} |
512 |
|
513 |
void |
514 |
_moddeinit (void) |
515 |
{ |
516 |
#if 0 |
517 |
jsonrpc_unregister_method (PACKAGE_NAME ".login"); |
518 |
jsonrpc_unregister_method (PACKAGE_NAME ".logout"); |
519 |
jsonrpc_unregister_method (PACKAGE_NAME ".command"); |
520 |
#endif |
521 |
|
522 |
if (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path)) |
523 |
== pathhandlers.end ()) |
524 |
{ |
525 |
slog (LG_INFO, "jsonrpc/main.c: handler was not registered."); |
526 |
return; |
527 |
} |
528 |
|
529 |
pathhandlers.erase (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path))); |
530 |
|
531 |
del_conf_item ("PATH", conf_jsonrpc_table); |
532 |
del_top_conf ("JSONRPC"); |
533 |
if (handle_jsonrpc.path != NULL) |
534 |
sfree (handle_jsonrpc.path); |
535 |
handle_jsonrpc.path = NULL; |
536 |
|
537 |
ConfTable::callback.ready.detach (on_config_ready); |
538 |
} |