ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.5
Committed: Tue Aug 28 17:08:11 2007 UTC (19 years, 1 month ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.4: +63 -68 lines
Log Message:
- changed name
- updated the example config to the new system
- added more documentation
- enhanced documentation generators
- added a link to the pdf to the website
- added an RSS feed generator
- transitioned hooks to c++ callbacks
- did various merges with upstream along the way
- added const where appropriate
- removed the old block allocator
- fixed most memory leaks
- transitioned some dictionaries to std::map
- transitioned some lists to std::vector
- made some free functions members where appropriate
- renamed string to dynstr and added a static string ststr
- use NOW instead of time (NULL) if possible
- completely reworked database backends, crypto handlers and protocol handlers
  to use an object factory
- removed the old module system. ermyth does not do any dynamic loading anymore
- fixed most of the build system
- reworked how protocol commands work

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