ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.4
Committed: Wed Jul 25 00:03:21 2007 UTC (19 years, 2 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.3: +14 -4 lines
Log Message:
initial import

File Contents

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