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