]> jfr.im git - irc/evilnet/x3.git/blame - src/mod-python.c
More work on python scripting.
[irc/evilnet/x3.git] / src / mod-python.c
CommitLineData
0b350353 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 "sendmail.h"
34#include "timeq.h"
35
a2c8c575 36/* TODO notes
37 *
38 * - Impliment most of proto-p10 irc_* commands for calling from scripts
39 * - Impliment functions to look up whois, channel, account, and reg-channel info for scripts
40 * - Impliment x3.conf settings for python variables like include path, etc.
4c216694 41 * - modpython.py calls for everything you can reg_ a handler for in x3
a2c8c575 42 * - Some kind of system for getting needed binds bound automagicaly to make it easier
43 * to run peoples scripts and mod-python in general.
44 */
0b350353 45
46static const struct message_entry msgtab[] = {
caf97651 47 { "PYMSG_RELOAD_SUCCESS", "Reloaded Python scripts successfully." },
48 { "PYMSG_RELOAD_FAILED", "Error reloading Python scripts." },
0b350353 49 { NULL, NULL } /* sentenal */
50};
51
52static struct log_type *PY_LOG;
53const char *python_module_deps[] = { NULL };
54static struct module *python_module;
55
caf97651 56PyObject *base_module = NULL; /* Base python handling library */
cbfd323c 57PyObject *handler_object = NULL; /* instanciation of handler class */
caf97651 58
4c216694 59
60/* ---------------------------------------------------------------------- *
61 Some hooks you can call from modpython.py to interact with the
62 service, and IRC. These emb_* functions are available as svc.*
63 in python.
64 */
65
a2c8c575 66static PyObject*
67emb_dump(PyObject *self, PyObject *args)
68{
4c216694 69 /* Dump a raw string into the socket
70 usage: svc.dump(<P10 string>)
71 */
a2c8c575 72 char *buf;
73 int ret = 0;
74 char linedup[MAXLEN];
75
76 if(!PyArg_ParseTuple(args, "s:dump", &buf ))
77 return NULL;
78 safestrncpy(linedup, buf, sizeof(linedup));
79 if(parse_line(linedup, 1)) {
80 irc_raw(buf);
81 ret = 1;
82 }
83 return Py_BuildValue("i", ret);
84}
85
86static PyObject*
87emb_send_target_privmsg(PyObject *self, PyObject *args)
88{
4c216694 89 /* Send a privmsg
90 usage: svc.send_target_privmsg(<servicenick_from>, <nick_to>, <message>)
91 */
a2c8c575 92 int ret = 0;
93 char *servicenick;
94 char *channel;
95 char *buf;
96
97 struct service *service;
98
99 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &channel, &buf ))
100 return NULL;
101 if(!(service = service_find(servicenick))) {
102 /* TODO: generate python exception here */
8d670803 103 return NULL;
a2c8c575 104 }
105 send_target_message(5, channel, service->bot, "%s", buf);
106 return Py_BuildValue("i", ret);
107}
108
d4e0f0c4 109static PyObject*
110emb_send_target_notice(PyObject *self, PyObject *args)
111{
4c216694 112 /* send a notice
113 usage: svc.send_target_notice(<servicenick_from>, <nick_to>, <message>)
114 */
d4e0f0c4 115 int ret = 0;
116 char *servicenick;
117 char *target;
118 char *buf;
119
120 struct service *service;
121
122 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &target, &buf ))
123 return NULL;
124 if(!(service = service_find(servicenick))) {
125 /* TODO: generate python exception here */
126 return NULL;
127 }
128 send_target_message(4, target, service->bot, "%s", buf);
129 return Py_BuildValue("i", ret);
130}
131
8d670803 132static PyObject*
133emb_get_user(PyObject *self, PyObject *args)
134{
4c216694 135 /* Get a python object containing everything x3 knows about a user, by nick.
136 usage: svc.get_user(<nick>)
137 */
8d670803 138 char *nick;
139 struct userNode *user;
140 struct modeNode *mn;
cbfd323c 141 unsigned int n;
8d670803 142 PyObject* pChanList;
143 if(!PyArg_ParseTuple(args, "s", &nick))
144 return NULL;
145 if(!(user = GetUserH(nick))) {
146 /* TODO: generate python exception here */
147 return NULL;
148 }
149 pChanList = PyTuple_New(user->channels.used);
150 for(n=0;n<user->channels.used;n++) {
151 mn = user->channels.list[n];
152 PyTuple_SetItem(pChanList, n, Py_BuildValue("s", mn->channel->name));
153 }
154 return Py_BuildValue("{s:s,s:s,s:s,s:s,s:s" /* format strings. s=string, i=int */
155 ",s:s,s:s,s:s,s:s,s:s" /* (format is key:value) O=object */
156 ",s:i,s:i,s:s,s:s,s:s" /* blocks of 5 for readability */
157 "s:O}",
158
159 "nick", user->nick,
160 "ident", user->ident,
161 "info", user->info,
162 "hostname", user->hostname,
163 "ip", irc_ntoa(&user->ip),
164
165 "fakehost", user->fakehost,
166 "sethost", user->sethost,
167 "crypthost", user->crypthost,
168 "cryptip", user->cryptip,
169 "numeric", user->numeric, /* TODO: only ifdef WITH_PROTOCOL_P10 */
170
171 "loc", user->loc,
172 "no_notice", user->no_notice,
173 "mark", user->mark,
174 "version_reply", user->version_reply,
175 "account", user->handle_info?user->handle_info->handle:NULL,
176 "channels", pChanList);
177}
178
179static PyObject*
180emb_get_channel(PyObject *self, PyObject *args)
181{
4c216694 182 /* Returns a python dict object with all sorts of info about a channel.
183 usage: svc.get_channel(<name>)
184 */
8d670803 185 char *name;
186 struct chanNode *channel;
cbfd323c 187 unsigned int n;
8d670803 188 PyObject *pChannelMembers;
189 PyObject *pChannelBans;
190 PyObject *pChannelExempts;
191
192 if(!PyArg_ParseTuple(args, "s", &name))
193 return NULL;
194 if(!(channel = GetChannel(name))) {
195 /* TODO: generate py exception here */
196 return NULL;
197 }
198
199 /* build tuple of nicks in channel */
200 pChannelMembers = PyTuple_New(channel->members.used);
201 for(n=0;n < channel->members.used;n++) {
202 struct modeNode *mn = channel->members.list[n];
203 PyTuple_SetItem(pChannelMembers, n, Py_BuildValue("s", mn->user->nick));
204 }
205
206 /* build tuple of bans */
207 pChannelBans = PyTuple_New(channel->banlist.used);
208 for(n=0; n < channel->banlist.used;n++) {
209 struct banNode *bn = channel->banlist.list[n];
210 PyTuple_SetItem(pChannelBans, n,
211 Py_BuildValue("{s:s,s:s,s:i}",
212 "ban", bn->ban,
213 "who", bn->who,
214 "set", bn->set)
215 );
216 }
217
8d670803 218 /* build tuple of exempts */
219 pChannelExempts = PyTuple_New(channel->exemptlist.used);
220 for(n=0; n < channel->exemptlist.used;n++) {
221 struct exemptNode *en = channel->exemptlist.list[n];
222 PyTuple_SetItem(pChannelExempts, n,
223 Py_BuildValue("{s:s,s:s,s:i}",
d4e0f0c4 224 "ban", en->exempt,
8d670803 225 "who", en->who,
226 "set", en->set)
227 );
228 }
229
8d670803 230 return Py_BuildValue("{s:s,s:s,s:s,s:i"
231 ",s:i,s:i,s:O,s:O,s:O}",
232
233 "name", channel->name,
234 "topic", channel->topic,
235 "topic_nick", channel->topic_nick,
236 "topic_time", channel->topic_time,
237
238 "timestamp", channel->timestamp,
239 "modes", channel->modes,
240 "members", pChannelMembers,
241 "bans", pChannelBans,
242 "exempts", pChannelExempts
243 );
244}
245
8d670803 246static PyObject*
247emb_get_account(PyObject *self, PyObject *args)
248{
4c216694 249 /* Returns a python dict object with all sorts of info about an account.
250 usage: svc.get_account(<account name>)
251 */
8d670803 252 char *name;
4c216694 253 struct handle_info *hi;
254
8d670803 255 if(!PyArg_ParseTuple(args, "s", &name))
256 return NULL;
4c216694 257
258 hi = get_handle_info(name);
259 if(!hi) {
260 return NULL;
261 }
262 return Py_BuildValue("{s:s,s:i,s:s,s:s,s:s"
263 ",s:s,s:s}",
264
265 "account", hi->handle,
266 "registered", hi->registered,
267 "last_seen", hi->lastseen,
268 "infoline", hi->infoline ? hi->infoline : "",
269 "email", hi->email_addr ? hi->email_addr : "",
270
271 "fakehost", hi->fakehost ? hi->fakehost : "",
272 "last_quit_host", hi->last_quit_host
273
274 /* TODO: */
275 /* users online authed to this account */
276 /* cookies */
277 /* nicks (nickserv nets only?) */
278 /* masks */
279 /* ignores */
280 /* channels */
281 );
8d670803 282}
8d670803 283
284
a2c8c575 285static PyMethodDef EmbMethods[] = {
286 {"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"},
287 {"send_target_privmsg", emb_send_target_privmsg, METH_VARARGS, "Send a message to somewhere"},
d4e0f0c4 288 {"send_target_notice", emb_send_target_notice, METH_VARARGS, "Send a notice to somewhere"},
8d670803 289 {"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"},
290 {"get_channel", emb_get_channel, METH_VARARGS, "Get details about a channel"},
4c216694 291 {"get_account", emb_get_account, METH_VARARGS, "Get details about an account"},
a2c8c575 292 {NULL, NULL, 0, NULL}
293};
294
295
4c216694 296/* ------------------------------------------------------------------------------------------------ *
297 Thes functions set up the embedded environment for us to call out to modpython.py class
298 methods.
299 */
cbfd323c 300
e0f76584 301PyObject *python_build_handler_args(size_t argc, char *args[], PyObject *pIrcObj) {
4c216694 302 /* Sets up a python tuple with passed in arguments, prefixed by the Irc instance
303 which handlers use to interact with c.
304 argc = number of args
305 args = array of args
306 pIrcObj = instance of the irc class
307 */
e0f76584 308 size_t i = 0, n;
309 PyObject *pArgs = NULL;
cbfd323c 310
e0f76584 311 pArgs = PyTuple_New(argc + 1);
312 Py_INCREF(pIrcObj);
313 PyTuple_SetItem(pArgs, i++, pIrcObj);
cbfd323c 314
e0f76584 315 if(args && argc) {
cbfd323c 316 PyObject *pValue;
e0f76584 317 for(n = 0; n < argc; ++n) {
318 pValue = PyString_FromString(args[n]);
cbfd323c 319 if(!pValue) {
e0f76584 320 Py_DECREF(pArgs);
321 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[n]);
322 return NULL;
cbfd323c 323 }
e0f76584 324 PyTuple_SetItem(pArgs, n+i, pValue);
cbfd323c 325 }
cbfd323c 326 }
e0f76584 327 return pArgs;
cbfd323c 328}
329
330PyObject *python_build_args(size_t argc, char *args[]) {
4c216694 331 /* Builds the passed in arguments into a python argument tuple.
332 argc = number of args
333 args = array of args
334 */
cbfd323c 335 size_t i;
336 PyObject *pArgs = NULL;
337
338 if(args && argc) {
339 pArgs = PyTuple_New(argc);
340 PyObject *pValue;
341 for(i = 0; i< argc; ++i) {
342 pValue = PyString_FromString(args[i]);
343 if(!pValue) {
344 Py_DECREF(pArgs);
345 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[i]);
346 return NULL;
347 }
348 PyTuple_SetItem(pArgs, i, pValue);
349 }
350 }
351 return pArgs;
d4e0f0c4 352}
caf97651 353
cbfd323c 354
e0f76584 355PyObject *new_irc_object(char *command_service, char *command_caller, char *command_target) {
4c216694 356 /* Creates a new instance of the irc class (from modpython.py) which is initalized
357 with current environment details like which service were talking to.
358 command_service = which service we are talking to, or empty string if none
359 command_caller = nick of user generating message, or empty string if none
360 command_target = If were reacting to something on a channel, this will
361 be set to the name of the channel. Otherwise empty
362 */
e0f76584 363 PyObject *pIrcArgs = NULL;
364 PyObject *pIrcClass;
365 PyObject *pIrcObj;
366
4c216694 367 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate irc class; %s %s %s", command_service, command_caller, command_target);
e0f76584 368 pIrcClass = PyObject_GetAttrString(base_module, "irc");
369 /* pIrcClass is a new reference */
370 if(pIrcClass && PyCallable_Check(pIrcClass)) {
371 //size_t i;
372 char *ircargs[] = {command_service, command_caller, command_target};
373 //PyObject *pValue;
374
375 pIrcArgs = python_build_args(3, ircargs);
e0f76584 376 pIrcObj = PyObject_CallObject(pIrcClass, pIrcArgs);
377 if(!pIrcObj) {
378 log_module(PY_LOG, LOG_ERROR, "IRC Class failed to load");
379 PyErr_Print();
380 }
381 if(pIrcArgs != NULL) {
382 Py_DECREF(pIrcArgs);
383 }
4c216694 384 Py_DECREF(pIrcClass);
e0f76584 385 return pIrcObj;
386 }
387 else {
4c216694 388 /* need to free pIrcClass here if it WAS found but was NOT callable? */
e0f76584 389 log_module(PY_LOG, LOG_ERROR, "Unable to find irc class");
390 return NULL;
391 }
392}
393
394
cbfd323c 395int python_call_handler(char *handler, char *args[], size_t argc, char *command_service, char *command_caller, char *command_target) {
4c216694 396 /* This is how we talk to modpython.c. First a new instance of the irc class is created using these
397 arguments to setup the current environment. Then the named method of the handler object is called
398 with the givin arguments.
cbfd323c 399 */
400 PyObject *pIrcObj;
401 PyObject *pArgs;
402 PyObject *pMethod;
403 PyObject *pValue;
404
e0f76584 405 log_module(PY_LOG, LOG_INFO, "attempting to call handler %s.", handler);
cbfd323c 406 if(base_module != NULL) {
407 pIrcObj = new_irc_object(command_service, command_caller, command_target);
e0f76584 408 if(!pIrcObj) {
409 log_module(PY_LOG, LOG_INFO, "Can't get irc object. Bailing.");
410 return 0;
411 }
cbfd323c 412
e0f76584 413 pArgs = python_build_handler_args(argc, args, pIrcObj);
cbfd323c 414 pMethod = PyObject_GetAttrString(handler_object, handler);
415 if(pMethod && PyCallable_Check(pMethod)) {
416 pValue = PyObject_CallObject(pMethod, pArgs);
417 if(pArgs) {
418 Py_DECREF(pArgs);
419 }
420 if(pValue != NULL) {
421 int ret;
422 ret = PyInt_AsLong(pValue);
423 if(ret == -1 && PyErr_Occurred()) {
424 PyErr_Print();
425 log_module(PY_LOG, LOG_INFO, "error converting return value of handler %s to type long. ", handler);
426 ret = 0;
427 }
428 log_module(PY_LOG, LOG_INFO, "handler %s was run successfully, returned %d.", handler, ret);
cbfd323c 429 Py_DECREF(pValue);
4c216694 430 Py_DECREF(pIrcObj);
431 Py_DECREF(pMethod);
cbfd323c 432 return ret;
433 }
434 else {
cbfd323c 435 /* TODO: instead of print errors, get them as strings
436 * and deal with them with normal x3 log system. */
437 PyErr_Print();
438 log_module(PY_LOG, LOG_WARNING, "call to handler %s failed", handler);
4c216694 439 Py_DECREF(pIrcObj);
440 Py_DECREF(pMethod);
cbfd323c 441 return 0;
442 }
443 }
444 else { /* couldn't find handler methed */
4c216694 445 Py_DECREF(pArgs);
446 /* Free pMethod if it was found but not callable? */
cbfd323c 447 log_module(PY_LOG, LOG_ERROR, "Cannot find handler %s.", handler);
448 return 0;
449
450 }
451 }
452 else { /* No base module.. no python? */
e0f76584 453 log_module(PY_LOG, LOG_INFO, "Cannot handle %s, Python is not initialized.", handler);
cbfd323c 454 return 0;
455 }
456}
457
458PyObject *python_new_handler_object() {
4c216694 459 /* Create a new instance of the handler class.
460 This is called during python initilization (or reload)
461 and the result is saved and re-used.
462 */
cbfd323c 463 PyObject *pHandlerClass, *pHandlerObj;
464
465 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate python class handler");
466 pHandlerClass = PyObject_GetAttrString(base_module, "handler");
467 /* Class is a new reference */
468 if(pHandlerClass && PyCallable_Check(pHandlerClass)) {
e0f76584 469 /*PyObject *pValue; */
cbfd323c 470
471 pHandlerObj = PyObject_CallObject(pHandlerClass, NULL);
472 return pHandlerObj;
473 }
474 else {
475 log_module(PY_LOG, LOG_ERROR, "Unable to find handler class");
476 return NULL;
477 }
478}
479
4c216694 480/* ------------------------------------------------------------------------------- *
481 Some gateway functions to convert x3 callbacks into modpython.py callbacks.
482 Mostly we just build relevant args and call the proper handler method
483
484 debate: do we just register these and check them in python
485 for every one (slow?) or actually work out if a plugin needs
486 it first? We will start by doing it every time.
cbfd323c 487 */
0b350353 488static int
489python_handle_join(struct modeNode *mNode)
490{
4c216694 491 /* callback for handle_join events.
492 */
0b350353 493 struct userNode *user = mNode->user;
494 struct chanNode *channel = mNode->channel;
495
a2c8c575 496
caf97651 497 log_module(PY_LOG, LOG_INFO, "python module handle_join");
a2c8c575 498 if(!channel||!user) {
4c216694 499 log_module(PY_LOG, LOG_WARNING, "Python code got join without channel or user!");
a2c8c575 500 return 0;
501 }
502 else {
503 char *args[] = {channel->name, user->nick};
e0f76584 504 return python_call_handler("join", args, 2, "", "", "");
a2c8c575 505 }
0b350353 506}
507
4c216694 508/* ----------------------------------------------------------------------------- */
509
cbfd323c 510
caf97651 511int python_load() {
4c216694 512 /* Init the python engine and do init work on modpython.py
513 This is called during x3 startup, and on a python reload
514 */
caf97651 515 PyObject *pName;
516
517 setenv("PYTHONPATH", "/home/rubin/afternet/services/x3/x3-run/", 1);
518 Py_Initialize();
a2c8c575 519 Py_InitModule("svc", EmbMethods);
d4e0f0c4 520 /* TODO: get "modpython" from x3.conf */
521 pName = PyString_FromString("modpython");
caf97651 522 base_module = PyImport_Import(pName);
523 Py_DECREF(pName);
524 if(base_module != NULL) {
cbfd323c 525 handler_object = python_new_handler_object();
526 if(handler_object) {
e0f76584 527 python_call_handler("init", NULL, 0, "", "", "");
cbfd323c 528 return 1;
529 }
530 else {
531 /* error handler class not found */
532 log_module(PY_LOG, LOG_WARNING, "Failed to create handler object");
533 return 0;
534 }
caf97651 535 }
536 else {
a2c8c575 537 PyErr_Print();
d4e0f0c4 538 log_module(PY_LOG, LOG_WARNING, "Failed to load modpython.py");
a2c8c575 539 return 0;
caf97651 540 }
cbfd323c 541 return 0;
caf97651 542}
543
caf97651 544int
545python_finalize(void) {
4c216694 546 /* Called after X3 is fully up and running.
547 Code can be put here that needs to run to init things, but
548 which is sensitive to everything else in x3 being up and ready
549 to go.
550 */
caf97651 551
552 PyRun_SimpleString("print 'Hello, World of Python!'");
553 log_module(PY_LOG, LOG_INFO, "python module finalize");
554
555 return 1;
556}
557
caf97651 558static void
4c216694 559python_cleanup(void) {
560 /* Called on shutdown of the python module (or before reloading)
561 */
caf97651 562 log_module(PY_LOG, LOG_INFO, "python module cleanup");
563 Py_Finalize(); /* Shut down python enterpriter */
564 return;
565}
566
4c216694 567/* ---------------------------------------------------------------------------------- *
568 Python module command handlers.
569*/
caf97651 570static MODCMD_FUNC(cmd_reload) {
4c216694 571 /* reload the python system completely
572 */
caf97651 573 log_module(PY_LOG, LOG_INFO, "Shutting python down");
574 python_cleanup();
575 log_module(PY_LOG, LOG_INFO, "Loading python stuff");
576 if(python_load()) {
a2c8c575 577 reply("PYMSG_RELOAD_SUCCESS");
caf97651 578 }
579 else {
a2c8c575 580 reply("PYMSG_RELOAD_FAILED");
caf97651 581 }
582 return 1;
583}
584
8d670803 585static MODCMD_FUNC(cmd_run) {
4c216694 586 /* run an arbitrary python command. This can include shell commands, so should be disabled on
587 production, and needs to be handled extremely cautiously as far as access control
588 */
a2c8c575 589 char *msg;
590 msg = unsplit_string(argv + 1, argc - 1, NULL);
d4e0f0c4 591 char *args[] = {msg};
4c216694 592 python_call_handler("cmd_run", args, 1, cmd->parent->bot->nick, user?user->nick:"", channel?channel->name:"");
a2c8c575 593 return 1;
594}
595
0b350353 596int python_init(void) {
4c216694 597 /* X3 calls this function on init of the module during startup. We use it to
598 do all our setup tasks and bindings
599 */
0b350353 600
0b350353 601 PY_LOG = log_register_type("Python", "file:python.log");
caf97651 602 python_module = module_register("python", PY_LOG, "mod-python.help", NULL);
603 log_module(PY_LOG, LOG_INFO, "python module init");
604 message_register_table(msgtab);
605
0b350353 606/*
607 reg_auth_func(python_check_messages);
608 reg_handle_rename_func(python_rename_account);
609 reg_unreg_func(python_unreg_account);
610 conf_register_reload(python_conf_read);
0b350353 611 saxdb_register("python", python_saxdb_read, python_saxdb_write);
0b350353 612 modcmd_register(python_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
613*/
caf97651 614 modcmd_register(python_module, "reload", cmd_reload, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
8d670803 615 modcmd_register(python_module, "run", cmd_run, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
caf97651 616 reg_join_func(python_handle_join);
617 reg_exit_func(python_cleanup);
0b350353 618
caf97651 619 python_load();
0b350353 620 return 1;
621}
622
623#endif /* WITH_PYTHON */