X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/5345ea76e71bb77f269478a8919cd08933b7dfa4..2bee6a6e20b793a5a5749c28e7caa00e2a54c75d:/src/mod-python.c diff --git a/src/mod-python.c b/src/mod-python.c index 50359ea..4ffab92 100644 --- a/src/mod-python.c +++ b/src/mod-python.c @@ -36,6 +36,7 @@ #include "mail.h" #include "timeq.h" #include "compat.h" +#include "nickserv.h" /* TODO notes * @@ -83,109 +84,86 @@ struct _tuple_dict_extra { size_t* extra; }; -static int _dict_iter_get_users(char const* key, UNUSED_ARG(void* data), void* extra) { - PyObject* tmp; - struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra; - - if ((tmp = PyString_FromString(key)) == NULL) - return 1; +static void pyobj_release_tuple(PyObject* tuple, size_t n) { + size_t i; - if (PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, tmp)) - return 1; + if (tuple == NULL) + return; - *real_extra->extra = *real_extra->extra + 1; + for (i = 0; i < n; ++i) + Py_XDECREF(PyTuple_GET_ITEM(tuple, i)); - return 0; + Py_XDECREF(tuple); } -static int _dict_iter_get_channels(char const* key, UNUSED_ARG(void* data), void* extra) { +static int _dict_iter_fill_tuple(char const* key, UNUSED_ARG(void* data), void* extra) { + PyObject* tmp; struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra; - PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, - PyString_FromString(key)); - *real_extra->extra = *real_extra->extra + 1; - return 0; -} + if ((tmp = PyString_FromString(key)) == NULL) + return 1; -static int _dict_iter_get_servers(char const* key, UNUSED_ARG(void* data), void* extra) { - struct _tuple_dict_extra* real_extra = (struct _tuple_dict_extra*)extra; + if (PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, tmp)) { + Py_DECREF(tmp); + return 1; + } - PyTuple_SetItem(real_extra->data, *(int*)real_extra->extra, - PyString_FromString(key)); *real_extra->extra = *real_extra->extra + 1; return 0; } -/* get a tuple with all users in it */ static PyObject* -emb_get_users(UNUSED_ARG(PyObject *self), PyObject *args) { +pyobj_from_dict_t(dict_t d) { PyObject* retval; - PyObject* tmp; - size_t num_clients, n = 0, i; + size_t n = 0; struct _tuple_dict_extra extra; - if (!PyArg_ParseTuple(args, "")) - return NULL; - - num_clients = dict_size(clients); - retval = PyTuple_New(num_clients); - if (retval == NULL) + if ((retval = PyTuple_New(dict_size(d))) == NULL) return NULL; extra.extra = &n; extra.data = retval; - if (dict_foreach(clients, _dict_iter_get_users, (void*)&extra) != NULL) { - for (i = 0; i < n; ++i) { - tmp = PyTuple_GetItem(retval, i); - PyTuple_SET_ITEM(retval, i, NULL); - Py_DECREF(tmp); - } - Py_DECREF(retval); + if (dict_foreach(d, _dict_iter_fill_tuple, (void*)&extra) != NULL) { + pyobj_release_tuple(retval, n); return NULL; } return retval; } -/* get a tuple with all channels in it */ +/* get a tuple with all users in it */ static PyObject* -emb_get_channels(UNUSED_ARG(PyObject* self), PyObject* args) { - PyObject* retval; - size_t num_channels, n = 0; - struct _tuple_dict_extra extra; - +emb_get_users(UNUSED_ARG(PyObject *self), PyObject *args) { if (!PyArg_ParseTuple(args, "")) return NULL; - num_channels = dict_size(channels); - retval = PyTuple_New(num_channels); - - extra.extra = &n; - extra.data = retval; + return pyobj_from_dict_t(clients); +} - dict_foreach(channels, _dict_iter_get_channels, (void*)&extra); +/* get a tuple with all channels in it */ +static PyObject* +emb_get_channels(UNUSED_ARG(PyObject* self), PyObject* args) { + if (!PyArg_ParseTuple(args, "")) + return NULL; - return retval; + return pyobj_from_dict_t(channels); } static PyObject* emb_get_servers(UNUSED_ARG(PyObject* self), PyObject* args) { - PyObject* retval; - size_t n = 0; - struct _tuple_dict_extra extra; - if (!PyArg_ParseTuple(args, "")) return NULL; - retval = PyTuple_New(dict_size(servers)); - - extra.extra = &n; - extra.data = retval; + return pyobj_from_dict_t(servers); +} - dict_foreach(servers, _dict_iter_get_servers, (void*)&extra); +static PyObject* +emb_get_accounts(UNUSED_ARG(PyObject* self), PyObject* args) { + if (!PyArg_ParseTuple(args, "")) + return NULL; - return retval; + return pyobj_from_dict_t(nickserv_handle_dict); } static PyObject* @@ -281,14 +259,19 @@ static PyObject* pyobj_from_usernode(struct userNode* user) { unsigned int n; struct modeNode *mn; + PyObject* retval = NULL; PyObject* pChanList = PyTuple_New(user->channels.used); + if (pChanList == NULL) + return NULL; + for (n=0; n < user->channels.used; n++) { mn = user->channels.list[n]; - PyTuple_SetItem(pChanList, n, Py_BuildValue("s", mn->channel->name)); + if (PyTuple_SetItem(pChanList, n, Py_BuildValue("s", mn->channel->name))) + goto cleanup; } - return Py_BuildValue("{" + retval = Py_BuildValue("{" "s: s, " /* nick */ "s: s, " /* ident */ "s: s, " /* info */ @@ -321,6 +304,17 @@ pyobj_from_usernode(struct userNode* user) { "version_reply", user->version_reply, "account", user->handle_info ? user->handle_info->handle : NULL, "channels", pChanList); + + if (retval == NULL) + goto cleanup; + + return retval; + +cleanup: + Py_XDECREF(retval); + pyobj_release_tuple(pChanList, n); + + return NULL; } static PyObject* @@ -404,13 +398,7 @@ pyobj_from_server(struct server* srv) { cleanup: Py_XDECREF(retval); - - for (n = 0; n < idx; ++n) { - tmp = PyTuple_GetItem(users, n); - PyTuple_SetItem(users, n, NULL); - Py_DECREF(tmp); - } - Py_DECREF(users); + pyobj_release_tuple(users, idx); return NULL; } @@ -436,6 +424,82 @@ emb_get_server(UNUSED_ARG(PyObject* self), PyObject* args) { return pyobj_from_server(srv); } +static PyObject* +pyobj_from_modelist(struct modeList* mode) { + size_t n; + PyObject* tmp; + PyObject* retval = PyTuple_New(mode->used); + + if (retval == NULL) + return NULL; + + for (n = 0; n < mode->used; ++n) { + struct modeNode* mn = mode->list[n]; + tmp = PyString_FromString(mn->user->nick); + if (tmp == NULL) { + pyobj_release_tuple(retval, n); + return NULL; + } + + if (PyTuple_SetItem(retval, n, tmp)) { + pyobj_release_tuple(retval, n); + return NULL; + } + } + + return retval; +} + +static PyObject* +pyobj_from_banlist(struct banList* bans) { + size_t n; + struct banNode* bn; + PyObject* tmp; + PyObject* retval = PyTuple_New(bans->used); + + if (retval == NULL) + return NULL; + + for (n = 0; n < bans->used; ++n) { + bn = bans->list[n]; + + tmp = Py_BuildValue("{s:s,s:s,s:l}", + "ban", bn->ban, "who", bn->who, "set", bn->set); + + if (tmp == NULL || PyTuple_SetItem(retval, n, tmp)) { + pyobj_release_tuple(retval, n); + return NULL; + } + } + + return retval; +} + +static PyObject* +pyobj_from_exemptlist(struct exemptList* exmp) { + size_t n; + struct exemptNode* en; + PyObject* tmp; + PyObject* retval = PyTuple_New(exmp->used); + + if (retval == NULL) + return NULL; + + for (n = 0; n < exmp->used; ++n) { + en = exmp->list[n]; + + tmp = Py_BuildValue("{s:s,s:s,s:l}", + "ban", en->exempt, "who", en->who, "set", en->set); + + if (tmp == NULL || PyTuple_SetItem(retval, n, tmp)) { + pyobj_release_tuple(retval, n); + return NULL; + } + } + + return retval; +} + static PyObject* emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args) { @@ -444,10 +508,10 @@ emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args) */ char *name; struct chanNode *channel; - unsigned int n; - PyObject *pChannelMembers; - PyObject *pChannelBans; - PyObject *pChannelExempts; + PyObject *pChannelMembers = NULL; + PyObject *pChannelBans = NULL; + PyObject *pChannelExempts = NULL; + PyObject *retval = NULL; if(!PyArg_ParseTuple(args, "s", &name)) @@ -459,37 +523,21 @@ emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args) } /* build tuple of nicks in channel */ - pChannelMembers = PyTuple_New(channel->members.used); - for(n=0;n < channel->members.used;n++) { - struct modeNode *mn = channel->members.list[n]; - PyTuple_SetItem(pChannelMembers, n, Py_BuildValue("s", mn->user->nick)); - } + pChannelMembers = pyobj_from_modelist(&channel->members); + if (pChannelMembers == NULL) + goto cleanup; /* build tuple of bans */ - pChannelBans = PyTuple_New(channel->banlist.used); - for(n=0; n < channel->banlist.used;n++) { - struct banNode *bn = channel->banlist.list[n]; - PyTuple_SetItem(pChannelBans, n, - Py_BuildValue("{s:s,s:s,s:i}", - "ban", bn->ban, - "who", bn->who, - "set", bn->set) - ); - } + pChannelBans = pyobj_from_banlist(&channel->banlist); + if (pChannelBans == NULL) + goto cleanup; /* build tuple of exempts */ - pChannelExempts = PyTuple_New(channel->exemptlist.used); - for(n=0; n < channel->exemptlist.used;n++) { - struct exemptNode *en = channel->exemptlist.list[n]; - PyTuple_SetItem(pChannelExempts, n, - Py_BuildValue("{s:s,s:s,s:i}", - "ban", en->exempt, - "who", en->who, - "set", en->set) - ); - } + pChannelExempts = pyobj_from_exemptlist(&channel->exemptlist); + if (pChannelExempts == NULL) + goto cleanup; - return Py_BuildValue("{s:s,s:s,s:s,s:i" + retval = Py_BuildValue("{s:s,s:s,s:s,s:i" ",s:i,s:i,s:O,s:O,s:O}", "name", channel->name, @@ -503,6 +551,18 @@ emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args) "bans", pChannelBans, "exempts", pChannelExempts ); + if (retval == NULL) + goto cleanup; + + return retval; + +cleanup: + Py_XDECREF(retval); + pyobj_release_tuple(pChannelExempts, channel->exemptlist.used); + pyobj_release_tuple(pChannelBans, channel->banlist.used); + pyobj_release_tuple(pChannelMembers, channel->members.used); + + return NULL; } static PyObject* @@ -585,6 +645,188 @@ emb_log_module(UNUSED_ARG(PyObject *self), PyObject *args) return Py_BuildValue("i", ret); } +static PyObject* +emb_kill(UNUSED_ARG(PyObject* self), PyObject* args) { + char const* from_nick, *target_nick, *message; + struct userNode *target; + struct service *service; + + if (!PyArg_ParseTuple(args, "sss", &from_nick, &target_nick, &message)) + return NULL; + + if(!(service = service_find(from_nick))) { + PyErr_SetString(PyExc_Exception, "unknown service user specified as from user"); + return NULL; + } + + if ((target = GetUserH(target_nick)) == NULL) { + PyErr_SetString(PyExc_Exception, "unknown target user"); + return NULL; + } + + irc_kill(service->bot, target, message); + + return Py_None; +} + +struct py_timeq_extra { + PyObject* func; + PyObject* arg; +}; + +static +void py_timeq_callback(void* data) { + struct py_timeq_extra* extra = (struct py_timeq_extra*)data; + + PyObject* retval = PyObject_Call(extra->func, extra->arg, NULL); + Py_XDECREF(retval); + + Py_DECREF(extra->func); + Py_DECREF(extra->arg); +} + +static PyObject* +emb_timeq_add(UNUSED_ARG(PyObject* self), PyObject* args) { + time_t when; + PyObject* func, *arg; + struct py_timeq_extra* extra; + + if (!PyArg_ParseTuple(args, "lOO", &when, &func, &arg)) + return NULL; + + if (!PyFunction_Check(func)) { + PyErr_SetString(PyExc_Exception, "first argument must be a function"); + return NULL; + } + + if (!PyTuple_Check(arg)) { + PyErr_SetString(PyExc_Exception, "second argument must be a tuple"); + return NULL; + } + + extra = malloc(sizeof(struct py_timeq_extra)); + if (extra == NULL) { + PyErr_SetString(PyExc_Exception, "out of memory"); + return NULL; + } + + Py_INCREF(func); + Py_INCREF(arg); + + extra->func = func; + extra->arg = arg; + + timeq_add(when, py_timeq_callback, (void*)extra); + + return Py_None; +} + +static PyObject* +emb_timeq_del(UNUSED_ARG(PyObject* self), PyObject* args) { + /* NOTE: + * This function will delete all python-added callbacks registered + * to run at the given time, regardless of their data. This is due to + * the unnecessary extra burden it would require to get the same data for + * multiple runs. + */ + time_t when; + + if (!PyArg_ParseTuple(args, "l", &when)) + return NULL; + + timeq_del(when, py_timeq_callback, NULL, TIMEQ_IGNORE_DATA); + + return Py_None; +} + +static int pyobj_config_make_dict(char const* key, void* data_, void* extra) { + struct record_data* data = (struct record_data*)data_; + PyObject* dict = (PyObject*)extra; + PyObject* value = NULL, *tmp; + size_t n, idx; + int success; + + switch (data->type) { + case RECDB_QSTRING: + value = PyString_FromString(data->d.qstring); + break; + + case RECDB_STRING_LIST: + value = PyList_New(data->d.slist->used); + if (value == NULL) + break; + + success = 1; + for (n = 0; n < data->d.slist->used; ++n) { + tmp = PyString_FromString(data->d.slist->list[n]); + if (tmp == NULL) { + success = 0; + break; + } + + if (PyList_SetItem(value, n, tmp)) { + Py_DECREF(tmp); + success = 0; + break; + } + } + if (!success) { + for (idx = 0; idx < n; ++idx) { + tmp = PyList_GET_ITEM(value, idx); + Py_DECREF(tmp); + PyList_SET_ITEM(value, idx, NULL); + } + Py_DECREF(value); + value = NULL; + } + break; + + case RECDB_OBJECT: + value = PyDict_New(); + if (value == NULL) + break; + + if (dict_foreach(data->d.object, pyobj_config_make_dict, (void*)value) != NULL) { + PyDict_Clear(value); + value = NULL; + break; + } + + break; + + default: + value = Py_None; + } + + if (value == NULL) + return 1; + + if (PyDict_SetItemString(dict, key, value)) + return 1; + + return 0; +} + +static PyObject* +emb_get_config(UNUSED_ARG(PyObject* self), PyObject* args) { + PyObject* dict; + + if (!PyArg_ParseTuple(args, "")) + return NULL; + + dict = PyDict_New(); + if (dict == NULL) + return NULL; + + if (conf_enum_root(pyobj_config_make_dict, (void*)dict) != NULL) { + PyDict_Clear(dict); + PyErr_SetString(PyExc_Exception, "unable to iterate config"); + return NULL; + } + + return dict; +} + static PyMethodDef EmbMethods[] = { /* Communication methods */ {"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"}, @@ -593,7 +835,7 @@ static PyMethodDef EmbMethods[] = { {"log_module", emb_log_module, METH_VARARGS, "Log something using the X3 log subsystem"}, //TODO: {"exec_cmd", emb_exec_cmd, METH_VARARGS, "execute x3 command provided"}, // This should use environment from "python command" call to pass in, if available -//TODO: {"kill" + {"kill", emb_kill, METH_VARARGS, "Kill someone"}, //TODO: {"shun" //TODO: {"unshun" //TODO: {"gline", emb_gline, METH_VARARGS, "gline a mask"}, @@ -602,11 +844,12 @@ static PyMethodDef EmbMethods[] = { //TODO: {"channel_mode", emb_channel_mode, METH_VARARGS, "set modes on a channel"}, //TODO: {"user_mode", emb_user_mode, METH_VARARGS, "Have x3 set usermodes on one of its own nicks"}, // -//TODO: {"get_config", emb_get_config, METH_VARARGS, "get x3.conf settings into a nested dict"}, + {"get_config", emb_get_config, METH_VARARGS, "get x3.conf settings into a nested dict"}, //TODO: {"config_set", emb_config_set, METH_VARARGS, "change a config setting 'on-the-fly'."}, // -//TODO: {"timeq_add", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."}, -//TODO: {"timeq_del", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."}, + {"timeq_add", emb_timeq_add, METH_VARARGS, "add function to callback to the event system"}, + {"timeq_del", emb_timeq_del, METH_VARARGS, "remove function to callback from the event system"}, + /* Information gathering methods */ {"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"}, {"get_users", emb_get_users, METH_VARARGS, "Get all connected users"}, @@ -615,6 +858,7 @@ static PyMethodDef EmbMethods[] = { {"get_server", emb_get_server, METH_VARARGS, "Get details about a server"}, {"get_servers", emb_get_servers, METH_VARARGS, "Get all server names"}, {"get_account", emb_get_account, METH_VARARGS, "Get details about an account"}, + {"get_accounts", emb_get_accounts, METH_VARARGS, "Get all nickserv accounts"}, {"get_info", emb_get_info, METH_VARARGS, "Get various misc info about x3"}, /* null terminator */ {NULL, NULL, 0, NULL}