ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/rpc/jsonrpc.C
Revision: 1.10
Committed: Sat Sep 22 14:27:28 2007 UTC (16 years, 8 months ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +3 -2 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# Content
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 }