ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.1
Committed: Thu Jul 19 08:24:55 2007 UTC (19 years, 2 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Log Message:
initial import. the most important changes since Atheme are:
- fixed many memory leaks
- fixed many bugs
- converted to C++ and use more STL containers
- added a (not very enhanced yet) perl module
- greatly improved XML-RPC speed
- added a JSON-RPC module with code from json-cpp
- added a valgrind memcheck module to operserv
- added a more object oriented base64 implementation
- added a specialised unit test framework
- improved stability
- use gettimeofday() if available
- reworked adding/removing commands
- MemoServ IGNORE DEL can now remove indices

File Contents

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