ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/jsonrpc.C
Revision: 1.9
Committed: Sun Sep 16 18:54:44 2007 UTC (19 years ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.8: +10 -5 lines
Log Message:
#defines to enum

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