From 6bdd7b0febb76004b5e6a07a0f3e595fb0a1819a Mon Sep 17 00:00:00 2001 From: psychon Date: Sat, 22 Aug 2009 15:45:32 +0000 Subject: [PATCH] Import SilverLeo's modtcl from znc-extra The configure and Makefile stuff was taken from there, too. By default modtcl is disabled, use --enable-extra --enable-tcl to get it. git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@1609 726aef4b-f618-498e-8847-2d620e286838 --- configure | 86 +++++++ configure.in | 48 ++++ modules/Makefile.in | 18 +- modules/extra/binds.tcl | 140 +++++++++++ modules/extra/modtcl.cpp | 494 +++++++++++++++++++++++++++++++++++++++ modules/extra/modtcl.tcl | 122 ++++++++++ 6 files changed, 906 insertions(+), 2 deletions(-) create mode 100644 modules/extra/binds.tcl create mode 100644 modules/extra/modtcl.cpp create mode 100644 modules/extra/modtcl.tcl diff --git a/configure b/configure index 813cc36c..53555790 100755 --- a/configure +++ b/configure @@ -556,6 +556,7 @@ LIBOBJS DATADIR MODDIR SASL +TCL_FLAGS EXTRA NOSSL MODTARGET @@ -636,6 +637,9 @@ enable_optimization enable_c_ares with_module_prefix with_module_data_prefix +enable_tcl +with_tcl_flags +with_tcl ' ac_precious_vars='build_alias host_alias @@ -1274,6 +1278,7 @@ Optional Features: --disable-optimization Disable some compiler optimizations to decrease memory usage while compiling --disable-c-ares disable c-ares usage + --enable-tcl enable modtcl Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1282,6 +1287,8 @@ Optional Packages: --with-module-prefix module object code [LIBDIR/znc] --with-module-data-prefix=DIR static module data (webadmin skins) [DATADIR/znc] + --with-tcl-flags=FLAGS The flags needed for compiling and linking modtcl + --with-tcl=DIR directory containing tclConfig.sh Some influential environment variables: CXX C++ compiler command @@ -3408,6 +3415,79 @@ fi fi + # Check if we want modtcl + # Check whether --enable-tcl was given. +if test "${enable_tcl+set}" = set; then : + enableval=$enable_tcl; TCL="yes" +else + TCL="no" +fi + + + +# Check whether --with-tcl-flags was given. +if test "${with_tcl_flags+set}" = set; then : + withval=$with_tcl_flags; TCL_FLAGS="$withval" +fi + + + if test x"$TCL" = "xyes" + then + +# Check whether --with-tcl was given. +if test "${with_tcl+set}" = set; then : + withval=$with_tcl; TCL_DIR="${withval}" +fi + + + # This need most likely most be extended in the future, but I dont think + # it's a good idea to stuff a shitload of random stuff in here right now + for path in $TCL_DIR /usr/lib /usr/lib/tcl8.4 /usr/lib/tcl8.5 + do + file="${path}/tclConfig.sh" + as_ac_File=`$as_echo "ac_cv_file_${file}" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${file}" >&5 +$as_echo_n "checking for ${file}... " >&6; } +if { as_var=$as_ac_File; eval "test \"\${$as_var+set}\" = set"; }; then : + $as_echo_n "(cached) " >&6 +else + test "$cross_compiling" = yes && + as_fn_error "cannot check for file existence when cross compiling" "$LINENO" 5 +if test -r "${file}"; then + eval "$as_ac_File=yes" +else + eval "$as_ac_File=no" +fi +fi +eval ac_res=\$$as_ac_File + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +eval as_val=\$$as_ac_File + if test "x$as_val" = x""yes; then : + TCL_CONF="$file" ; break +fi + + done + + if test x"${TCL_CONF}" = x + then + # They --enable-tcl'd, so give them some sane default + TCL_FLAGS="-I/usr/include/tcl -ltcl" + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Could not find tclConfig.sh, using some sane defaults." >&5 +$as_echo "$as_me: WARNING: Could not find tclConfig.sh, using some sane defaults." >&2;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking modtcl flags" >&5 +$as_echo_n "checking modtcl flags... " >&6; } + . ${TCL_CONF} + # eval because those vars depend on other vars in there + eval "TCL_LIB_SPEC=\"${TCL_LIB_SPEC}\"" + eval "TCL_INCLUDE_SPEC=\"${TCL_INCLUDE_SPEC}\"" + TCL_FLAGS="$TCL_INCLUDE_SPEC $TCL_LIB_SPEC" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TCL_FLAGS" >&5 +$as_echo "$TCL_FLAGS" >&6; } + fi + fi + appendCXX "-D_MODDIR_=\\\"${MODDIR}\\\"" appendCXX "-D_DATADIR_=\\\"${DATADIR}\\\"" fi @@ -3427,6 +3507,7 @@ fi + ac_config_files="$ac_config_files Makefile" ac_config_files="$ac_config_files znc-config" @@ -4606,3 +4687,8 @@ else echo "sasl: yes" fi echo "extra: $EXTRA" +if test x"$TCL_FLAGS" = "x" ; then + echo "tcl: no" +else + echo "tcl: yes" +fi diff --git a/configure.in b/configure.in index 59be24fd..014ee303 100644 --- a/configure.in +++ b/configure.in @@ -234,6 +234,48 @@ if test "$MODULES" = "yes"; then AC_MSG_ERROR([could not find libsasl2. Try --disable-sasl.])) fi + # Check if we want modtcl + AC_ARG_ENABLE( [tcl], + AS_HELP_STRING([--enable-tcl], [enable modtcl]), + [TCL="yes"], + [TCL="no"]) + + AC_ARG_WITH( [tcl-flags], + AS_HELP_STRING([--with-tcl-flags=FLAGS], + [The flags needed for compiling and linking modtcl]), + [TCL_FLAGS="$withval"],) + + if test x"$TCL" = "xyes" + then + AC_ARG_WITH( [tcl], + AS_HELP_STRING([--with-tcl=DIR], + [directory containing tclConfig.sh]), + TCL_DIR="${withval}") + + # This need most likely most be extended in the future, but I dont think + # it's a good idea to stuff a shitload of random stuff in here right now + for path in $TCL_DIR /usr/lib /usr/lib/tcl8.4 /usr/lib/tcl8.5 + do + file="${path}/tclConfig.sh" + AC_CHECK_FILE(${file}, [TCL_CONF="$file" ; break]) + done + + if test x"${TCL_CONF}" = x + then + # They --enable-tcl'd, so give them some sane default + TCL_FLAGS="-I/usr/include/tcl -ltcl" + AC_MSG_WARN([Could not find tclConfig.sh, using some sane defaults.]) + else + AC_MSG_CHECKING([modtcl flags]) + . ${TCL_CONF} + # eval because those vars depend on other vars in there + eval "TCL_LIB_SPEC=\"${TCL_LIB_SPEC}\"" + eval "TCL_INCLUDE_SPEC=\"${TCL_INCLUDE_SPEC}\"" + TCL_FLAGS="$TCL_INCLUDE_SPEC $TCL_LIB_SPEC" + AC_MSG_RESULT([$TCL_FLAGS]) + fi + fi + appendCXX "-D_MODDIR_=\\\"${MODDIR}\\\"" appendCXX "-D_DATADIR_=\\\"${DATADIR}\\\"" fi @@ -250,6 +292,7 @@ AC_SUBST([MODTARGET]) AC_SUBST([NOSSL]) AC_SUBST([EXTRA]) AC_SUBST([PERL]) +AC_SUBST([TCL_FLAGS]) AC_SUBST([SASL]) AC_SUBST([MODDIR]) AC_SUBST([DATADIR]) @@ -287,3 +330,8 @@ else echo "sasl: yes" fi echo "extra: $EXTRA" +if test x"$TCL_FLAGS" = "x" ; then + echo "tcl: no" +else + echo "tcl: yes" +fi diff --git a/modules/Makefile.in b/modules/Makefile.in index 142b1744..56ffb387 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -25,6 +25,8 @@ DATADIR := @DATADIR@ LIBZNC := @LIBZNC@ LIBZNCDIR:= @LIBZNCDIR@ +TCL_FLAGS:= @TCL_FLAGS@ + ifneq "$(LIBZNC)" "" LDFLAGS += -L.. -lznc -Wl,-rpath,$(LIBZNCDIR) endif @@ -62,6 +64,14 @@ FILES := $(shell echo $(FILES) | sed -e "s/saslauth//") endif saslauthFLAGS := -lsasl2 +ifeq "$(TCL_FLAGS)" "" +FILES := $(shell echo $(FILES) | sed -e "s:extra/modtcl::g") +else +TCLHOOK := modtcl_install +endif +modtclFLAGS := $(TCL_FLAGS) + + TARGETS := $(addsuffix .so, $(FILES)) CLEAN := *.so extra/*.so @@ -70,7 +80,7 @@ CLEAN := *.so extra/*.so all: $(TARGETS) -install: all create_install_dir install_metadirs $(PERLHOOK) +install: all create_install_dir install_metadirs $(PERLHOOK) $(TCLHOOK) install -m 0755 $(TARGETS) $(DESTDIR)$(MODDIR) create_install_dir: @@ -93,13 +103,17 @@ clean: @mkdir -p .depend @mkdir -p .depend/extra @mkdir -p extra - $(CXX) $(MODFLAGS) $(LDFLAGS) $(MODLINK) -o $@ $< $($(basename $@)FLAGS) -MMD -MF .depend/$@.dep + $(CXX) $(MODFLAGS) $(LDFLAGS) $(MODLINK) -o $@ $< $($(shell echo '$(basename $@)' | sed -e 's:extra/::')FLAGS) -MMD -MF .depend/$@.dep modperl_install: create_install_dir for i in $(srcdir)/*.pm; do \ install -m 0644 $$i $(DESTDIR)$(MODDIR); \ done +modtcl_install: + mkdir -p $(DESTDIR)$(DATADIR)/modtcl/ + install -m 0644 $(srcdir)/extra/modtcl.tcl $(srcdir)/extra/binds.tcl $(DESTDIR)$(DATADIR)/modtcl/ + uninstall: # Yes, we are lazy, just remove everything in there rm -rf $(DESTDIR)$(MODDIR)/* diff --git a/modules/extra/binds.tcl b/modules/extra/binds.tcl new file mode 100644 index 00000000..c5b00456 --- /dev/null +++ b/modules/extra/binds.tcl @@ -0,0 +1,140 @@ +# Copyright (C) 2004-2009 See the AUTHORS file for details. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published +# by the Free Software Foundation. +# +# Binds module to process incoming messages with ZNC modtcl +# +# Supported bind types: pubm/pub, time, evnt, nick, bot, dcc, kick +# evnt: prerehash,rehash,init-server,disconnect-server +# + +namespace eval Binds { + +# vars + if {![info exists Binds::List]} { + variable List [list] + } + +# procs + proc Add {type flags cmd {procname ""}} { + if {![regexp {^(pub|pubm|nick|mode|raw|bot|time|evnt|dcc|kick)$} $type]} { + PutModule "Tcl error: Bind type: $type not supported" + return + } + # ToDo: Flags check from user info (IsAdmin, etc) + if {$procname == ""} { + PutModule "Tcl error: Without a proc binds are useless" + return + } + # ToDo: bind hit counter + if {[lsearch $Binds::List "$type $flags [list $cmd] 0 [list $procname]"] == -1} { + lappend Binds::List "$type $flags [list $cmd] 0 [list $procname]" + } + return $cmd + } + + proc Del {type flags cmd procname} { + if {[set match [lsearch [binds] "$type $flags $cmd 0 $procname"]] != -1 || [set match [lsearch [binds] "$type $flags {${cmd}} 0 $procname"]] != -1} { + set Binds::List [lreplace $Binds::List $match $match] + return $cmd + } else { + error "Tcl error: no such binding" + } + } + + proc ProcessPubm {nick user handle channel text} { + # Loop bind list and execute + foreach n [binds pub] { + if {[string match [lindex $n 2] [lindex [split $text] 0]]} { + [lindex $n 4] $nick $user $handle $channel [lrange [join $text] 1 end] + } + } + foreach {type flags mask hits proc} [join [binds pubm]] { + regsub {^%} $mask {*} mask + if {[ModuleLoaded crypt]} {regsub {^¤} $nick {} nick} + if {[string match -nocase $mask "$channel $text"]} { + $proc $nick $user $handle $channel $text + } + } + } + + proc ProcessTime {} { + if {[clock format [clock seconds] -format "%S"] != 0} {return} + set time [clock format [clock seconds] -format "%M %H %d %m %Y"] + foreach {mi ho da mo ye} $time {} + # Loop bind list and execute + foreach n [binds time] { + if {[string match [lindex $n 2] $time]} { + [lindex $n 4] $mi $ho $da $mo $ye + } + } + } + + proc ProcessEvnt {event} { + foreach n [binds evnt] { + if {[string match [lindex $n 2] $event]} { + [lindex $n 4] $event + } + } + switch $event { + init-server { + set ::botnick [GetCurNick] + set ::server [GetServer] + set ::server-online [expr [GetServerOnline] / 1000] + } + disconnect-server { + set ::botnick "" + set ::server "" + set ::server-online 0 + } + } + } + + proc ProcessBot {from-bot cmd text} { + foreach n [binds bot] { + if {[string match [lindex $n 2] $cmd]} { + [lindex $n 4] ${from-bot} $cmd $text + } + } + } + + proc ProcessNick {nick user handle channel newnick} { + if {$nick == $::botnick} {set ::botnick $newnick} + foreach n [binds nick] { + if {[string match [lindex $n 2] $newnick]} { + [lindex $n 4] $nick $user $handle $channel $newnick + } + } + } + + proc ProcessKick {nick user handle channel target reason} { + foreach n [binds kick] { + if {[string match [lindex $n 2 0] $channel] && [string match [lindex $n 2 1] $target]} { + [lindex $n 4] $nick $user $handle $channel $target $reason + } + } + } + + proc ProcessDcc {handle idx text} { + set match 0 + foreach n [binds dcc] { + if {[string match -nocase [lindex $n 2] [string range [lindex $text 0] 1 end]]} { + [lindex $n 4] $handle $idx [lrange $text 1 end] + set match 1 + } + } + if {!$match} { + PutModule "Error, dcc trigger '[string range [lindex $text 0] 1 end]' doesnt exist" + } + } +} + +# Provide aliases according to eggdrop specs +proc ::bind {type flags cmd {procname ""}} {Binds::Add $type $flags $cmd $procname} +proc ::unbind {type flags cmd procname} {Binds::Del $type $flags $cmd $procname} +proc ::binds {{type ""}} {if {$type != ""} {set type "$type "};return [lsearch -all -inline $Binds::List "$type*"]} +proc ::bindlist {{type ""}} {foreach bind $Binds::List {PutModule "$bind"}} + +PutModule "modtcl script loaded: Binds v0.1" diff --git a/modules/extra/modtcl.cpp b/modules/extra/modtcl.cpp new file mode 100644 index 00000000..a32bd01a --- /dev/null +++ b/modules/extra/modtcl.cpp @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2004-2009 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "Chan.h" +#include "IRCSock.h" +#include "Modules.h" +#include "Server.h" +#include "User.h" +#include "znc.h" + +#include + +#define STDVAR (ClientData cd, Tcl_Interp *irp, int argc, const char *argv[]) + +#define BADARGS(nl, nh, example) do { \ + if ((argc < (nl)) || (argc > (nh))) { \ + Tcl_AppendResult(irp, "wrong # args: should be \"", \ + argv[0], (example), "\"", NULL); \ + return TCL_ERROR; \ + } \ +} while (0) + +class CModTcl; + +class CModTclTimer : public CTimer { +public: + + CModTclTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {} + virtual ~CModTclTimer() {} +protected: + virtual void RunJob(); + CModTcl* m_pParent; +}; + +class CModTclStartTimer : public CTimer { +public: + + CModTclStartTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {} + virtual ~CModTclStartTimer() {} +protected: + virtual void RunJob(); + CModTcl* m_pParent; +}; + + +class CModTcl : public CModule { +public: + MODCONSTRUCTOR(CModTcl) {} + + virtual ~CModTcl() { + if (interp) { + Tcl_DeleteInterp(interp); + } + } + + virtual bool OnLoad(const CString& sArgs, CString& sErrorMsg) { + interp = NULL; + AddTimer(new CModTclStartTimer(this, 1, 1, "ModTclStarter", "Timer for modtcl to load the interpreter.")); + return true; + } + + void Start() { + CString sMyArgs = GetArgs(); + + interp = Tcl_CreateInterp(); + Tcl_Init(interp); + Tcl_CreateCommand(interp, "Binds::ProcessPubm", tcl_Bind, this, NULL); + Tcl_CreateCommand(interp, "Binds::ProcessTime", tcl_Bind, this, NULL); + Tcl_CreateCommand(interp, "Binds::ProcessEvnt", tcl_Bind, this, NULL); + Tcl_CreateCommand(interp, "Binds::ProcessNick", tcl_Bind, this, NULL); + Tcl_CreateCommand(interp, "Binds::ProcessKick", tcl_Bind, this, NULL); + Tcl_CreateCommand(interp, "PutIRC", tcl_PutIRC, this, NULL); + Tcl_CreateCommand(interp, "PutIRCAs", tcl_PutIRCAs, this, NULL); + Tcl_CreateCommand(interp, "PutModule", tcl_PutModule, this, NULL); + Tcl_CreateCommand(interp, "PutStatus", tcl_PutStatus, this, NULL); + Tcl_CreateCommand(interp, "PutStatusNotice", tcl_PutStatusNotice, this, NULL); + Tcl_CreateCommand(interp, "PutUser", tcl_PutUser, this, NULL); + + Tcl_CreateCommand(interp, "GetLocalIP", tcl_GetLocalIP, this, NULL); + Tcl_CreateCommand(interp, "GetCurNick", tcl_GetCurNick, this, NULL); + Tcl_CreateCommand(interp, "GetUsername", tcl_GetUsername, this, NULL); + Tcl_CreateCommand(interp, "GetRealName", tcl_GetRealName, this, NULL); + Tcl_CreateCommand(interp, "GetVHost", tcl_GetVHost, this, NULL); + Tcl_CreateCommand(interp, "GetChans", tcl_GetChans, this, NULL); + Tcl_CreateCommand(interp, "GetChannelUsers", tcl_GetChannelUsers, this, NULL); + Tcl_CreateCommand(interp, "GetChannelModes", tcl_GetChannelModes, this, NULL); + Tcl_CreateCommand(interp, "GetServer", tcl_GetServer, this, NULL); + Tcl_CreateCommand(interp, "GetServerOnline", tcl_GetServerOnline, this, NULL); + Tcl_CreateCommand(interp, "GetModules", tcl_GetModules, this, NULL); + + Tcl_CreateCommand(interp, "exit", tcl_exit, this, NULL); + + if (!sMyArgs.empty()) { + i = Tcl_EvalFile(interp, sMyArgs.c_str()); + if (i != TCL_OK) { + result = interp->result; + PutModule(CString(result)); + } + } + + AddTimer(new CModTclTimer(this, 1, 0, "ModTclUpdate", "Timer for modtcl to process pending events and idle callbacks.")); + } + + virtual void OnModCommand(const CString& sCommand) { + CString sResult; + VCString vsResult; + CString sCmd = sCommand; + + if (sCmd.Token(0).CaseCmp(".tcl") == 0) + sCmd = sCmd.Token(1,true); + + if (sCmd.Left(1).CaseCmp(".") == 0) + sCmd = "Binds::ProcessDcc - - {" + sCmd + "}"; + + Tcl_Eval(interp, sCmd.c_str()); + + sResult = CString(Tcl_GetStringResult(interp)); + if (!sResult.empty()) { + sResult.Split("\n", vsResult); + unsigned int a = 0; + for (a = 0; a < vsResult.size(); a++) + PutModule(vsResult[a].TrimRight_n()); + } + } + + virtual void TclUpdate() { + while (Tcl_DoOneEvent(TCL_DONT_WAIT)) {} + i = Tcl_Eval(interp,"Binds::ProcessTime"); + if (i != TCL_OK) { + result = interp->result; + PutModule(CString(result)); + } + } + + CString TclEscape(CString sLine) { + sLine.Replace("\\","\\\\"); + sLine.Replace("{","\\{"); + sLine.Replace("}","\\}"); + return sLine; + } + + virtual void OnPreRehash() { + if (interp) + Tcl_Eval(interp,"Binds::ProcessEvnt prerehash"); + } + + virtual void OnPostRehash() { + if (interp) { + Tcl_Eval(interp,"rehash"); + Tcl_Eval(interp,"Binds::ProcessEvnt rehash"); + } + } + + virtual void OnIRCConnected() { + if (interp) + Tcl_Eval(interp, "Binds::ProcessEvnt init-server"); + } + + virtual void OnIRCDisconnected() { + if (interp) + Tcl_Eval(interp, "Binds::ProcessEvnt disconnect-server"); + } + + virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { + CString sMes = TclEscape(sMessage); + CString sNick = TclEscape(CString(Nick.GetNick())); + CString sHost = TclEscape(CString(Nick.GetIdent() + "@" + Nick.GetHost())); + CString sChannel = TclEscape(CString(Channel.GetName())); + + CString sCommand = "Binds::ProcessPubm {" + sNick + "} {" + sHost + "} - {" + sChannel + "} {" + sMes + "}"; + i = Tcl_Eval(interp, sCommand.c_str()); + if (i != TCL_OK) { + result = interp->result; + PutModule(CString(result)); + } + return CONTINUE; + } + + virtual void OnNick(const CNick& OldNick, const CString& sNewNick, const vector& vChans) { + CString sOldNick = TclEscape(CString(OldNick.GetNick())); + CString sNewNickTmp = TclEscape(sNewNick); + CString sHost = TclEscape(CString(OldNick.GetIdent() + "@" + OldNick.GetHost())); + + CString sCommand; + // Nick change is triggered for each common chan so that binds can be chan specific + unsigned int nLength = vChans.size(); + for (unsigned int n = 0; n < nLength; n++) { + sCommand = "Binds::ProcessNick {" + sOldNick + "} {" + sHost + "} - {" + vChans[n]->GetName() + "} {" + sNewNickTmp + "}"; + i = Tcl_Eval(interp, sCommand.c_str()); + if (i != TCL_OK) { + result = interp->result; + PutModule(CString(result)); + } + } + } + + virtual void OnKick(const CNick& OpNick, const CString& sKickedNick, CChan& Channel, const CString& sMessage) { + CString sOpNick = TclEscape(CString(OpNick.GetNick())); + CString sNick = TclEscape(sKickedNick); + CString sOpHost = TclEscape(CString(OpNick.GetIdent() + "@" + OpNick.GetHost())); + + CString sCommand = "Binds::ProcessKick {" + sOpNick + "} {" + sOpHost + "} - {" + Channel.GetName() + "} {" + sNick + "} {" + sMessage + "}"; + i = Tcl_Eval(interp, sCommand.c_str()); + if (i != TCL_OK) { + result = interp->result; + PutModule(CString(result)); + } + } + + +private: + Tcl_Interp *interp; + int i; + char *result; + + static CString argvit(const char *argv[], unsigned int end, unsigned int begin, CString delim) { + CString sRet; + unsigned int i; + if (begin < end) + sRet = CString(argv[begin]); + + for (i = begin + 1; i < end; i++) { + sRet = sRet + delim + CString(argv[i]); + } + + return sRet; + } + + // Placeholder for binds incase binds.tcl isn't used + static int tcl_Bind STDVAR {return TCL_OK;} + + static int tcl_GetLocalIP STDVAR { + CModTcl *mod = static_cast(cd); + Tcl_SetResult(irp, (char *)mod->m_pUser->GetLocalIP().c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetCurNick STDVAR { + CModTcl *mod = static_cast(cd); + Tcl_SetResult(irp, (char *)mod->m_pUser->GetCurNick().c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetUsername STDVAR { + CModTcl *mod = static_cast(cd); + Tcl_SetResult(irp, (char *)mod->m_pUser->GetUserName().c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetRealName STDVAR { + CModTcl *mod = static_cast(cd); + Tcl_SetResult(irp, (char *)mod->m_pUser->GetRealName().c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetVHost STDVAR { + CModTcl *mod = static_cast(cd); + Tcl_SetResult(irp, (char *)mod->m_pUser->GetVHost().c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetChans STDVAR { + char *p; + const char *l[1]; + CModTcl *mod = static_cast(cd); + + BADARGS(1, 1, ""); + + const vector& Channels = mod->m_pUser->GetChans(); + for (unsigned int c = 0; c < Channels.size(); c++) { + CChan* pChan = Channels[c]; + l[0] = pChan->GetName().c_str(); + p = Tcl_Merge(1, l); + Tcl_AppendElement(irp, p); + Tcl_Free((char *)p); + } + + return TCL_OK; + } + + static int tcl_GetChannelUsers STDVAR { + char *p; + const char *l[4]; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " channel"); + + CString sChannel = argvit(argv, argc, 1, " "); + CChan *pChannel = mod->m_pUser->FindChan(sChannel); + + if (!pChannel) { + CString sMsg = "invalid channel: " + sChannel; + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_ERROR; + } + + const map& msNicks = pChannel->GetNicks(); + for (map::const_iterator it = msNicks.begin(); it != msNicks.end(); it++) { + const CNick& Nick = *it->second; + l[0] = (Nick.GetNick()).c_str(); + l[1] = (Nick.GetIdent()).c_str(); + l[2] = (Nick.GetHost()).c_str(); + l[3] = (Nick.GetPermStr()).c_str(); + p = Tcl_Merge(4, l); + Tcl_AppendElement(irp, p); + Tcl_Free((char *)p); + } + + return TCL_OK; + } + + static int tcl_GetChannelModes STDVAR { + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " channel"); + + CString sChannel = argvit(argv, argc, 1, " "); + CChan *pChannel = mod->m_pUser->FindChan(sChannel); + CString sMsg; + + if (!pChannel) { + sMsg = "invalid channel: " + sChannel; + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_ERROR; + } + + sMsg = pChannel->GetModeString(); + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetServer STDVAR { + CModTcl *mod = static_cast(cd); + CServer* pServer = mod->m_pUser->GetCurrentServer(); + CString sMsg; + if (pServer) + sMsg = pServer->GetName() + ":" + CString(pServer->GetPort()); + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetServerOnline STDVAR { + CModTcl *mod = static_cast(cd); + CIRCSock* pIRCSock = mod->m_pUser->GetIRCSock(); + CString sMsg = "0"; + if (pIRCSock) + sMsg = CString(pIRCSock->GetStartTime()); + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_OK; + } + + static int tcl_GetModules STDVAR { + char *p; + const char *l[3]; + CModTcl *mod = static_cast(cd); + + BADARGS(1, 1, ""); + + CModules& GModules = CZNC::Get().GetModules(); + CModules& Modules = mod->m_pUser->GetModules(); + + for (unsigned int b = 0; b < GModules.size(); b++) { + l[0] = GModules[b]->GetModName().c_str(); + l[1] = GModules[b]->GetArgs().c_str(); + l[2] = "1"; // IsGlobal + p = Tcl_Merge(3, l); + Tcl_AppendElement(irp, p); + Tcl_Free((char *)p); + } + for (unsigned int b = 0; b < Modules.size(); b++) { + l[0] = Modules[b]->GetModName().c_str(); + l[1] = Modules[b]->GetArgs().c_str(); + l[2] = "0"; // IsGlobal + p = Tcl_Merge(3, l); + Tcl_AppendElement(irp, p); + Tcl_Free((char *)p); + } + + return TCL_OK; + } + + static int tcl_PutIRC STDVAR { + CString sMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " string"); + sMsg = argvit(argv, argc, 1, " "); + mod->m_pUser->PutIRC(sMsg); + return TCL_OK; + } + + static int tcl_PutIRCAs STDVAR { + CString sMsg; + + BADARGS(3, 999, " user string"); + + CUser *pUser = CZNC::Get().FindUser(argv[1]); + if (!pUser) { + sMsg = "invalid user " + CString(argv[1]); + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_ERROR; + } + + sMsg = argvit(argv, argc, 2, " "); + pUser->PutIRC(sMsg); + return TCL_OK; + } + + static int tcl_PutModule STDVAR { + CString sMsg; + VCString vsMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " string"); + sMsg = argvit(argv, argc, 1, " "); + //mod->PutModule(sMsg); + sMsg.Split("\n", vsMsg); + unsigned int a = 0; + for (a = 0; a < vsMsg.size(); a++) + mod->PutModule(vsMsg[a].TrimRight_n()); + return TCL_OK; + } + + static int tcl_PutStatus STDVAR { + CString sMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " string"); + sMsg = argvit(argv, argc, 1, " "); + mod->PutStatus(sMsg); + return TCL_OK; + } + + static int tcl_PutStatusNotice STDVAR { + CString sMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " string"); + sMsg = argvit(argv, argc, 1, " "); + mod->m_pUser->PutStatusNotice(sMsg); + return TCL_OK; + } + + static int tcl_PutUser STDVAR { + CString sMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(2, 999, " string"); + sMsg = argvit(argv, argc, 1, " "); + mod->m_pUser->PutUser(sMsg); + return TCL_OK; + } + + static int tcl_exit STDVAR { + CString sMsg; + CModTcl *mod = static_cast(cd); + + BADARGS(1, 2, " ?reason?"); + + if (! mod->m_pUser->IsAdmin()) { + sMsg = "You need to be administrator to shutdown the bnc."; + Tcl_SetResult(irp, (char*)sMsg.c_str(), TCL_VOLATILE); + return TCL_ERROR; + } + if (argc > 1) { + sMsg = argvit(argv, argc, 1, " "); + CZNC::Get().Broadcast(sMsg); + usleep(100000); // Sleep for 10ms to attempt to allow the previous Broadcast() to go through to all users + } + + throw CException(CException::EX_Shutdown); + + return TCL_OK; + } +}; + +void CModTclTimer::RunJob() { + CModTcl *p = (CModTcl *)m_pModule; + if (p) + p->TclUpdate(); +} + +void CModTclStartTimer::RunJob() { + CModTcl *p = (CModTcl *)m_pModule; + if (p) + p->Start(); +} + +MODULEDEFS(CModTcl, "Loads Tcl scripts as ZNC modules"); diff --git a/modules/extra/modtcl.tcl b/modules/extra/modtcl.tcl new file mode 100644 index 00000000..da037dd5 --- /dev/null +++ b/modules/extra/modtcl.tcl @@ -0,0 +1,122 @@ +# Copyright (C) 2004-2009 See the AUTHORS file for details. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published +# by the Free Software Foundation. +# +# TCL master file for ZNC modtcl +# +# Usage: LoadModule = modtcl /path/to/this/file +# + +# rehash simply reloads this file and shows info for any errors +set ::MasterFile [info script] +proc rehash {} { + if {[catch {source $::MasterFile}]} { + PutModule "Error rehashing tcl master file: $::errorInfo" + } +} +proc bgerror {message} { + PutModule "Background error: $::errorInfo" +} + +# set basic eggdrop style variables +set ::botnet-nick ZNC_[GetUsername] +set ::botnick [GetCurNick] +set ::server [GetServer] +set ::server-online [expr [GetServerOnline] / 1000] + +# add some eggdrop style procs +proc putlog message {PutModule $message} +proc puthelp {text {option ""}} { + if {[regexp -nocase {^(?:privmsg|notice) (\S+) :(.*)} $text . target line]} { + if {$target == "*modtcl"} {PutModule $line; return} + if {$target == "*status"} {PutStatus $line; return} + if {[botonchan $target]} {PutUser ":$::botnick![getchanhost $::botnick] $text"} + } + PutIRC $text +} +proc putserv {text {option ""}} {puthelp $text} +proc putquick {text {option ""}} {puthelp $text} +proc stripcodes {flags args} {return [regsub -all {\xF|\x2|\x16|\x1F|\x7|\x3([0-9]{1,2})?(,[0-9]{1,2})?} [join $args] {}]} +proc unixtime {} {return [clock seconds]} +proc duration {s} { + set ret "" + foreach {n m} "year 31536000 week 604800 day 86400 hour 3600 minute 60 second 1" { + if {$s >= $m} { + set tmp [expr $s / $m] + if {$tmp == 1} {set i ""} else {set i "s"} + set ret $ret[format "%lu %s%s " $tmp $n $i] + incr s -[expr $tmp * $m] + } + } + return $ret +} +proc encrypt {key string} {return [bencrypt $key $string]} +proc decrypt {key string} {return [bdecrypt $key $string]} +proc channels {} {return [GetChans]} +proc chanlist {channel {flags ""}} { + set ret "" + foreach u [GetChannelUsers [string trim $channel "{}"]] {lappend ret [lindex $u 0]} + return $ret +} +proc getchanmode channel {return [GetChannelModes [string trim $channel "{}"]]} +proc getchanhost {nick {channel ""}} { + # TODO: to have full info here we need to use /who #chan when we join + if {$channel == ""} {set channel [join [channels]]} + foreach c $channel { + foreach u [GetChannelUsers $c] { + if {[string match $nick [lindex $u 0]]} { + return "[lindex $u 1]@[lindex $u 2]" + } + } + } + return "" +} +proc onchan {nick chan} {return [expr [lsearch -exact [string tolower [chanlist $chan]] [string tolower $nick]] != -1]} +proc botonchan channel {return [onchan $::botnick $channel]} + +proc isop {nick {channel ""}} {return [PermCheck $nick "@" $channel]} +proc isvoice {nick {channel ""}} {return [PermCheck $nick "+" $channel]} +proc botisop {{channel ""}} {return [isop $::botnick $channel]} +proc botisvoice {{channel ""}} {return [isvoice $::botnick $channel]} + +proc PermCheck {nick perm channel} { + if {$channel == ""} {set channel [channels]} + if {[ModuleLoaded crypt]} {regsub {^¤} $nick {} nick} + foreach c $channel { + foreach u [GetChannelUsers $c] { + if {[string match -nocase $nick [lindex $u 0]] && [string match *$perm* [lindex $u 3]]} { + return 1 + } + } + } + return 0 +} +proc ModuleLoaded modname { + foreach {mod args glob} [join [GetModules]] { + if {[string match -nocase $modname $mod]} {return 1} + } + return 0 +} + +# rewrite all timers to use the after command +proc utimer {seconds tcl-command} {after [expr $seconds * 1000] ${tcl-command}} +proc timer {minutes tcl-command} {after [expr $minutes * 60 * 1000] ${tcl-command}} +proc utimers {} {set t {}; foreach a [after info] {lappend t "0 [lindex [after info $a] 0] $a"}; return $t} +proc timers {} {set t {}; foreach a [after info] {lappend t "0 [lindex [after info $a] 0] $a"}; return $t} +proc killtimer id {return [after cancel $id]} +proc killutimer id {return [after cancel $id]} + +# pseudo procs to satisfy script loading, no functionality +proc setudef {type name} {return} +proc modules {} {return "encryption"} +proc channel {command {options ""}} {return ""} +proc queuesize {{queue ""}} {return 0} +proc matchattr {handle flags {channel ""}} {return 0} + +# load other script files +source [file dirname $::MasterFile]/binds.tcl + + +PutModule "Succesfully loaded modtcl with master file: [info script]"