ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.10
Committed: Sat Sep 22 14:27:28 2007 UTC (19 years 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

# User Rev Content
1 pippijn 1.9 /**
2     * jsonrpc.C: JSON-RPC for Ermyth
3     *
4 pippijn 1.1 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
5 pippijn 1.9 * 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 pippijn 1.2 * Rights to this code are as documented in doc/pod/license.pod.
11 pippijn 1.1 *
12 pippijn 1.10 * $Id: jsonrpc.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $
13 pippijn 1.1 */
14    
15     #include "atheme.h"
16 pippijn 1.10 #include <libermyth.h>
17 pippijn 1.5 #include <ermyth/module.h>
18 pippijn 1.1 #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 pippijn 1.10 static char const rcsid[] = "$Id: jsonrpc.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $";
32 pippijn 1.1
33 pippijn 1.5 static void on_config_ready (void);
34 pippijn 1.1
35     #define JSONRPC_BUFSIZE 2048
36 pippijn 1.5 #define MAX_CMD_PARC 20
37 pippijn 1.1
38 pippijn 1.5 REGISTER_MODULE ("rpc/jsonrpc", false, "The Ermyth Team <http://ermyth.one09.net>");
39 pippijn 1.1
40     static void handle_request (connection_t *cptr, void *requestbuf);
41    
42     Value current_response; // XXX: Hack
43    
44 pippijn 1.5 E pathvec pathhandlers;
45 pippijn 1.1
46 pippijn 1.7 static void jsonrpc_command_fail (sourceinfo_t *si, fault::code code, char const *message);
47 pippijn 1.1 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 pippijn 1.8 ConfTable::list_type conf_jsonrpc_table;
56 pippijn 1.1
57 pippijn 1.5 path_handler_t handle_jsonrpc =
58     {
59     NULL,
60     handle_request
61     };
62    
63 pippijn 1.1 static int
64     conf_jsonrpc_path (config_entry_t *ce)
65     {
66 pippijn 1.6 if (!ce->valid ())
67 pippijn 1.1 return -1;
68    
69 pippijn 1.5 if (handle_jsonrpc.path != NULL)
70     {
71 pippijn 1.6 if (!strcmp (handle_jsonrpc.path, ce->vardata<char *> ()))
72 pippijn 1.5 return 0;
73     sfree (handle_jsonrpc.path);
74     }
75 pippijn 1.6 handle_jsonrpc.path = sstrdup (ce->vardata<char *> ());
76 pippijn 1.1
77     return 0;
78     }
79    
80     static int
81     conf_jsonrpc (config_entry_t *ce)
82     {
83 pippijn 1.8 subblock_handler (ce, conf_jsonrpc_table);
84 pippijn 1.1 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 pippijn 1.4
119 pippijn 1.3 error["name"] = "JSONRPCError";
120 pippijn 1.1 error["code"] = Value (code);
121 pippijn 1.5 error["message"] = Value (string);
122 pippijn 1.1
123 pippijn 1.3 response["version"] = "1.1";
124 pippijn 1.1 response["error"] = error;
125     }
126    
127     static void
128     handle_request (connection_t *cptr, void *requestbuf)
129     {
130 pippijn 1.5 char response[JSONRPC_BUFSIZE];
131     jsonrpc_process (&cptr, response, static_cast<char const *> (requestbuf), JSONRPC_BUFSIZE);
132 pippijn 1.1 dump_buffer (cptr, response, strlen (response));
133 pippijn 1.3 current_response = Value (nullValue);
134 pippijn 1.1 }
135    
136 pippijn 1.5 static void
137     on_config_ready (void)
138 pippijn 1.1 {
139     if (handle_jsonrpc.handler != NULL)
140     {
141 pippijn 1.5 if (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path))
142     != pathhandlers.end ())
143 pippijn 1.1 {
144     slog (LG_INFO, "jsonrpc/main.C: handler already in the list");
145     return;
146     }
147    
148 pippijn 1.5 pathhandlers.push_back (handle_jsonrpc);
149 pippijn 1.1 }
150     else
151 pippijn 1.5 slog (LG_ERROR, "on_config_ready (): jsonrpc { } block missing or invalid");
152 pippijn 1.1 }
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 pippijn 1.7 jsonrpc_command_fail (sourceinfo_t *si, fault::code code, char const *message)
165 pippijn 1.1 {
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 pippijn 1.7 hd->replybuf = salloc<char> (msglen + 1);
199 pippijn 1.1 p = hd->replybuf;
200     }
201     else
202     {
203     size_t replybuf_len = strlen (hd->replybuf);
204 pippijn 1.7 hd->replybuf = salloc (replybuf_len + msglen + 2, hd->replybuf);
205 pippijn 1.1 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 pippijn 1.7 char **parv = salloc<char *> (MAX_CMD_PARC);
273 pippijn 1.1 connection_t *cptr = static_cast<connection_t *> (conn);
274    
275     parc = params.size ();
276    
277     if (parc < 2)
278     {
279 pippijn 1.7 jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters.");
280 pippijn 1.5 sfree (parv);
281 pippijn 1.1 return;
282     }
283    
284     for (i = 0; i < parc; i++)
285 pippijn 1.5 parv[i] = const_cast<char *> (static_cast<char const *> ((params[i])));
286 pippijn 1.1
287     sourceip = parc >= 3 && *parv[2] != '\0' ? parv[2] : NULL;
288    
289 pippijn 1.6 if (! (mu = myuser_t::find (parv[0])))
290 pippijn 1.1 {
291 pippijn 1.7 jsonrpc_generic_error (response, fault::nosuch_source, "The account is not registered.");
292 pippijn 1.5 sfree (parv);
293 pippijn 1.1 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 pippijn 1.7 jsonrpc_generic_error (response, fault::noprivs, "The account has been frozen.");
300 pippijn 1.5 sfree (parv);
301 pippijn 1.1 return;
302     }
303    
304 pippijn 1.5 if (!mu->verify_password (parv[1]))
305 pippijn 1.1 {
306     logcommand_external (nicksvs.me, "jsonrpc", cptr, sourceip, NULL, CMDLOG_LOGIN, "failed LOGIN to %s (bad password)", mu->name);
307 pippijn 1.7 jsonrpc_generic_error (response, fault::authfail, "The password is not valid for this account.");
308 pippijn 1.5 sfree (parv);
309 pippijn 1.1 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 pippijn 1.5 sfree (parv);
321 pippijn 1.1 }
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 pippijn 1.7 char **parv = salloc<char *> (MAX_CMD_PARC);
347 pippijn 1.1 connection_t *cptr = static_cast<connection_t *> (conn);
348    
349     parc = params.size ();
350    
351     if (parc < 2)
352     {
353 pippijn 1.7 jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters.");
354 pippijn 1.5 sfree (parv);
355 pippijn 1.1 return;
356     }
357    
358     for (i = 0; i < parc; i++)
359 pippijn 1.5 parv[i] = const_cast<char *> (static_cast<char const *> ((params[i])));
360 pippijn 1.1
361 pippijn 1.6 if ( (mu = myuser_t::find (parv[1])) == NULL)
362 pippijn 1.1 {
363 pippijn 1.7 jsonrpc_generic_error (response, fault::nosuch_source, "Unknown user.");
364 pippijn 1.5 sfree (parv);
365 pippijn 1.1 return;
366     }
367    
368     if (authcookie_validate (parv[0], mu) == false)
369     {
370 pippijn 1.7 jsonrpc_generic_error (response, fault::authfail, "Invalid authcookie for this account.");
371 pippijn 1.5 sfree (parv);
372 pippijn 1.1 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 pippijn 1.5 sfree (parv);
383 pippijn 1.1 }
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 pippijn 1.7 char **parv = salloc<char *> (MAX_CMD_PARC);
413 pippijn 1.1
414     parc = params.size ();
415    
416     if (parc < 5)
417     {
418 pippijn 1.7 jsonrpc_generic_error (response, fault::needmoreparams, "Insufficient parameters.");
419 pippijn 1.5 sfree (parv);
420 pippijn 1.1 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 pippijn 1.7 jsonrpc_generic_error (response, fault::badparams, "Invalid parameters.");
431 pippijn 1.1 return;
432     }
433     }
434    
435     if (*parv[1] != '\0' && strlen (parv[0]) > 1)
436     {
437 pippijn 1.6 if ( (mu = myuser_t::find (parv[1])) == NULL)
438 pippijn 1.1 {
439 pippijn 1.7 jsonrpc_generic_error (response, fault::nosuch_source, "Unknown user.");
440 pippijn 1.5 sfree (parv);
441 pippijn 1.1 return;
442     }
443    
444     if (authcookie_validate (parv[0], mu) == false)
445     {
446 pippijn 1.7 jsonrpc_generic_error (response, fault::authfail, "Invalid authcookie for this account.");
447 pippijn 1.5 sfree (parv);
448 pippijn 1.1 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 pippijn 1.7 jsonrpc_generic_error (response, fault::nosuch_source, "Invalid service name.");
459 pippijn 1.5 sfree (parv);
460 pippijn 1.1 return;
461     }
462    
463     cmd = svs->cmdtree->find (parv[4]);
464     if (cmd == NULL)
465     {
466 pippijn 1.7 jsonrpc_generic_error (response, fault::nosuch_source, "Invalid command name.");
467 pippijn 1.5 sfree (parv);
468 pippijn 1.1 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 pippijn 1.3 response = current_response;
486    
487 pippijn 1.1 if (!hd->sent_reply)
488     {
489     if (hd->replybuf != NULL)
490     jsonrpc_generic_reply (response, hd->replybuf);
491     else
492 pippijn 1.7 jsonrpc_generic_error (response, fault::unimplemented, "Command did not return a result.");
493 pippijn 1.1 }
494    
495 pippijn 1.5 sfree (parv);
496 pippijn 1.1 }
497    
498 pippijn 1.5 bool
499     _modinit (module *m)
500 pippijn 1.1 {
501 pippijn 1.5 ConfTable::callback.ready.attach (on_config_ready);
502 pippijn 1.1
503     add_top_conf ("JSONRPC", conf_jsonrpc);
504 pippijn 1.8 add_conf_item ("PATH", conf_jsonrpc_table, conf_jsonrpc_path);
505 pippijn 1.1
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 pippijn 1.5
510     return true;
511 pippijn 1.1 }
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 pippijn 1.5 if (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path))
523     == pathhandlers.end ())
524 pippijn 1.1 {
525     slog (LG_INFO, "jsonrpc/main.c: handler was not registered.");
526     return;
527     }
528    
529 pippijn 1.5 pathhandlers.erase (std::find_if (pathhandlers.begin (), pathhandlers.end (), pathhandler_eq (handle_jsonrpc.path)));
530 pippijn 1.1
531 pippijn 1.8 del_conf_item ("PATH", conf_jsonrpc_table);
532 pippijn 1.1 del_top_conf ("JSONRPC");
533 pippijn 1.5 if (handle_jsonrpc.path != NULL)
534     sfree (handle_jsonrpc.path);
535     handle_jsonrpc.path = NULL;
536 pippijn 1.1
537 pippijn 1.5 ConfTable::callback.ready.detach (on_config_ready);
538 pippijn 1.1 }