diff --git a/configure b/configure index 4b2385d1d..a59374034 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac 95f630ee. +# From configure.ac 9068a673. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71 for Eggdrop 1.9.5. # @@ -10638,7 +10638,6 @@ printf "%s\n" "#define EGG_TDNS 1" >>confdefs.h # Check for Python -EGG_PYTHON_ENABLE # Check whether --with-python-config was given. diff --git a/configure.ac b/configure.ac index 1100e99d1..9a5e1b7c2 100644 --- a/configure.ac +++ b/configure.ac @@ -164,7 +164,6 @@ EGG_TLS_DETECT EGG_TDNS_ENABLE # Check for Python -EGG_PYTHON_ENABLE EGG_PYTHON_WITHCONFIG diff --git a/doc/sphinx_source/modules/mod/python.rst b/doc/sphinx_source/modules/mod/python.rst index 55307eb6f..5e06a45b2 100644 --- a/doc/sphinx_source/modules/mod/python.rst +++ b/doc/sphinx_source/modules/mod/python.rst @@ -8,6 +8,11 @@ Python Module This module adds a Python interpreter to Eggdrop, allowing you to run Python scripts. +------------------- +System Requirements +------------------- +This module requires Python version 3.8 or higher in order to run. Similar to Tcl requirements, Eggdrop requires both python and python development libraries to be installed on the host machine. On Debian/Ubuntu machines, this means the packages ``python``, ``python-dev`` AND ``python-is-python3`` to be installed. The python-is-python3 updates symlinks on the host system that allow Eggdrop to find it. + -------------- Loading Python -------------- @@ -50,105 +55,3 @@ pysource ^^^^^^^^^^^^^^^^^^^^^^^ The ``pysource`` command is analogous to the Tcl ``source`` command, except that it loads a Python script into Eggdrop instead of a Tcl script. - ------------------------ -Eggdrop Python Commands ------------------------ - -The Python module is built to use the existing core Tcl commands integrated into Eggdrop via the ``eggdrop.tcl`` module. To call an existing Tcl command from Python, you can either load the entire catalog by running ``import eggdrop.tcl``, or be more specific by ``from eggdrop.tcl import putserv, putlog, chanlist``, etc. - -Arguments to the Tcl functions are automatically converted as follows: - -* ``None`` is converted to an empty Tcl object (the empty string, ``""``) -* ``List`` and ``Tuple`` is converted to a ``Tcl list`` -* ``Dict`` is converted to a ``Tcl dictionary`` -* Everything else is converted to a string using the str() method - -Return values from Tcl functions must be manually converted: - -* ``""`` the empty string is automatically converted to None -* everything else is returned as string -* ``Tcl list`` as string can be converted to a Python ``List`` using ``parse_tcl_list`` -* ``Tcl dictionary`` as string can be converted to a Python ``Dict`` using ``parse_tcl_list`` - -Additionally, a few extra python commands have been created for use without these conversions: - -^^^^^^^^^^^^^^^^ -bind -^^^^^^^^^^^^^^^^ - -The python version of the bind command is used to create a bind that triggers a python function. The python bind takes the same arguments as the Tcl binds, but here each argument is passed individually. For example, a bind that would look like ``bind pub * !foo myproc`` in Tcl is written as ``bind("pub", "*", "!foo", myproc)``. For more information on Eggsrop bind argument syntax please see :ref:`bind_types`. The eggdrop.tcl.bind command should not be used as it will attempt to call a Tcl proc. - -^^^^^^^^^^^^^^^^^^^^^^^ -parse_tcl_list -^^^^^^^^^^^^^^^^^^^^^^^ - -When a python script calls a Tcl command that returns a list via the eggdrop.tcl module, the return value will be a Tcl-formatted list- also simply known as a string. The ``parse_tcl_list`` command will convert the Tcl-formatted list into a Python list, which can then freely be used within the Python script. - -^^^^^^^^^^^^^^^^^^^^^^^ -parse_tcl_dict -^^^^^^^^^^^^^^^^^^^^^^^ - -When a python script calls a Tcl command that returns a dict via the eggdrop.tcl module, the return value will be a Tcl-formatted dict- also simply known as a string. The ``parse_tcl_dict`` command will c -onvert the Tcl-formatted dict into a Python list, which can then freely be used within the Python script. - ----------------- -Config variables ----------------- - -There are also some variables you can set in your config file: - - set allow-resync 0 - When two bots get disconnected, this setting allows them to create a - resync buffer which saves all changes done to the userfile during - the disconnect. When they reconnect, they will not have to transfer - the complete user file, but, instead, just send the resync buffer. - --------------------------------- -Writing an Eggdrop Python script --------------------------------- - -This is how to write a python script for Eggdrop. - -You can view examples of Python scripts in the exampleScripts folder included with this module. - -.. glossary:: - - bestfriend.py - This example script demonstrates how to use the parse_tcl_list() python command to convert a list returned by a Tcl command into a list that is usable by Python. - - greet.py - This is a very basic script that demonstrates how a Python script with binds can be run by Eggdrop. - - imdb.py - This script shows how to use an existing third-party module to extend a Python script, in this case retrieving information from imdb.com. - - listtls.py - This script demonstrates how to use parse-tcl_list() and parse_tcl_dict() to convert a list of dicts provided by Tcl into something that is usable by Python. - - urltitle.py - This script shows how to use an existing third-party module to extend a Python script, in this case using an http parser to collect title information from a provided web page. - - -^^^^^^^^^^^^^^ -Header section -^^^^^^^^^^^^^^ - -An Eggdrop python script requires you to import X Y and Z, in this format. - -^^^^^^^^^^^^ -Code Section -^^^^^^^^^^^^ - -Normal python code works here. To run a command from the Eggdrop Tcl library, use this format. - -Use this format all over. - -------------------------------------- -Writing a module for use with Eggdrop -------------------------------------- - -This is how you import a module for use with an egg python script. - - -Copyright (C) 2000 - 2024 Eggheads Development Team diff --git a/doc/sphinx_source/using/python.rst b/doc/sphinx_source/using/python.rst new file mode 100644 index 000000000..520aa1810 --- /dev/null +++ b/doc/sphinx_source/using/python.rst @@ -0,0 +1,113 @@ +======================= +Using the Python Module +======================= + +In Eggdrop 1.10.0, Eggdrop was shipped with a Python module that, similar to the existing core Tcl capability, allows Eggdrop to run python scripts. + +------------------- +System Requirements +------------------- +Similar to Tcl requirements, Eggdrop requires both python and python development libraries to be installed on the host machine. On Debian/Ubuntu machines, this requires the packages python-dev AND python-is-python3 to be installed. The python-is-python3 updates symlinks on the host system that allow Eggdrop to find it. + +-------------- +Loading Python +-------------- + +Put this line into your Eggdrop configuration file to load the python module:: + + loadmodule python + +To load a python script from your config file, place the .py file in the scripts/ folder and add the following line to your config:: + + pysource scripts/myscript.py + +----------------------- +Eggdrop Python Commands +----------------------- + +The Python module is built to use the existing core Tcl commands integrated into Eggdrop via the ``eggdrop.tcl`` module. To call an existing Tcl command from Python, you can either load the entire catalog by running ``import eggdrop.tcl``, or be more specific by ``from eggdrop.tcl import putserv, putlog, chanlist``, etc. + +Arguments to the Tcl functions are automatically converted as follows: + +* ``None`` is converted to an empty Tcl object (the empty string, ``""``) +* ``List`` and ``Tuple`` is converted to a ``Tcl list`` +* ``Dict`` is converted to a ``Tcl dictionary`` +* Everything else is converted to a string using the str() method + +Return values from Tcl functions must be manually converted: + +* ``""`` the empty string is automatically converted to None +* everything else is returned as string +* ``Tcl list`` as string can be converted to a Python ``List`` using ``parse_tcl_list`` +* ``Tcl dictionary`` as string can be converted to a Python ``Dict`` using ``parse_tcl_list`` + +^^^^^^^^^^^^^^^^ +bind +^^^^^^^^^^^^^^^^ + +An important difference to note is that Eggdrop Python has its own ``bind`` command implemented. You will generally want to create binds using the Python ``bind`` command and not import bind from eggdrop.tcl because a Python bind will call a Python function, whereas using the Tcl bind will call a Tcl function (not one from the script you are writing). + +The python version of the bind command is used to create a bind that triggers a python function. The python bind takes the same arguments as the Tcl binds, but here each argument is passed individually. For example, a bind that would look like ``bind pub * !foo myproc`` in Tcl is written as ``bind("pub", "*", "!foo", myproc)``. For more information on Eggsrop bind argument syntax please see :ref:`bind_types`. The eggdrop.tcl.bind command should not be used as it will attempt to call a Tcl proc. + +^^^^^^^^^^^^^^^^^^^^^^^ +parse_tcl_list +^^^^^^^^^^^^^^^^^^^^^^^ + +When a python script calls a Tcl command that returns a list via the eggdrop.tcl module, the return value will be a Tcl-formatted list- also simply known as a string. The ``parse_tcl_list`` command will convert the Tcl-formatted list into a Python list, which can then freely be used within the Python script. + +^^^^^^^^^^^^^^^^^^^^^^^ +parse_tcl_dict +^^^^^^^^^^^^^^^^^^^^^^^ + +When a python script calls a Tcl command that returns a dict via the eggdrop.tcl module, the return value will be a Tcl-formatted dict- also simply known as a string. The ``parse_tcl_dict`` command will convert the Tcl-formatted dict into a Python list, which can then freely be used within the Python script. + +-------------------------------- +Writing an Eggdrop Python script +-------------------------------- + +Some example scripts, complete with documentation, are included with the Python module that ships with Eggdrop (src/mod/python.mod/scripts). These scripts are included to help demonstrate script formatting and usage. The scripts are: + + +.. glossary:: + + bestfriend.py + This example script demonstrates how to use the parse_tcl_list() python command to convert a list returned by a Tcl command into a list that is usable by Python. + + greet.py + This is a very basic script that demonstrates how a Python script with binds can be run by Eggdrop. + + imdb.py + This script shows how to use an existing third-party module to extend a Python script, in this case retrieving information from imdb.com. + + listtls.py + This script demonstrates how to use parse-tcl_list() and parse_tcl_dict() to convert a list of dicts provided by Tcl into something that is usable by Python. + + urltitle.py + This script shows how to use an existing third-party module to extend a Python script, in this case using an http parser to collect title information from a provided web page. + + +^^^^^^^^^^^^^^ +Header section +^^^^^^^^^^^^^^ + +Python is able to call any Tcl command by importing the ``eggdrop`` module. For example, to use the ``putlog`` command in a python script, you would import it as:: + + from eggdrop.tcl import putlog + +and then call it using:: + + putlog("This is a logged message") + + +An important difference to note is that Eggdrop Python has its own ``bind`` command implemented. You will generally want to create binds using the Python ``bind`` command and not import bind from eggdrop.tcl because a Python bind will call a Python function, whereas using the Tcl bind will call a Tcl function (not one from the script you are writing). + +Where does python print go? + +------------------------------------- +Writing a module for use with Eggdrop +------------------------------------- + +This is how you import a module for use with an egg python script. + +Copyright (C) 2000 - 2024 Eggheads Development Team + diff --git a/eggdrop.conf b/eggdrop.conf index 86ac99e64..520e8546d 100755 --- a/eggdrop.conf +++ b/eggdrop.conf @@ -656,6 +656,7 @@ loadmodule pbkdf2 # This setting is planned to be enabled by default in Eggdrop 2.0. #set remove-pass 0 + #### BLOWFISH MODULE #### # # This module is planned to be removed in Eggdrop 2.0 @@ -676,21 +677,14 @@ loadmodule blowfish set blowfish-use-mode cbc -#### DNS MODULE (Deprecated) #### - -## This module provided asynchronous dns support, but as of v1.9.2, this -## functionality was moved into the core code. If you are having issues with the -## new DNS functionality, or just want to continue using this module, compile -## Eggdrop with the --disable-tdns flag (./configure --disdable-tdns). -# -## You really probably don't want to uncomment this!!!! +#### PYTHON MODULE ##### # -#loadmodule dns -#set dns-servers "8.8.8.8 1.1.1.1 185.222.222.222" -#set dns-cache 86400 -#set dns-negcache 600 -#set dns-maxsends 4 -#set dns-retrydelay 3 +# This module gives Eggdrop the ability to run python scripts. if loaded, +# Python scripts can be loaded at the end of the config file using the pysouce +# command to tell Eggdrop where the file is loaded. The module requires Python +# version 3.8 or higher to run. To load the python module, uncomment it below. +#loadmodule python + #### CHANNELS MODULE #### @@ -1448,6 +1442,7 @@ set xfer-timeout 30 # be v1.9.0 or higher). #set sharefail-unlink 1 + #### SHARE MODULE #### # This module provides userfile sharing support between two directly diff --git a/src/Makefile.in b/src/Makefile.in index 4a3cc4d7e..21e359131 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -176,7 +176,8 @@ modules.o: modules.c main.h ../config.h ../eggint.h ../lush.h lang.h \ net.o: net.c main.h ../config.h ../eggint.h ../lush.h lang.h eggdrop.h \ compat/in6.h flags.h proto.h misc_file.h cmdt.h tclegg.h tclhash.h \ chan.h users.h compat/compat.h compat/base64.h compat/inet_aton.h \ - ../src/main.h compat/snprintf.h compat/explicit_bzero.h compat/strlcpy.h + ../src/main.h compat/snprintf.h compat/explicit_bzero.h compat/strlcpy.h \ + modules.h mod/modvals.h rfc1459.o: rfc1459.c main.h ../config.h ../eggint.h ../lush.h lang.h \ eggdrop.h compat/in6.h flags.h proto.h misc_file.h cmdt.h tclegg.h \ tclhash.h chan.h users.h compat/compat.h compat/base64.h \ diff --git a/src/cmds.c b/src/cmds.c index e1730507a..507c69687 100644 --- a/src/cmds.c +++ b/src/cmds.c @@ -1908,11 +1908,11 @@ static int add_to_handle(struct userrec *u, int idx, char *handle, char *host, i } } if ( !type && !glob_botmast(fr) && !chan_master(fr) && get_user_by_host(host)) { - dprintf(idx, "You cannot add %s matching another user!\n", - type ? "an account" : "a host"); + dprintf(idx, "You cannot add a host matching another user!\n"); return 1; } if (type) { + // host-variable contains account u2 = get_user_by_account(host); if (u2) { dprintf(idx, "That account already exists for user %s\n", u2->handle); @@ -1920,6 +1920,7 @@ static int add_to_handle(struct userrec *u, int idx, char *handle, char *host, i } addaccount_by_handle(handle, host); } else { + // host for (q = get_user(&USERENTRY_HOSTS, u); q; q = q->next) { if (!strcasecmp(q->extra, host)) { dprintf(idx, "That %s is already there.\n", diff --git a/src/main.c b/src/main.c index 82ed9ef80..e78b18dd3 100644 --- a/src/main.c +++ b/src/main.c @@ -1023,6 +1023,10 @@ int main(int arg_c, char **arg_v) sigaction(SIGILL, &sv, NULL); sv.sa_handler = got_alarm; sigaction(SIGALRM, &sv, NULL); + // Added for python.mod because the _signal handler otherwise overwrites it + // see https://discuss.python.org/t/asyncio-skipping-signal-handling-setup-during-import-for-python-embedded-context/37054/6 + sv.sa_handler = got_term; + sigaction(SIGINT, &sv, NULL); /* Initialize variables and stuff */ now = time(NULL); diff --git a/src/mod/compress.mod/configure b/src/mod/compress.mod/configure index 1c23a76d4..2d3dc6c26 100755 --- a/src/mod/compress.mod/configure +++ b/src/mod/compress.mod/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac 95f630ee. +# From configure.ac 9068a673. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71 for Eggdrop Compress Module 1.9.5. # diff --git a/src/mod/dns.mod/configure b/src/mod/dns.mod/configure index 6f348a637..7a99e8b4f 100755 --- a/src/mod/dns.mod/configure +++ b/src/mod/dns.mod/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac 95f630ee. +# From configure.ac 9068a673. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71 for Eggdrop DNS Module 1.9.5. # diff --git a/src/mod/irc.mod/chan.c b/src/mod/irc.mod/chan.c index f0cb827d7..9124d428c 100644 --- a/src/mod/irc.mod/chan.c +++ b/src/mod/irc.mod/chan.c @@ -240,15 +240,7 @@ static int detect_chan_flood(char *floodnick, char *floodhost, char *from, if (!m && (which != FLOOD_JOIN)) return 0; - if (m) { - u = get_user_from_member(m); - } else { - u = victim_or_account ? get_user_by_account(victim_or_account) : NULL; - if (!u) { - u = get_user_by_host(from); - } - } - + u = lookup_user_record(m, victim_or_account, from); get_user_flagrec(u, &fr, chan->dname); if (glob_bot(fr) || ((which == FLOOD_DEOP) && (glob_master(fr) || chan_master(fr)) && (glob_friend(fr) || chan_friend(fr))) || @@ -333,7 +325,6 @@ static int detect_chan_flood(char *floodnick, char *floodhost, char *from, chan->floodwho[which][0] = 0; if (which == FLOOD_DEOP) chan->deopd[0] = 0; - u = get_user_from_member(m); if (check_tcl_flud(floodnick, floodhost, u, ftype, chan->dname)) return 0; switch (which) { @@ -1843,7 +1834,7 @@ static int gottopic(char *from, char *msg) if (m != NULL) m->last = now; set_topic(chan, msg); - u = get_user_from_member(m); + u = lookup_user_record(m, NULL, from); // TODO: get account from msgtags check_tcl_topc(nick, from, u, chan->dname, msg); } return 0; @@ -2079,9 +2070,8 @@ static int gotjoin(char *from, char *channame) strlcpy(m->userhost, uhost, sizeof m->userhost); m->flags |= STOPWHO; - u = get_user_from_member(m); - if (extjoin) { + u = lookup_user_record(m, account, from); /* calls check_tcl_account which can delete the channel */ setaccount(nick, account); @@ -2089,6 +2079,8 @@ static int gotjoin(char *from, char *channame) /* The channel doesn't exist anymore, so get out of here. */ goto exit; } + } else { + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags } check_tcl_join(nick, uhost, u, chan->dname); @@ -2129,9 +2121,9 @@ static int gotjoin(char *from, char *channame) if (u) { struct laston_info *li = 0; - cr = get_chanrec(get_user_from_member(m), chan->dname); + cr = get_chanrec(u, chan->dname); if (!cr && no_chanrec_info) - li = get_user(&USERENTRY_LASTON, get_user_from_member(m)); + li = get_user(&USERENTRY_LASTON, u); if (channel_greet(chan) && use_info && ((cr && now - cr->laston > wait_info) || (no_chanrec_info && (!li || now - li->laston > wait_info)))) { @@ -2353,7 +2345,7 @@ static int gotkick(char *from, char *origmsg) return 0; m = ismember(chan, whodid); - u = get_user_from_member(m); + u = lookup_user_record(m, NULL, from); // TODO: get account from msgtags if (m) m->last = now; /* This _needs_ to use chan->dname */ @@ -2569,7 +2561,6 @@ static int gotmsg(char *from, char *msg) int ctcp_count = 0, ignoring; struct chanset_t *chan; struct userrec *u; - memberlist *m; /* Only handle if message is to a channel, or to @#channel. */ /* FIXME: Properly handle ovNotices (@+#channel), vNotices (+#channel), etc. */ @@ -2613,15 +2604,7 @@ static int gotmsg(char *from, char *msg) ctcp_count++; if (ctcp[0] != ' ') { code = newsplit(&ctcp); - u = NULL; - for (chan = chanset; chan; chan = chan->next) { - for (m = chan->channel.member; m && m->nick[0]; m = m->next) { - if (!rfc_casecmp(m->nick, nick)) { - u = get_user_from_member(m); - break; - } - } - } + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags if (!ignoring || trigger_on_ignore) { if (!check_tcl_ctcp(nick, uhost, u, to, code, ctcp)) { chan = findchan(realto); @@ -2700,7 +2683,6 @@ static int gotnotice(char *from, char *msg) char *ctcp, *code; struct userrec *u; struct chanset_t *chan; - memberlist *m; int ignoring; if (!strchr(CHANMETA "@", *msg)) @@ -2714,15 +2696,7 @@ static int gotnotice(char *from, char *msg) fixcolon(msg); strlcpy(uhost, from, sizeof buf); nick = splitnick(&uhost); - u = NULL; - for (chan = chanset; chan; chan = chan->next) { - for (m = chan->channel.member; m && m->nick[0]; m = m->next) { - if (!rfc_casecmp(m->nick, nick)) { - u = get_user_from_member(m); - break; - } - } - } + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags /* Check for CTCP: */ p = strchr(msg, 1); while (p && *p) { diff --git a/src/mod/irc.mod/cmdsirc.c b/src/mod/irc.mod/cmdsirc.c index 3b387c8f6..c9bf58ab1 100644 --- a/src/mod/irc.mod/cmdsirc.c +++ b/src/mod/irc.mod/cmdsirc.c @@ -751,7 +751,6 @@ static void cmd_channel(struct userrec *u, int idx, char *par) } else strlcpy(s, " --- ", sizeof s); egg_snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost); - u = get_user_from_member(m); if (u == NULL) strlcpy(handle, "*", sizeof handle); diff --git a/src/mod/irc.mod/irc.c b/src/mod/irc.mod/irc.c index dc03c7f24..bc3f578e4 100644 --- a/src/mod/irc.mod/irc.c +++ b/src/mod/irc.mod/irc.c @@ -224,7 +224,7 @@ static void punish_badguy(struct chanset_t *chan, char *whobad, static void maybe_revenge(struct chanset_t *chan, char *whobad, char *whovictim, int type) { - char *badnick, *victim; + char *badnick, *victim, buf[NICKLEN + UHOSTLEN]; int mevictim; struct userrec *u, *u2; memberlist *m; @@ -233,14 +233,16 @@ static void maybe_revenge(struct chanset_t *chan, char *whobad, return; /* Get info about offender */ + strlcpy(buf, whobad, sizeof buf); badnick = splitnick(&whobad); m = ismember(chan, badnick); - u = get_user_from_member(m); + u = lookup_user_record(m, NULL, buf); // TODO: get account from msgtags /* Get info about victim */ + strlcpy(buf, whovictim, sizeof buf); victim = splitnick(&whovictim); m = ismember(chan, victim); - u2 = get_user_from_member(m); + u2 = lookup_user_record(m, NULL, buf); // TODO: get account from msgtags mevictim = match_my_nick(victim); /* Do we want to revenge? */ @@ -908,7 +910,7 @@ static int check_tcl_pub(char *nick, char *from, char *chname, char *msg) int x; char buf[512], *args = buf, *cmd, host[161], *hand; struct chanset_t *chan; - struct userrec *u; + struct userrec *u = NULL; memberlist *m; strlcpy(buf, msg, sizeof buf); @@ -916,7 +918,7 @@ static int check_tcl_pub(char *nick, char *from, char *chname, char *msg) simple_sprintf(host, "%s!%s", nick, from); chan = findchan(chname); m = ismember(chan, nick); - u = get_user_from_member(m); + u = lookup_user_record(m ? m : find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags hand = u ? u->handle : "*"; get_user_flagrec(u, &fr, chname); Tcl_SetVar(interp, "_pub1", nick, 0); @@ -946,7 +948,7 @@ static int check_tcl_pubm(char *nick, char *from, char *chname, char *msg) simple_sprintf(host, "%s!%s", nick, from); chan = findchan(chname); m = ismember(chan, nick); - u = get_user_from_member(m); + u = lookup_user_record(m ? m : find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags get_user_flagrec(u, &fr, chname); Tcl_SetVar(interp, "_pubm1", nick, 0); Tcl_SetVar(interp, "_pubm2", from, 0); diff --git a/src/mod/irc.mod/mode.c b/src/mod/irc.mod/mode.c index 88814d3bf..5350fafd7 100644 --- a/src/mod/irc.mod/mode.c +++ b/src/mod/irc.mod/mode.c @@ -52,11 +52,11 @@ static struct chanset_t *modebind_refresh(char *chname, if (!chname || !(chan = findchan(chname))) return NULL; if (usrhost) { - u = get_user_by_host(usrhost); + u = lookup_user_record(NULL, NULL, usrhost); // TODO: get account from somewhere get_user_flagrec(u, usr, chan->dname); } if (vcrhost) { - u = get_user_by_host(vcrhost); + u = lookup_user_record(NULL, NULL, vcrhost); // TODO: get account from somewhere get_user_flagrec(u, vcr, chan->dname); } return chan; diff --git a/src/mod/irc.mod/msgcmds.c b/src/mod/irc.mod/msgcmds.c index 282687425..1c8a45f6b 100644 --- a/src/mod/irc.mod/msgcmds.c +++ b/src/mod/irc.mod/msgcmds.c @@ -383,6 +383,7 @@ static int msg_who(char *nick, char *host, struct userrec *u, char *par) struct userrec *u; egg_snprintf(s, sizeof s, "%s!%s", m->nick, m->userhost); + /* Don't use s for host here, b/c if we don't have m, we won't have s */ u = get_user_from_member(m); info = get_user(&USERENTRY_INFO, u); if (u && (u->flags & USER_BOT)) @@ -431,7 +432,7 @@ static int msg_who(char *nick, char *host, struct userrec *u, char *par) static int msg_whois(char *nick, char *host, struct userrec *u, char *par) { - char s1[143], *s2, stime[14]; + char s1[143], *s2, stime[14], uhost[NICKLEN + UHOSTLEN]; int ok; struct chanset_t *chan; memberlist *m; @@ -454,7 +455,8 @@ static int msg_whois(char *nick, char *host, struct userrec *u, char *par) } if (strlen(par) > NICKMAX) par[NICKMAX] = 0; - putlog(LOG_CMDS, "*", "(%s!%s) !%s! WHOIS %s", nick, host, u->handle, par); + egg_snprintf(uhost, sizeof uhost, "%s!%s", nick, host); + putlog(LOG_CMDS, "*", "(%s) !%s! WHOIS %s", uhost, u->handle, par); u2 = get_user_by_handle(userlist, par); if (!u2) { /* No such handle -- maybe it's a nickname of someone on a chan? */ diff --git a/src/mod/module.h b/src/mod/module.h index da1651c0a..9f84411ea 100644 --- a/src/mod/module.h +++ b/src/mod/module.h @@ -524,8 +524,10 @@ typedef void (*chanout_butfunc)(int, int, const char *, ...) ATTRIBUTE_FORMAT(pr #define bind_bind_entry ((int(*)(tcl_bind_list_t *, const char *, const char *, const char *))global[320]) #define unbind_bind_entry ((int(*)(tcl_bind_list_t *, const char *, const char *, const char *))global[321]) #define argv0 ((char *)global[322]) -#define get_user_from_member ((struct userrec * (*)(memberlist *))global[323]) +#define lookup_user_record ((struct userrec * (*)(memberlist *, char *, char *))global[323]) /* 324 - 327 */ +#define find_member_from_nick ((memberlist * (*) (char *))global[324]) +#define get_user_from_member ((struct userrec * (*) (memberlist *))global[325]) /* hostmasking */ diff --git a/src/mod/modvals.h b/src/mod/modvals.h index 6b955cad3..a840e09cf 100644 --- a/src/mod/modvals.h +++ b/src/mod/modvals.h @@ -39,7 +39,11 @@ #define HOOK_LOADED 13 #define HOOK_BACKUP 14 #define HOOK_DIE 15 -#define REAL_HOOKS 16 +#define HOOK_PRE_SELECT 16 +#define HOOK_POST_SELECT 17 + +#define REAL_HOOKS 18 + #define HOOK_SHAREOUT 105 #define HOOK_SHAREIN 106 #define HOOK_ENCRYPT_PASS 107 diff --git a/src/mod/python.mod/configure b/src/mod/python.mod/configure index f5bb2e5c7..6b0a130f5 100755 --- a/src/mod/python.mod/configure +++ b/src/mod/python.mod/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac 95f630ee. +# From configure.ac 9068a673. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71 for Eggdrop Python Module 1.10.0. # diff --git a/src/mod/python.mod/python.c b/src/mod/python.mod/python.c index 02e33672a..b2521661e 100644 --- a/src/mod/python.mod/python.c +++ b/src/mod/python.mod/python.c @@ -42,6 +42,7 @@ static PyObject *pirp, *pglobals; #undef global static Function *global = NULL, *irc_funcs = NULL; +static PyThreadState *_pythreadsave; #include "src/mod/python.mod/pycmds.c" #include "src/mod/python.mod/tclpython.c" @@ -52,6 +53,16 @@ static int python_expmem() return 0; // TODO } +static int python_gil_unlock() { + _pythreadsave = PyEval_SaveThread(); + return 0; +} + +static int python_gil_lock() { + PyEval_RestoreThread(_pythreadsave); + return 0; +} + static char *init_python() { PyObject *pmodule; PyStatus status; @@ -109,6 +120,8 @@ static void python_report(int idx, int details) static char *python_close() { Context; + del_hook(HOOK_PRE_SELECT, (Function)python_gil_unlock); + del_hook(HOOK_POST_SELECT, (Function)python_gil_lock); kill_python(); rem_builtins(H_dcc, mydcc); rem_tcl_commands(my_tcl_cmds); @@ -152,5 +165,8 @@ char *python_start(Function *global_funcs) /* Add command table to bind list */ add_builtins(H_dcc, mydcc); add_tcl_commands(my_tcl_cmds); + add_hook(HOOK_PRE_SELECT, (Function)python_gil_unlock); + add_hook(HOOK_POST_SELECT, (Function)python_gil_lock); + return NULL; } diff --git a/src/mod/python.mod/scripts/urlTitle.py b/src/mod/python.mod/scripts/urlTitle.py index f8b498226..b2d35899a 100644 --- a/src/mod/python.mod/scripts/urlTitle.py +++ b/src/mod/python.mod/scripts/urlTitle.py @@ -17,6 +17,7 @@ def pubGetTitle(nick, host, handle, channel, text, **kwargs): try: reqs = requests.get(text) soup = BeautifulSoup(reqs.text, 'html.parser') + putlog("Found title for "+text) putmsg(channel, "The title of the webpage is: "+soup.find_all('title')[0].get_text()) except Exception as e: putmsg(channel, "Error: " + str(e)) diff --git a/src/mod/server.mod/servmsg.c b/src/mod/server.mod/servmsg.c index 952ff70bf..91a50e2e9 100644 --- a/src/mod/server.mod/servmsg.c +++ b/src/mod/server.mod/servmsg.c @@ -493,7 +493,7 @@ static int detect_flood(char *floodnick, char *floodhost, char *from, int which) if (!strcasecmp(floodhost, botuserhost)) return 0; - u = get_user_by_host(from); + u = lookup_user_record(NULL, NULL, from); // TODO: get account somehow atr = u ? u->flags : 0; if (atr & (USER_BOT | USER_FRIEND)) return 0; @@ -539,7 +539,7 @@ static int detect_flood(char *floodnick, char *floodhost, char *from, int which) lastmsgs[which] = 0; lastmsgtime[which] = 0; lastmsghost[which][0] = 0; - u = get_user_by_host(from); + u = lookup_user_record(NULL, NULL, from); // TODO: get account somehow if (check_tcl_flud(floodnick, floodhost, u, ftype, "*")) return 0; /* Private msg */ @@ -607,7 +607,7 @@ static int gotmsg(char *from, char *msg) putlog(LOG_PUBLIC, to, "CTCP %s: %s from %s (%s) to %s", code, ctcp, nick, uhost, to); } else { - u = get_user_by_host(from); + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags if (!ignoring || trigger_on_ignore) { if (!check_tcl_ctcp(nick, uhost, u, to, code, ctcp) && !ignoring) { if ((lowercase_ctcp && !strcasecmp(code, "DCC")) || @@ -670,7 +670,7 @@ static int gotmsg(char *from, char *msg) } detect_flood(nick, uhost, from, FLOOD_PRIVMSG); - u = get_user_by_host(from); + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags code = newsplit(&msg); rmspace(msg); @@ -730,7 +730,7 @@ static int gotnotice(char *from, char *msg) "CTCP reply %s: %s from %s (%s) to %s", code, ctcp, nick, uhost, to); } else { - u = get_user_by_host(from); + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags if (!ignoring || trigger_on_ignore) { check_tcl_ctcr(nick, uhost, u, to, code, ctcp); if (!ignoring) @@ -766,7 +766,7 @@ static int gotnotice(char *from, char *msg) } detect_flood(nick, uhost, from, FLOOD_NOTICE); - u = get_user_by_host(from); + u = lookup_user_record(find_member_from_nick(nick), NULL, from); // TODO: get account from msgtags if (!ignoring || trigger_on_ignore) if (check_tcl_notc(nick, uhost, u, botname, msg) == 2) diff --git a/src/modules.c b/src/modules.c index 61ae64716..1fd1dfb97 100644 --- a/src/modules.c +++ b/src/modules.c @@ -60,11 +60,7 @@ # ifndef RTLD_NOW # define RTLD_NOW 1 # endif -# ifdef RTLD_LAZY -# define DLFLAGS RTLD_LAZY|RTLD_GLOBAL -# else -# define DLFLAGS RTLD_NOW|RTLD_GLOBAL -# endif +# define DLFLAGS RTLD_NOW|RTLD_GLOBAL # endif /* MOD_USE_DL */ #endif /* !STATIC */ @@ -301,7 +297,7 @@ Function global_table[] = { (Function) open_telnet, /* 88 - 91 */ (Function) check_tcl_event, - (Function) 0, /* was egg_memcpy -- use memcpy() instead */ + (Function) memcpy, /* was egg_memcpy -- use memcpy() instead */ (Function) my_atoul, (Function) my_strcpy, /* 92 - 95 */ @@ -524,10 +520,10 @@ Function global_table[] = { /* 252 - 255 */ (Function) egg_snprintf, (Function) egg_vsnprintf, - (Function) 0, /* was egg_memset -- use memset() or egg_bzero() instead */ - (Function) 0, /* was egg_strcasecmp -- use strcasecmp() instead */ + (Function) memset, /* was egg_memset -- use memset() or egg_bzero() instead */ + (Function) strcasecmp, /* was egg_strcasecmp -- use strcasecmp() instead */ /* 256 - 259 */ - (Function) 0, /* was egg_strncasecmp -- use strncasecmp() instead */ + (Function) strncasecmp, /* was egg_strncasecmp -- use strncasecmp() instead */ (Function) is_file, (Function) & must_be_owner, /* int */ (Function) & tandbot, /* tand_t * */ @@ -627,8 +623,10 @@ Function global_table[] = { (Function) bind_bind_entry, (Function) unbind_bind_entry, (Function) & argv0, - (Function) get_user_from_member + (Function) lookup_user_record, /* 324 - 327 */ + (Function) find_member_from_nick, + (Function) get_user_from_member, }; void init_modules(void) diff --git a/src/net.c b/src/net.c index 3e23acb97..53d55546d 100644 --- a/src/net.c +++ b/src/net.c @@ -27,6 +27,7 @@ #include #include "main.h" +#include "modules.h" #include #include #include @@ -924,11 +925,13 @@ int sockread(char *s, int *len, sock_list *slist, int slistmax, int tclonly) t.tv_sec = td->blocktime.tv_sec; t.tv_usec = td->blocktime.tv_usec; + call_hook(HOOK_PRE_SELECT); x = select((SELECT_TYPE_ARG1) maxfd + 1, SELECT_TYPE_ARG234 (maxfd_r >= 0 ? &fdr : NULL), SELECT_TYPE_ARG234 (maxfd_w >= 0 ? &fdw : NULL), SELECT_TYPE_ARG234 (maxfd_e >= 0 ? &fde : NULL), SELECT_TYPE_ARG5 &t); + call_hook(HOOK_POST_SELECT); if (x == -1) return -2; /* socket error */ if (x == 0) @@ -973,7 +976,12 @@ int sockread(char *s, int *len, sock_list *slist, int slistmax, int tclonly) { if (slist[i].ssl) { x = SSL_read(slist[i].ssl, s, grab); - if (x < 0) { + if (!x && (SSL_get_shutdown(slist[i].ssl) == SSL_RECEIVED_SHUTDOWN)) { + *len = slist[i].sock; + slist[i].flags &= ~SOCK_CONNECT; + debug1("net: SSL_read(): received shutdown sock %i", slist[i].sock); + return -1; + } else if (x < 0) { int err = SSL_get_error(slist[i].ssl, x); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) errno = EAGAIN; diff --git a/src/tls.c b/src/tls.c index 180af6b37..d3af0c6ea 100644 --- a/src/tls.c +++ b/src/tls.c @@ -531,6 +531,12 @@ static char *ssl_printname(X509_NAME *name) /* X509_NAME_oneline() is easier and shorter, but is deprecated and the manual discourages it's usage, so let's not be lazy ;) */ + if (!bio) { + debug0("TLS: ssl_printname(): BIO_new(): error"); + buf = nmalloc(1); + *buf = 0; + return buf; + } if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_ONELINE & ~XN_FLAG_SPC_EQ)) { len = BIO_get_mem_data(bio, &data); if (len > 0) { @@ -564,6 +570,12 @@ static char *ssl_printtime(ASN1_UTCTIME *t) char *data, *buf; BIO *bio = BIO_new(BIO_s_mem()); + if (!bio) { + debug0("TLS: ssl_printtime(): BIO_new(): error"); + buf = nmalloc(1); + *buf = 0; + return buf; + } ASN1_UTCTIME_print(bio, t); len = BIO_get_mem_data(bio, &data); if (len > 0) { @@ -591,6 +603,12 @@ static char *ssl_printnum(ASN1_INTEGER *i) char *data, *buf; BIO *bio = BIO_new(BIO_s_mem()); + if (!bio) { + debug0("TLS: ssl_printnum(): BIO_new(): error"); + buf = nmalloc(1); + *buf = 0; + return buf; + } i2a_ASN1_INTEGER(bio, i); len = BIO_get_mem_data(bio, &data); if (len > 0) { @@ -712,7 +730,7 @@ int ssl_verify(int ok, X509_STORE_CTX *ctx) !(data->verify & TLS_VERIFYFROM)) || ((err == X509_V_ERR_CERT_HAS_EXPIRED) && !(data->verify & TLS_VERIFYTO))) { - debug1("TLS: peer certificate warning: %s", + putlog(data->loglevel, "*", "TLS: peer certificate warning: %s", X509_verify_cert_error_string(err)); ok = 1; } @@ -799,7 +817,7 @@ static void ssl_info(const SSL *ssl, int where, int ret) SSL_alert_desc_string_long(ret)); } else { /* Ignore close notify warnings */ - debug1("Received close notify warning during %s", + debug1("TLS: Received close notify during %s", (where & SSL_CB_READ) ? "read" : "write"); } } else if (where & SSL_CB_EXIT) { @@ -819,10 +837,16 @@ static void ssl_info(const SSL *ssl, int where, int ret) SSL_state_string_long(ssl)); } } - } else { - /* Display the state of the engine for debugging purposes */ - debug1("TLS: state change: %s", SSL_state_string_long(ssl)); } + /* Display the state of the engine for debugging purposes */ + else if (where == SSL_CB_HANDSHAKE_START) + debug1("TLS: handshake start: %s", SSL_state_string_long(ssl)); + else if (where == SSL_CB_CONNECT_LOOP) + debug1("TLS: connect loop: %s", SSL_state_string_long(ssl)); + else if (where == SSL_CB_ACCEPT_LOOP) + debug1("TLS: accept loop: %s", SSL_state_string_long(ssl)); + else + debug1("TLS: state change: %s", SSL_state_string_long(ssl)); } /* Switch a socket to SSL communication @@ -902,9 +926,9 @@ int ssl_handshake(int sock, int flags, int verify, int loglevel, char *host, SSL_set_mode(td->socklist[i].ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); if (data->flags & TLS_CONNECT) { - struct timespec req = { 0, 1000000L }; SSL_set_verify(td->socklist[i].ssl, SSL_VERIFY_PEER, ssl_verify); /* Introduce 1ms lag so an unpatched hub has time to setup the ssl handshake */ + const struct timespec req = { 0, 1000000L }; nanosleep(&req, NULL); #ifdef SSL_set_tlsext_host_name if (*data->host) diff --git a/src/userrec.c b/src/userrec.c index 084dbcd64..66bb8f16c 100644 --- a/src/userrec.c +++ b/src/userrec.c @@ -171,6 +171,23 @@ static struct userrec *check_dcclist_hand(char *handle) return NULL; } +/* Search every channel record for the provided nickname. Used in cases where + * we are searching for a user record but don't have a memberlist to start from + */ +memberlist *find_member_from_nick(char *nick) { + struct chanset_t *chan; + memberlist *m = NULL; + + for (chan = chanset; chan; chan = chan->next) { + for (m = chan->channel.member; m && m->nick[0]; m = m->next) { + if (!rfc_casecmp(m->nick, nick)) { + return m; + } + } + } + return m; +} + /* Search userlist for a provided account name * Returns: userrecord for user containing the account */ @@ -255,6 +272,43 @@ struct userrec *get_user_from_member(memberlist *m) return NULL; } +/* Wrapper function to find an Eggdrop user record based on either a provided + * channel memberlist record, host, or account. This function will first check + * a provided memberlist and return the result. If no user record is found (or + * the memberlist itself was NULL), this function will try again based on a + * provided account, and then again on a provided host. + * + * When calling this function it is best to provide all available independent + * variables- ie, if you provide 'm' for the memberlist, don't provide + * 'm->account' for the account, use the independent source variable 'account' + * if available. This allows redundant checking in case of unexpected NULLs + */ +struct userrec *lookup_user_record(memberlist *m, char *host, char *account) +{ + struct userrec *u = NULL; + +/* First check for a user record tied to a memberlist */ + if (m) { + u = get_user_from_member(m); + if (u) { + return u; + } + } +/* Next check for a user record tied to an account */ + if (account && account[0]) { + u = get_user_by_account(account); + if (u) { + return u; + } + } +/* Last check for a user record tied to a hostmask */ + if (host && host[0]) { + u = get_user_by_host(host); + return u; + } + return NULL; +} + /* Fix capitalization, etc */ void correct_handle(char *handle) diff --git a/src/users.c b/src/users.c index d5c48f234..928f85ae9 100644 --- a/src/users.c +++ b/src/users.c @@ -523,8 +523,6 @@ void tell_user_ident(int idx, char *id) struct userrec *u; u = get_user_by_handle(userlist, id); - if (u == NULL) - u = get_user_by_host(id); if (u == NULL) { dprintf(idx, "%s.\n", USERF_NOMATCH); return; diff --git a/src/users.h b/src/users.h index a273ca95c..6bc27c1ce 100644 --- a/src/users.h +++ b/src/users.h @@ -185,7 +185,9 @@ struct userrec *get_user_by_host(char *); struct userrec *get_user_by_account(char *); struct userrec *get_user_by_nick(char *); struct userrec *get_user_from_member(memberlist *); +struct userrec *lookup_user_record(memberlist *, char *, char *); struct userrec *check_chanlist(const char *); +memberlist *find_member_from_nick(char *); /* All the default userentry stuff, for code re-use */