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