ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/xmlrpc.C
Revision: 1.8
Committed: Sat Sep 22 14:27:28 2007 UTC (19 years ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +3 -2 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

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