ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.3
Committed: Sun Jul 22 18:38:43 2007 UTC (19 years, 2 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.2: +9 -8 lines
Log Message:
-fixed help
- temporarily work around corruption in jsonrpc

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