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