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