]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-python.c
mod-python: improve error logic for emb_get_channels
[irc/evilnet/x3.git] / src / mod-python.c
1 /* mod-python.c - Script module for x3
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
3 * Copyright 2005-2006 X3 Development Team
4 *
5 * This file is part of x3.
6 *
7 * x3 is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with srvx; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 */
21
22 #include "config.h"
23 #ifdef WITH_PYTHON /* just disable this file if python doesnt exist */
24
25 #ifndef WITH_PROTOCOL_P10
26 #error mod-python is only supported with p10 protocol enabled
27 #endif /* WITH_PROTOCOL_P10 */
28
29 #include <Python.h>
30 #include "chanserv.h"
31 #include "conf.h"
32 #include "modcmd.h"
33 #include "nickserv.h"
34 #include "opserv.h"
35 #include "saxdb.h"
36 #include "mail.h"
37 #include "timeq.h"
38 #include "compat.h"
39
40 /* TODO notes
41 *
42 * - Implement most of proto-p10 irc_* commands for calling from scripts
43 * - Implement functions to look up whois, channel, account, and reg-channel info for scripts
44 * - Implement x3.conf settings for python variables like include path, etc.
45 * - modpython.py calls for everything you can reg_ a handler for in x3
46 * - Some kind of system for getting needed binds bound automagicaly to make it easier
47 * to run peoples' scripts and mod-python in general.
48 * - An interface to reading/writing data to x3.db. Maybe generic, or attached to account or channel reg records?
49 */
50
51 static const struct message_entry msgtab[] = {
52 { "PYMSG_RELOAD_SUCCESS", "Reloaded Python scripts successfully." },
53 { "PYMSG_RELOAD_FAILED", "Error reloading Python scripts." },
54 { "PYMSG_RUN_UNKNOWN_EXCEPTION", "Error running python: unknown exception." },
55 { "PYMSG_RUN_EXCEPTION", "Error running python: %s: %s." },
56 { NULL, NULL } /* sentinel */
57 };
58
59 #define MODPYTHON_CONF_NAME "modules/python"
60
61 static
62 struct {
63 char const* scripts_dir;
64 char const* main_module;
65 } modpython_conf;
66
67 static struct log_type *PY_LOG;
68 const char *python_module_deps[] = { NULL };
69 static struct module *python_module;
70
71 PyObject *base_module = NULL; /* Base python handling library */
72 PyObject *handler_object = NULL; /* instance of handler class */
73
74
75 extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
76
77 /*
78 Some hooks you can call from modpython.py to interact with the
79 service. These emb_* functions are available as _svc.* in python. */
80
81 struct _tuple_dict_extra {
82 PyObject* data;
83 size_t* extra;
84 };
85
86 static int _dict_iter_get_users(char const* key, UNUSED_ARG(void* data), void* extra) {
87 PyObject* tmp;
88 struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra;
89
90 if ((tmp = PyString_FromString(key)) == NULL)
91 return 1;
92
93 if (PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, tmp))
94 return 1;
95
96 *real_extra->extra = *real_extra->extra + 1;
97
98 return 0;
99 }
100
101 static int _dict_iter_get_channels(char const* key, UNUSED_ARG(void* data), void* extra) {
102 PyObject* tmp;
103 struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra;
104
105 if ((tmp = PyString_FromString(key)) == NULL)
106 return 1;
107
108 if (PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, tmp))
109 return 1;
110
111 *real_extra->extra = *real_extra->extra + 1;
112 return 0;
113 }
114
115 static int _dict_iter_get_servers(char const* key, UNUSED_ARG(void* data), void* extra) {
116 struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra;
117
118 PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra,
119 PyString_FromString(key));
120 *real_extra->extra = *real_extra->extra + 1;
121 return 0;
122 }
123
124 /* get a tuple with all users in it */
125 static PyObject*
126 emb_get_users(UNUSED_ARG(PyObject *self), PyObject *args) {
127 PyObject* retval;
128 PyObject* tmp;
129 size_t num_clients, n = 0, i;
130 struct _tuple_dict_extra extra;
131
132 if (!PyArg_ParseTuple(args, ""))
133 return NULL;
134
135 num_clients = dict_size(clients);
136 retval = PyTuple_New(num_clients);
137 if (retval == NULL)
138 return NULL;
139
140 extra.extra = &n;
141 extra.data = retval;
142
143 if (dict_foreach(clients, _dict_iter_get_users, (void*)&extra) != NULL) {
144 for (i = 0; i < n; ++i) {
145 tmp = PyTuple_GetItem(retval, i);
146 PyTuple_SET_ITEM(retval, i, NULL);
147 Py_DECREF(tmp);
148 }
149 Py_DECREF(retval);
150 return NULL;
151 }
152
153 return retval;
154 }
155
156 /* get a tuple with all channels in it */
157 static PyObject*
158 emb_get_channels(UNUSED_ARG(PyObject* self), PyObject* args) {
159 PyObject* retval;
160 PyObject* tmp;
161 size_t num_channels, n = 0, i;
162 struct _tuple_dict_extra extra;
163
164 if (!PyArg_ParseTuple(args, ""))
165 return NULL;
166
167 num_channels = dict_size(channels);
168 retval = PyTuple_New(num_channels);
169 if (retval == NULL)
170 return NULL;
171
172 extra.extra = &n;
173 extra.data = retval;
174
175 if (dict_foreach(channels, _dict_iter_get_channels, (void*)&extra) != NULL) {
176 for (i = 0; i < n; ++i) {
177 tmp = PyTuple_GetItem(retval, i);
178 PyTuple_SET_ITEM(retval, i, NULL);
179 Py_DECREF(tmp);
180 }
181 Py_DECREF(retval);
182 return NULL;
183 }
184
185 return retval;
186 }
187
188 static PyObject*
189 emb_get_servers(UNUSED_ARG(PyObject* self), PyObject* args) {
190 PyObject* retval;
191 size_t n = 0;
192 struct _tuple_dict_extra extra;
193
194 if (!PyArg_ParseTuple(args, ""))
195 return NULL;
196
197 retval = PyTuple_New(dict_size(servers));
198
199 extra.extra = &n;
200 extra.data = retval;
201
202 dict_foreach(servers, _dict_iter_get_servers, (void*)&extra);
203
204 return retval;
205 }
206
207 static PyObject*
208 emb_dump(UNUSED_ARG(PyObject *self), PyObject *args)
209 {
210 /* Dump a raw string into the socket
211 usage: _svc.dump(<P10 string>)
212 */
213 char *buf;
214 int ret = 0;
215 char linedup[MAXLEN];
216
217
218 if(!PyArg_ParseTuple(args, "s:dump", &buf ))
219 return NULL;
220
221 safestrncpy(linedup, buf, sizeof(linedup));
222
223 if(parse_line(linedup, 1)) {
224 irc_raw(buf);
225 ret = 1;
226 } else {
227 PyErr_SetString(PyExc_Exception, "invalid protocol message");
228 return NULL;
229 }
230
231 return Py_BuildValue("i", ret);
232 }
233
234 static PyObject*
235 emb_send_target_privmsg(UNUSED_ARG(PyObject *self), PyObject *args)
236 {
237 /* Send a privmsg
238 usage: _svc.send_target_privmsg(<servicenick_from>, <nick_to>, <message>)
239 */
240 int ret = 0;
241 char *servicenick;
242 char *channel;
243 char *buf;
244
245 struct service *service;
246
247
248 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &channel, &buf ))
249 return NULL;
250
251 if (buf == NULL || strlen(buf) == 0) {
252 PyErr_SetString(PyExc_Exception, "invalid empty message");
253 return NULL;
254 }
255
256 if(!(service = service_find(servicenick))) {
257 PyErr_SetString(PyExc_Exception, "no such service nick");
258 return NULL;
259 }
260
261 ret = send_target_message(5, channel, service->bot, "%s", buf);
262 return Py_BuildValue("i", ret);
263 }
264
265 static PyObject*
266 emb_send_target_notice(UNUSED_ARG(PyObject *self), PyObject *args)
267 {
268 /* send a notice
269 usage: _svc.send_target_notice(<servicenick_from>, <nick_to>, <message>)
270 */
271 int ret = 0;
272 char *servicenick;
273 char *target;
274 char *buf;
275
276 struct service *service;
277
278 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &target, &buf ))
279 return NULL;
280
281 if (buf == NULL || strlen(buf) == 0) {
282 PyErr_SetString(PyExc_Exception, "invalid empty message");
283 return NULL;
284 }
285
286 if(!(service = service_find(servicenick))) {
287 PyErr_SetString(PyExc_Exception, "no such service nick");
288 return NULL;
289 }
290
291 ret = send_target_message(4, target, service->bot, "%s", buf);
292
293 return Py_BuildValue("i", ret);
294 }
295
296 static PyObject*
297 pyobj_from_usernode(struct userNode* user) {
298 unsigned int n;
299 struct modeNode *mn;
300 PyObject* pChanList = PyTuple_New(user->channels.used);
301
302 for (n=0; n < user->channels.used; n++) {
303 mn = user->channels.list[n];
304 PyTuple_SetItem(pChanList, n, Py_BuildValue("s", mn->channel->name));
305 }
306
307 return Py_BuildValue("{"
308 "s: s, " /* nick */
309 "s: s, " /* ident */
310 "s: s, " /* info */
311 "s: s, " /* hostname */
312 "s: s, " /* ip */
313 "s: s, " /* fakehost */
314 "s: s, " /* sethost */
315 "s: s, " /* crypthost */
316 "s: s, " /* cryptip */
317 "s: s, " /* numeric */
318 "s: i, " /* loc */
319 "s: i, " /* no_notice */
320 "s: s, " /* mark */
321 "s: s, " /* version_reply */
322 "s: s, " /* account */
323 "s: O}", /* channels */
324 "nick", user->nick,
325 "ident", user->ident,
326 "info", user->info,
327 "hostname", user->hostname,
328 "ip", irc_ntoa(&user->ip),
329 "fakehost", user->fakehost,
330 "sethost", user->sethost,
331 "crypthost", user->crypthost,
332 "cryptip", user->cryptip,
333 "numeric", user->numeric,
334 "loc", user->loc,
335 "no_notice", user->no_notice,
336 "mark", user->mark,
337 "version_reply", user->version_reply,
338 "account", user->handle_info ? user->handle_info->handle : NULL,
339 "channels", pChanList);
340 }
341
342 static PyObject*
343 emb_get_user(UNUSED_ARG(PyObject *self), PyObject *args)
344 {
345 /* Get a python object containing everything x3 knows about a user, by nick.
346 usage: _svc.get_user(<nick>)
347 */
348 char const* nick;
349 struct userNode *user;
350
351 if(!PyArg_ParseTuple(args, "s", &nick))
352 return NULL;
353
354 if(!(user = GetUserH(nick))) {
355 PyErr_SetString(PyExc_Exception, "no such user");
356 return NULL;
357 }
358
359 return pyobj_from_usernode(user);
360 }
361
362 static PyObject*
363 pyobj_from_server(struct server* srv) {
364 size_t n, idx;
365 PyObject* tmp = NULL;
366 PyObject* retval = NULL;
367 PyObject* users = PyTuple_New(srv->clients);
368
369 if (users == NULL)
370 return NULL;
371
372 idx = 0;
373 for (n = 0; n < srv->num_mask; ++n) {
374 if (srv->users[n] == NULL)
375 continue;
376
377 tmp = PyString_FromString(srv->users[n]->nick);
378 if (tmp == NULL)
379 goto cleanup;
380
381 if (PyTuple_SetItem(users, idx++, tmp))
382 goto cleanup;
383 }
384
385 retval = Py_BuildValue("{"
386 "s:s," /* name */
387 "s:l," /* boot */
388 "s:l," /* link_time */
389 "s:s," /* description */
390 "s:s," /* numeric */
391 "s:I," /* num_mask */
392 "s:I," /* hops */
393 "s:I," /* clients */
394 "s:I," /* max_clients */
395 "s:I," /* burst */
396 "s:I," /* self_burst */
397 "s:s" /* uplink */
398 "s:O" /* users */
399 /* TODO: Children */
400 "}",
401 "name", srv->name,
402 "boot", srv->boot,
403 "link_time", srv->link_time,
404 "description", srv->description,
405 "numeric", srv->numeric,
406 "num_mask", srv->num_mask,
407 "hops", srv->hops,
408 "clients", srv->clients,
409 "max_clients", srv->max_clients,
410 "burst", srv->burst,
411 "self_burst", srv->self_burst,
412 "uplink", srv->uplink ? srv->uplink->name : NULL,
413 "users", users
414 );
415
416 if (retval == NULL)
417 goto cleanup;
418
419 return retval;
420
421 cleanup:
422 Py_XDECREF(retval);
423
424 for (n = 0; n < idx; ++n) {
425 tmp = PyTuple_GetItem(users, n);
426 PyTuple_SetItem(users, n, NULL);
427 Py_DECREF(tmp);
428 }
429 Py_DECREF(users);
430
431 return NULL;
432 }
433
434 static PyObject*
435 emb_get_server(UNUSED_ARG(PyObject* self), PyObject* args) {
436 struct server* srv;
437 char const* name;
438
439 if (!PyArg_ParseTuple(args, "s", &name))
440 return NULL;
441
442 if (name == NULL || strlen(name) == 0) {
443 PyErr_SetString(PyExc_Exception, "invalid server name");
444 return NULL;
445 }
446
447 if ((srv = GetServerH(name)) == NULL) {
448 PyErr_SetString(PyExc_Exception, "unknown server");
449 return NULL;
450 }
451
452 return pyobj_from_server(srv);
453 }
454
455 static PyObject*
456 emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args)
457 {
458 /* Returns a python dict object with all sorts of info about a channel.
459 usage: _svc.get_channel(<name>)
460 */
461 char *name;
462 struct chanNode *channel;
463 unsigned int n;
464 PyObject *pChannelMembers;
465 PyObject *pChannelBans;
466 PyObject *pChannelExempts;
467
468
469 if(!PyArg_ParseTuple(args, "s", &name))
470 return NULL;
471
472 if(!(channel = GetChannel(name))) {
473 PyErr_SetString(PyExc_Exception, "unknown channel");
474 return NULL;
475 }
476
477 /* build tuple of nicks in channel */
478 pChannelMembers = PyTuple_New(channel->members.used);
479 for(n=0;n < channel->members.used;n++) {
480 struct modeNode *mn = channel->members.list[n];
481 PyTuple_SetItem(pChannelMembers, n, Py_BuildValue("s", mn->user->nick));
482 }
483
484 /* build tuple of bans */
485 pChannelBans = PyTuple_New(channel->banlist.used);
486 for(n=0; n < channel->banlist.used;n++) {
487 struct banNode *bn = channel->banlist.list[n];
488 PyTuple_SetItem(pChannelBans, n,
489 Py_BuildValue("{s:s,s:s,s:i}",
490 "ban", bn->ban,
491 "who", bn->who,
492 "set", bn->set)
493 );
494 }
495
496 /* build tuple of exempts */
497 pChannelExempts = PyTuple_New(channel->exemptlist.used);
498 for(n=0; n < channel->exemptlist.used;n++) {
499 struct exemptNode *en = channel->exemptlist.list[n];
500 PyTuple_SetItem(pChannelExempts, n,
501 Py_BuildValue("{s:s,s:s,s:i}",
502 "ban", en->exempt,
503 "who", en->who,
504 "set", en->set)
505 );
506 }
507
508 return Py_BuildValue("{s:s,s:s,s:s,s:i"
509 ",s:i,s:i,s:O,s:O,s:O}",
510
511 "name", channel->name,
512 "topic", channel->topic,
513 "topic_nick", channel->topic_nick,
514 "topic_time", channel->topic_time,
515
516 "timestamp", channel->timestamp,
517 "modes", channel->modes,
518 "members", pChannelMembers,
519 "bans", pChannelBans,
520 "exempts", pChannelExempts
521 );
522 }
523
524 static PyObject*
525 emb_get_account(UNUSED_ARG(PyObject *self), PyObject *args)
526 {
527 /* Returns a python dict object with all sorts of info about an account.
528 usage: _svc.get_account(<account name>)
529 */
530 char *name;
531 struct handle_info *hi;
532
533
534 if(!PyArg_ParseTuple(args, "s", &name))
535 return NULL;
536
537 hi = get_handle_info(name);
538
539 if(!hi) {
540 PyErr_SetString(PyExc_Exception, "unknown account name");
541 return NULL;
542 }
543
544 return Py_BuildValue("{s:s,s:i,s:s,s:s,s:s"
545 ",s:s,s:s}",
546
547 "account", hi->handle,
548 "registered", hi->registered,
549 "last_seen", hi->lastseen,
550 "infoline", hi->infoline ? hi->infoline : "",
551 "email", hi->email_addr ? hi->email_addr : "",
552
553 "fakehost", hi->fakehost ? hi->fakehost : "",
554 "last_quit_host", hi->last_quit_host
555
556 /* TODO: */
557 /* users online authed to this account */
558 /* cookies */
559 /* nicks (nickserv nets only?) */
560 /* masks */
561 /* ignores */
562 /* channels */
563 );
564 }
565
566 static PyObject*
567 emb_get_info(UNUSED_ARG(PyObject *self), UNUSED_ARG(PyObject *args))
568 {
569 /* return some info about the general setup
570 * of X3, such as what the chanserv's nickname
571 * is.
572 */
573
574
575 return Py_BuildValue("{s:s,s:s,s:s,s:s,s:s}",
576 "chanserv", chanserv? chanserv->nick : "ChanServ",
577 "nickserv", nickserv?nickserv->nick : "NickServ",
578 "opserv", opserv?opserv->nick : "OpServ",
579 "global", global?global->nick : "Global",
580 "spamserv", spamserv?spamserv->nick : "SpamServ");
581 }
582
583 static PyObject*
584 emb_log_module(UNUSED_ARG(PyObject *self), PyObject *args)
585 {
586 /* a gateway to standard X3 logging subsystem.
587 * level is a value 0 to 9 as defined by the log_severity enum in log.h.
588 *
589 * for now, all logs go to the PY_LOG log. In the future this will change.
590 */
591 char *message;
592 int ret = 0;
593 int level;
594
595
596 if(!PyArg_ParseTuple(args, "is", &level, &message))
597 return NULL;
598
599 log_module(PY_LOG, level, "%s", message);
600
601 return Py_BuildValue("i", ret);
602 }
603
604 static PyMethodDef EmbMethods[] = {
605 /* Communication methods */
606 {"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"},
607 {"send_target_privmsg", emb_send_target_privmsg, METH_VARARGS, "Send a message to somewhere"},
608 {"send_target_notice", emb_send_target_notice, METH_VARARGS, "Send a notice to somewhere"},
609 {"log_module", emb_log_module, METH_VARARGS, "Log something using the X3 log subsystem"},
610 //TODO: {"exec_cmd", emb_exec_cmd, METH_VARARGS, "execute x3 command provided"},
611 // This should use environment from "python command" call to pass in, if available
612 //TODO: {"kill"
613 //TODO: {"shun"
614 //TODO: {"unshun"
615 //TODO: {"gline", emb_gline, METH_VARARGS, "gline a mask"},
616 //TODO: {"ungline", emb_ungline, METH_VARARGS, "remove a gline"},
617 //TODO: {"kick", emb_kick, METH_VARARGS, "kick someone from a channel"},
618 //TODO: {"channel_mode", emb_channel_mode, METH_VARARGS, "set modes on a channel"},
619 //TODO: {"user_mode", emb_user_mode, METH_VARARGS, "Have x3 set usermodes on one of its own nicks"},
620 //
621 //TODO: {"get_config", emb_get_config, METH_VARARGS, "get x3.conf settings into a nested dict"},
622 //TODO: {"config_set", emb_config_set, METH_VARARGS, "change a config setting 'on-the-fly'."},
623 //
624 //TODO: {"timeq_add", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."},
625 //TODO: {"timeq_del", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."},
626 /* Information gathering methods */
627 {"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"},
628 {"get_users", emb_get_users, METH_VARARGS, "Get all connected users"},
629 {"get_channel", emb_get_channel, METH_VARARGS, "Get details about a channel"},
630 {"get_channels", emb_get_channels, METH_VARARGS, "Get all channels"},
631 {"get_server", emb_get_server, METH_VARARGS, "Get details about a server"},
632 {"get_servers", emb_get_servers, METH_VARARGS, "Get all server names"},
633 {"get_account", emb_get_account, METH_VARARGS, "Get details about an account"},
634 {"get_info", emb_get_info, METH_VARARGS, "Get various misc info about x3"},
635 /* null terminator */
636 {NULL, NULL, 0, NULL}
637 };
638
639
640 /*
641 These functions set up the embedded environment for us to call out to
642 modpython.py class methods.
643 */
644
645 void python_log_module() {
646 /* Attempt to convert python errors to x3 log system */
647 PyObject *exc, *tb, *value, *tmp;
648 char *str_exc = "NONE";
649 char *str_value = "NONE";
650 char *str_tb = "NONE";
651
652 PyErr_Fetch(&exc, &value, &tb);
653
654 if(exc) {
655 if((tmp = PyObject_Str(exc)))
656 str_exc = PyString_AsString(tmp);
657 }
658 if(value) {
659 if((tmp = PyObject_Str(value)))
660 str_value = PyString_AsString(tmp);
661 }
662 if(tb) {
663 if((tmp = PyObject_Str(tb)))
664 str_tb = PyString_AsString(tmp);
665 }
666
667 /* Now restore it so we can print it (just in case)
668 * (should we do this only when running in debug mode?) */
669 PyErr_Restore(exc, value, tb);
670 PyErr_Print(); /* which of course, clears it again.. */
671
672 log_module(PY_LOG, LOG_WARNING, "PYTHON error: %s, value: %s", str_exc, str_value);
673
674 /* TODO: get the traceback using the traceback module via C api so we can add it to the X3 logs. See
675 * http://mail.python.org/pipermail/python-list/2003-March/192226.html */
676 // (this doesnt work, str_tb is just an object hashid) log_module(PY_LOG, LOG_INFO, "PYTHON error, traceback: %s", str_tb);
677 }
678
679
680 PyObject *python_build_handler_args(size_t argc, char *args[], PyObject *pIrcObj) {
681 /* Sets up a python tuple with passed in arguments, prefixed by the Irc instance
682 which handlers use to interact with C.
683 argc = number of args
684 args = array of args
685 pIrcObj = instance of the irc class
686 */
687 size_t i = 0, n;
688 PyObject *pArgs = NULL;
689
690 pArgs = PyTuple_New(argc + 1);
691 Py_INCREF(pIrcObj);
692 PyTuple_SetItem(pArgs, i++, pIrcObj);
693
694 if(args && argc) {
695 PyObject *pValue;
696 for(n = 0; n < argc; ++n) {
697 pValue = PyString_FromString(args[n]);
698 if(!pValue) {
699 Py_DECREF(pArgs);
700 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[n]);
701 return NULL;
702 }
703 PyTuple_SetItem(pArgs, n+i, pValue);
704 }
705 }
706 return pArgs;
707 }
708
709 PyObject *python_build_args(size_t argc, char *args[]) {
710 /* Builds the passed in arguments into a python argument tuple.
711 argc = number of args
712 args = array of args
713 */
714 size_t i;
715 PyObject *pArgs = NULL;
716
717 if(args && argc) {
718 pArgs = PyTuple_New(argc);
719 PyObject *pValue;
720 for(i = 0; i< argc; ++i) {
721 pValue = PyString_FromString(args[i]);
722 if(!pValue) {
723 Py_DECREF(pArgs);
724 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[i]);
725 return NULL;
726 }
727 PyTuple_SetItem(pArgs, i, pValue);
728 }
729 }
730 return pArgs;
731 }
732
733
734 PyObject *new_irc_object(char *command_service, char *command_caller, char *command_target) {
735 /* Creates a new instance of the irc class (from modpython.py) which is initalized
736 with current environment details like which service were talking to.
737 command_service = which service we are talking to, or empty string if none
738 command_caller = nick of user generating message, or empty string if none
739 command_target = If were reacting to something on a channel, this will
740 be set to the name of the channel. Otherwise empty
741 */
742 PyObject *pIrcArgs = NULL;
743 PyObject *pIrcClass;
744 PyObject *pIrcObj;
745
746 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate irc class; %s %s %s", command_service, command_caller, command_target);
747 pIrcClass = PyObject_GetAttrString(base_module, "irc");
748 /* pIrcClass is a new reference */
749 if(pIrcClass && PyCallable_Check(pIrcClass)) {
750 //size_t i;
751 char *ircargs[] = {command_service, command_caller, command_target};
752 //PyObject *pValue;
753
754 pIrcArgs = python_build_args(3, ircargs);
755 pIrcObj = PyObject_CallObject(pIrcClass, pIrcArgs);
756 if(!pIrcObj) {
757 log_module(PY_LOG, LOG_ERROR, "IRC Class failed to load");
758 python_log_module();
759 //PyErr_Print();
760 }
761 if(pIrcArgs != NULL) {
762 Py_DECREF(pIrcArgs);
763 }
764 Py_DECREF(pIrcClass);
765 return pIrcObj;
766 }
767 else {
768 /* need to free pIrcClass here if it WAS found but was NOT callable? */
769 log_module(PY_LOG, LOG_ERROR, "Unable to find irc class");
770 return NULL;
771 }
772 }
773
774 int python_call_handler(char *handler, char *args[], size_t argc, char *command_service, char *command_caller, char *command_target) {
775 /* This is how we talk to modpython.c. First a new instance of the irc class is created using these
776 arguments to setup the current environment. Then the named method of the handler object is called
777 with the givin arguments.
778 */
779 PyObject *pIrcObj;
780 PyObject *pArgs;
781 PyObject *pMethod;
782 PyObject *pValue;
783
784 log_module(PY_LOG, LOG_INFO, "attempting to call handler %s.", handler);
785 if(base_module != NULL && handler_object != NULL) {
786 pIrcObj = new_irc_object(command_service, command_caller, command_target);
787 if(!pIrcObj) {
788 log_module(PY_LOG, LOG_INFO, "Can't get irc object. Bailing.");
789 return 0;
790 }
791
792 pArgs = python_build_handler_args(argc, args, pIrcObj);
793 pMethod = PyObject_GetAttrString(handler_object, handler);
794 if(pMethod && PyCallable_Check(pMethod)) {
795 /* Call the method, with the arguments */
796 pValue = PyObject_CallObject(pMethod, pArgs);
797 if(pArgs) {
798 Py_DECREF(pArgs);
799 }
800 if(pValue != NULL) {
801 int ret;
802 ret = PyInt_AsLong(pValue);
803 if(ret == -1 && PyErr_Occurred()) {
804 //PyErr_Print();
805 log_module(PY_LOG, LOG_INFO, "error converting return value of handler %s to type long. ", handler);
806 python_log_module();
807 ret = 0;
808 }
809 log_module(PY_LOG, LOG_INFO, "handler %s was run successfully, returned %d.", handler, ret);
810 Py_DECREF(pValue);
811 Py_DECREF(pIrcObj);
812 Py_DECREF(pMethod);
813 return ret;
814 }
815 else {
816 /* TODO: instead of print errors, get them as strings
817 * and deal with them with normal x3 log system. */
818 log_module(PY_LOG, LOG_WARNING, "call to handler %s failed", handler);
819 //PyErr_Print();
820 python_log_module();
821 Py_DECREF(pIrcObj);
822 Py_DECREF(pMethod);
823 return 0;
824 }
825 }
826 else { /* couldn't find handler methed */
827 Py_DECREF(pArgs);
828 /* Free pMethod if it was found but not callable? */
829 log_module(PY_LOG, LOG_ERROR, "Cannot find handler %s.", handler);
830 return 0;
831
832 }
833 }
834 else { /* No base module.. no python? */
835 log_module(PY_LOG, LOG_INFO, "Cannot handle %s, Python is not initialized.", handler);
836 return 0;
837 }
838 }
839
840 PyObject *python_new_handler_object() {
841 /* Create a new instance of the handler class.
842 This is called during python initilization (or reload)
843 and the result is saved and re-used.
844 */
845 PyObject *pHandlerClass, *pHandlerObj;
846
847 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate python class handler");
848 pHandlerClass = PyObject_GetAttrString(base_module, "handler");
849 /* Class is a new reference */
850 if(pHandlerClass && PyCallable_Check(pHandlerClass)) {
851 /*PyObject *pValue; */
852
853 pHandlerObj = PyObject_CallObject(pHandlerClass, NULL);
854 if(pHandlerObj != NULL) {
855 log_module(PY_LOG, LOG_INFO, "Created new python handler object.");
856 return pHandlerObj;
857 }
858 else {
859 log_module(PY_LOG, LOG_ERROR, "Unable to instanciate handler object");
860 //PyErr_Print();
861 python_log_module();
862 return NULL;
863 }
864 }
865 else {
866 log_module(PY_LOG, LOG_ERROR, "Unable to find handler class");
867 //PyErr_Print();
868 python_log_module();
869 if(pHandlerClass) {
870 Py_DECREF(pHandlerClass);
871 }
872 return NULL;
873 }
874 }
875
876 /* ------------------------------------------------------------------------------- *
877 Some gateway functions to convert x3 callbacks into modpython.py callbacks.
878 Mostly we just build relevant args and call the proper handler method
879
880 debate: do we just register these and check them in python
881 for every one (slow?) or actually work out if a plugin needs
882 it first? We will start by doing it every time.
883 */
884 static int
885 python_handle_join(struct modeNode *mNode)
886 {
887 /* callback for handle_join events.
888 */
889 struct userNode *user = mNode->user;
890 struct chanNode *channel = mNode->channel;
891
892
893 log_module(PY_LOG, LOG_INFO, "python module handle_join");
894 if(!channel||!user) {
895 log_module(PY_LOG, LOG_WARNING, "Python code got join without channel or user!");
896 return 0;
897 }
898 else {
899 char *args[] = {channel->name, user->nick};
900 return python_call_handler("join", args, 2, "", "", "");
901 }
902 }
903
904 static int
905 python_handle_server_link(struct server *server)
906 {
907 log_module(PY_LOG, LOG_INFO, "python module handle_server_link");
908 if(!server) {
909 log_module(PY_LOG, LOG_WARNING, "Python code got server link without server!");
910 return 0;
911 }
912 else {
913 char *args[] = {server->name, server->description};
914 return python_call_handler("server_link", args, 2, "", "", "");
915 }
916 }
917
918 static int
919 python_handle_new_user(struct userNode *user)
920 {
921 log_module(PY_LOG, LOG_INFO, "Python module handle_new_user");
922 if(!user) {
923 log_module(PY_LOG, LOG_WARNING, "Python code got new_user without the user");
924 return 0;
925 }
926 else {
927 char *args[] = {user->nick, user->ident, user->hostname, user->info};
928 return python_call_handler("new_user", args, 4, "", "", "");
929 }
930 }
931
932 static void
933 python_handle_nick_change(struct userNode *user, const char *old_nick)
934 {
935 log_module(PY_LOG, LOG_INFO, "Python module handle_nick_change");
936 if(!user) {
937 log_module(PY_LOG, LOG_WARNING, "Python code got nick_change without the user!");
938 }
939 else {
940 char *args[] = {user->nick, (char *)old_nick};
941 python_call_handler("nick_change", args, 2, "", "", "");
942 }
943 }
944
945 /* ----------------------------------------------------------------------------- */
946
947
948 int python_load() {
949 /* Init the python engine and do init work on modpython.py
950 This is called during x3 startup, and on a python reload
951 */
952 PyObject *pName;
953 char* buffer;
954 char* env = getenv("PYTHONPATH");
955
956 if (env)
957 env = strdup(env);
958
959 if (!env)
960 setenv("PYTHONPATH", modpython_conf.scripts_dir, 1);
961 else if (!strstr(env, modpython_conf.scripts_dir)) {
962 buffer = (char*)malloc(strlen(env) + strlen(modpython_conf.scripts_dir) + 2);
963 sprintf(buffer, "%s:%s", modpython_conf.scripts_dir, env);
964 setenv("PYTHONPATH", buffer, 1);
965 free(buffer);
966 free(env);
967 }
968
969 Py_Initialize();
970 Py_InitModule("_svc", EmbMethods);
971 pName = PyString_FromString(modpython_conf.main_module);
972 base_module = PyImport_Import(pName);
973 Py_DECREF(pName);
974
975 Py_XDECREF(handler_object);
976 handler_object = NULL;
977
978 if(base_module != NULL) {
979 handler_object = python_new_handler_object();
980 if(handler_object) {
981 python_call_handler("init", NULL, 0, "", "", "");
982 return 1;
983 }
984 else {
985 /* error handler class not found */
986 log_module(PY_LOG, LOG_WARNING, "Failed to create handler object");
987 return 0;
988 }
989 }
990 else {
991 //PyErr_Print();
992 python_log_module();
993 log_module(PY_LOG, LOG_WARNING, "Failed to load modpython.py");
994 return 0;
995 }
996 return 0;
997 }
998
999 int
1000 python_finalize(void) {
1001 /* Called after X3 is fully up and running.
1002 Code can be put here that needs to run to init things, but
1003 which is sensitive to everything else in x3 being up and ready
1004 to go.
1005 */
1006
1007 PyRun_SimpleString("print 'Hello, World of Python!'");
1008 log_module(PY_LOG, LOG_INFO, "python module finalize");
1009
1010 return 1;
1011 }
1012
1013 static void
1014 python_cleanup(void) {
1015 /* Called on shutdown of the python module (or before reloading)
1016 */
1017
1018 log_module(PY_LOG, LOG_INFO, "python module cleanup");
1019 if (PyErr_Occurred())
1020 PyErr_Clear();
1021 Py_Finalize(); /* Shut down python enterpreter */
1022 }
1023
1024 /* ---------------------------------------------------------------------------------- *
1025 Python module command handlers.
1026 */
1027 static MODCMD_FUNC(cmd_reload) {
1028 /* reload the python system completely
1029 */
1030 log_module(PY_LOG, LOG_INFO, "Shutting python down");
1031 python_cleanup();
1032 log_module(PY_LOG, LOG_INFO, "Loading python stuff");
1033 if(python_load()) {
1034 reply("PYMSG_RELOAD_SUCCESS");
1035 }
1036 else {
1037 reply("PYMSG_RELOAD_FAILED");
1038 }
1039 return 1;
1040 }
1041
1042 static char* format_python_error(int space_nls) {
1043 PyObject* extype = NULL, *exvalue = NULL, *extraceback = NULL;
1044 PyObject* pextypestr = NULL, *pexvaluestr = NULL;
1045 char* extypestr = NULL, *exvaluestr = NULL;
1046 size_t retvallen = 0;
1047 char* retval = NULL, *tmp;
1048
1049 PyErr_Fetch(&extype, &exvalue, &extraceback);
1050 if (!extype)
1051 goto cleanup;
1052
1053 pextypestr = PyObject_Str(extype);
1054 if (!pextypestr)
1055 goto cleanup;
1056 extypestr = PyString_AsString(pextypestr);
1057 if (!extypestr)
1058 goto cleanup;
1059
1060 pexvaluestr = PyObject_Str(exvalue);
1061 if (pexvaluestr)
1062 exvaluestr = PyString_AsString(pexvaluestr);
1063
1064 retvallen = strlen(extypestr) + (exvaluestr ? strlen(exvaluestr) + 2 : 0) + 1;
1065 retval = (char*)malloc(retvallen);
1066 if (exvaluestr)
1067 snprintf(retval, retvallen, "%s: %s", extypestr, exvaluestr);
1068 else
1069 strncpy(retval, extypestr, retvallen);
1070
1071 if (space_nls) {
1072 tmp = retval;
1073 while (*tmp) {
1074 if (*tmp == '\n')
1075 *tmp = ' ';
1076 ++tmp;
1077 }
1078 }
1079
1080 cleanup:
1081 if (PyErr_Occurred())
1082 PyErr_Clear(); /* ignore errors caused by formatting */
1083 Py_XDECREF(extype);
1084 Py_XDECREF(exvalue);
1085 Py_XDECREF(extraceback);
1086 Py_XDECREF(pextypestr);
1087 Py_XDECREF(pexvaluestr);
1088
1089 if (retval)
1090 return retval;
1091
1092 return strdup("unknown exception");
1093 }
1094
1095 static MODCMD_FUNC(cmd_run) {
1096 /* this method allows running arbitrary python commands.
1097 * use with care.
1098 */
1099 char* msg;
1100 PyObject* py_main_module;
1101 PyObject* py_globals;
1102 PyObject* py_locals;
1103 PyObject* py_retval;
1104 PyObject* extype, *exvalue, *extraceback;
1105 PyObject* exvaluestr = NULL;
1106 char* exmsg = NULL, *exmsgptr;
1107
1108 py_main_module = PyImport_AddModule("__main__");
1109 py_globals = py_locals = PyModule_GetDict(py_main_module);
1110
1111 msg = unsplit_string(argv + 1, argc - 1, NULL);
1112
1113 py_retval = PyRun_String(msg, Py_file_input, py_globals, py_locals);
1114 if (py_retval == NULL) {
1115 PyErr_Fetch(&extype, &exvalue, &extraceback);
1116 if (exvalue != NULL) {
1117 exvaluestr = PyObject_Str(exvalue);
1118 exmsg = strdup(PyString_AS_STRING(exvaluestr));
1119 exmsgptr = exmsg;
1120 while (exmsgptr && *exmsgptr) {
1121 if (*exmsgptr == '\n' || *exmsgptr == '\r' || *exmsgptr == '\t')
1122 *exmsgptr = ' ';
1123 exmsgptr++;
1124 }
1125 }
1126 if (extype != NULL && exvalue != NULL && PyType_Check(extype)) {
1127 reply("PYMSG_RUN_EXCEPTION", ((PyTypeObject*)extype)->tp_name, exmsg);
1128 } else
1129 reply("PYMSG_RUN_UNKNOWN_EXCEPTION");
1130
1131 if (extype != NULL)
1132 Py_DECREF(extype);
1133 if (exvalue != NULL)
1134 Py_DECREF(exvalue);
1135 if (extraceback != NULL)
1136 Py_DECREF(extraceback);
1137 if (exvaluestr != NULL)
1138 Py_DECREF(exvaluestr);
1139 if (exmsg)
1140 free(exmsg);
1141 } else {
1142 Py_DECREF(py_retval);
1143 }
1144
1145 return 1;
1146 }
1147
1148 #define numstrargs(X) sizeof(X) / sizeof(*X)
1149 static MODCMD_FUNC(cmd_command) {
1150 char *plugin = argv[1];
1151 char *command = argv[2];
1152 char *msg; /* args */
1153 if(argc > 3) {
1154 msg = unsplit_string(argv + 3, argc - 3, NULL);
1155 }
1156 else {
1157 msg = "";
1158 }
1159 char *args[] = {plugin, command, msg};
1160 python_call_handler("cmd_command", args, numstrargs(args), cmd->parent->bot->nick, user?user->nick:"", channel?channel->name:"");
1161 return 1;
1162 }
1163
1164 static void modpython_conf_read(void) {
1165 dict_t conf_node;
1166 char const* str;
1167
1168 if (!(conf_node = conf_get_data(MODPYTHON_CONF_NAME, RECDB_OBJECT))) {
1169 log_module(PY_LOG, LOG_ERROR, "config node '%s' is missing or has wrong type", MODPYTHON_CONF_NAME);
1170 return;
1171 }
1172
1173 str = database_get_data(conf_node, "scripts_dir", RECDB_QSTRING);
1174 modpython_conf.scripts_dir = str ? str : "./";
1175
1176 str = database_get_data(conf_node, "main_module", RECDB_QSTRING);
1177 modpython_conf.main_module = str ? str : "modpython";
1178 }
1179
1180 int python_init(void) {
1181 /* X3 calls this function on init of the module during startup. We use it to
1182 do all our setup tasks and bindings
1183 */
1184
1185 PY_LOG = log_register_type("Python", "file:python.log");
1186 python_module = module_register("python", PY_LOG, "mod-python.help", NULL);
1187 conf_register_reload(modpython_conf_read);
1188
1189 log_module(PY_LOG, LOG_INFO, "python module init");
1190 message_register_table(msgtab);
1191
1192 /*
1193 reg_auth_func(python_check_messages);
1194 reg_handle_rename_func(python_rename_account);
1195 reg_unreg_func(python_unreg_account);
1196 conf_register_reload(python_conf_read);
1197 saxdb_register("python", python_saxdb_read, python_saxdb_write);
1198 modcmd_register(python_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
1199 */
1200 modcmd_register(python_module, "reload", cmd_reload, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1201 modcmd_register(python_module, "run", cmd_run, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1202 modcmd_register(python_module, "command", cmd_command, 3, MODCMD_REQUIRE_STAFF, NULL);
1203
1204 // Please help us by implementing any of the callbacks listed as TODO below. They already exist
1205 // in x3, they just need handle_ bridges implemented. (see python_handle_join for an example)
1206 reg_server_link_func(python_handle_server_link);
1207 reg_new_user_func(python_handle_new_user);
1208 reg_nick_change_func(python_handle_nick_change);
1209 //TODO: reg_del_user_func(python_handle_del_user);
1210 //TODO: reg_account_func(python_handle_account); /* stamping of account name to the ircd */
1211 //TODO: reg_handle_rename_func(python_handle_handle_rename); /* handle used to ALSO mean account name */
1212 //TODO: reg_failpw_func(python_handle_failpw);
1213 //TODO: reg_allowauth_func(python_handle_allowauth);
1214 //TODO: reg_handle_merge_func(python_handle_merge);
1215 //
1216 //TODO: reg_oper_func(python_handle_oper);
1217 //TODO: reg_new_channel_func(python_handle_new_channel);
1218 reg_join_func(python_handle_join);
1219 //TODO: reg_del_channel_func(python_handle_del_channel);
1220 //TODO: reg_part_func(python_handle_part);
1221 //TODO: reg_kick_func(python_handle_kick);
1222 //TODO: reg_topic_func(python_handle_topic);
1223 //TODO: reg_channel_mode_func(python_handle_channel_mode);
1224
1225 //TODO: reg_privmsg_func(python_handle_privmsg);
1226 //TODO: reg_notice_func
1227 //TODO: reg_svccmd_unbind_func(python_handle_svccmd_unbind);
1228 //TODO: reg_chanmsg_func(python_handle_chanmsg);
1229 //TODO: reg_allchanmsg_func
1230 //TODO: reg_user_mode_func
1231
1232 reg_exit_func(python_cleanup);
1233
1234 python_load();
1235 return 1;
1236 }
1237
1238 #endif /* WITH_PYTHON */