]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-python.c
5548cc625193b42f58fecb4e40949f03339c2721
[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 struct py_timeq_extra {
673 PyObject* func;
674 PyObject* arg;
675 };
676
677 static
678 void py_timeq_callback(void* data) {
679 struct py_timeq_extra* extra = (struct py_timeq_extra*)data;
680
681 PyObject* retval = PyObject_Call(extra->func, extra->arg, NULL);
682 Py_XDECREF(retval);
683
684 Py_DECREF(extra->func);
685 Py_DECREF(extra->arg);
686 }
687
688 static PyObject*
689 emb_timeq_add(UNUSED_ARG(PyObject* self), PyObject* args) {
690 time_t when;
691 PyObject* func, *arg;
692 struct py_timeq_extra* extra;
693
694 if (!PyArg_ParseTuple(args, "lOO", &when, &func, &arg))
695 return NULL;
696
697 if (!PyFunction_Check(func)) {
698 PyErr_SetString(PyExc_Exception, "first argument must be a function");
699 return NULL;
700 }
701
702 if (!PyTuple_Check(arg)) {
703 PyErr_SetString(PyExc_Exception, "second argument must be a tuple");
704 return NULL;
705 }
706
707 extra = malloc(sizeof(struct py_timeq_extra));
708 if (extra == NULL) {
709 PyErr_SetString(PyExc_Exception, "out of memory");
710 return NULL;
711 }
712
713 Py_INCREF(func);
714 Py_INCREF(arg);
715
716 extra->func = func;
717 extra->arg = arg;
718
719 timeq_add(when, py_timeq_callback, (void*)extra);
720
721 return Py_None;
722 }
723
724 static PyObject*
725 emb_timeq_del(UNUSED_ARG(PyObject* self), PyObject* args) {
726 /* NOTE:
727 * This function will delete all python-added callbacks registered
728 * to run at the given time, regardless of their data. This is due to
729 * the unnecessary extra burden it would require to get the same data for
730 * multiple runs.
731 */
732 time_t when;
733
734 if (!PyArg_ParseTuple(args, "l", &when))
735 return NULL;
736
737 timeq_del(when, py_timeq_callback, NULL, TIMEQ_IGNORE_DATA);
738
739 return Py_None;
740 }
741
742 static PyMethodDef EmbMethods[] = {
743 /* Communication methods */
744 {"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"},
745 {"send_target_privmsg", emb_send_target_privmsg, METH_VARARGS, "Send a message to somewhere"},
746 {"send_target_notice", emb_send_target_notice, METH_VARARGS, "Send a notice to somewhere"},
747 {"log_module", emb_log_module, METH_VARARGS, "Log something using the X3 log subsystem"},
748 //TODO: {"exec_cmd", emb_exec_cmd, METH_VARARGS, "execute x3 command provided"},
749 // This should use environment from "python command" call to pass in, if available
750 {"kill", emb_kill, METH_VARARGS, "Kill someone"},
751 //TODO: {"shun"
752 //TODO: {"unshun"
753 //TODO: {"gline", emb_gline, METH_VARARGS, "gline a mask"},
754 //TODO: {"ungline", emb_ungline, METH_VARARGS, "remove a gline"},
755 //TODO: {"kick", emb_kick, METH_VARARGS, "kick someone from a channel"},
756 //TODO: {"channel_mode", emb_channel_mode, METH_VARARGS, "set modes on a channel"},
757 //TODO: {"user_mode", emb_user_mode, METH_VARARGS, "Have x3 set usermodes on one of its own nicks"},
758 //
759 //TODO: {"get_config", emb_get_config, METH_VARARGS, "get x3.conf settings into a nested dict"},
760 //TODO: {"config_set", emb_config_set, METH_VARARGS, "change a config setting 'on-the-fly'."},
761 //
762 {"timeq_add", emb_timeq_add, METH_VARARGS, "add function to callback to the event system"},
763 {"timeq_del", emb_timeq_del, METH_VARARGS, "remove function to callback from the event system"},
764
765 /* Information gathering methods */
766 {"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"},
767 {"get_users", emb_get_users, METH_VARARGS, "Get all connected users"},
768 {"get_channel", emb_get_channel, METH_VARARGS, "Get details about a channel"},
769 {"get_channels", emb_get_channels, METH_VARARGS, "Get all channels"},
770 {"get_server", emb_get_server, METH_VARARGS, "Get details about a server"},
771 {"get_servers", emb_get_servers, METH_VARARGS, "Get all server names"},
772 {"get_account", emb_get_account, METH_VARARGS, "Get details about an account"},
773 {"get_accounts", emb_get_accounts, METH_VARARGS, "Get all nickserv accounts"},
774 {"get_info", emb_get_info, METH_VARARGS, "Get various misc info about x3"},
775 /* null terminator */
776 {NULL, NULL, 0, NULL}
777 };
778
779
780 /*
781 These functions set up the embedded environment for us to call out to
782 modpython.py class methods.
783 */
784
785 void python_log_module() {
786 /* Attempt to convert python errors to x3 log system */
787 PyObject *exc, *tb, *value, *tmp;
788 char *str_exc = "NONE";
789 char *str_value = "NONE";
790 char *str_tb = "NONE";
791
792 PyErr_Fetch(&exc, &value, &tb);
793
794 if(exc) {
795 if((tmp = PyObject_Str(exc)))
796 str_exc = PyString_AsString(tmp);
797 }
798 if(value) {
799 if((tmp = PyObject_Str(value)))
800 str_value = PyString_AsString(tmp);
801 }
802 if(tb) {
803 if((tmp = PyObject_Str(tb)))
804 str_tb = PyString_AsString(tmp);
805 }
806
807 /* Now restore it so we can print it (just in case)
808 * (should we do this only when running in debug mode?) */
809 PyErr_Restore(exc, value, tb);
810 PyErr_Print(); /* which of course, clears it again.. */
811
812 log_module(PY_LOG, LOG_WARNING, "PYTHON error: %s, value: %s", str_exc, str_value);
813
814 /* TODO: get the traceback using the traceback module via C api so we can add it to the X3 logs. See
815 * http://mail.python.org/pipermail/python-list/2003-March/192226.html */
816 // (this doesnt work, str_tb is just an object hashid) log_module(PY_LOG, LOG_INFO, "PYTHON error, traceback: %s", str_tb);
817 }
818
819
820 PyObject *python_build_handler_args(size_t argc, char *args[], PyObject *pIrcObj) {
821 /* Sets up a python tuple with passed in arguments, prefixed by the Irc instance
822 which handlers use to interact with C.
823 argc = number of args
824 args = array of args
825 pIrcObj = instance of the irc class
826 */
827 size_t i = 0, n;
828 PyObject *pArgs = NULL;
829
830 pArgs = PyTuple_New(argc + 1);
831 Py_INCREF(pIrcObj);
832 PyTuple_SetItem(pArgs, i++, pIrcObj);
833
834 if(args && argc) {
835 PyObject *pValue;
836 for(n = 0; n < argc; ++n) {
837 pValue = PyString_FromString(args[n]);
838 if(!pValue) {
839 Py_DECREF(pArgs);
840 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[n]);
841 return NULL;
842 }
843 PyTuple_SetItem(pArgs, n+i, pValue);
844 }
845 }
846 return pArgs;
847 }
848
849 PyObject *python_build_args(size_t argc, char *args[]) {
850 /* Builds the passed in arguments into a python argument tuple.
851 argc = number of args
852 args = array of args
853 */
854 size_t i;
855 PyObject *pArgs = NULL;
856
857 if(args && argc) {
858 pArgs = PyTuple_New(argc);
859 PyObject *pValue;
860 for(i = 0; i< argc; ++i) {
861 pValue = PyString_FromString(args[i]);
862 if(!pValue) {
863 Py_DECREF(pArgs);
864 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[i]);
865 return NULL;
866 }
867 PyTuple_SetItem(pArgs, i, pValue);
868 }
869 }
870 return pArgs;
871 }
872
873
874 PyObject *new_irc_object(char *command_service, char *command_caller, char *command_target) {
875 /* Creates a new instance of the irc class (from modpython.py) which is initalized
876 with current environment details like which service were talking to.
877 command_service = which service we are talking to, or empty string if none
878 command_caller = nick of user generating message, or empty string if none
879 command_target = If were reacting to something on a channel, this will
880 be set to the name of the channel. Otherwise empty
881 */
882 PyObject *pIrcArgs = NULL;
883 PyObject *pIrcClass;
884 PyObject *pIrcObj;
885
886 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate irc class; %s %s %s", command_service, command_caller, command_target);
887 pIrcClass = PyObject_GetAttrString(base_module, "irc");
888 /* pIrcClass is a new reference */
889 if(pIrcClass && PyCallable_Check(pIrcClass)) {
890 //size_t i;
891 char *ircargs[] = {command_service, command_caller, command_target};
892 //PyObject *pValue;
893
894 pIrcArgs = python_build_args(3, ircargs);
895 pIrcObj = PyObject_CallObject(pIrcClass, pIrcArgs);
896 if(!pIrcObj) {
897 log_module(PY_LOG, LOG_ERROR, "IRC Class failed to load");
898 python_log_module();
899 //PyErr_Print();
900 }
901 if(pIrcArgs != NULL) {
902 Py_DECREF(pIrcArgs);
903 }
904 Py_DECREF(pIrcClass);
905 return pIrcObj;
906 }
907 else {
908 /* need to free pIrcClass here if it WAS found but was NOT callable? */
909 log_module(PY_LOG, LOG_ERROR, "Unable to find irc class");
910 return NULL;
911 }
912 }
913
914 int python_call_handler(char *handler, char *args[], size_t argc, char *command_service, char *command_caller, char *command_target) {
915 /* This is how we talk to modpython.c. First a new instance of the irc class is created using these
916 arguments to setup the current environment. Then the named method of the handler object is called
917 with the givin arguments.
918 */
919 PyObject *pIrcObj;
920 PyObject *pArgs;
921 PyObject *pMethod;
922 PyObject *pValue;
923
924 log_module(PY_LOG, LOG_INFO, "attempting to call handler %s.", handler);
925 if(base_module != NULL && handler_object != NULL) {
926 pIrcObj = new_irc_object(command_service, command_caller, command_target);
927 if(!pIrcObj) {
928 log_module(PY_LOG, LOG_INFO, "Can't get irc object. Bailing.");
929 return 0;
930 }
931
932 pArgs = python_build_handler_args(argc, args, pIrcObj);
933 pMethod = PyObject_GetAttrString(handler_object, handler);
934 if(pMethod && PyCallable_Check(pMethod)) {
935 /* Call the method, with the arguments */
936 pValue = PyObject_CallObject(pMethod, pArgs);
937 if(pArgs) {
938 Py_DECREF(pArgs);
939 }
940 if(pValue != NULL) {
941 int ret;
942 ret = PyInt_AsLong(pValue);
943 if(ret == -1 && PyErr_Occurred()) {
944 //PyErr_Print();
945 log_module(PY_LOG, LOG_INFO, "error converting return value of handler %s to type long. ", handler);
946 python_log_module();
947 ret = 0;
948 }
949 log_module(PY_LOG, LOG_INFO, "handler %s was run successfully, returned %d.", handler, ret);
950 Py_DECREF(pValue);
951 Py_DECREF(pIrcObj);
952 Py_DECREF(pMethod);
953 return ret;
954 }
955 else {
956 /* TODO: instead of print errors, get them as strings
957 * and deal with them with normal x3 log system. */
958 log_module(PY_LOG, LOG_WARNING, "call to handler %s failed", handler);
959 //PyErr_Print();
960 python_log_module();
961 Py_DECREF(pIrcObj);
962 Py_DECREF(pMethod);
963 return 0;
964 }
965 }
966 else { /* couldn't find handler methed */
967 Py_DECREF(pArgs);
968 /* Free pMethod if it was found but not callable? */
969 log_module(PY_LOG, LOG_ERROR, "Cannot find handler %s.", handler);
970 return 0;
971
972 }
973 }
974 else { /* No base module.. no python? */
975 log_module(PY_LOG, LOG_INFO, "Cannot handle %s, Python is not initialized.", handler);
976 return 0;
977 }
978 }
979
980 PyObject *python_new_handler_object() {
981 /* Create a new instance of the handler class.
982 This is called during python initilization (or reload)
983 and the result is saved and re-used.
984 */
985 PyObject *pHandlerClass, *pHandlerObj;
986
987 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate python class handler");
988 pHandlerClass = PyObject_GetAttrString(base_module, "handler");
989 /* Class is a new reference */
990 if(pHandlerClass && PyCallable_Check(pHandlerClass)) {
991 /*PyObject *pValue; */
992
993 pHandlerObj = PyObject_CallObject(pHandlerClass, NULL);
994 if(pHandlerObj != NULL) {
995 log_module(PY_LOG, LOG_INFO, "Created new python handler object.");
996 return pHandlerObj;
997 }
998 else {
999 log_module(PY_LOG, LOG_ERROR, "Unable to instanciate handler object");
1000 //PyErr_Print();
1001 python_log_module();
1002 return NULL;
1003 }
1004 }
1005 else {
1006 log_module(PY_LOG, LOG_ERROR, "Unable to find handler class");
1007 //PyErr_Print();
1008 python_log_module();
1009 if(pHandlerClass) {
1010 Py_DECREF(pHandlerClass);
1011 }
1012 return NULL;
1013 }
1014 }
1015
1016 /* ------------------------------------------------------------------------------- *
1017 Some gateway functions to convert x3 callbacks into modpython.py callbacks.
1018 Mostly we just build relevant args and call the proper handler method
1019
1020 debate: do we just register these and check them in python
1021 for every one (slow?) or actually work out if a plugin needs
1022 it first? We will start by doing it every time.
1023 */
1024 static int
1025 python_handle_join(struct modeNode *mNode)
1026 {
1027 /* callback for handle_join events.
1028 */
1029 struct userNode *user = mNode->user;
1030 struct chanNode *channel = mNode->channel;
1031
1032
1033 log_module(PY_LOG, LOG_INFO, "python module handle_join");
1034 if(!channel||!user) {
1035 log_module(PY_LOG, LOG_WARNING, "Python code got join without channel or user!");
1036 return 0;
1037 }
1038 else {
1039 char *args[] = {channel->name, user->nick};
1040 return python_call_handler("join", args, 2, "", "", "");
1041 }
1042 }
1043
1044 static int
1045 python_handle_server_link(struct server *server)
1046 {
1047 log_module(PY_LOG, LOG_INFO, "python module handle_server_link");
1048 if(!server) {
1049 log_module(PY_LOG, LOG_WARNING, "Python code got server link without server!");
1050 return 0;
1051 }
1052 else {
1053 char *args[] = {server->name, server->description};
1054 return python_call_handler("server_link", args, 2, "", "", "");
1055 }
1056 }
1057
1058 static int
1059 python_handle_new_user(struct userNode *user)
1060 {
1061 log_module(PY_LOG, LOG_INFO, "Python module handle_new_user");
1062 if(!user) {
1063 log_module(PY_LOG, LOG_WARNING, "Python code got new_user without the user");
1064 return 0;
1065 }
1066 else {
1067 char *args[] = {user->nick, user->ident, user->hostname, user->info};
1068 return python_call_handler("new_user", args, 4, "", "", "");
1069 }
1070 }
1071
1072 static void
1073 python_handle_nick_change(struct userNode *user, const char *old_nick)
1074 {
1075 log_module(PY_LOG, LOG_INFO, "Python module handle_nick_change");
1076 if(!user) {
1077 log_module(PY_LOG, LOG_WARNING, "Python code got nick_change without the user!");
1078 }
1079 else {
1080 char *args[] = {user->nick, (char *)old_nick};
1081 python_call_handler("nick_change", args, 2, "", "", "");
1082 }
1083 }
1084
1085 /* ----------------------------------------------------------------------------- */
1086
1087
1088 int python_load() {
1089 /* Init the python engine and do init work on modpython.py
1090 This is called during x3 startup, and on a python reload
1091 */
1092 PyObject *pName;
1093 char* buffer;
1094 char* env = getenv("PYTHONPATH");
1095
1096 if (env)
1097 env = strdup(env);
1098
1099 if (!env)
1100 setenv("PYTHONPATH", modpython_conf.scripts_dir, 1);
1101 else if (!strstr(env, modpython_conf.scripts_dir)) {
1102 buffer = (char*)malloc(strlen(env) + strlen(modpython_conf.scripts_dir) + 2);
1103 sprintf(buffer, "%s:%s", modpython_conf.scripts_dir, env);
1104 setenv("PYTHONPATH", buffer, 1);
1105 free(buffer);
1106 free(env);
1107 }
1108
1109 Py_Initialize();
1110 Py_InitModule("_svc", EmbMethods);
1111 pName = PyString_FromString(modpython_conf.main_module);
1112 base_module = PyImport_Import(pName);
1113 Py_DECREF(pName);
1114
1115 Py_XDECREF(handler_object);
1116 handler_object = NULL;
1117
1118 if(base_module != NULL) {
1119 handler_object = python_new_handler_object();
1120 if(handler_object) {
1121 python_call_handler("init", NULL, 0, "", "", "");
1122 return 1;
1123 }
1124 else {
1125 /* error handler class not found */
1126 log_module(PY_LOG, LOG_WARNING, "Failed to create handler object");
1127 return 0;
1128 }
1129 }
1130 else {
1131 //PyErr_Print();
1132 python_log_module();
1133 log_module(PY_LOG, LOG_WARNING, "Failed to load modpython.py");
1134 return 0;
1135 }
1136 return 0;
1137 }
1138
1139 int
1140 python_finalize(void) {
1141 /* Called after X3 is fully up and running.
1142 Code can be put here that needs to run to init things, but
1143 which is sensitive to everything else in x3 being up and ready
1144 to go.
1145 */
1146
1147 PyRun_SimpleString("print 'Hello, World of Python!'");
1148 log_module(PY_LOG, LOG_INFO, "python module finalize");
1149
1150 return 1;
1151 }
1152
1153 static void
1154 python_cleanup(void) {
1155 /* Called on shutdown of the python module (or before reloading)
1156 */
1157
1158 log_module(PY_LOG, LOG_INFO, "python module cleanup");
1159 if (PyErr_Occurred())
1160 PyErr_Clear();
1161 Py_Finalize(); /* Shut down python enterpreter */
1162 }
1163
1164 /* ---------------------------------------------------------------------------------- *
1165 Python module command handlers.
1166 */
1167 static MODCMD_FUNC(cmd_reload) {
1168 /* reload the python system completely
1169 */
1170 log_module(PY_LOG, LOG_INFO, "Shutting python down");
1171 python_cleanup();
1172 log_module(PY_LOG, LOG_INFO, "Loading python stuff");
1173 if(python_load()) {
1174 reply("PYMSG_RELOAD_SUCCESS");
1175 }
1176 else {
1177 reply("PYMSG_RELOAD_FAILED");
1178 }
1179 return 1;
1180 }
1181
1182 static char* format_python_error(int space_nls) {
1183 PyObject* extype = NULL, *exvalue = NULL, *extraceback = NULL;
1184 PyObject* pextypestr = NULL, *pexvaluestr = NULL;
1185 char* extypestr = NULL, *exvaluestr = NULL;
1186 size_t retvallen = 0;
1187 char* retval = NULL, *tmp;
1188
1189 PyErr_Fetch(&extype, &exvalue, &extraceback);
1190 if (!extype)
1191 goto cleanup;
1192
1193 pextypestr = PyObject_Str(extype);
1194 if (!pextypestr)
1195 goto cleanup;
1196 extypestr = PyString_AsString(pextypestr);
1197 if (!extypestr)
1198 goto cleanup;
1199
1200 pexvaluestr = PyObject_Str(exvalue);
1201 if (pexvaluestr)
1202 exvaluestr = PyString_AsString(pexvaluestr);
1203
1204 retvallen = strlen(extypestr) + (exvaluestr ? strlen(exvaluestr) + 2 : 0) + 1;
1205 retval = (char*)malloc(retvallen);
1206 if (exvaluestr)
1207 snprintf(retval, retvallen, "%s: %s", extypestr, exvaluestr);
1208 else
1209 strncpy(retval, extypestr, retvallen);
1210
1211 if (space_nls) {
1212 tmp = retval;
1213 while (*tmp) {
1214 if (*tmp == '\n')
1215 *tmp = ' ';
1216 ++tmp;
1217 }
1218 }
1219
1220 cleanup:
1221 if (PyErr_Occurred())
1222 PyErr_Clear(); /* ignore errors caused by formatting */
1223 Py_XDECREF(extype);
1224 Py_XDECREF(exvalue);
1225 Py_XDECREF(extraceback);
1226 Py_XDECREF(pextypestr);
1227 Py_XDECREF(pexvaluestr);
1228
1229 if (retval)
1230 return retval;
1231
1232 return strdup("unknown exception");
1233 }
1234
1235 static MODCMD_FUNC(cmd_run) {
1236 /* this method allows running arbitrary python commands.
1237 * use with care.
1238 */
1239 char* msg;
1240 PyObject* py_main_module;
1241 PyObject* py_globals;
1242 PyObject* py_locals;
1243 PyObject* py_retval;
1244 PyObject* extype, *exvalue, *extraceback;
1245 PyObject* exvaluestr = NULL;
1246 char* exmsg = NULL, *exmsgptr;
1247
1248 py_main_module = PyImport_AddModule("__main__");
1249 py_globals = py_locals = PyModule_GetDict(py_main_module);
1250
1251 msg = unsplit_string(argv + 1, argc - 1, NULL);
1252
1253 py_retval = PyRun_String(msg, Py_file_input, py_globals, py_locals);
1254 if (py_retval == NULL) {
1255 PyErr_Fetch(&extype, &exvalue, &extraceback);
1256 if (exvalue != NULL) {
1257 exvaluestr = PyObject_Str(exvalue);
1258 exmsg = strdup(PyString_AS_STRING(exvaluestr));
1259 exmsgptr = exmsg;
1260 while (exmsgptr && *exmsgptr) {
1261 if (*exmsgptr == '\n' || *exmsgptr == '\r' || *exmsgptr == '\t')
1262 *exmsgptr = ' ';
1263 exmsgptr++;
1264 }
1265 }
1266 if (extype != NULL && exvalue != NULL && PyType_Check(extype)) {
1267 reply("PYMSG_RUN_EXCEPTION", ((PyTypeObject*)extype)->tp_name, exmsg);
1268 } else
1269 reply("PYMSG_RUN_UNKNOWN_EXCEPTION");
1270
1271 if (extype != NULL)
1272 Py_DECREF(extype);
1273 if (exvalue != NULL)
1274 Py_DECREF(exvalue);
1275 if (extraceback != NULL)
1276 Py_DECREF(extraceback);
1277 if (exvaluestr != NULL)
1278 Py_DECREF(exvaluestr);
1279 if (exmsg)
1280 free(exmsg);
1281 } else {
1282 Py_DECREF(py_retval);
1283 }
1284
1285 return 1;
1286 }
1287
1288 #define numstrargs(X) sizeof(X) / sizeof(*X)
1289 static MODCMD_FUNC(cmd_command) {
1290 char *plugin = argv[1];
1291 char *command = argv[2];
1292 char *msg; /* args */
1293 if(argc > 3) {
1294 msg = unsplit_string(argv + 3, argc - 3, NULL);
1295 }
1296 else {
1297 msg = "";
1298 }
1299 char *args[] = {plugin, command, msg};
1300 python_call_handler("cmd_command", args, numstrargs(args), cmd->parent->bot->nick, user?user->nick:"", channel?channel->name:"");
1301 return 1;
1302 }
1303
1304 static void modpython_conf_read(void) {
1305 dict_t conf_node;
1306 char const* str;
1307
1308 if (!(conf_node = conf_get_data(MODPYTHON_CONF_NAME, RECDB_OBJECT))) {
1309 log_module(PY_LOG, LOG_ERROR, "config node '%s' is missing or has wrong type", MODPYTHON_CONF_NAME);
1310 return;
1311 }
1312
1313 str = database_get_data(conf_node, "scripts_dir", RECDB_QSTRING);
1314 modpython_conf.scripts_dir = str ? str : "./";
1315
1316 str = database_get_data(conf_node, "main_module", RECDB_QSTRING);
1317 modpython_conf.main_module = str ? str : "modpython";
1318 }
1319
1320 int python_init(void) {
1321 /* X3 calls this function on init of the module during startup. We use it to
1322 do all our setup tasks and bindings
1323 */
1324
1325 PY_LOG = log_register_type("Python", "file:python.log");
1326 python_module = module_register("python", PY_LOG, "mod-python.help", NULL);
1327 conf_register_reload(modpython_conf_read);
1328
1329 log_module(PY_LOG, LOG_INFO, "python module init");
1330 message_register_table(msgtab);
1331
1332 /*
1333 reg_auth_func(python_check_messages);
1334 reg_handle_rename_func(python_rename_account);
1335 reg_unreg_func(python_unreg_account);
1336 conf_register_reload(python_conf_read);
1337 saxdb_register("python", python_saxdb_read, python_saxdb_write);
1338 modcmd_register(python_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
1339 */
1340 modcmd_register(python_module, "reload", cmd_reload, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1341 modcmd_register(python_module, "run", cmd_run, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1342 modcmd_register(python_module, "command", cmd_command, 3, MODCMD_REQUIRE_STAFF, NULL);
1343
1344 // Please help us by implementing any of the callbacks listed as TODO below. They already exist
1345 // in x3, they just need handle_ bridges implemented. (see python_handle_join for an example)
1346 reg_server_link_func(python_handle_server_link);
1347 reg_new_user_func(python_handle_new_user);
1348 reg_nick_change_func(python_handle_nick_change);
1349 //TODO: reg_del_user_func(python_handle_del_user);
1350 //TODO: reg_account_func(python_handle_account); /* stamping of account name to the ircd */
1351 //TODO: reg_handle_rename_func(python_handle_handle_rename); /* handle used to ALSO mean account name */
1352 //TODO: reg_failpw_func(python_handle_failpw);
1353 //TODO: reg_allowauth_func(python_handle_allowauth);
1354 //TODO: reg_handle_merge_func(python_handle_merge);
1355 //
1356 //TODO: reg_oper_func(python_handle_oper);
1357 //TODO: reg_new_channel_func(python_handle_new_channel);
1358 reg_join_func(python_handle_join);
1359 //TODO: reg_del_channel_func(python_handle_del_channel);
1360 //TODO: reg_part_func(python_handle_part);
1361 //TODO: reg_kick_func(python_handle_kick);
1362 //TODO: reg_topic_func(python_handle_topic);
1363 //TODO: reg_channel_mode_func(python_handle_channel_mode);
1364
1365 //TODO: reg_privmsg_func(python_handle_privmsg);
1366 //TODO: reg_notice_func
1367 //TODO: reg_svccmd_unbind_func(python_handle_svccmd_unbind);
1368 //TODO: reg_chanmsg_func(python_handle_chanmsg);
1369 //TODO: reg_allchanmsg_func
1370 //TODO: reg_user_mode_func
1371
1372 reg_exit_func(python_cleanup);
1373
1374 python_load();
1375 return 1;
1376 }
1377
1378 #endif /* WITH_PYTHON */