diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:18:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:18:39 +0000 |
commit | fff5217f02d91268ce90c8c05665602c059faaef (patch) | |
tree | 2ba24d32dc96eafe7ed0a85269548e76796d849d /src/irc/core | |
parent | Initial commit. (diff) | |
download | irssi-fff5217f02d91268ce90c8c05665602c059faaef.tar.xz irssi-fff5217f02d91268ce90c8c05665602c059faaef.zip |
Adding upstream version 1.4.5.upstream/1.4.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/irc/core')
52 files changed, 13054 insertions, 0 deletions
diff --git a/src/irc/core/Makefile.am b/src/irc/core/Makefile.am new file mode 100644 index 0000000..5881481 --- /dev/null +++ b/src/irc/core/Makefile.am @@ -0,0 +1,62 @@ +noinst_LIBRARIES = libirc_core.a + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + $(GLIB_CFLAGS) + +libirc_core_a_SOURCES = \ + bans.c \ + ctcp.c \ + channels-query.c \ + channel-events.c \ + channel-rejoin.c \ + irc.c \ + irc-core.c \ + irc-channels.c \ + irc-channels-setup.c \ + irc-chatnets.c \ + irc-commands.c \ + irc-expandos.c \ + irc-masks.c \ + irc-nicklist.c \ + irc-queries.c \ + irc-servers.c \ + irc-servers-reconnect.c \ + irc-servers-setup.c \ + irc-session.c \ + irc-cap.c \ + sasl.c \ + lag.c \ + massjoin.c \ + modes.c \ + mode-lists.c \ + netsplit.c \ + servers-idle.c \ + servers-redirect.c + +pkginc_irc_coredir=$(pkgincludedir)/src/irc/core +pkginc_irc_core_HEADERS = \ + bans.h \ + ctcp.h \ + channel-events.h \ + channel-rejoin.h \ + irc.h \ + irc-channels.h \ + irc-chatnets.h \ + irc-commands.h \ + irc-masks.h \ + irc-nicklist.h \ + irc-queries.h \ + irc-servers.h \ + irc-servers-setup.h \ + irc-cap.h \ + sasl.h \ + modes.h \ + mode-lists.h \ + module.h \ + netsplit.h \ + servers-idle.h \ + servers-redirect.h + +EXTRA_DIST = meson.build diff --git a/src/irc/core/Makefile.in b/src/irc/core/Makefile.in new file mode 100644 index 0000000..22d3fcf --- /dev/null +++ b/src/irc/core/Makefile.in @@ -0,0 +1,870 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/irc/core +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \ + $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_irc_core_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/irssi-config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libirc_core_a_AR = $(AR) $(ARFLAGS) +libirc_core_a_LIBADD = +am_libirc_core_a_OBJECTS = bans.$(OBJEXT) ctcp.$(OBJEXT) \ + channels-query.$(OBJEXT) channel-events.$(OBJEXT) \ + channel-rejoin.$(OBJEXT) irc.$(OBJEXT) irc-core.$(OBJEXT) \ + irc-channels.$(OBJEXT) irc-channels-setup.$(OBJEXT) \ + irc-chatnets.$(OBJEXT) irc-commands.$(OBJEXT) \ + irc-expandos.$(OBJEXT) irc-masks.$(OBJEXT) \ + irc-nicklist.$(OBJEXT) irc-queries.$(OBJEXT) \ + irc-servers.$(OBJEXT) irc-servers-reconnect.$(OBJEXT) \ + irc-servers-setup.$(OBJEXT) irc-session.$(OBJEXT) \ + irc-cap.$(OBJEXT) sasl.$(OBJEXT) lag.$(OBJEXT) \ + massjoin.$(OBJEXT) modes.$(OBJEXT) mode-lists.$(OBJEXT) \ + netsplit.$(OBJEXT) servers-idle.$(OBJEXT) \ + servers-redirect.$(OBJEXT) +libirc_core_a_OBJECTS = $(am_libirc_core_a_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/bans.Po \ + ./$(DEPDIR)/channel-events.Po ./$(DEPDIR)/channel-rejoin.Po \ + ./$(DEPDIR)/channels-query.Po ./$(DEPDIR)/ctcp.Po \ + ./$(DEPDIR)/irc-cap.Po ./$(DEPDIR)/irc-channels-setup.Po \ + ./$(DEPDIR)/irc-channels.Po ./$(DEPDIR)/irc-chatnets.Po \ + ./$(DEPDIR)/irc-commands.Po ./$(DEPDIR)/irc-core.Po \ + ./$(DEPDIR)/irc-expandos.Po ./$(DEPDIR)/irc-masks.Po \ + ./$(DEPDIR)/irc-nicklist.Po ./$(DEPDIR)/irc-queries.Po \ + ./$(DEPDIR)/irc-servers-reconnect.Po \ + ./$(DEPDIR)/irc-servers-setup.Po ./$(DEPDIR)/irc-servers.Po \ + ./$(DEPDIR)/irc-session.Po ./$(DEPDIR)/irc.Po \ + ./$(DEPDIR)/lag.Po ./$(DEPDIR)/massjoin.Po \ + ./$(DEPDIR)/mode-lists.Po ./$(DEPDIR)/modes.Po \ + ./$(DEPDIR)/netsplit.Po ./$(DEPDIR)/sasl.Po \ + ./$(DEPDIR)/servers-idle.Po ./$(DEPDIR)/servers-redirect.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libirc_core_a_SOURCES) +DIST_SOURCES = $(libirc_core_a_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_irc_coredir)" +HEADERS = $(pkginc_irc_core_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHAT_MODULES = @CHAT_MODULES@ +COMMON_LIBS = @COMMON_LIBS@ +COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +FUZZER_LIBS = @FUZZER_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBOTR_CFLAGS = @LIBOTR_CFLAGS@ +LIBOTR_LIBS = @LIBOTR_LIBS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +OTR_CFLAGS = @OTR_CFLAGS@ +OTR_LDFLAGS = @OTR_LDFLAGS@ +OTR_LINK_FLAGS = @OTR_LINK_FLAGS@ +OTR_LINK_LIBS = @OTR_LINK_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL_CFLAGS = @PERL_CFLAGS@ +PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@ +PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@ +PERL_LDFLAGS = @PERL_LDFLAGS@ +PERL_LINK_FLAGS = @PERL_LINK_FLAGS@ +PERL_LINK_LIBS = @PERL_LINK_LIBS@ +PERL_MM_OPT = @PERL_MM_OPT@ +PERL_MM_PARAMS = @PERL_MM_PARAMS@ +PERL_STATIC_LIBS = @PERL_STATIC_LIBS@ +PERL_USE_LIB = @PERL_USE_LIB@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROG_LIBS = @PROG_LIBS@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TEXTUI_LIBS = @TEXTUI_LIBS@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +installed_test_metadir = @installed_test_metadir@ +installed_testdir = @installed_testdir@ +irc_MODULES = @irc_MODULES@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +otr_module_lib = @otr_module_lib@ +otr_static_lib = @otr_static_lib@ +pdfdir = @pdfdir@ +perl_module_fe_lib = @perl_module_fe_lib@ +perl_module_lib = @perl_module_lib@ +perl_static_fe_lib = @perl_static_fe_lib@ +perl_static_lib = @perl_static_lib@ +perlpath = @perlpath@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sedpath = @sedpath@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LIBRARIES = libirc_core.a +AM_CPPFLAGS = \ + -I$(top_builddir) \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + $(GLIB_CFLAGS) + +libirc_core_a_SOURCES = \ + bans.c \ + ctcp.c \ + channels-query.c \ + channel-events.c \ + channel-rejoin.c \ + irc.c \ + irc-core.c \ + irc-channels.c \ + irc-channels-setup.c \ + irc-chatnets.c \ + irc-commands.c \ + irc-expandos.c \ + irc-masks.c \ + irc-nicklist.c \ + irc-queries.c \ + irc-servers.c \ + irc-servers-reconnect.c \ + irc-servers-setup.c \ + irc-session.c \ + irc-cap.c \ + sasl.c \ + lag.c \ + massjoin.c \ + modes.c \ + mode-lists.c \ + netsplit.c \ + servers-idle.c \ + servers-redirect.c + +pkginc_irc_coredir = $(pkgincludedir)/src/irc/core +pkginc_irc_core_HEADERS = \ + bans.h \ + ctcp.h \ + channel-events.h \ + channel-rejoin.h \ + irc.h \ + irc-channels.h \ + irc-chatnets.h \ + irc-commands.h \ + irc-masks.h \ + irc-nicklist.h \ + irc-queries.h \ + irc-servers.h \ + irc-servers-setup.h \ + irc-cap.h \ + sasl.h \ + modes.h \ + mode-lists.h \ + module.h \ + netsplit.h \ + servers-idle.h \ + servers-redirect.h + +EXTRA_DIST = meson.build +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/irc/core/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/core/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libirc_core.a: $(libirc_core_a_OBJECTS) $(libirc_core_a_DEPENDENCIES) $(EXTRA_libirc_core_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc_core.a + $(AM_V_AR)$(libirc_core_a_AR) libirc_core.a $(libirc_core_a_OBJECTS) $(libirc_core_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc_core.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bans.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channel-events.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channel-rejoin.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channels-query.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-cap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-channels-setup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-channels.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-chatnets.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-commands.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-core.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-expandos.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-masks.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-nicklist.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-queries.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-servers-reconnect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-servers-setup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-servers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc-session.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lag.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/massjoin.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mode-lists.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/modes.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netsplit.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sasl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers-idle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers-redirect.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_irc_coreHEADERS: $(pkginc_irc_core_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_irc_core_HEADERS)'; test -n "$(pkginc_irc_coredir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_irc_coredir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_irc_coredir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_irc_coredir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_irc_coredir)" || exit $$?; \ + done + +uninstall-pkginc_irc_coreHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_irc_core_HEADERS)'; test -n "$(pkginc_irc_coredir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_irc_coredir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_irc_coredir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/bans.Po + -rm -f ./$(DEPDIR)/channel-events.Po + -rm -f ./$(DEPDIR)/channel-rejoin.Po + -rm -f ./$(DEPDIR)/channels-query.Po + -rm -f ./$(DEPDIR)/ctcp.Po + -rm -f ./$(DEPDIR)/irc-cap.Po + -rm -f ./$(DEPDIR)/irc-channels-setup.Po + -rm -f ./$(DEPDIR)/irc-channels.Po + -rm -f ./$(DEPDIR)/irc-chatnets.Po + -rm -f ./$(DEPDIR)/irc-commands.Po + -rm -f ./$(DEPDIR)/irc-core.Po + -rm -f ./$(DEPDIR)/irc-expandos.Po + -rm -f ./$(DEPDIR)/irc-masks.Po + -rm -f ./$(DEPDIR)/irc-nicklist.Po + -rm -f ./$(DEPDIR)/irc-queries.Po + -rm -f ./$(DEPDIR)/irc-servers-reconnect.Po + -rm -f ./$(DEPDIR)/irc-servers-setup.Po + -rm -f ./$(DEPDIR)/irc-servers.Po + -rm -f ./$(DEPDIR)/irc-session.Po + -rm -f ./$(DEPDIR)/irc.Po + -rm -f ./$(DEPDIR)/lag.Po + -rm -f ./$(DEPDIR)/massjoin.Po + -rm -f ./$(DEPDIR)/mode-lists.Po + -rm -f ./$(DEPDIR)/modes.Po + -rm -f ./$(DEPDIR)/netsplit.Po + -rm -f ./$(DEPDIR)/sasl.Po + -rm -f ./$(DEPDIR)/servers-idle.Po + -rm -f ./$(DEPDIR)/servers-redirect.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_irc_coreHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/bans.Po + -rm -f ./$(DEPDIR)/channel-events.Po + -rm -f ./$(DEPDIR)/channel-rejoin.Po + -rm -f ./$(DEPDIR)/channels-query.Po + -rm -f ./$(DEPDIR)/ctcp.Po + -rm -f ./$(DEPDIR)/irc-cap.Po + -rm -f ./$(DEPDIR)/irc-channels-setup.Po + -rm -f ./$(DEPDIR)/irc-channels.Po + -rm -f ./$(DEPDIR)/irc-chatnets.Po + -rm -f ./$(DEPDIR)/irc-commands.Po + -rm -f ./$(DEPDIR)/irc-core.Po + -rm -f ./$(DEPDIR)/irc-expandos.Po + -rm -f ./$(DEPDIR)/irc-masks.Po + -rm -f ./$(DEPDIR)/irc-nicklist.Po + -rm -f ./$(DEPDIR)/irc-queries.Po + -rm -f ./$(DEPDIR)/irc-servers-reconnect.Po + -rm -f ./$(DEPDIR)/irc-servers-setup.Po + -rm -f ./$(DEPDIR)/irc-servers.Po + -rm -f ./$(DEPDIR)/irc-session.Po + -rm -f ./$(DEPDIR)/irc.Po + -rm -f ./$(DEPDIR)/lag.Po + -rm -f ./$(DEPDIR)/massjoin.Po + -rm -f ./$(DEPDIR)/mode-lists.Po + -rm -f ./$(DEPDIR)/modes.Po + -rm -f ./$(DEPDIR)/netsplit.Po + -rm -f ./$(DEPDIR)/sasl.Po + -rm -f ./$(DEPDIR)/servers-idle.Po + -rm -f ./$(DEPDIR)/servers-redirect.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_irc_coreHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkginc_irc_coreHEADERS install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkginc_irc_coreHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/irc/core/bans.c b/src/irc/core/bans.c new file mode 100644 index 0000000..8a43d39 --- /dev/null +++ b/src/irc/core/bans.c @@ -0,0 +1,359 @@ +/* + bans.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-masks.h> +#include <irssi/src/irc/core/irc-commands.h> +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/irc/core/mode-lists.h> +#include <irssi/src/core/nicklist.h> + +#define BAN_TYPE_NORMAL (IRC_MASK_USER | IRC_MASK_DOMAIN) +#define BAN_TYPE_USER (IRC_MASK_USER) +#define BAN_TYPE_HOST (IRC_MASK_HOST | IRC_MASK_DOMAIN) +#define BAN_TYPE_DOMAIN (IRC_MASK_DOMAIN) +#define BAN_FIRST "1" +#define BAN_LAST "-1" + +static char *default_ban_type_str; +static int default_ban_type; + +char *ban_get_mask(IRC_CHANNEL_REC *channel, const char *nick, int ban_type) +{ + NICK_REC *rec; + char *str, *user, *host; + int size; + + g_return_val_if_fail(IS_IRC_CHANNEL(channel), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = nicklist_find(CHANNEL(channel), nick); + if (rec == NULL) return NULL; + if (rec->host == NULL) { + g_warning("channel %s is not synced, using nick ban for %s", channel->name, nick); + return g_strdup_printf("%s!*@*", nick); + } + + if (ban_type <= 0) + ban_type = default_ban_type; + + str = irc_get_mask(nick, rec->host, ban_type); + + /* there's a limit of 10 characters in user mask. so, banning + someone with user mask of 10 characters gives us "*1234567890", + which is one too much.. so, remove the first character after "*" + so we'll get "*234567890" */ + user = strchr(str, '!'); + if (user == NULL) return str; + + host = strchr(++user, '@'); + if (host == NULL) return str; + + size = (int) (host-user); + if (size >= 10) { + /* too long user mask */ + memmove(user+1, user+(size-9), strlen(user+(size-9))+1); + } + return str; +} + +char *ban_get_masks(IRC_CHANNEL_REC *channel, const char *nicks, int ban_type) +{ + GString *str; + char **ban, **banlist, *realban, *ret; + + str = g_string_new(NULL); + banlist = g_strsplit(nicks, " ", -1); + for (ban = banlist; *ban != NULL; ban++) { + if (**ban == '$' || strchr(*ban, '!') != NULL) { + /* explicit ban */ + g_string_append_printf(str, "%s ", *ban); + continue; + } + + /* ban nick */ + realban = ban_get_mask(channel, *ban, ban_type); + if (realban != NULL) { + g_string_append_printf(str, "%s ", realban); + g_free(realban); + } + } + g_strfreev(banlist); + + if (str->len > 0) + g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +void ban_set(IRC_CHANNEL_REC *channel, const char *bans, int ban_type) +{ + char *masks; + + g_return_if_fail(bans != NULL); + + if (ban_type <= 0) + ban_type = default_ban_type; + + masks = ban_get_masks(channel, bans, ban_type); + channel_set_singlemode(channel, masks, "+b"); + g_free(masks); +} + +void ban_remove(IRC_CHANNEL_REC *channel, const char *bans) +{ + GString *str; + GSList *tmp; + BAN_REC *rec; + char **ban, **banlist; + int found; + + g_return_if_fail(bans != NULL); + + str = g_string_new(NULL); + banlist = g_strsplit(bans, " ", -1); + for (ban = banlist; *ban != NULL; ban++) { + found = FALSE; + for (tmp = channel->banlist; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (match_wildcards(*ban, rec->ban)) { + g_string_append_printf(str, "%s ", rec->ban); + found = TRUE; + } + } + + if (!found) { + rec = NULL; + if (!g_ascii_strcasecmp(*ban, BAN_LAST)) { + /* unnbanning last set ban */ + rec = g_slist_nth_data(channel->banlist, + g_slist_length(channel->banlist) - 1); + } + else if (is_numeric(*ban, '\0')) { + /* unbanning with ban# */ + rec = g_slist_nth_data(channel->banlist, + atoi(*ban)-1); + } + if (rec != NULL) + g_string_append_printf(str, "%s ", rec->ban); + else if (!channel->synced) + g_warning("channel %s is not synced", channel->name); + } + } + g_strfreev(banlist); + + if (str->len > 0) + channel_set_singlemode(channel, str->str, "-b"); + g_string_free(str, TRUE); +} + +static void command_set_ban(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item, int set, int ban_type) +{ + IRC_CHANNEL_REC *chanrec; + char *channel, *nicks; + void *free_arg; + + g_return_if_fail(data != NULL); + if (server == NULL || !server->connected || !IS_IRC_SERVER(server)) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, item, &channel, &nicks)) return; + if (!server_ischannel(SERVER(server), channel)) cmd_param_error(CMDERR_NOT_JOINED); + if (*nicks == '\0') { + if (g_strcmp0(data, "*") != 0) + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + /* /BAN * or /UNBAN * - ban/unban everyone */ + nicks = (char *) data; + } + + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) + cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + if (set) + ban_set(chanrec, nicks, ban_type); + else + ban_remove(chanrec, nicks); + + cmd_params_free(free_arg); +} + +static int parse_custom_ban(const char *type) +{ + char **list; + int n, ban_type; + + ban_type = 0; + list = g_strsplit(type, " ", -1); + for (n = 0; list[n] != NULL; n++) { + if (i_toupper(list[n][0]) == 'N') + ban_type |= IRC_MASK_NICK; + else if (i_toupper(list[n][0]) == 'U') + ban_type |= IRC_MASK_USER; + else if (i_toupper(list[n][0]) == 'H') + ban_type |= IRC_MASK_HOST | IRC_MASK_DOMAIN; + else if (i_toupper(list[n][0]) == 'D') + ban_type |= IRC_MASK_DOMAIN; + } + g_strfreev(list); + + return ban_type; +} + +static int parse_ban_type(const char *type) +{ + const char *pos; + + g_return_val_if_fail(type != NULL, 0); + + if (i_toupper(type[0]) == 'N') + return BAN_TYPE_NORMAL; + if (i_toupper(type[0]) == 'U') + return BAN_TYPE_USER; + if (i_toupper(type[0]) == 'H') + return BAN_TYPE_HOST; + if (i_toupper(type[0]) == 'D') + return BAN_TYPE_DOMAIN; + if (i_toupper(type[0]) == 'C') { + pos = strchr(type, ' '); + if (pos != NULL) + return parse_custom_ban(pos+1); + } + + return 0; +} + +/* SYNTAX: BAN [-normal | -user | -host | -domain | -custom <type>] <nicks/masks> */ +static void cmd_ban(const char *data, IRC_SERVER_REC *server, void *item) +{ + GHashTable *optlist; + const char *custom_type; + char *ban; + int ban_type; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "ban", &optlist, &ban)) + return; + + if (g_hash_table_lookup(optlist, "normal") != NULL) + ban_type = BAN_TYPE_NORMAL; + else if (g_hash_table_lookup(optlist, "user") != NULL) + ban_type = BAN_TYPE_USER; + else if (g_hash_table_lookup(optlist, "host") != NULL) + ban_type = BAN_TYPE_HOST; + else if (g_hash_table_lookup(optlist, "domain") != NULL) + ban_type = BAN_TYPE_DOMAIN; + else { + custom_type = g_hash_table_lookup(optlist, "custom"); + if (custom_type != NULL) + ban_type = parse_custom_ban(custom_type); + else + ban_type = default_ban_type; + } + + command_set_ban(ban, server, item, TRUE, ban_type); + + cmd_params_free(free_arg); +} + +/* SYNTAX: UNBAN -first | -last | <id> | <masks> */ +static void cmd_unban(const char *data, IRC_SERVER_REC *server, void *item) +{ + GHashTable *optlist; + char *ban; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "unban", &optlist, &ban)) + return; + + ban = NULL; + if (g_hash_table_lookup(optlist, "first") != NULL) + ban = g_strdup(BAN_FIRST); + else if (g_hash_table_lookup(optlist, "last") != NULL) + ban = g_strdup(BAN_LAST); + + command_set_ban(ban ? ban : data, server, item, FALSE, 0); + + g_free(ban); + + cmd_params_free(free_arg); +} + +static void read_settings(void) +{ + if (default_ban_type_str != NULL && + g_strcmp0(default_ban_type_str, settings_get_str("ban_type")) == 0) + return; + + g_free_not_null(default_ban_type_str); + default_ban_type = parse_ban_type(settings_get_str("ban_type")); + if (default_ban_type <= 0 || default_ban_type_str != NULL) { + signal_emit("ban type changed", 1, + GINT_TO_POINTER(default_ban_type)); + } + + if (default_ban_type <= 0) + default_ban_type = IRC_MASK_USER|IRC_MASK_DOMAIN; + + default_ban_type_str = g_strdup(settings_get_str("ban_type")); +} + +void bans_init(void) +{ + default_ban_type_str = NULL; + settings_add_str("misc", "ban_type", "normal"); + + command_bind_irc("ban", NULL, (SIGNAL_FUNC) cmd_ban); + command_bind_irc("unban", NULL, (SIGNAL_FUNC) cmd_unban); + command_set_options("ban", "normal user host domain +custom"); + command_set_options("unban", "first last"); + + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void bans_deinit(void) +{ + g_free_not_null(default_ban_type_str); + + command_unbind("ban", (SIGNAL_FUNC) cmd_ban); + command_unbind("unban", (SIGNAL_FUNC) cmd_unban); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/irc/core/bans.h b/src/irc/core/bans.h new file mode 100644 index 0000000..5f05266 --- /dev/null +++ b/src/irc/core/bans.h @@ -0,0 +1,14 @@ +#ifndef IRSSI_IRC_CORE_BANS_H +#define IRSSI_IRC_CORE_BANS_H + +void bans_init(void); +void bans_deinit(void); + +/* if ban_type is <= 0, use the default */ +char *ban_get_mask(IRC_CHANNEL_REC *channel, const char *nick, int ban_type); +char *ban_get_masks(IRC_CHANNEL_REC *channel, const char *nicks, int ban_type); + +void ban_set(IRC_CHANNEL_REC *channel, const char *bans, int ban_type); +void ban_remove(IRC_CHANNEL_REC *channel, const char *bans); + +#endif diff --git a/src/irc/core/channel-events.c b/src/irc/core/channel-events.c new file mode 100644 index 0000000..3cdfe96 --- /dev/null +++ b/src/irc/core/channel-events.c @@ -0,0 +1,402 @@ +/* + channel-events.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/irc/core/channel-events.h> +#include <irssi/src/core/channels-setup.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/recode.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> + +static void check_join_failure(IRC_SERVER_REC *server, const char *channel) +{ + CHANNEL_REC *chanrec; + char *chan2; + + if (channel[0] == '!' && channel[1] == '!') + channel++; /* server didn't understand !channels */ + + chanrec = channel_find(SERVER(server), channel); + if (chanrec == NULL && channel[0] == '!' && strlen(channel) > 6) { + /* it probably replied with the full !channel name, + find the channel with the short name.. */ + chan2 = g_strdup_printf("!%s", channel+6); + chanrec = channel_find(SERVER(server), chan2); + g_free(chan2); + } + + if (chanrec != NULL && !chanrec->joined) { + chanrec->left = TRUE; + channel_destroy(chanrec); + } +} + +static void irc_server_event(IRC_SERVER_REC *server, const char *line) +{ + char *params, *numeric, *channel; + + /* We'll be checking "4xx <your nick> <channel>" for channels + which we haven't joined yet. 4xx are error codes and should + indicate that the join failed. */ + params = event_get_params(line, 3, &numeric, NULL, &channel); + + if (numeric[0] == '4') + check_join_failure(server, channel); + + g_free(params); +} + +static void event_no_such_channel(IRC_SERVER_REC *server, const char *data) +{ + CHANNEL_REC *chanrec; + CHANNEL_SETUP_REC *setup; + char *params, *channel; + + params = event_get_params(data, 2, NULL, &channel); + chanrec = *channel == '!' && channel[1] != '\0' ? + channel_find(SERVER(server), channel) : NULL; + + if (chanrec != NULL) { + /* !channel didn't exist, so join failed */ + setup = channel_setup_find(chanrec->name, + chanrec->server->connrec->chatnet); + if (setup != NULL && setup->autojoin) { + /* it's autojoin channel though, so create it */ + irc_send_cmdv(server, "JOIN !%s", chanrec->name); + g_free(params); + return; + } + } + + check_join_failure(server, channel); + g_free(params); +} + +static void event_duplicate_channel(IRC_SERVER_REC *server, const char *data) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *p; + + g_return_if_fail(data != NULL); + + /* this new addition to ircd breaks completely with older + "standards", "nick Duplicate ::!!channel ...." */ + params = event_get_params(data, 3, NULL, NULL, &channel); + p = strchr(channel, ' '); + if (p != NULL) *p = '\0'; + + if (channel[0] == '!') { + chanrec = channel_find(SERVER(server), + channel+(channel[1] == '!')); + if (chanrec != NULL && !chanrec->names_got) { + chanrec->left = TRUE; + channel_destroy(chanrec); + } + } + + g_free(params); +} + +static void channel_change_topic(IRC_SERVER_REC *server, const char *channel, + const char *topic, const char *setby, + time_t settime) +{ + CHANNEL_REC *chanrec; + char *recoded = NULL; + + chanrec = channel_find(SERVER(server), channel); + if (chanrec == NULL) return; + /* the topic may be send out encoded, so we need to + recode it back or /topic <tab> will not work properly */ + recoded = recode_in(SERVER(server), topic, channel); + if (topic != NULL) { + g_free_not_null(chanrec->topic); + chanrec->topic = recoded == NULL ? NULL : g_strdup(recoded); + } + g_free(recoded); + + g_free_not_null(chanrec->topic_by); + chanrec->topic_by = g_strdup(setby); + + if (chanrec->topic_by == NULL) { + /* ensure invariant topic_time > 0 <=> topic_by != NULL. + this could be triggered by a topic command without sender */ + chanrec->topic_time = 0; + } else { + chanrec->topic_time = settime; + } + + signal_emit("channel topic changed", 1, chanrec); +} + +static void event_topic_get(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel, *topic; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &channel, &topic); + channel_change_topic(server, channel, topic, NULL, 0); + g_free(params); +} + +static void event_topic(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *channel, *topic, *mask; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &channel, &topic); + mask = addr == NULL ? g_strdup(nick) : + g_strconcat(nick, "!", addr, NULL); + channel_change_topic(server, channel, topic, mask, time(NULL)); + g_free(mask); + g_free(params); +} + +static void event_topic_info(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel, *topicby, *topictime; + time_t t; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &channel, + &topicby, &topictime); + + t = (time_t) atol(topictime); + channel_change_topic(server, channel, NULL, topicby, t); + g_free(params); +} + +/* Find any unjoined channel that matches `channel'. Long channel names are + also a bit problematic, so find a channel where start of the name matches. */ +static IRC_CHANNEL_REC *channel_find_unjoined(IRC_SERVER_REC *server, + const char *channel) +{ + GSList *tmp; + int len; + + len = strlen(channel); + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + IRC_CHANNEL_REC *rec = tmp->data; + + if (!IS_IRC_CHANNEL(rec) || rec->joined) + continue; + + if (g_ascii_strncasecmp(channel, rec->name, len) == 0 && + (len > 20 || rec->name[len] == '\0')) + return rec; + } + + return NULL; +} + +static void event_join(IRC_SERVER_REC *server, const char *data, const char *nick, const char *address) +{ + char *params, *channel, *tmp, *shortchan; + IRC_CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + if (g_ascii_strcasecmp(nick, server->nick) != 0) { + /* someone else joined channel, no need to do anything */ + return; + } + + if (server->userhost == NULL) + server->userhost = g_strdup(address); + + params = event_get_params(data, 1, &channel); + tmp = strchr(channel, 7); /* ^G does something weird.. */ + if (tmp != NULL) *tmp = '\0'; + + if (*channel != '!' || strlen(channel) < 7) + shortchan = NULL; + else { + /* !channels have 5 chars long identification string before + it's name, it's not known when /join is called so rename + !channel here to !ABCDEchannel */ + shortchan = g_strdup_printf("!%s", channel+6); + chanrec = channel_find_unjoined(server, shortchan); + if (chanrec != NULL) { + channel_change_name(CHANNEL(chanrec), channel); + g_free(chanrec->name); + chanrec->name = g_strdup(channel); + } else { + /* well, did we join it with full name? if so, and if + this was the first short one, change it's name. */ + chanrec = channel_find_unjoined(server, channel); + if (chanrec != NULL && + irc_channel_find(server, shortchan) == NULL) { + channel_change_visible_name(CHANNEL(chanrec), + shortchan); + } + } + } + + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL && chanrec->joined) { + /* already joined this channel - probably a broken proxy that + forgot to send PART between */ + chanrec->left = TRUE; + channel_destroy(CHANNEL(chanrec)); + chanrec = NULL; + } + + if (chanrec == NULL) { + /* look again, because of the channel name cut issues. */ + chanrec = channel_find_unjoined(server, channel); + } + + if (chanrec == NULL) { + /* didn't get here with /join command.. */ + chanrec = irc_channel_create(server, channel, shortchan, TRUE); + } + + chanrec->joined = TRUE; + if (g_strcmp0(chanrec->name, channel) != 0) { + g_free(chanrec->name); + chanrec->name = g_strdup(channel); + } + + g_free(shortchan); + g_free(params); +} + +static void event_part(IRC_SERVER_REC *server, const char *data, const char *nick) +{ + char *params, *channel, *reason; + CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + if (g_ascii_strcasecmp(nick, server->nick) != 0) { + /* someone else part, no need to do anything here */ + return; + } + + params = event_get_params(data, 2, &channel, &reason); + + chanrec = channel_find(SERVER(server), channel); + if (chanrec != NULL && chanrec->joined) { + chanrec->left = TRUE; + channel_destroy(chanrec); + } + + g_free(params); +} + +static void event_kick(IRC_SERVER_REC *server, const char *data) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *nick, *reason; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, &channel, &nick, &reason); + + if (g_ascii_strcasecmp(nick, server->nick) != 0) { + /* someone else was kicked, no need to do anything */ + g_free(params); + return; + } + + chanrec = channel_find(SERVER(server), channel); + if (chanrec != NULL) { + irc_server_purge_output(server, channel); + chanrec->kicked = TRUE; + channel_destroy(chanrec); + } + + g_free(params); +} + +static void event_invite(IRC_SERVER_REC *server, const char *data) +{ + char *params, *nick, *channel, *shortchan; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &nick, &channel); + + if (server->nick_comp_func(nick, server->nick) != 0) { + /* someone else was invited, no need to do anything */ + g_free(params); + return; + } + + if (irc_channel_find(server, channel) == NULL) { + /* check if we're supposed to autojoin this channel */ + CHANNEL_SETUP_REC *setup; + + setup = channel_setup_find(channel, server->connrec->chatnet); + if (setup == NULL && channel[0] == '!' && + strlen(channel) > 6) { + shortchan = g_strdup_printf("!%s", channel+6); + setup = channel_setup_find(shortchan, + server->connrec->chatnet); + g_free(shortchan); + } + if (setup != NULL && setup->autojoin && settings_get_bool("join_auto_chans_on_invite")) + server->channels_join(SERVER(server), channel, TRUE); + } + + g_free_not_null(server->last_invite); + server->last_invite = g_strdup(channel); + g_free(params); +} + +void channel_events_init(void) +{ + settings_add_bool("misc", "join_auto_chans_on_invite", TRUE); + + signal_add_last("server event", (SIGNAL_FUNC) irc_server_event); + signal_add_first("event 403", (SIGNAL_FUNC) event_no_such_channel); /* no such channel */ + signal_add_first("event 407", (SIGNAL_FUNC) event_duplicate_channel); /* duplicate channel */ + + signal_add("event topic", (SIGNAL_FUNC) event_topic); + signal_add_first("event join", (SIGNAL_FUNC) event_join); + signal_add("event part", (SIGNAL_FUNC) event_part); + signal_add("event kick", (SIGNAL_FUNC) event_kick); + signal_add("event invite", (SIGNAL_FUNC) event_invite); + signal_add("event 332", (SIGNAL_FUNC) event_topic_get); + signal_add("event 333", (SIGNAL_FUNC) event_topic_info); +} + +void channel_events_deinit(void) +{ + signal_remove("server event", (SIGNAL_FUNC) irc_server_event); + signal_remove("event 403", (SIGNAL_FUNC) event_no_such_channel); /* no such channel */ + signal_remove("event 407", (SIGNAL_FUNC) event_duplicate_channel); /* duplicate channel */ + + signal_remove("event topic", (SIGNAL_FUNC) event_topic); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event part", (SIGNAL_FUNC) event_part); + signal_remove("event kick", (SIGNAL_FUNC) event_kick); + signal_remove("event invite", (SIGNAL_FUNC) event_invite); + signal_remove("event 332", (SIGNAL_FUNC) event_topic_get); + signal_remove("event 333", (SIGNAL_FUNC) event_topic_info); +} diff --git a/src/irc/core/channel-events.h b/src/irc/core/channel-events.h new file mode 100644 index 0000000..ada3e3a --- /dev/null +++ b/src/irc/core/channel-events.h @@ -0,0 +1,9 @@ +#ifndef IRSSI_IRC_CORE_CHANNEL_EVENTS_H +#define IRSSI_IRC_CORE_CHANNEL_EVENTS_H + +#include <irssi/src/irc/core/irc.h> + +void channel_events_init(void); +void channel_events_deinit(void); + +#endif diff --git a/src/irc/core/channel-rejoin.c b/src/irc/core/channel-rejoin.c new file mode 100644 index 0000000..f49c69b --- /dev/null +++ b/src/irc/core/channel-rejoin.c @@ -0,0 +1,286 @@ +/* + channel-rejoin.c : rejoin to channel if it's "temporarily unavailable" + this has nothing to do with autorejoin if kicked + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-commands.h> +#include <irssi/src/irc/core/channel-rejoin.h> + +#define REJOIN_TIMEOUT (1000*60*5) /* try to rejoin every 5 minutes */ + +static int rejoin_tag; + +static void rejoin_destroy(IRC_SERVER_REC *server, REJOIN_REC *rec) +{ + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(rec != NULL); + + server->rejoin_channels = + g_slist_remove(server->rejoin_channels, rec); + + signal_emit("channel rejoin remove", 2, server, rec); + + g_free(rec->channel); + g_free_not_null(rec->key); + g_free(rec); +} + +static REJOIN_REC *rejoin_find(IRC_SERVER_REC *server, const char *channel) +{ + GSList *tmp; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + g_return_val_if_fail(channel != NULL, NULL); + + for (tmp = server->rejoin_channels; tmp != NULL; tmp = tmp->next) { + REJOIN_REC *rec = tmp->data; + + if (g_ascii_strcasecmp(rec->channel, channel) == 0) + return rec; + } + + return NULL; +} + +#define channel_have_key(chan) \ + ((chan) != NULL && (chan)->key != NULL && (chan)->key[0] != '\0') + +static int channel_rejoin(IRC_SERVER_REC *server, const char *channel) +{ + IRC_CHANNEL_REC *chanrec; + REJOIN_REC *rec; + + g_return_val_if_fail(IS_IRC_SERVER(server), 0); + g_return_val_if_fail(channel != NULL, 0); + + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL || chanrec->joined) return 0; + + if (!settings_get_bool("channels_rejoin_unavailable")) { + chanrec->left = TRUE; + channel_destroy(CHANNEL(chanrec)); + return 0; + } + + rec = rejoin_find(server, channel); + if (rec != NULL) { + /* already exists */ + rec->joining = FALSE; + + /* update channel key */ + g_free_and_null(rec->key); + if (channel_have_key(chanrec)) + rec->key = g_strdup(chanrec->key); + } else { + /* new rejoin */ + rec = g_new0(REJOIN_REC, 1); + rec->channel = g_strdup(channel); + if (channel_have_key(chanrec)) + rec->key = g_strdup(chanrec->key); + + server->rejoin_channels = + g_slist_append(server->rejoin_channels, rec); + signal_emit("channel rejoin new", 2, server, rec); + } + + chanrec->left = TRUE; + channel_destroy(CHANNEL(chanrec)); + return 1; +} + +static void event_duplicate_channel(IRC_SERVER_REC *server, const char *data) +{ + CHANNEL_REC *chanrec; + char *params, *channel, *p; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, NULL, &channel); + p = strchr(channel, ' '); + if (p != NULL) *p = '\0'; + + if (channel[0] == '!' && channel[1] != '!') { + chanrec = channel_find(SERVER(server), channel); + if (chanrec != NULL && !chanrec->names_got) { + /* duplicate channel - this should only happen when + there's some sync problem with servers, rejoining + after a while should help. + + note that this same 407 is sent when trying to + create !!channel that already exists so we don't + want to try rejoining then. */ + if (channel_rejoin(server, channel)) { + signal_stop(); + } + } + } + + g_free(params); +} + +static void event_target_unavailable(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel; + IRC_CHANNEL_REC *chanrec; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (server_ischannel(SERVER(server), channel)) { + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL && chanrec->joined) { + /* dalnet event - can't change nick while + banned in channel */ + } else { + /* channel is unavailable - try to join again + a bit later */ + if (channel_rejoin(server, channel)) { + signal_stop(); + } + } + } + + g_free(params); +} + +/* join ok/failed - remove from rejoins list. this happens always after join + except if the "target unavailable" error happens again */ +static void sig_remove_rejoin(IRC_CHANNEL_REC *channel) +{ + REJOIN_REC *rec; + + if (!IS_IRC_CHANNEL(channel)) + return; + + rec = rejoin_find(channel->server, channel->name); + if (rec != NULL && rec->joining) { + /* join failed, remove the rejoin */ + rejoin_destroy(channel->server, rec); + } +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + while (server->rejoin_channels != NULL) + rejoin_destroy(server, server->rejoin_channels->data); +} + +static void server_rejoin_channels(IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + GString *channels, *keys; + int use_keys; + + g_return_if_fail(IS_IRC_SERVER(server)); + + channels = g_string_new(NULL); + keys = g_string_new(NULL); + + use_keys = FALSE; + for (tmp = server->rejoin_channels; tmp != NULL; tmp = next) { + REJOIN_REC *rec = tmp->data; + next = tmp->next; + + if (rec->joining) { + /* we missed the join (failed) message, + remove from rejoins.. */ + rejoin_destroy(server, rec); + continue; + } + + rec->joining = TRUE; + g_string_append_printf(channels, "%s,", rec->channel); + if (rec->key == NULL) + g_string_append(keys, "x,"); + else { + g_string_append_printf(keys, "%s,", rec->key); + use_keys = TRUE; + } + } + + if (channels->len > 0) { + g_string_truncate(channels, channels->len-1); + g_string_truncate(keys, keys->len-1); + + if (use_keys) g_string_append_printf(channels, " %s", keys->str); + server->channels_join(SERVER(server), channels->str, TRUE); + } + + g_string_free(channels, TRUE); + g_string_free(keys, TRUE); +} + +static int sig_rejoin(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + if (IS_IRC_SERVER(rec) && rec->rejoin_channels != NULL) + server_rejoin_channels(rec); + } + + return TRUE; +} + +static void cmd_rmrejoins(const char *data, IRC_SERVER_REC *server) +{ + CMD_IRC_SERVER(server); + + while (server->rejoin_channels != NULL) + rejoin_destroy(server, server->rejoin_channels->data); +} + +void channel_rejoin_init(void) +{ + settings_add_bool("servers", "channels_rejoin_unavailable", TRUE); + + rejoin_tag = g_timeout_add(REJOIN_TIMEOUT, + (GSourceFunc) sig_rejoin, NULL); + + command_bind_irc("rmrejoins", NULL, (SIGNAL_FUNC) cmd_rmrejoins); + signal_add_first("event 407", (SIGNAL_FUNC) event_duplicate_channel); + signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_add_first("channel joined", (SIGNAL_FUNC) sig_remove_rejoin); + signal_add_first("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void channel_rejoin_deinit(void) +{ + g_source_remove(rejoin_tag); + + command_unbind("rmrejoins", (SIGNAL_FUNC) cmd_rmrejoins); + signal_remove("event 407", (SIGNAL_FUNC) event_duplicate_channel); + signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_remove("channel joined", (SIGNAL_FUNC) sig_remove_rejoin); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_remove_rejoin); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/channel-rejoin.h b/src/irc/core/channel-rejoin.h new file mode 100644 index 0000000..3c46579 --- /dev/null +++ b/src/irc/core/channel-rejoin.h @@ -0,0 +1,13 @@ +#ifndef IRSSI_IRC_CORE_CHANNEL_REJOIN_H +#define IRSSI_IRC_CORE_CHANNEL_REJOIN_H + +typedef struct { + char *channel; + char *key; + unsigned int joining:1; +} REJOIN_REC; + +void channel_rejoin_init(void); +void channel_rejoin_deinit(void); + +#endif diff --git a/src/irc/core/channels-query.c b/src/irc/core/channels-query.c new file mode 100644 index 0000000..05c9768 --- /dev/null +++ b/src/irc/core/channels-query.c @@ -0,0 +1,674 @@ +/* + channels-query.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* + + How the thing works: + + - After channel is joined and NAMES list is got, send "channel joined" signal + - "channel joined" : add channel to server->queries lists + +loop: + - Wait for NAMES list from all channels before doing anything else.. + - After got the last NAMES list, start sending the queries .. + - find the query to send, check where server->queries list isn't NULL + (mode, who, banlist, ban exceptions, invite list) + - if not found anything -> all channels are synced + - send "command #chan1,#chan2,#chan3,.." command to server + - wait for reply from server, then check if it was last query to be sent to + channel. If it was, send "channel sync" signal + - check if the reply was for last channel in the command list. If so, + goto loop +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/signals.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/irc/core/mode-lists.h> +#include <irssi/src/core/nicklist.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/servers-redirect.h> + +/* here are the WHOX commands we send. the full spec can be found on [1]. + + (1) WHOX_CHANNEL_FULL_CMD for getting the user list when we join a channel. we request the fields + c (channel), u (user), h (host), n (nick), f (flags), d (hops), a (account), and r (the real + name goes last because it is the only that can contain spaces.) we request all those fields + as they are also included in the "regular" WHO reply we would get without WHOX. + + (2) WHOX_USERACCOUNT_CMD for getting the account names of people that joined. this code is + obviously only used when we don't have extended-joins. we request n (nick) and a (account) + only, and we only send WHO nick with this command. + + [1] https://github.com/UndernetIRC/ircu2/blob/u2_10_12_branch/doc/readme.who + */ +#define WHOX_CHANNEL_FULL_CMD "WHO %s %%tcuhnfdar," WHOX_CHANNEL_FULL_ID +#define WHOX_USERACCOUNT_CMD "WHO %s %%tna," WHOX_USERACCOUNT_ID + +static void sig_connected(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(server != NULL); + if (!IS_IRC_SERVER(server)) + return; + + rec = g_new0(SERVER_QUERY_REC, 1); + rec->accountqueries = g_hash_table_new_full( + (GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal, (GDestroyNotify) g_free, NULL); + server->chanqueries = rec; +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + int n; + + g_return_if_fail(server != NULL); + if (!IS_IRC_SERVER(server)) + return; + + rec = server->chanqueries; + if (rec == NULL) + return; + g_return_if_fail(rec != NULL); + + g_hash_table_destroy(rec->accountqueries); + for (n = 0; n < CHANNEL_QUERIES; n++) + g_slist_free(rec->queries[n]); + g_slist_free(rec->current_queries); + g_free(rec); + + server->chanqueries = NULL; +} + +/* Add channel to query list */ +static void query_add_channel(IRC_CHANNEL_REC *channel, int query_type) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(channel != NULL); + + rec = channel->server->chanqueries; + rec->queries[query_type] = + g_slist_append(rec->queries[query_type], channel); +} + +static void query_check(IRC_SERVER_REC *server); + +static void query_remove_all(IRC_CHANNEL_REC *channel) +{ + SERVER_QUERY_REC *rec; + int n; + + rec = channel->server->chanqueries; + if (rec == NULL) return; + + /* remove channel from query lists */ + for (n = 0; n < CHANNEL_QUERIES; n++) + rec->queries[n] = g_slist_remove(rec->queries[n], channel); + rec->current_queries = g_slist_remove(rec->current_queries, channel); + + if (!channel->server->disconnected) + query_check(channel->server); +} + +static void sig_channel_destroyed(IRC_CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + if (IS_IRC_CHANNEL(channel)) + query_remove_all(channel); +} + +static int channels_have_all_names(IRC_SERVER_REC *server) +{ + GSList *tmp; + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + IRC_CHANNEL_REC *rec = tmp->data; + + if (IS_IRC_CHANNEL(rec) && !rec->names_got) + return 0; + } + + return 1; +} + +static int query_find_next(SERVER_QUERY_REC *server) +{ + int n; + + for (n = 0; n < CHANNEL_QUERIES; n++) { + if (server->queries[n] != NULL) + return n; + } + + return -1; +} + +static void query_send(IRC_SERVER_REC *server, int query) +{ + SERVER_QUERY_REC *rec; + IRC_CHANNEL_REC *chanrec; + GSList *chans; + char *cmd, *chanstr_commas, *chanstr; + int onlyone, count; + + rec = server->chanqueries; + + /* get the list of channels to query */ + onlyone = (server->no_multi_who && query == CHANNEL_QUERY_WHO) || + (server->no_multi_mode && CHANNEL_IS_MODE_QUERY(query)); + + if (onlyone) { + chans = rec->queries[query]; + rec->queries[query] = + g_slist_remove_link(rec->queries[query], chans); + + chanrec = chans->data; + chanstr_commas = g_strdup(chanrec->name); + chanstr = g_strdup(chanrec->name); + count = 1; + } else { + char *chanstr_spaces; + + chans = rec->queries[query]; + count = g_slist_length(chans); + + if (count > server->max_query_chans) { + GSList *lastchan; + + lastchan = g_slist_nth(rec->queries[query], + server->max_query_chans-1); + count = server->max_query_chans; + rec->queries[query] = lastchan->next; + lastchan->next = NULL; + } else { + rec->queries[query] = NULL; + } + + chanstr_commas = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), ","); + chanstr_spaces = gslistptr_to_string(chans, G_STRUCT_OFFSET(IRC_CHANNEL_REC, name), " "); + + chanstr = g_strconcat(chanstr_commas, " ", chanstr_spaces, NULL); + g_free(chanstr_spaces); + } + + rec->current_query_type = query; + rec->current_queries = chans; + + switch (query) { + case CHANNEL_QUERY_MODE: + cmd = g_strdup_printf("MODE %s", chanstr_commas); + + /* the stop-event is received once for each channel, + and we want to print 329 event (channel created). */ + server_redirect_event(server, "mode channel", count, + chanstr, -1, "chanquery abort", + "event 324", "chanquery mode", + "event 329", "event 329", + "", "chanquery abort", NULL); + break; + + case CHANNEL_QUERY_WHO: + if (server->isupport != NULL && + g_hash_table_lookup(server->isupport, "whox") != NULL) { + cmd = g_strdup_printf(WHOX_CHANNEL_FULL_CMD, chanstr_commas); + } else { + cmd = g_strdup_printf("WHO %s", chanstr_commas); + } + + server_redirect_event(server, "who", server->one_endofwho ? 1 : count, chanstr, -1, + "chanquery abort", /* failure signal */ + "event 315", "chanquery who end", /* */ + "event 352", "silent event who", /* */ + "event 354", "silent event whox", /* */ + "", "chanquery abort", NULL); + break; + + case CHANNEL_QUERY_BMODE: + cmd = g_strdup_printf("MODE %s b", chanstr_commas); + /* check all the multichannel problems with all + mode requests - if channels are joined manually + irssi could ask modes separately but afterwards + join the two b/e/I modes together */ + server_redirect_event(server, "mode b", count, chanstr, -1, + "chanquery abort", + "event 367", "chanquery ban", + "event 368", "chanquery ban end", + "", "chanquery abort", NULL); + break; + + default: + cmd = NULL; + } + + irc_send_cmd_later(server, cmd); + + g_free(chanstr); + g_free(chanstr_commas); + g_free(cmd); +} + +static void query_check(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + int query; + + g_return_if_fail(server != NULL); + + rec = server->chanqueries; + if (rec->current_queries != NULL) + return; /* old queries haven't been answered yet */ + + if (server->max_query_chans > 1 && !server->no_multi_who && !server->no_multi_mode && !channels_have_all_names(server)) { + /* all channels haven't sent /NAMES list yet */ + /* only do this if there would be a benefit in combining + * queries -- jilles */ + return; + } + + query = query_find_next(rec); + if (query == -1) { + /* no queries left */ + return; + } + + query_send(server, query); +} + +/* if there's no more queries in queries in buffer, send the sync signal */ +static void channel_checksync(IRC_CHANNEL_REC *channel) +{ + SERVER_QUERY_REC *rec; + int n; + + g_return_if_fail(channel != NULL); + + if (channel->synced) + return; /* already synced */ + + rec = channel->server->chanqueries; + for (n = 0; n < CHANNEL_QUERIES; n++) { + if (g_slist_find(rec->queries[n], channel)) + return; + } + + channel->synced = TRUE; + signal_emit("channel sync", 1, channel); +} + +/* Error occurred when trying to execute query - abort and try again. */ +static void query_current_error(IRC_SERVER_REC *server) +{ + SERVER_QUERY_REC *rec; + GSList *tmp; + int query, abort_query; + + rec = server->chanqueries; + + /* fix the thing that went wrong - or if it was already fixed, + then all we can do is abort. */ + abort_query = FALSE; + + query = rec->current_query_type; + if (query == CHANNEL_QUERY_WHO) { + if (server->no_multi_who) + abort_query = TRUE; + else + server->no_multi_who = TRUE; + } else { + if (server->no_multi_mode) + abort_query = TRUE; + else + server->no_multi_mode = TRUE; + } + + if (!abort_query) { + /* move all currently queried channels to main query lists */ + for (tmp = rec->current_queries; tmp != NULL; tmp = tmp->next) { + rec->queries[query] = + g_slist_append(rec->queries[query], tmp->data); + } + } else { + /* check if failed channels are synced after this error */ + g_slist_foreach(rec->current_queries, + (GFunc) channel_checksync, NULL); + } + + g_slist_free(rec->current_queries); + rec->current_queries = NULL; + + query_check(server); +} + +static void sig_channel_joined(IRC_CHANNEL_REC *channel) +{ + if (!IS_IRC_CHANNEL(channel)) + return; + + if (!settings_get_bool("channel_sync")) + return; + + /* Add channel to query lists */ + if (!channel->no_modes) + query_add_channel(channel, CHANNEL_QUERY_MODE); + if (g_hash_table_size(channel->nicks) < + settings_get_int("channel_max_who_sync")) + query_add_channel(channel, CHANNEL_QUERY_WHO); + if (!channel->no_modes) + query_add_channel(channel, CHANNEL_QUERY_BMODE); + + query_check(channel->server); +} + +static void channel_got_query(IRC_CHANNEL_REC *chanrec, int query_type) +{ + SERVER_QUERY_REC *rec; + + g_return_if_fail(chanrec != NULL); + + rec = chanrec->server->chanqueries; + if (query_type != rec->current_query_type) + return; /* shouldn't happen */ + + /* got the query for channel.. */ + rec->current_queries = + g_slist_remove(rec->current_queries, chanrec); + channel_checksync(chanrec); + + /* check if we need to send another query.. */ + query_check(chanrec->server); +} + +void irc_channels_query_purge_accountquery(IRC_SERVER_REC *server, const char *nick) +{ + GSList *tmp, *next, *prev; + REDIRECT_REC *redirect; + char *cmd, *target_cmd; + gboolean was_removed; + + /* remove the marker */ + was_removed = g_hash_table_remove(server->chanqueries->accountqueries, nick); + + /* if it was removed we may have an outstanding query */ + if (was_removed) { + target_cmd = g_strdup_printf(WHOX_USERACCOUNT_CMD "\r\n", nick); + + /* remove queued WHO command */ + prev = NULL; + for (tmp = server->cmdqueue; tmp != NULL; tmp = next) { + next = tmp->next->next; + cmd = tmp->data; + redirect = tmp->next->data; + + if (g_strcmp0(cmd, target_cmd) == 0) { + if (prev != NULL) + prev->next = next; + else + server->cmdqueue = next; + + /* remove the redirection */ + g_slist_free_1(tmp->next); + if (redirect != NULL) + server_redirect_destroy(redirect); + + /* remove the command */ + g_slist_free_1(tmp); + g_free(cmd); + + server->cmdcount--; + server->cmdlater--; + } else { + prev = tmp->next; + } + } + + g_free(target_cmd); + } +} + +static void query_useraccount_error(IRC_SERVER_REC *server, const char *cmd, const char *arg) +{ + /* query failed, ignore it but remove the marker */ + g_hash_table_remove(server->chanqueries->accountqueries, arg); +} + +static void sig_event_join(IRC_SERVER_REC *server, const char *data, const char *nick, + const char *address) +{ + char *params, *channel, *ptr, *account; + GSList *nicks, *tmp; + IRC_CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + if (i_slist_find_string(server->cap_active, CAP_EXTENDED_JOIN)) { + /* no need to chase accounts */ + return; + } + + if (g_ascii_strcasecmp(nick, server->nick) == 0) { + /* You joined, do nothing */ + return; + } + + params = event_get_params(data, 3, &channel, NULL, NULL); + + ptr = strchr(channel, 7); /* ^G does something weird.. */ + if (ptr != NULL) + *ptr = '\0'; + + /* find channel */ + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) { + g_free(params); + return; + } + + g_free(params); + + if (!chanrec->wholist) { + return; + } + + /* find nick */ + nickrec = nicklist_find(CHANNEL(chanrec), nick); + if (nickrec == NULL) { + return; + } + + if (nickrec->account != NULL) { + return; + } + + if (g_hash_table_contains(server->chanqueries->accountqueries, nick)) { + /* query already sent */ + return; + } + account = NULL; + + /* Check if user is already in some other channel, get the account from there */ + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + if (rec->account != NULL) { + account = rec->account; + break; + } + } + g_slist_free(nicks); + + if (account != NULL) { + nicklist_set_account(CHANNEL(chanrec), nickrec, account); + return; + } + + if (g_hash_table_size(chanrec->nicks) < settings_get_int("channel_max_who_sync") && + server->isupport != NULL && g_hash_table_lookup(server->isupport, "whox") != NULL && + server->split_servers == NULL && + g_hash_table_size(server->chanqueries->accountqueries) < + settings_get_int("account_max_chase")) { + char *cmd; + server_redirect_event(server, "who user", 1, nick, -1, + "chanquery useraccount abort", /* failure signal */ + "event 354", "silent event whox useraccount", /* */ + "", "event empty", /* */ + NULL); + cmd = g_strdup_printf(WHOX_USERACCOUNT_CMD, nick); + g_hash_table_add(server->chanqueries->accountqueries, g_strdup(nick)); + /* queue the command */ + irc_send_cmd_later(server, cmd); + g_free(cmd); + } +} + +static void event_channel_mode(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + IRC_CHANNEL_REC *chanrec; + char *params, *channel, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3 | PARAM_FLAG_GETREST, + NULL, &channel, &mode); + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL) { + if (chanrec->key != NULL && strchr(mode, 'k') == NULL) { + /* we joined the channel with a key, + but it didn't have +k mode.. */ + parse_channel_modes(chanrec, NULL, "-k", TRUE); + } + parse_channel_modes(chanrec, nick, mode, FALSE); + channel_got_query(chanrec, CHANNEL_QUERY_MODE); + } + + g_free(params); +} + +static void event_end_of_who(IRC_SERVER_REC *server, const char *data) +{ + SERVER_QUERY_REC *rec; + GSList *tmp, *next; + char *params, *channel, **channels; + int failed, multiple; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + multiple = strchr(channel, ',') != NULL; + channels = g_strsplit(channel, ",", -1); + + failed = FALSE; + rec = server->chanqueries; + for (tmp = rec->current_queries; tmp != NULL; tmp = next) { + IRC_CHANNEL_REC *chanrec = tmp->data; + + next = tmp->next; + if (strarray_find(channels, chanrec->name) == -1) + continue; + + if (chanrec->ownnick->host == NULL && multiple && + !server->one_endofwho) { + /* we should receive our own host for each channel. + However, some servers really are stupid enough + not to reply anything to /WHO requests.. */ + failed = TRUE; + } else { + chanrec->wholist = TRUE; + signal_emit("channel wholist", 1, chanrec); + channel_got_query(chanrec, CHANNEL_QUERY_WHO); + } + } + + g_strfreev(channels); + if (multiple) + server->one_endofwho = TRUE; + + if (failed) { + /* server didn't understand multiple WHO replies, + send them again separately */ + query_current_error(server); + } + + g_free(params); +} + +static void event_end_of_banlist(IRC_SERVER_REC *server, const char *data) +{ + IRC_CHANNEL_REC *chanrec; + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + chanrec = irc_channel_find(server, channel); + + if (chanrec != NULL) + channel_got_query(chanrec, CHANNEL_QUERY_BMODE); + + g_free(params); +} + +void channels_query_init(void) +{ + settings_add_bool("misc", "channel_sync", TRUE); + settings_add_int("misc", "channel_max_who_sync", 1000); + settings_add_int("misc", "account_max_chase", 10); + + signal_add("server connected", (SIGNAL_FUNC) sig_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + signal_add("event join", (SIGNAL_FUNC) sig_event_join); + + signal_add("chanquery mode", (SIGNAL_FUNC) event_channel_mode); + signal_add("chanquery who end", (SIGNAL_FUNC) event_end_of_who); + + signal_add("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); + signal_add("chanquery abort", (SIGNAL_FUNC) query_current_error); + signal_add("chanquery useraccount abort", (SIGNAL_FUNC) query_useraccount_error); +} + +void channels_query_deinit(void) +{ + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + signal_remove("event join", (SIGNAL_FUNC) sig_event_join); + + signal_remove("chanquery mode", (SIGNAL_FUNC) event_channel_mode); + signal_remove("chanquery who end", (SIGNAL_FUNC) event_end_of_who); + + signal_remove("chanquery ban end", (SIGNAL_FUNC) event_end_of_banlist); + signal_remove("chanquery abort", (SIGNAL_FUNC) query_current_error); + signal_remove("chanquery useraccount abort", (SIGNAL_FUNC) query_useraccount_error); +} diff --git a/src/irc/core/ctcp.c b/src/irc/core/ctcp.c new file mode 100644 index 0000000..42b9341 --- /dev/null +++ b/src/irc/core/ctcp.c @@ -0,0 +1,368 @@ +/* + ctcp.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/special-vars.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-idle.h> +#include <irssi/src/core/ignore.h> +#include <irssi/src/irc/core/ctcp.h> + +typedef struct { + char *name; + int refcount; +} CTCP_CMD_REC; + +static GSList *ctcp_cmds; + +static CTCP_CMD_REC *ctcp_cmd_find(const char *name) +{ + GSList *tmp; + + for (tmp = ctcp_cmds; tmp != NULL; tmp = tmp->next) { + CTCP_CMD_REC *rec = tmp->data; + + if (g_ascii_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +void ctcp_register(const char *name) +{ + CTCP_CMD_REC *rec; + + rec = ctcp_cmd_find(name); + if (rec == NULL) { + rec = g_new0(CTCP_CMD_REC, 1); + rec->name = g_ascii_strup(name, -1); + + ctcp_cmds = g_slist_append(ctcp_cmds, rec); + } + + rec->refcount++; +} + +static void ctcp_cmd_destroy(CTCP_CMD_REC *rec) +{ + ctcp_cmds = g_slist_remove(ctcp_cmds, rec); + g_free(rec->name); + g_free(rec); +} + +void ctcp_unregister(const char *name) +{ + CTCP_CMD_REC *rec; + + rec = ctcp_cmd_find(name); + if (rec != NULL && --rec->refcount == 0) + ctcp_cmd_destroy(rec); +} + +static void ctcp_queue_clean(IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + + for (tmp = server->ctcpqueue; tmp != NULL; tmp = next) { + next = tmp->next; + if (!server_idle_find(server, GPOINTER_TO_INT(tmp->data))) { + server->ctcpqueue = + g_slist_remove(server->ctcpqueue, tmp->data); + } + } +} + +/* Send CTCP reply with flood protection */ +void ctcp_send_reply(IRC_SERVER_REC *server, const char *data) +{ + int tag; + + g_return_if_fail(server != NULL); + g_return_if_fail(data != NULL); + + ctcp_queue_clean(server); + + if ((int)g_slist_length(server->ctcpqueue) >= + settings_get_int("max_ctcp_queue")) + return; + + /* Add to first in idle queue */ + tag = server_idle_add(server, data); + server->ctcpqueue = + g_slist_append(server->ctcpqueue, GINT_TO_POINTER(tag)); +} + +/* CTCP ping */ +static void ctcp_ping(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + char *str; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + if (strlen(data) > 100) { + /* Yes, this is kind of a kludge, but people who PING you + with messages this long deserve not to get the reply. + + The problem with long messages is that when you send lots + of data to server, it's input buffer gets full and you get + killed from server because of "excess flood". + + Irssi's current flood protection doesn't count the message + length, but even if it did, the CTCP flooder would still + be able to at least slow down your possibility to send + messages to server. */ + return; + } + + str = g_strdup_printf("NOTICE %s :\001PING %s\001", nick, data); + ctcp_send_reply(server, str); + g_free(str); +} + +static void ctcp_send_parsed_reply(IRC_SERVER_REC *server, const char *nick, + const char *cmd, const char *args) +{ + char *str, *pstr; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + if (*args == '\0') + return; + + pstr = parse_special_string(args, SERVER(server), NULL, "", NULL, 0); + str = g_strdup_printf("NOTICE %s :\001%s %s\001", nick, cmd, pstr); + ctcp_send_reply(server, str); + g_free(str); + g_free(pstr); +} + +/* CTCP version */ +static void ctcp_version(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + ctcp_send_parsed_reply(server, nick, "VERSION", + settings_get_str("ctcp_version_reply")); +} + +/* CTCP time */ +static void ctcp_time(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + char *str, *reply; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + reply = my_asctime(time(NULL)); + str = g_strdup_printf("NOTICE %s :\001TIME %s\001", nick, reply); + ctcp_send_reply(server, str); + g_free(str); + g_free(reply); +} + +/* CTCP userinfo */ +static void ctcp_userinfo(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + ctcp_send_parsed_reply(server, nick, "USERINFO", + settings_get_str("ctcp_userinfo_reply")); +} + +/* CTCP clientinfo */ +static void ctcp_clientinfo(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + GString *str; + GSList *tmp; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + str = g_string_new(NULL); + g_string_printf(str, "NOTICE %s :\001CLIENTINFO", nick); + for (tmp = ctcp_cmds; tmp != NULL; tmp = tmp->next) { + CTCP_CMD_REC *rec = tmp->data; + + g_string_append_c(str, ' '); + g_string_append(str, rec->name); + } + g_string_append_c(str, '\001'); + + ctcp_send_reply(server, str->str); + g_string_free(str, TRUE); +} + +static void ctcp_msg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + char *args, *str; + + if (g_ascii_strncasecmp(data, "ACTION ", 7) == 0) { + /* special treatment for actions */ + signal_emit("ctcp action", 5, server, data+7, + nick, addr, target); + return; + } + + if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_CTCPS)) + return; + + str = g_strconcat("ctcp msg ", data, NULL); + args = strchr(str+9, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+9); + if (!signal_emit(str, 5, server, args, nick, addr, target)) { + signal_emit("default ctcp msg", 5, + server, data, nick, addr, target); + } + g_free(str); +} + +static void ctcp_reply(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + char *args, *str; + + if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_CTCPS)) + return; + + str = g_strconcat("ctcp reply ", data, NULL); + args = strchr(str+11, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+11); + if (!signal_emit(str, 5, server, args, nick, addr, target)) { + signal_emit("default ctcp reply", 5, + server, data, nick, addr, target); + } + g_free(str); +} + +static void event_privmsg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *target, *msg; + int len; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &target, &msg); + + /* handle only ctcp messages.. */ + if (*msg == 1) { + /* remove the \001 at beginning and end */ + msg++; + len = strlen(msg); + if (msg[len-1] == '\001') + msg[len-1] = '\0'; + + signal_emit("ctcp msg", 5, server, msg, nick, addr, target); + signal_stop(); + } + + g_free(params); +} + +static void event_notice(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *target, *ptr, *msg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &target, &msg); + + /* handle only ctcp replies */ + if (*msg == 1) { + ptr = strrchr(++msg, 1); + if (ptr != NULL) *ptr = '\0'; + + signal_emit("ctcp reply", 5, server, msg, nick, addr, target); + signal_stop(); + } + + g_free(params); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + g_slist_free(server->ctcpqueue); + server->ctcpqueue = NULL; +} + +void ctcp_init(void) +{ + ctcp_cmds = NULL; + + settings_add_str("misc", "ctcp_version_reply", + PACKAGE_TARNAME" v$J - running on $sysname $sysarch"); + settings_add_str("misc", "ctcp_userinfo_reply", "$Y"); + settings_add_int("flood", "max_ctcp_queue", 5); + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_first("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add_first("event notice", (SIGNAL_FUNC) event_notice); + signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_add("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping); + signal_add("ctcp msg version", (SIGNAL_FUNC) ctcp_version); + signal_add("ctcp msg time", (SIGNAL_FUNC) ctcp_time); + signal_add("ctcp msg userinfo", (SIGNAL_FUNC) ctcp_userinfo); + signal_add("ctcp msg clientinfo", (SIGNAL_FUNC) ctcp_clientinfo); + + ctcp_register("ping"); + ctcp_register("version"); + ctcp_register("time"); + ctcp_register("userinfo"); + ctcp_register("clientinfo"); +} + +void ctcp_deinit(void) +{ + while (ctcp_cmds != NULL) + ctcp_cmd_destroy(ctcp_cmds->data); + + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event notice", (SIGNAL_FUNC) event_notice); + signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_remove("ctcp msg ping", (SIGNAL_FUNC) ctcp_ping); + signal_remove("ctcp msg version", (SIGNAL_FUNC) ctcp_version); + signal_remove("ctcp msg time", (SIGNAL_FUNC) ctcp_time); + signal_remove("ctcp msg userinfo", (SIGNAL_FUNC) ctcp_userinfo); + signal_remove("ctcp msg clientinfo", (SIGNAL_FUNC) ctcp_clientinfo); +} diff --git a/src/irc/core/ctcp.h b/src/irc/core/ctcp.h new file mode 100644 index 0000000..c8a8a84 --- /dev/null +++ b/src/irc/core/ctcp.h @@ -0,0 +1,14 @@ +#ifndef IRSSI_IRC_CORE_CTCP_H +#define IRSSI_IRC_CORE_CTCP_H + +/* Register/unregister CTCP command, so it shows in CTCP CLIENTINFO */ +void ctcp_register(const char *name); +void ctcp_unregister(const char *name); + +/* Send CTCP reply with flood protection */ +void ctcp_send_reply(IRC_SERVER_REC *server, const char *data); + +void ctcp_init(void); +void ctcp_deinit(void); + +#endif diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c new file mode 100644 index 0000000..ce7e161 --- /dev/null +++ b/src/irc/core/irc-cap.c @@ -0,0 +1,334 @@ +/* irc-cap.c : irssi + + Copyright (C) 2015 The Lemon Man + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-cap.h> +#include <irssi/src/irc/core/irc-servers.h> + +int irc_cap_toggle (IRC_SERVER_REC *server, char *cap, int enable) +{ + if (cap == NULL || *cap == '\0') + return FALSE; + + /* If the negotiation hasn't been completed yet just queue the requests */ + if (!server->cap_complete) { + if (enable && !i_slist_find_string(server->cap_queue, cap)) { + server->cap_queue = g_slist_prepend(server->cap_queue, g_strdup(cap)); + return TRUE; + } else if (!enable && i_slist_find_string(server->cap_queue, cap)) { + server->cap_queue = i_slist_delete_string(server->cap_queue, cap, g_free); + return TRUE; + } + + return FALSE; + } + + if (enable && !i_slist_find_string(server->cap_active, cap)) { + /* Make sure the required cap is supported by the server */ + if (!g_hash_table_lookup_extended(server->cap_supported, cap, NULL, NULL)) + return FALSE; + + signal_emit("server cap req", 2, server, cap); + irc_send_cmdv(server, "CAP REQ %s", cap); + return TRUE; + } else if (!enable && i_slist_find_string(server->cap_active, cap)) { + char *negcap = g_strdup_printf("-%s", cap); + + signal_emit("server cap req", 2, server, negcap); + irc_send_cmdv(server, "CAP REQ %s", negcap); + + g_free(negcap); + return TRUE; + } + + return FALSE; +} + +void irc_cap_finish_negotiation (IRC_SERVER_REC *server) +{ + if (server->cap_complete) + return; + + server->cap_complete = TRUE; + irc_send_cmd_now(server, "CAP END"); + + signal_emit("server cap end", 1, server); +} + +static void cap_emit_signal (IRC_SERVER_REC *server, char *cmd, char *args) +{ + char *signal_name; + + signal_name = g_strdup_printf("server cap %s %s", cmd, args? args: ""); + signal_emit(signal_name, 1, server); + g_free(signal_name); +} + +static gboolean parse_cap_name(char *name, char **key, char **val) +{ + const char *eq; + + g_return_val_if_fail(name != NULL, FALSE); + g_return_val_if_fail(name[0] != '\0', FALSE); + + eq = strchr(name, '='); + /* KEY only value */ + if (eq == NULL) { + *key = g_strdup(name); + *val = NULL; + /* Some values are in a KEY=VALUE form, parse them */ + } else { + *key = g_strndup(name, (gsize)(eq - name)); + *val = g_strdup(eq + 1); + } + + return TRUE; +} + +static void cap_process_request_queue(IRC_SERVER_REC *server) +{ + /* No CAP has been requested */ + if (server->cap_queue == NULL) { + irc_cap_finish_negotiation(server); + } else { + GSList *tmp; + GString *cmd; + int avail_caps = 0; + + cmd = g_string_new("CAP REQ :"); + + /* To process the queue in order, we need to reverse the stack once */ + server->cap_queue = g_slist_reverse(server->cap_queue); + + /* Check whether the cap is supported by the server */ + for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { + if (g_hash_table_lookup_extended(server->cap_supported, tmp->data, NULL, + NULL)) { + if (avail_caps > 0) + g_string_append_c(cmd, ' '); + g_string_append(cmd, tmp->data); + + avail_caps++; + } + } + + /* Clear the queue here */ + i_slist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; + + /* If the server doesn't support any cap we requested close the negotiation here */ + if (avail_caps > 0) { + signal_emit("server cap req", 2, server, + cmd->str + sizeof("CAP REQ :") - 1); + irc_send_cmd_now(server, cmd->str); + } else { + irc_cap_finish_negotiation(server); + } + + g_string_free(cmd, TRUE); + } +} + +static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *address) +{ + char *params, *evt, *list, *star, **caps; + int i, caps_length, disable, multiline; + + params = event_get_params(args, 4, NULL, &evt, &star, &list); + if (params == NULL) + return; + + /* Multiline responses have an additional parameter and we have to do + * this stupid dance to parse them */ + if (!g_ascii_strcasecmp(evt, "LS") && !strcmp(star, "*")) { + multiline = TRUE; + } + /* This branch covers the '*' parameter isn't present, adjust the + * parameter pointer to compensate for this */ + else if (list[0] == '\0') { + multiline = FALSE; + list = star; + } + /* Malformed request, terminate the negotiation */ + else { + irc_cap_finish_negotiation(server); + g_free(params); + g_warn_if_reached(); + return; + } + + /* The table is created only when needed */ + if (server->cap_supported == NULL) { + server->cap_supported = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, g_free); + } + + /* Strip the trailing whitespaces before splitting the string, some servers send responses with + * superfluous whitespaces that g_strsplit the interprets as tokens */ + caps = g_strsplit(g_strchomp(list), " ", -1); + caps_length = g_strv_length(caps); + + if (!g_ascii_strcasecmp(evt, "LS")) { + if (!server->cap_in_multiline) { + /* Throw away everything and start from scratch */ + g_hash_table_remove_all(server->cap_supported); + } + + server->cap_in_multiline = multiline; + + /* Create a list of the supported caps */ + for (i = 0; i < caps_length; i++) { + char *key, *val; + + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + if (g_hash_table_lookup_extended(server->cap_supported, key, NULL, NULL)) { + /* The specification doesn't say anything about + * duplicated values, let's just warn the user */ + g_warning("The server sent the %s capability twice", key); + } + g_hash_table_replace(server->cap_supported, key, val); + } + + /* A multiline response is always terminated by a normal one, + * wait until we receive that one to require any CAP */ + if (multiline == FALSE) { + gboolean want_starttls = + i_slist_find_string(server->cap_queue, CAP_STARTTLS) != NULL; + server->cap_queue = + i_slist_delete_string(server->cap_queue, CAP_STARTTLS, g_free); + if (server->connrec->starttls) { + /* the connection has requested starttls, + no more data must be sent now */ + } else if (want_starttls && + g_hash_table_lookup_extended(server->cap_supported, CAP_STARTTLS, + NULL, NULL)) { + irc_server_send_starttls(server); + /* no more data must be sent now */ + } else { + cap_process_request_queue(server); + } + } + } + else if (!g_ascii_strcasecmp(evt, "ACK")) { + int got_sasl = FALSE; + + /* Emit a signal for every ack'd cap */ + for (i = 0; i < caps_length; i++) { + disable = (*caps[i] == '-'); + + if (disable) + server->cap_active = + i_slist_delete_string(server->cap_active, caps[i] + 1, g_free); + else if (!i_slist_find_string(server->cap_active, caps[i])) + server->cap_active = g_slist_prepend(server->cap_active, g_strdup(caps[i])); + + if (!strcmp(caps[i], "sasl")) + got_sasl = TRUE; + + cap_emit_signal(server, "ack", caps[i]); + } + + /* Hopefully the server has ack'd all the caps requested and we're ready to terminate the + * negotiation, unless sasl was requested. In this case we must not terminate the negotiation + * until the sasl handshake is over. */ + if (got_sasl == FALSE) + irc_cap_finish_negotiation(server); + } + else if (!g_ascii_strcasecmp(evt, "NAK")) { + g_warning("The server answered with a NAK to our CAP request, this should not happen"); + + /* A NAK'd request means that a required cap can't be enabled or disabled, don't update the + * list of active caps and notify the listeners. */ + for (i = 0; i < caps_length; i++) + cap_emit_signal(server, "nak", caps[i]); + } + else if (!g_ascii_strcasecmp(evt, "NEW")) { + for (i = 0; i < caps_length; i++) { + char *key, *val; + + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + g_hash_table_replace(server->cap_supported, key, val); + cap_emit_signal(server, "new", key); + } + } + else if (!g_ascii_strcasecmp(evt, "DEL")) { + for (i = 0; i < caps_length; i++) { + char *key, *val; + + if (!parse_cap_name(caps[i], &key, &val)) { + g_warning("Invalid CAP %s key/value pair", evt); + continue; + } + + g_hash_table_remove(server->cap_supported, key); + cap_emit_signal(server, "delete", key); + /* The server removed this CAP, remove it from the list + * of the active ones if we had requested it */ + server->cap_active = i_slist_delete_string(server->cap_active, key, g_free); + /* We don't transfer the ownership of those two + * variables this time, just free them when we're done. */ + g_free(key); + g_free(val); + } + } + else if (!g_ascii_strcasecmp(evt, "LIST")) { + /* do nothing, fe-cap will handle it */ + } + else { + g_warning("Unhandled CAP subcommand %s", evt); + } + + g_strfreev(caps); + g_free(params); +} + +static void event_invalid_cap (IRC_SERVER_REC *server, const char *data, const char *from) +{ + /* The server didn't understand one (or more) requested caps, terminate the negotiation. + * This could be handled in a graceful way but since it shouldn't really ever happen this seems a + * good way to deal with 410 errors. */ + server->cap_complete = FALSE; + irc_send_cmd_now(server, "CAP END"); +} + +void irc_cap_init (void) +{ + signal_add_last("server cap continue", (SIGNAL_FUNC) cap_process_request_queue); + signal_add_first("event cap", (SIGNAL_FUNC) event_cap); + signal_add_first("event 410", (SIGNAL_FUNC) event_invalid_cap); +} + +void irc_cap_deinit (void) +{ + signal_remove("server cap continue", (SIGNAL_FUNC) cap_process_request_queue); + signal_remove("event cap", (SIGNAL_FUNC) event_cap); + signal_remove("event 410", (SIGNAL_FUNC) event_invalid_cap); +} diff --git a/src/irc/core/irc-cap.h b/src/irc/core/irc-cap.h new file mode 100644 index 0000000..aa13023 --- /dev/null +++ b/src/irc/core/irc-cap.h @@ -0,0 +1,9 @@ +#ifndef IRSSI_IRC_CORE_IRC_CAP_H +#define IRSSI_IRC_CORE_IRC_CAP_H + +void irc_cap_init(void); +void irc_cap_deinit(void); +int irc_cap_toggle (IRC_SERVER_REC *server, char *cap, int enable); +void irc_cap_finish_negotiation (IRC_SERVER_REC *server); + +#endif diff --git a/src/irc/core/irc-channels-setup.c b/src/irc/core/irc-channels-setup.c new file mode 100644 index 0000000..93874d3 --- /dev/null +++ b/src/irc/core/irc-channels-setup.c @@ -0,0 +1,35 @@ +/* + irc-channels-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/channels.h> + +void irc_channels_setup_init(void) +{ + signal_add("channel wholist", (SIGNAL_FUNC) channel_send_botcommands); + signal_add("channel joined", (SIGNAL_FUNC) channel_send_autocommands); +} + +void irc_channels_setup_deinit(void) +{ + signal_remove("channel wholist", (SIGNAL_FUNC) channel_send_botcommands); + signal_remove("channel joined", (SIGNAL_FUNC) channel_send_autocommands); +} diff --git a/src/irc/core/irc-channels.c b/src/irc/core/irc-channels.c new file mode 100644 index 0000000..24f57f8 --- /dev/null +++ b/src/irc/core/irc-channels.c @@ -0,0 +1,289 @@ +/* + irc-channels.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/channels-setup.h> + +#include <irssi/src/irc/core/bans.h> +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/irc/core/mode-lists.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-nicklist.h> +#include <irssi/src/irc/core/channel-rejoin.h> + +void channels_query_init(void); +void channels_query_deinit(void); + +void channel_events_init(void); +void channel_events_deinit(void); + +void irc_channels_setup_init(void); +void irc_channels_setup_deinit(void); + +void massjoin_init(void); +void massjoin_deinit(void); + +IRC_CHANNEL_REC *irc_channel_create(IRC_SERVER_REC *server, const char *name, + const char *visible_name, int automatic) +{ + IRC_CHANNEL_REC *rec; + + g_return_val_if_fail(server == NULL || IS_IRC_SERVER(server), NULL); + g_return_val_if_fail(name != NULL, NULL); + + rec = g_new0(IRC_CHANNEL_REC, 1); + if (*name == '+') rec->no_modes = TRUE; + + channel_init((CHANNEL_REC *) rec, (SERVER_REC *) server, + name, visible_name, automatic); + return rec; +} + +#define get_join_key(key) \ + (((key) == NULL || *(key) == '\0') ? "x" : (key)) + +static char *force_channel_name(IRC_SERVER_REC *server, const char *name) +{ + char *chantypes; + + if (server_ischannel(SERVER(server), name)) + return g_strdup(name); + + chantypes = g_hash_table_lookup(server->isupport, "chantypes"); + if (chantypes == NULL || *chantypes == '\0' || strchr(chantypes, '#') != NULL) + chantypes = "#"; + + return g_strdup_printf("%c%s", *chantypes, name); +} + +static void irc_channels_join(IRC_SERVER_REC *server, const char *data, + int automatic) +{ + CHANNEL_SETUP_REC *schannel; + IRC_CHANNEL_REC *chanrec; + GString *outchans, *outkeys; + char *channels, *keys, *key, *space; + char **chanlist, **keylist, **tmp, **tmpkey, **tmpstr, *channel, *channame; + void *free_arg; + int use_keys, cmdlen; + + g_return_if_fail(data != NULL); + g_return_if_fail(IS_IRC_SERVER(server) && server->connected); + if (*data == '\0') return; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, + &channels, &keys)) + return; + + /* keys shouldn't contain space */ + space = strchr(keys, ' '); + if (space != NULL) { + *space = '\0'; + } + + chanlist = g_strsplit(channels, ",", -1); + keylist = g_strsplit(keys, ",", -1); + + outchans = g_string_new(NULL); + outkeys = g_string_new(NULL); + + use_keys = *keys != '\0'; + tmpkey = keylist; + tmp = chanlist; + for (;; tmp++) { + if (*tmp != NULL) { + channel = force_channel_name(server, *tmp); + + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) { + schannel = channel_setup_find(channel, server->connrec->chatnet); + + g_string_append_printf(outchans, "%s,", channel); + if (*tmpkey != NULL && **tmpkey != '\0') + key = *tmpkey; + else if (schannel != NULL && schannel->password != NULL) { + /* get password from setup record */ + use_keys = TRUE; + key = schannel->password; + } else key = NULL; + + g_string_append_printf(outkeys, "%s,", get_join_key(key)); + channame = channel + (channel[0] == '!' && + channel[1] == '!'); + chanrec = irc_channel_create(server, channame, NULL, + automatic); + if (key != NULL) chanrec->key = g_strdup(key); + } + g_free(channel); + + if (*tmpkey != NULL) + tmpkey++; + + tmpstr = tmp; + tmpstr++; + cmdlen = outchans->len-1; + + if (use_keys) + cmdlen += outkeys->len; + if (*tmpstr != NULL) + cmdlen += server_ischannel(SERVER(server), *tmpstr) ? strlen(*tmpstr) : + strlen(*tmpstr)+1; + if (*tmpkey != NULL) + cmdlen += strlen(*tmpkey); + + /* don't try to send too long lines + make sure it's not longer than 510 + so 510 - strlen("JOIN ") = 505 */ + if (cmdlen < server->max_message_len - 5 /* strlen("JOIN ") */) + continue; + } + if (outchans->len > 0) { + g_string_truncate(outchans, outchans->len - 1); + g_string_truncate(outkeys, outkeys->len - 1); + + if (use_keys) + irc_send_cmdv(IRC_SERVER(server), "JOIN %s %s", outchans->str, outkeys->str); + else + irc_send_cmdv(IRC_SERVER(server), "JOIN %s", outchans->str); + } + cmdlen = 0; + g_string_truncate(outchans,0); + g_string_truncate(outkeys,0); + if (*tmp == NULL || tmp[1] == NULL) + break; + } + g_string_free(outchans, TRUE); + g_string_free(outkeys, TRUE); + + g_strfreev(chanlist); + g_strfreev(keylist); + + cmd_params_free(free_arg); +} + +/* function for finding IRC channels - adds support for !channels */ +static CHANNEL_REC *irc_channel_find_server(IRC_SERVER_REC *server, const char *channel) +{ + GSList *tmp; + char *fmt_channel; + + /* if 'channel' has no leading # this lookup is going to fail, add a + * octothorpe in front of it to handle this case. */ + fmt_channel = force_channel_name(server, channel); + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (rec->chat_type != server->chat_type) + continue; + + /* check both !ABCDEchannel and !channel */ + if (server->nick_comp_func(fmt_channel, rec->name) == 0) { + g_free(fmt_channel); + return rec; + } + + if (server->nick_comp_func(fmt_channel, rec->visible_name) == 0) { + g_free(fmt_channel); + return rec; + } + } + + g_free(fmt_channel); + + return NULL; +} + +static void sig_server_connected(SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + server->channel_find_func = + (CHANNEL_REC * (*) (SERVER_REC *, const char *) ) irc_channel_find_server; + server->channels_join = (void (*) (SERVER_REC *, const char *, int)) + irc_channels_join; +} + +static char *irc_get_join_data(CHANNEL_REC *channel) +{ + IRC_CHANNEL_REC *irc_channel = (IRC_CHANNEL_REC *) channel; + + return irc_channel->key == NULL ? g_strdup(irc_channel->name) : + g_strconcat(irc_channel->name, " ", irc_channel->key, NULL); +} + +static void sig_channel_created(IRC_CHANNEL_REC *channel) +{ + if (IS_IRC_CHANNEL(channel)) + channel->get_join_data = irc_get_join_data; +} + +static void sig_channel_destroyed(IRC_CHANNEL_REC *channel) +{ + if (!IS_IRC_CHANNEL(channel)) + return; + + if (!channel->server->disconnected && !channel->left && !channel->kicked) { + /* destroying channel record without actually + having left the channel yet */ + signal_emit("command part", 3, "", channel->server, channel); + } +} + +void irc_channels_init(void) +{ + signal_add_first("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_add("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + channel_events_init(); + channel_rejoin_init(); /* after channel_events_init() */ + channels_query_init(); + irc_channels_setup_init(); + + bans_init(); + modes_init(); + mode_lists_init(); + massjoin_init(); + irc_nicklist_init(); +} + +void irc_channels_deinit(void) +{ + signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + channel_events_deinit(); + channel_rejoin_deinit(); + channels_query_deinit(); + irc_channels_setup_deinit(); + + bans_deinit(); + modes_deinit(); + mode_lists_deinit(); + massjoin_deinit(); + irc_nicklist_deinit(); +} diff --git a/src/irc/core/irc-channels.h b/src/irc/core/irc-channels.h new file mode 100644 index 0000000..0f955dd --- /dev/null +++ b/src/irc/core/irc-channels.h @@ -0,0 +1,59 @@ +#ifndef IRSSI_IRC_CORE_IRC_CHANNELS_H +#define IRSSI_IRC_CORE_IRC_CHANNELS_H + +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/channels.h> + +/* Returns IRC_CHANNEL_REC if it's IRC channel, NULL if it isn't. */ +#define IRC_CHANNEL(channel) \ + PROTO_CHECK_CAST(CHANNEL(channel), IRC_CHANNEL_REC, chat_type, "IRC") + +#define IS_IRC_CHANNEL(channel) \ + (IRC_CHANNEL(channel) ? TRUE : FALSE) + +enum { + CHANNEL_QUERY_MODE, + CHANNEL_QUERY_WHO, + CHANNEL_QUERY_BMODE, + + CHANNEL_QUERIES +}; + +/* arbitrary 3-digit identifiers so we can find our WHOX responses */ +#define WHOX_CHANNEL_FULL_ID "743" +#define WHOX_USERACCOUNT_ID "745" + +#define CHANNEL_IS_MODE_QUERY(a) ((a) != CHANNEL_QUERY_WHO) + +#define STRUCT_SERVER_REC IRC_SERVER_REC +struct _IRC_CHANNEL_REC { +#include <irssi/src/core/channel-rec.h> + + GSList *banlist; /* list of bans */ + + time_t massjoin_start; /* Massjoin start time */ + int massjoins; /* Number of nicks waiting for massjoin signal.. */ + int last_massjoins; /* Massjoins when last checked in timeout function */ +}; + +typedef struct _SERVER_QUERY_REC { + int current_query_type; /* query type that is currently being asked */ + GSList *current_queries; /* All channels that are currently being queried */ + + GSList *queries[CHANNEL_QUERIES]; /* All queries that need to be asked from server */ + GHashTable *accountqueries; /* Per-nick account queries */ +} SERVER_QUERY_REC; + +void irc_channels_query_purge_accountquery(IRC_SERVER_REC *server, const char *nick); + +void irc_channels_init(void); +void irc_channels_deinit(void); + +/* Create new IRC channel record */ +IRC_CHANNEL_REC *irc_channel_create(IRC_SERVER_REC *server, const char *name, + const char *visible_name, int automatic); + +#define irc_channel_find(server, name) \ + IRC_CHANNEL(channel_find(SERVER(server), name)) + +#endif diff --git a/src/irc/core/irc-chatnets.c b/src/irc/core/irc-chatnets.c new file mode 100644 index 0000000..c3b24a1 --- /dev/null +++ b/src/irc/core/irc-chatnets.c @@ -0,0 +1,132 @@ +/* + irc-chatnets.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/lib-config/iconfig.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-chatnets.h> + +void ircnet_create(IRC_CHATNET_REC *rec) +{ + g_return_if_fail(rec != NULL); + + rec->chat_type = IRC_PROTOCOL; + chatnet_create((CHATNET_REC *) rec); +} + +static void sig_chatnet_read(IRC_CHATNET_REC *rec, CONFIG_NODE *node) +{ + char *value; + + if (!IS_IRC_CHATNET(rec)) + return; + + value = config_node_get_str(node, "usermode", NULL); + rec->usermode = (value != NULL && *value != '\0') ? g_strdup(value) : NULL; + + value = config_node_get_str(node, "alternate_nick", NULL); + rec->alternate_nick = (value != NULL && *value != '\0') ? g_strdup(value) : NULL; + + rec->max_cmds_at_once = config_node_get_int(node, "cmdmax", 0); + rec->cmd_queue_speed = config_node_get_int(node, "cmdspeed", 0); + rec->max_query_chans = config_node_get_int(node, "max_query_chans", 0); + + rec->max_kicks = config_node_get_int(node, "max_kicks", 0); + rec->max_msgs = config_node_get_int(node, "max_msgs", 0); + rec->max_modes = config_node_get_int(node, "max_modes", 0); + rec->max_whois = config_node_get_int(node, "max_whois", 0); + + rec->sasl_mechanism = g_strdup(config_node_get_str(node, "sasl_mechanism", NULL)); + rec->sasl_username = g_strdup(config_node_get_str(node, "sasl_username", NULL)); + rec->sasl_password = g_strdup(config_node_get_str(node, "sasl_password", NULL)); +} + +static void sig_chatnet_saved(IRC_CHATNET_REC *rec, CONFIG_NODE *node) +{ + if (!IS_IRC_CHATNET(rec)) + return; + + if (rec->usermode != NULL) + iconfig_node_set_str(node, "usermode", rec->usermode); + + if (rec->alternate_nick != NULL) + iconfig_node_set_str(node, "alternate_nick", rec->alternate_nick); + + if (rec->max_cmds_at_once > 0) + iconfig_node_set_int(node, "cmdmax", rec->max_cmds_at_once); + if (rec->cmd_queue_speed > 0) + iconfig_node_set_int(node, "cmdspeed", rec->cmd_queue_speed); + if (rec->max_query_chans > 0) + iconfig_node_set_int(node, "max_query_chans", rec->max_query_chans); + + if (rec->max_kicks > 0) + iconfig_node_set_int(node, "max_kicks", rec->max_kicks); + if (rec->max_msgs > 0) + iconfig_node_set_int(node, "max_msgs", rec->max_msgs); + if (rec->max_modes > 0) + iconfig_node_set_int(node, "max_modes", rec->max_modes); + if (rec->max_whois > 0) + iconfig_node_set_int(node, "max_whois", rec->max_whois); + + if (rec->sasl_mechanism != NULL) + iconfig_node_set_str(node, "sasl_mechanism", rec->sasl_mechanism); + if (rec->sasl_username != NULL) + iconfig_node_set_str(node, "sasl_username", rec->sasl_username); + if (rec->sasl_password != NULL) + iconfig_node_set_str(node, "sasl_password", rec->sasl_password); +} + +static void sig_chatnet_destroyed(IRC_CHATNET_REC *rec) +{ + if (IS_IRC_CHATNET(rec)) { + g_free(rec->usermode); + g_free(rec->alternate_nick); + g_free(rec->sasl_mechanism); + g_free(rec->sasl_username); + g_free(rec->sasl_password); + } +} + + +void irc_chatnets_init(void) +{ + signal_add("chatnet read", (SIGNAL_FUNC) sig_chatnet_read); + signal_add("chatnet saved", (SIGNAL_FUNC) sig_chatnet_saved); + signal_add("chatnet destroyed", (SIGNAL_FUNC) sig_chatnet_destroyed); +} + +void irc_chatnets_deinit(void) +{ + GSList *tmp, *next; + + for (tmp = chatnets; tmp != NULL; tmp = next) { + CHATNET_REC *rec = tmp->data; + + next = tmp->next; + if (IS_IRC_CHATNET(rec)) + chatnet_destroy(rec); + } + + signal_remove("chatnet read", (SIGNAL_FUNC) sig_chatnet_read); + signal_remove("chatnet saved", (SIGNAL_FUNC) sig_chatnet_saved); + signal_remove("chatnet destroyed", (SIGNAL_FUNC) sig_chatnet_destroyed); +} diff --git a/src/irc/core/irc-chatnets.h b/src/irc/core/irc-chatnets.h new file mode 100644 index 0000000..10e9892 --- /dev/null +++ b/src/irc/core/irc-chatnets.h @@ -0,0 +1,44 @@ +#ifndef IRSSI_IRC_CORE_IRC_CHATNETS_H +#define IRSSI_IRC_CORE_IRC_CHATNETS_H + +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/chatnets.h> + +/* returns IRC_CHATNET_REC if it's IRC network, NULL if it isn't */ +#define IRC_CHATNET(chatnet) \ + PROTO_CHECK_CAST(CHATNET(chatnet), IRC_CHATNET_REC, chat_type, "IRC") + +#define IS_IRC_CHATNET(chatnet) \ + (IRC_CHATNET(chatnet) ? TRUE : FALSE) + +#define IS_IRCNET(ircnet) IS_IRC_CHATNET(ircnet) +#define IRCNET(ircnet) IRC_CHATNET(ircnet) + +struct _IRC_CHATNET_REC { +#include <irssi/src/core/chatnet-rec.h> + + char *usermode; + char *alternate_nick; + + char *sasl_mechanism; + char *sasl_username; + char *sasl_password; + + int max_cmds_at_once; + int cmd_queue_speed; + int max_query_chans; /* when syncing, max. number of channels to put in one MODE/WHO command */ + + /* max. number of kicks/msgs/mode/whois per command */ + int max_kicks, max_msgs, max_modes, max_whois; +}; + +void ircnet_create(IRC_CHATNET_REC *rec); + +#define irc_chatnet_find(name) \ + IRC_CHATNET(chatnet_find(name)) +#define ircnet_find(name) irc_chatnet_find(name) + +void irc_chatnets_init(void); +void irc_chatnets_deinit(void); + +#endif diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c new file mode 100644 index 0000000..5f234c5 --- /dev/null +++ b/src/irc/core/irc-commands.c @@ -0,0 +1,1128 @@ +/* + irc-commands.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/recode.h> +#include <irssi/src/core/special-vars.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/window-item-def.h> + +#include <irssi/src/core/servers-reconnect.h> +#include <irssi/src/irc/core/servers-redirect.h> +#include <irssi/src/core/servers-setup.h> +#include <irssi/src/core/nicklist.h> + +#include <irssi/src/irc/core/bans.h> +#include <irssi/src/irc/core/irc-commands.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-queries.h> + +/* How often to check if there's anyone to be unbanned in knockout list */ +#define KNOCKOUT_TIMECHECK 10000 + +/* /LIST: Max. number of channels in IRC network before -yes option + is required */ +#define LIST_MAX_CHANNELS_PASS 1000 + +/* When /PARTing a channel, if there's more messages in output queue + than this, purge the output for channel. The idea behind this is that + if you accidentally pasted some large text and /PART the channel, the + text won't be fully pasted. Note that this counter is the whole size + of the output queue, not channel specific.. */ +#define MAX_COMMANDS_ON_PART_UNTIL_PURGE 10 + +typedef struct { + IRC_CHANNEL_REC *channel; + char *ban; + time_t unban_time; +} KNOCKOUT_REC; + +static GString *tmpstr; +static int knockout_tag; + +/* SYNTAX: NOTICE <targets> <message> */ +static void cmd_notice(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + const char *target, *msg; + char *recoded; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, + &target, &msg)) + return; + if (g_strcmp0(target, "*") == 0) + target = item == NULL ? NULL : window_item_get_target(item); + if (target == NULL || *target == '\0' || *msg == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + recoded = recode_out(SERVER(server), msg, target); + g_string_printf(tmpstr, "NOTICE %s :%s", target, recoded); + g_free(recoded); + + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + cmd_params_free(free_arg); +} + +/* SYNTAX: CTCP <targets> <ctcp command> [<ctcp data>] */ +static void cmd_ctcp(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + const char *target; + char *ctcpcmd, *ctcpdata; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &target, &ctcpcmd, &ctcpdata)) + return; + if (g_strcmp0(target, "*") == 0) + target = item == NULL ? NULL : window_item_get_target(item); + if (target == NULL || *target == '\0' || *ctcpcmd == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + ascii_strup(ctcpcmd); + if (*ctcpdata == '\0') + g_string_printf(tmpstr, "PRIVMSG %s :\001%s\001", target, ctcpcmd); + else { + char *recoded; + + recoded = recode_out(SERVER(server), ctcpdata, target); + g_string_printf(tmpstr, "PRIVMSG %s :\001%s %s\001", target, ctcpcmd, recoded); + g_free(recoded); + } + + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + cmd_params_free(free_arg); +} + +/* SYNTAX: NCTCP <targets> <ctcp command> [<ctcp data>] */ +static void cmd_nctcp(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + const char *target; + char *ctcpcmd, *ctcpdata, *recoded; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &target, &ctcpcmd, &ctcpdata)) + return; + if (g_strcmp0(target, "*") == 0) + target = item == NULL ? NULL : window_item_get_target(item); + if (target == NULL || *target == '\0' || *ctcpcmd == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + ascii_strup(ctcpcmd); + recoded = recode_out(SERVER(server), ctcpdata, target); + g_string_printf(tmpstr, "NOTICE %s :\001%s %s\001", target, ctcpcmd, recoded); + g_free(recoded); + + irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); + + cmd_params_free(free_arg); +} + +/* SYNTAX: PART [<channels>] [<message>] */ +static void cmd_part(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + char *channame, *msg; + char *recoded = NULL; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_OPTCHAN, item, &channame, &msg)) + return; + if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*msg == '\0') msg = (char *) settings_get_str("part_message"); + + if (server->cmdcount > MAX_COMMANDS_ON_PART_UNTIL_PURGE) + irc_server_purge_output(server, channame); + + if (*msg != '\0') + recoded = recode_out(SERVER(server), msg, channame); + + if (recoded == NULL) + irc_send_cmdv(server, "PART %s", channame); + else + irc_send_cmdv(server, "PART %s :%s", channame, recoded); + + g_free(recoded); + cmd_params_free(free_arg); +} + +/* SYNTAX: KICK [<channel>] <nicks> [<reason>] */ +static void cmd_kick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + char *channame, *nicks, *reason, *recoded; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | + PARAM_FLAG_OPTCHAN, item, + &channame, &nicks, &reason)) + return; + + if (*channame == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (!server_ischannel(SERVER(server), channame)) cmd_param_error(CMDERR_NOT_JOINED); + + recoded = recode_out(SERVER(server), reason, channame); + g_string_printf(tmpstr, "KICK %s %s :%s", channame, nicks, recoded); + g_free(recoded); + + irc_send_cmd_split(server, tmpstr->str, 3, server->max_kicks_in_cmd); + + cmd_params_free(free_arg); +} + +/* SYNTAX: TOPIC [-delete] [<channel>] [<topic>] */ +static void cmd_topic(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *channame, *topic; + char *recoded = NULL; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | + PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + item, "topic", &optlist, &channame, &topic)) + return; + + if (*topic != '\0' || g_hash_table_lookup(optlist, "delete") != NULL) + recoded = recode_out(SERVER(server), topic, channame); + + if (recoded == NULL) + irc_send_cmdv(server, "TOPIC %s", channame); + else + irc_send_cmdv(server, "TOPIC %s :%s", channame, recoded); + + g_free(recoded); + + cmd_params_free(free_arg); +} + +/* SYNTAX: INVITE <nick> [<channel>] */ +static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + char *nick, *channame; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2, &nick, &channame)) + return; + + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (*channame == '\0' || g_strcmp0(channame, "*") == 0) { + if (!IS_IRC_CHANNEL(item)) + cmd_param_error(CMDERR_NOT_JOINED); + + channame = IRC_CHANNEL(item)->name; + } + + irc_send_cmdv(server, "INVITE %s %s", nick, channame); + cmd_params_free(free_arg); +} + +/* SYNTAX: LIST [-yes] [<channel>] */ +static void cmd_list(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *str; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "list", &optlist, &str)) + return; + + if (*str == '\0' && g_hash_table_lookup(optlist, "yes") == NULL && + (server->channels_formed <= 0 || + server->channels_formed > LIST_MAX_CHANNELS_PASS)) + cmd_param_error(CMDERR_NOT_GOOD_IDEA); + + irc_send_cmdv(server, "LIST %s", str); + cmd_params_free(free_arg); +} + +/* SYNTAX: WHO [<nicks> | <channels> | **] */ +static void cmd_who(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + char *channel, *rest; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, &channel, &rest)) + return; + + if (g_strcmp0(channel, "*") == 0 || *channel == '\0') { + if (!IS_IRC_CHANNEL(item)) + cmd_param_error(CMDERR_NOT_JOINED); + + channel = IRC_CHANNEL(item)->name; + } + if (g_strcmp0(channel, "**") == 0) { + /* ** displays all nicks.. */ + *channel = '\0'; + } + + if (rest[0] == '\0') + irc_send_cmdv(server, "WHO %s", channel); + else + irc_send_cmdv(server, "WHO %s %s", channel, rest); + + cmd_params_free(free_arg); +} + +static void cmd_names(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *channel; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "names", &optlist, &channel)) + return; + + if (g_strcmp0(channel, "*") == 0 || *channel == '\0') { + if (!IS_IRC_CHANNEL(item)) + cmd_param_error(CMDERR_NOT_JOINED); + + channel = IRC_CHANNEL(item)->name; + } + + if (g_strcmp0(channel, "**") == 0) { + /* ** displays all nicks.. */ + irc_send_cmd(server, "NAMES"); + } else { + irc_send_cmdv(server, "NAMES %s", channel); + } + + cmd_params_free(free_arg); +} + +/* SYNTAX: NICK <new nick> */ +static void cmd_nick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + char *nick; + void *free_arg; + + g_return_if_fail(data != NULL); + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1, &nick)) + return; + + g_free(server->last_nick); + server->last_nick = g_strdup(nick); + + irc_send_cmdv(server, "NICK %s", nick); + cmd_params_free(free_arg); +} + +static char *get_redirect_nicklist(const char *nicks, int *free) +{ + char *str, *ret; + + if (*nicks != '!' && strchr(nicks, ',') == NULL) { + *free = FALSE; + return (char *) nicks; + } + + *free = TRUE; + + /* ratbox-style operspy whois takes !nick, echoes that + * in RPL_ENDOFWHOIS as normal but gives output about the + * plain nick + */ + str = g_strdup(*nicks == '!' ? nicks + 1 : nicks); + g_strdelimit(str, ",", ' '); + ret = g_strconcat(str, " ", nicks, NULL); + g_free(str); + + return ret; +} + +/* SYNTAX: WHOIS [-<server tag>] [<server>] [<nicks>] */ +static void cmd_whois(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *qserver, *query, *event_402, *str; + void *free_arg; + int free_nick; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS, + "whois", &optlist, &qserver, &query)) + return; + + /* -<server tag> */ + server = IRC_SERVER(cmd_options_get_server("whois", optlist, + SERVER(server))); + if (server == NULL) { + cmd_params_free(free_arg); + return; + } + + if (*query == '\0') { + query = qserver; + qserver = ""; + } + if (*query == '\0') { + QUERY_REC *queryitem = QUERY(item); + if (queryitem == NULL) + query = server->nick; + else + query = qserver = queryitem->name; + } + + if (g_strcmp0(query, "*") == 0 && + g_hash_table_lookup(optlist, "yes") == NULL) + cmd_param_error(CMDERR_NOT_GOOD_IDEA); + + event_402 = "event 402"; + if (*qserver == '\0') + g_string_printf(tmpstr, "WHOIS %s", query); + else { + g_string_printf(tmpstr, "WHOIS %s %s", qserver, query); + if (g_ascii_strcasecmp(qserver, query) == 0) + event_402 = "whois event not found"; + } + + query = get_redirect_nicklist(query, &free_nick); + + str = g_strconcat(qserver, " ", query, NULL); + server_redirect_event( + server, "whois", 1, str, TRUE, /* */ + NULL, /* */ + "event 318", "whois end", /* */ + "event 402", event_402, /* */ + "event 301", "whois away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ + "event 313", "whois oper", /* */ + "event 330", "whois account", /* */ + "event 401", + (settings_get_bool("auto_whowas") ? "whois try whowas" : "whois event not found"), + "event 311", "whois event", /* */ + "", "whois default event", NULL); + g_free(str); + + server->whois_found = FALSE; + irc_send_cmd_split(server, tmpstr->str, 2, server->max_whois_in_cmd); + + if (free_nick) g_free(query); + cmd_params_free(free_arg); +} + +static void event_whois(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + server->whois_found = TRUE; + signal_emit("event 311", 4, server, data, nick, addr); +} + +static void sig_whois_try_whowas(IRC_SERVER_REC *server, const char *data) +{ + char *params, *nick; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + + server->whowas_found = FALSE; + server_redirect_event(server, "whowas", 1, nick, -1, NULL, + "event 314", "whowas event", + "event 369", "whowas event end", + "event 406", "event empty", NULL); + irc_send_cmdv(server, "WHOWAS %s 1", nick); + + g_free(params); +} + +static void event_end_of_whois(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + signal_emit("event 318", 4, server, data, nick, addr); + server->whois_found = FALSE; +} + +static void event_whowas(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + server->whowas_found = TRUE; + signal_emit("event 314", 4, server, data, nick, addr); +} + +/* SYNTAX: WHOWAS [<nicks> [<count> [server]]] */ +static void cmd_whowas(const char *data, IRC_SERVER_REC *server) +{ + char *nicks, *rest, *nicks_redir; + void *free_arg; + int free_nick; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + &nicks, &rest)) + return; + if (*nicks == '\0') nicks = server->nick; + + nicks_redir = get_redirect_nicklist(nicks, &free_nick); + server_redirect_event(server, "whowas", 1, nicks_redir, -1, NULL, + "event 301", "whowas away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ + "event 314", "whowas event", NULL); + if (free_nick) g_free(nicks_redir); + + server->whowas_found = FALSE; + + if (rest[0] == '\0') + irc_send_cmdv(server, "WHOWAS %s", nicks); + else + irc_send_cmdv(server, "WHOWAS %s %s", nicks, rest); + + cmd_params_free(free_arg); +} + +/* SYNTAX: PING [<nick> | <channel> | *] */ +static void cmd_ping(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + gint64 tv; + char *str; + + CMD_IRC_SERVER(server); + + if (*data == '\0') { + if (!IS_QUERY(item)) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + data = window_item_get_target(item); + } + + tv = g_get_real_time(); + + str = g_strdup_printf("%s PING " + "%" G_GINT64_FORMAT " " + "%" G_GINT64_FORMAT, + data, tv / G_TIME_SPAN_SECOND, tv % G_TIME_SPAN_SECOND); + signal_emit("command ctcp", 3, str, server, item); + g_free(str); +} + +/* SYNTAX: AWAY [-one | -all] [<reason>] */ +static void cmd_away(const char *data, IRC_SERVER_REC *server) +{ + GHashTable *optlist; + char *reason; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST, "away", &optlist, &reason)) return; + + if (g_hash_table_lookup(optlist, "one") != NULL) + irc_server_send_away(server, reason); + else + g_slist_foreach(servers, (GFunc) irc_server_send_away, reason); + + cmd_params_free(free_arg); +} + +/* SYNTAX: SCONNECT <new server> [[<port>] <existing server>] */ +static void cmd_sconnect(const char *data, IRC_SERVER_REC *server) +{ + CMD_IRC_SERVER(server); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "CONNECT %s", data); +} + +/* SYNTAX: QUOTE <data> */ +static void cmd_quote(const char *data, IRC_SERVER_REC *server) +{ + if (server != NULL && !IS_IRC_SERVER(server)) + return; + if (server == NULL || server->connect_time == 0) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!server->connected) + irc_send_cmd_now(server, data); + else + irc_send_cmd(server, data); +} + +static void cmd_wall_hash(gpointer key, NICK_REC *nick, GSList **nicks) +{ + if (nick->op) *nicks = g_slist_append(*nicks, nick); +} + +/* SYNTAX: WAIT [-<server tag>] <milliseconds> */ +static void cmd_wait(const char *data, IRC_SERVER_REC *server) +{ + GHashTable *optlist; + char *msecs; + void *free_arg; + int n; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + NULL, &optlist, &msecs)) + return; + + if (*msecs == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + /* -<server tag> */ + server = IRC_SERVER(cmd_options_get_server(NULL, optlist, + SERVER(server))); + + n = atoi(msecs); + if (server != NULL && n > 0) { + server->wait_cmd = g_get_real_time(); + server->wait_cmd += n * G_TIME_SPAN_MILLISECOND; + } + cmd_params_free(free_arg); +} + +/* SYNTAX: WALL [<channel>] <message> */ +static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + char *channame, *msg, *args, *recoded; + void *free_arg; + IRC_CHANNEL_REC *chanrec; + GSList *tmp, *nicks; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | + PARAM_FLAG_GETREST, item, &channame, &msg)) + return; + if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = irc_channel_find(server, channame); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + recoded = recode_out(SERVER(server), msg, channame); + /* See if the server has advertised support of wallchops */ + if (g_hash_table_lookup(chanrec->server->isupport, "statusmsg") || + g_hash_table_lookup(chanrec->server->isupport, "wallchops")) + irc_send_cmdv(server, "NOTICE @%s :%s", chanrec->name, recoded); + else { + /* Fall back to manually noticing each op */ + nicks = NULL; + g_hash_table_foreach(chanrec->nicks, + (GHFunc) cmd_wall_hash, &nicks); + + args = g_strconcat(chanrec->name, " ", recoded, NULL); + msg = parse_special_string(settings_get_str("wall_format"), + SERVER(server), item, args, NULL, 0); + g_free(args); + + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + if (rec != chanrec->ownnick) { + irc_send_cmdv(server, "NOTICE %s :%s", + rec->nick, msg); + } + } + + g_free(msg); + g_slist_free(nicks); + } + + g_free(recoded); + cmd_params_free(free_arg); +} + +/* SYNTAX: KICKBAN [<channel>] <nicks> <reason> */ +static void cmd_kickban(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + IRC_CHANNEL_REC *chanrec; + char *channel, *nicks, *reason, *kickcmd, *bancmd, *recoded; + char **nicklist, *spacenicks; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, + item, &channel, &nicks, &reason)) + return; + + if (*channel == '\0' || *nicks == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) + cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + nicklist = g_strsplit(nicks, ",", -1); + spacenicks = g_strjoinv(" ", nicklist); + g_strfreev(nicklist); + + recoded = recode_out(SERVER(server), reason, channel); + kickcmd = g_strdup_printf("%s %s %s", chanrec->name, nicks, recoded); + g_free(recoded); + + bancmd = g_strdup_printf("%s %s", chanrec->name, spacenicks); + g_free(spacenicks); + + if (settings_get_bool("kick_first_on_kickban")) { + signal_emit("command kick", 3, kickcmd, server, chanrec); + signal_emit("command ban", 3, bancmd, server, chanrec); + } else { + signal_emit("command ban", 3, bancmd, server, chanrec); + signal_emit("command kick", 3, kickcmd, server, chanrec); + } + g_free(kickcmd); + g_free(bancmd); + + cmd_params_free(free_arg); +} + +static void knockout_destroy(IRC_SERVER_REC *server, KNOCKOUT_REC *rec) +{ + server->knockoutlist = g_slist_remove(server->knockoutlist, rec); + g_free(rec->ban); + g_free(rec); +} + +/* timeout function: knockout */ +static void knockout_timeout_server(IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + time_t now; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + now = time(NULL); + for (tmp = server->knockoutlist; tmp != NULL; tmp = next) { + KNOCKOUT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->unban_time <= now) { + /* timeout, unban. */ + signal_emit("command unban", 3, rec->ban, server, rec->channel); + knockout_destroy(server, rec); + } + } +} + +static int knockout_timeout(void) +{ + g_slist_foreach(servers, (GFunc) knockout_timeout_server, NULL); + return 1; +} + +/* SYNTAX: KNOCKOUT [<time>] <nicks> <reason> */ +static void cmd_knockout(const char *data, IRC_SERVER_REC *server, + IRC_CHANNEL_REC *channel) +{ + KNOCKOUT_REC *rec; + char *nicks, *reason, *timeoutstr, *kickcmd, *bancmd, *recoded; + char **nicklist, *spacenicks, *banmasks; + void *free_arg; + int timeleft; + GSList *ptr; + + CMD_IRC_SERVER(server); + + if (!IS_IRC_CHANNEL(channel)) + cmd_return_error(CMDERR_NOT_JOINED); + + if (i_isdigit(*data)) { + /* first argument is the timeout */ + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &timeoutstr, &nicks, &reason)) + return; + + if (!parse_time_interval(timeoutstr, &timeleft)) + cmd_param_error(CMDERR_INVALID_TIME); + } else { + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, + &nicks, &reason)) + return; + timeleft = settings_get_time("knockout_time"); + } + + if (*nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + nicklist = g_strsplit(nicks, ",", -1); + spacenicks = g_strjoinv(" ", nicklist); + g_strfreev(nicklist); + + banmasks = ban_get_masks(channel, spacenicks, 0); + g_free(spacenicks); + + recoded = recode_out(SERVER(server), reason, channel->name); + kickcmd = g_strdup_printf("%s %s %s", channel->name, nicks, recoded); + g_free(recoded); + + bancmd = *banmasks == '\0'? NULL : + g_strdup_printf("%s %s", channel->name, banmasks); + + if (settings_get_bool("kick_first_on_kickban")) { + signal_emit("command kick", 3, kickcmd, server, channel); + if (bancmd != NULL) + signal_emit("command ban", 3, bancmd, server, channel); + } else { + if (bancmd != NULL) + signal_emit("command ban", 3, bancmd, server, channel); + signal_emit("command kick", 3, kickcmd, server, channel); + } + g_free(kickcmd); + g_free_not_null(bancmd); + + if (*banmasks == '\0') + g_free(banmasks); + else { + /* check if we already have this knockout */ + for (ptr = server->knockoutlist; ptr != NULL; ptr = ptr->next) { + rec = ptr->data; + if (channel == rec->channel && + !g_strcmp0(rec->ban, banmasks)) + break; + } + if (ptr == NULL) { + rec = g_new(KNOCKOUT_REC, 1); + rec->channel = channel; + rec->ban = banmasks; + server->knockoutlist = g_slist_append(server->knockoutlist, rec); + } + rec->unban_time = time(NULL)+timeleft/1000; + } + + cmd_params_free(free_arg); +} + +/* SYNTAX: SERVER PURGE [<target>] */ +static void cmd_server_purge(const char *data, IRC_SERVER_REC *server) +{ + char *target; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 1, &target)) + return; + + irc_server_purge_output(server, *target == '\0' ? NULL : target); + + cmd_params_free(free_arg); +} + +/* destroy all knockouts in server */ +static void sig_server_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + g_free(server->last_nick); + + while (server->knockoutlist != NULL) + knockout_destroy(server, server->knockoutlist->data); +} + +/* destroy all knockouts in channel */ +static void sig_channel_destroyed(IRC_CHANNEL_REC *channel) +{ + GSList *tmp, *next; + + if (!IS_IRC_CHANNEL(channel) || !IS_IRC_SERVER(channel->server)) + return; + + for (tmp = channel->server->knockoutlist; tmp != NULL; tmp = next) { + KNOCKOUT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->channel == channel) + knockout_destroy(channel->server, rec); + } +} + +/* SYNTAX: OPER [<nick> [<password>]] */ +static void cmd_oper(const char *data, IRC_SERVER_REC *server) +{ + char *nick, *password; + void *free_arg; + + CMD_IRC_SERVER(server); + + /* asking for password is handled by fe-common */ + if (!cmd_get_params(data, &free_arg, 2, &nick, &password)) + return; + if (*password == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "OPER %s %s", nick, password); + cmd_params_free(free_arg); +} + +/* SYNTAX: ACCEPT [[-]nick,...] */ +static void cmd_accept(const char *data, IRC_SERVER_REC *server) +{ + CMD_IRC_SERVER(server); + + if (*data == '\0') + irc_send_cmd(server, "ACCEPT *"); + else + irc_send_cmdv(server, "ACCEPT %s", data); +} + +/* SYNTAX: UNSILENCE <nick!user@host> */ +static void cmd_unsilence(const char *data, IRC_SERVER_REC *server) +{ + CMD_IRC_SERVER(server); + + if (*data == '\0') + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "SILENCE -%s", data); +} + +static void command_self(const char *data, IRC_SERVER_REC *server) +{ + CMD_IRC_SERVER(server); + + if (data[0] == '\0') + irc_send_cmdv(server, "%s", current_command); + else + irc_send_cmdv(server, "%s %s", current_command, data); +} + +static void command_1self(const char *data, IRC_SERVER_REC *server) +{ + g_return_if_fail(data != NULL); + if (!IS_IRC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + irc_send_cmdv(server, "%s :%s", current_command, data); +} + +static void command_2self(const char *data, IRC_SERVER_REC *server) +{ + char *target, *text; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &target, &text)) + return; + if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + irc_send_cmdv(server, "%s %s :%s", current_command, target, text); + cmd_params_free(free_arg); +} + +void irc_commands_init(void) +{ + tmpstr = g_string_new(NULL); + + settings_add_str("misc", "part_message", ""); + settings_add_time("misc", "knockout_time", "5min"); + settings_add_str("misc", "wall_format", "[Wall/$0] $1-"); + settings_add_bool("misc", "kick_first_on_kickban", FALSE); + settings_add_bool("misc", "auto_whowas", TRUE); + + knockout_tag = g_timeout_add(KNOCKOUT_TIMECHECK, (GSourceFunc) knockout_timeout, NULL); + + command_bind_irc("notice", NULL, (SIGNAL_FUNC) cmd_notice); + command_bind_irc("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); + command_bind_irc("nctcp", NULL, (SIGNAL_FUNC) cmd_nctcp); + command_bind_irc("part", NULL, (SIGNAL_FUNC) cmd_part); + command_bind_irc("kick", NULL, (SIGNAL_FUNC) cmd_kick); + command_bind_irc("topic", NULL, (SIGNAL_FUNC) cmd_topic); + command_bind_irc("invite", NULL, (SIGNAL_FUNC) cmd_invite); + command_bind_irc("list", NULL, (SIGNAL_FUNC) cmd_list); + command_bind_irc("who", NULL, (SIGNAL_FUNC) cmd_who); + command_bind_irc("names", NULL, (SIGNAL_FUNC) cmd_names); + command_bind_irc("nick", NULL, (SIGNAL_FUNC) cmd_nick); + command_bind_irc("whois", NULL, (SIGNAL_FUNC) cmd_whois); + command_bind_irc("whowas", NULL, (SIGNAL_FUNC) cmd_whowas); + command_bind_irc("ping", NULL, (SIGNAL_FUNC) cmd_ping); + /* SYNTAX: KILL <nick> <reason> */ + command_bind_irc("kill", NULL, (SIGNAL_FUNC) command_2self); + command_bind_irc("away", NULL, (SIGNAL_FUNC) cmd_away); + /* SYNTAX: ISON <nicks> */ + command_bind_irc("ison", NULL, (SIGNAL_FUNC) command_1self); + command_bind_irc("accept", NULL, (SIGNAL_FUNC) cmd_accept); + /* SYNTAX: ADMIN [<server>|<nickname>] */ + command_bind_irc("admin", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: INFO [<server>] */ + command_bind_irc("info", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: KNOCK <channel> */ + command_bind_irc("knock", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: LINKS [[<server>] <mask>] */ + command_bind_irc("links", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: LUSERS [<server mask> [<remote server>]] */ + command_bind_irc("lusers", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: MAP */ + command_bind_irc("map", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: MOTD [<server>|<nick>] */ + command_bind_irc("motd", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: REHASH [<option>] */ + command_bind_irc("rehash", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: STATS <type> [<server>] */ + command_bind_irc("stats", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: TIME [<server>|<nick>] */ + command_bind_irc("time", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: TRACE [<server>|<nick>] */ + command_bind_irc("trace", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: VERSION [<server>|<nick>] */ + command_bind_irc("version", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: SERVLIST [<mask> [<type>]] */ + command_bind_irc("servlist", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: SILENCE [[+|-]<nick!user@host>] + SILENCE [<nick>] */ + command_bind_irc("silence", NULL, (SIGNAL_FUNC) command_self); + command_bind_irc("unsilence", NULL, (SIGNAL_FUNC) cmd_unsilence); + command_bind_irc("sconnect", NULL, (SIGNAL_FUNC) cmd_sconnect); + /* SYNTAX: SQUERY <service> [<message>] */ + command_bind_irc("squery", NULL, (SIGNAL_FUNC) command_2self); + /* SYNTAX: DIE */ + command_bind_irc("die", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: HASH */ + command_bind_irc("hash", NULL, (SIGNAL_FUNC) command_self); + command_bind_irc("oper", NULL, (SIGNAL_FUNC) cmd_oper); + /* SYNTAX: RESTART */ + command_bind_irc("restart", NULL, (SIGNAL_FUNC) command_self); + /* SYNTAX: SQUIT <server>|<mask> <reason> */ + command_bind_irc("squit", NULL, (SIGNAL_FUNC) command_2self); + /* SYNTAX: USERHOST <nicks> */ + command_bind_irc("userhost", NULL, (SIGNAL_FUNC) command_self); + command_bind_irc("quote", NULL, (SIGNAL_FUNC) cmd_quote); + command_bind_irc("wall", NULL, (SIGNAL_FUNC) cmd_wall); + command_bind_irc("wait", NULL, (SIGNAL_FUNC) cmd_wait); + /* SYNTAX: WALLOPS <message> */ + command_bind_irc("wallops", NULL, (SIGNAL_FUNC) command_1self); + command_bind_irc("kickban", NULL, (SIGNAL_FUNC) cmd_kickban); + command_bind_irc("knockout", NULL, (SIGNAL_FUNC) cmd_knockout); + command_bind_irc("setname", NULL, (SIGNAL_FUNC) command_1self); + command_bind_irc("server purge", NULL, (SIGNAL_FUNC) cmd_server_purge); + + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("whois try whowas", (SIGNAL_FUNC) sig_whois_try_whowas); + signal_add("whois event", (SIGNAL_FUNC) event_whois); + signal_add("whois end", (SIGNAL_FUNC) event_end_of_whois); + signal_add("whowas event", (SIGNAL_FUNC) event_whowas); + + command_set_options("connect", "+ircnet starttls disallow_starttls nocap"); + command_set_options("topic", "delete"); + command_set_options("list", "yes"); + command_set_options("away", "one all"); + command_set_options("whois", "yes"); +} + +void irc_commands_deinit(void) +{ + g_source_remove(knockout_tag); + + command_unbind("notice", (SIGNAL_FUNC) cmd_notice); + command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); + command_unbind("nctcp", (SIGNAL_FUNC) cmd_nctcp); + command_unbind("part", (SIGNAL_FUNC) cmd_part); + command_unbind("kick", (SIGNAL_FUNC) cmd_kick); + command_unbind("topic", (SIGNAL_FUNC) cmd_topic); + command_unbind("invite", (SIGNAL_FUNC) cmd_invite); + command_unbind("list", (SIGNAL_FUNC) cmd_list); + command_unbind("who", (SIGNAL_FUNC) cmd_who); + command_unbind("names", (SIGNAL_FUNC) cmd_names); + command_unbind("nick", (SIGNAL_FUNC) cmd_nick); + command_unbind("whois", (SIGNAL_FUNC) cmd_whois); + command_unbind("whowas", (SIGNAL_FUNC) cmd_whowas); + command_unbind("ping", (SIGNAL_FUNC) cmd_ping); + command_unbind("kill", (SIGNAL_FUNC) command_2self); + command_unbind("away", (SIGNAL_FUNC) cmd_away); + command_unbind("ison", (SIGNAL_FUNC) command_1self); + command_unbind("accept", (SIGNAL_FUNC) cmd_accept); + command_unbind("admin", (SIGNAL_FUNC) command_self); + command_unbind("info", (SIGNAL_FUNC) command_self); + command_unbind("knock", (SIGNAL_FUNC) command_self); + command_unbind("links", (SIGNAL_FUNC) command_self); + command_unbind("lusers", (SIGNAL_FUNC) command_self); + command_unbind("map", (SIGNAL_FUNC) command_self); + command_unbind("motd", (SIGNAL_FUNC) command_self); + command_unbind("rehash", (SIGNAL_FUNC) command_self); + command_unbind("stats", (SIGNAL_FUNC) command_self); + command_unbind("time", (SIGNAL_FUNC) command_self); + command_unbind("trace", (SIGNAL_FUNC) command_self); + command_unbind("version", (SIGNAL_FUNC) command_self); + command_unbind("servlist", (SIGNAL_FUNC) command_self); + command_unbind("silence", (SIGNAL_FUNC) command_self); + command_unbind("unsilence", (SIGNAL_FUNC) cmd_unsilence); + command_unbind("sconnect", (SIGNAL_FUNC) cmd_sconnect); + command_unbind("squery", (SIGNAL_FUNC) command_2self); + command_unbind("die", (SIGNAL_FUNC) command_self); + command_unbind("hash", (SIGNAL_FUNC) command_self); + command_unbind("oper", (SIGNAL_FUNC) cmd_oper); + command_unbind("restart", (SIGNAL_FUNC) command_self); + command_unbind("squit", (SIGNAL_FUNC) command_2self); + command_unbind("userhost", (SIGNAL_FUNC) command_self); + command_unbind("quote", (SIGNAL_FUNC) cmd_quote); + command_unbind("wall", (SIGNAL_FUNC) cmd_wall); + command_unbind("wait", (SIGNAL_FUNC) cmd_wait); + command_unbind("wallops", (SIGNAL_FUNC) command_1self); + command_unbind("kickban", (SIGNAL_FUNC) cmd_kickban); + command_unbind("setname", (SIGNAL_FUNC) command_1self); + command_unbind("knockout", (SIGNAL_FUNC) cmd_knockout); + command_unbind("server purge", (SIGNAL_FUNC) cmd_server_purge); + + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("whois try whowas", (SIGNAL_FUNC) sig_whois_try_whowas); + signal_remove("whois event", (SIGNAL_FUNC) event_whois); + signal_remove("whois end", (SIGNAL_FUNC) event_end_of_whois); + signal_remove("whowas event", (SIGNAL_FUNC) event_whowas); + + g_string_free(tmpstr, TRUE); +} diff --git a/src/irc/core/irc-commands.h b/src/irc/core/irc-commands.h new file mode 100644 index 0000000..6f68931 --- /dev/null +++ b/src/irc/core/irc-commands.h @@ -0,0 +1,26 @@ +#ifndef IRSSI_IRC_CORE_IRC_COMMANDS_H +#define IRSSI_IRC_CORE_IRC_COMMANDS_H + +#include <irssi/src/core/commands.h> + +#define command_bind_irc(cmd, section, signal) \ + command_bind_proto(cmd, IRC_PROTOCOL, section, signal) +#define command_bind_irc_first(cmd, section, signal) \ + command_bind_proto_first(cmd, IRC_PROTOCOL, section, signal) +#define command_bind_irc_last(cmd, section, signal) \ + command_bind_proto_last(cmd, IRC_PROTOCOL, section, signal) + +/* Simply returns if server isn't for IRC protocol. Prints ERR_NOT_CONNECTED + error if there's no server or server isn't connected yet */ +#define CMD_IRC_SERVER(server) \ + G_STMT_START { \ + if (server != NULL && !IS_IRC_SERVER(server)) \ + return; \ + if (server == NULL || !(server)->connected) \ + cmd_return_error(CMDERR_NOT_CONNECTED); \ + } G_STMT_END + +void irc_commands_init(void); +void irc_commands_deinit(void); + +#endif diff --git a/src/irc/core/irc-core.c b/src/irc/core/irc-core.c new file mode 100644 index 0000000..d1a0f72 --- /dev/null +++ b/src/irc/core/irc-core.c @@ -0,0 +1,153 @@ +/* + irc-core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-chatnets.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-queries.h> +#include <irssi/src/irc/core/irc-cap.h> +#include <irssi/src/irc/core/sasl.h> + +#include <irssi/src/irc/core/irc-servers-setup.h> +#include <irssi/src/core/channels-setup.h> + +#include <irssi/src/irc/core/ctcp.h> +#include <irssi/src/irc/core/irc-commands.h> +#include <irssi/src/irc/core/netsplit.h> + +void irc_expandos_init(void); +void irc_expandos_deinit(void); + +void irc_session_init(void); +void irc_session_deinit(void); + +void lag_init(void); +void lag_deinit(void); + +static CHATNET_REC *create_chatnet(void) +{ + return g_malloc0(sizeof(IRC_CHATNET_REC)); +} + +static SERVER_SETUP_REC *create_server_setup(void) +{ + return g_malloc0(sizeof(IRC_SERVER_SETUP_REC)); +} + +static CHANNEL_SETUP_REC *create_channel_setup(void) +{ + return g_malloc0(sizeof(CHANNEL_SETUP_REC)); +} + +static SERVER_CONNECT_REC *create_server_connect(void) +{ + return g_malloc0(sizeof(IRC_SERVER_CONNECT_REC)); +} + +static void destroy_server_connect(SERVER_CONNECT_REC *conn) +{ + IRC_SERVER_CONNECT_REC *ircconn; + + ircconn = IRC_SERVER_CONNECT(conn); + if (ircconn == NULL) + return; + + g_free_not_null(ircconn->usermode); + g_free_not_null(ircconn->alternate_nick); + g_free_not_null(ircconn->sasl_username); + g_free_not_null(ircconn->sasl_password); +} + +void irc_core_init(void) +{ + CHAT_PROTOCOL_REC *rec; + + rec = g_new0(CHAT_PROTOCOL_REC, 1); + rec->name = "IRC"; + rec->fullname = "Internet Relay Chat"; + rec->chatnet = "ircnet"; + + rec->case_insensitive = TRUE; + + rec->create_chatnet = create_chatnet; + rec->create_server_setup = create_server_setup; + rec->create_channel_setup = create_channel_setup; + rec->create_server_connect = create_server_connect; + rec->destroy_server_connect = destroy_server_connect; + + rec->server_init_connect = irc_server_init_connect; + rec->server_connect = irc_server_connect; + rec->channel_create = + (CHANNEL_REC *(*) (SERVER_REC *, const char *, + const char *, int)) + irc_channel_create; + rec->query_create = + (QUERY_REC *(*) (const char *, const char *, int)) + irc_query_create; + + chat_protocol_register(rec); + g_free(rec); + + irc_session_init(); + irc_chatnets_init(); + irc_servers_init(); + irc_channels_init(); + irc_queries_init(); + + ctcp_init(); + irc_commands_init(); + irc_irc_init(); + lag_init(); + netsplit_init(); + irc_expandos_init(); + irc_cap_init(); + sasl_init(); + + settings_check(); + module_register("irc", "core"); +} + +void irc_core_deinit(void) +{ + signal_emit("chat protocol deinit", 1, chat_protocol_find("IRC")); + + sasl_deinit(); + irc_cap_deinit(); + irc_expandos_deinit(); + netsplit_deinit(); + lag_deinit(); + irc_commands_deinit(); + ctcp_deinit(); + + irc_queries_deinit(); + irc_channels_deinit(); + irc_irc_deinit(); + irc_servers_deinit(); + irc_chatnets_deinit(); + irc_session_deinit(); + + chat_protocol_unregister("IRC"); +} + +MODULE_ABICHECK(irc_core) diff --git a/src/irc/core/irc-expandos.c b/src/irc/core/irc-expandos.c new file mode 100644 index 0000000..625e6e8 --- /dev/null +++ b/src/irc/core/irc-expandos.c @@ -0,0 +1,205 @@ +/* + irc-expandos.c : irssi + + Copyright (C) 2000-2002 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/expandos.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/core/nicklist.h> + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + +static char *last_join; + +/* last person to join a channel you are on */ +static char *expando_lastjoin(SERVER_REC *server, void *item, int *free_ret) +{ + return last_join; +} + +/* current server numeric being processed */ +static char *expando_server_numeric(SERVER_REC *server, void *item, int *free_ret) +{ + return current_server_event == NULL || + !is_numeric(current_server_event, 0) ? NULL : + current_server_event; +} + +/* current server name */ +static char *expando_servername(SERVER_REC *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver = IRC_SERVER(server); + + return ircserver == NULL ? "" : ircserver->real_address; +} + +/* your /userhost $N address (user@host) */ +static char *expando_userhost(SERVER_REC *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver; + const char *username; + char hostname[HOST_NAME_MAX + 1]; + + ircserver = IRC_SERVER(server); + + /* prefer the _real_ /userhost reply */ + if (ircserver != NULL && ircserver->userhost != NULL) + return ircserver->userhost; + + /* haven't received userhost reply yet. guess something */ + *free_ret = TRUE; + if (ircserver == NULL) + username = settings_get_str("user_name"); + else + username = ircserver->connrec->username; + + if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0') + strcpy(hostname, "(none)"); + return g_strconcat(username, "@", hostname, NULL);; +} + +/* your hostname address (host) */ +static char *expando_hostname(SERVER_REC *server, void *item, int *free_ret) +{ + IRC_SERVER_REC *ircserver; + char hostname[HOST_NAME_MAX + 1]; + char **list; + char *hostname_split; + + ircserver = IRC_SERVER(server); + + *free_ret = TRUE; + + /* prefer the _real_ /userhost reply */ + if (ircserver != NULL && ircserver->userhost != NULL) { + list = g_strsplit(ircserver->userhost, "@", -1); + hostname_split = g_strdup(list[1]); + g_strfreev(list); + return hostname_split; + } + + /* haven't received userhost reply yet. guess something */ + if (gethostname(hostname, sizeof(hostname)) != 0 || *hostname == '\0') + strcpy(hostname, "(none)"); + return g_strdup(hostname); +} + +/* user mode in active server */ +static char *expando_usermode(SERVER_REC *server, void *item, int *free_ret) +{ + return IS_IRC_SERVER(server) ? IRC_SERVER(server)->usermode : ""; +} + +/* expands to your usermode on channel, op '@', halfop '%', "+" voice or other */ +static char *expando_cumode(SERVER_REC *server, void *item, int *free_ret) +{ + if (IS_IRC_CHANNEL(item) && CHANNEL(item)->ownnick) { + char prefix = NICK(CHANNEL(item)->ownnick)->prefixes[0]; + if (prefix != '\0') { + char *cumode = g_malloc(2); + cumode[0] = prefix; + cumode[1] = '\0'; + *free_ret = TRUE; + return cumode; + } + } + return ""; +} + +/* expands to your usermode on channel, + op '@', halfop '%', "+" voice, " " normal */ +static char *expando_cumode_space(SERVER_REC *server, void *item, int *free_ret) +{ + char *ret; + + if (!IS_IRC_SERVER(server)) + return ""; + + ret = expando_cumode(server, item, free_ret); + return *ret == '\0' ? " " : ret; +} + +static void event_join(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + g_return_if_fail(nick != NULL); + + if (g_ascii_strcasecmp(nick, server->nick) != 0) { + g_free_not_null(last_join); + last_join = g_strdup(nick); + } +} + +void irc_expandos_init(void) +{ + last_join = NULL; + + expando_create(":", expando_lastjoin, + "event join", EXPANDO_ARG_SERVER, NULL); + expando_create("H", expando_server_numeric, + "server event", EXPANDO_ARG_SERVER, NULL); + expando_create("S", expando_servername, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("X", expando_userhost, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("x", expando_hostname, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("usermode", expando_usermode, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, + "user mode changed", EXPANDO_ARG_SERVER, NULL); + expando_create("cumode", expando_cumode, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, + "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, + "channel joined", EXPANDO_ARG_WINDOW_ITEM, NULL); + expando_create("cumode_space", expando_cumode_space, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, + "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, + "channel joined", EXPANDO_ARG_WINDOW_ITEM, NULL); + + expando_add_signal("I", "event invite", EXPANDO_ARG_SERVER); + + signal_add("event join", (SIGNAL_FUNC) event_join); +} + +void irc_expandos_deinit(void) +{ + g_free_not_null(last_join); + + expando_destroy(":", expando_lastjoin); + expando_destroy("H", expando_server_numeric); + expando_destroy("S", expando_servername); + expando_destroy("X", expando_userhost); + expando_destroy("x", expando_hostname); + expando_destroy("usermode", expando_usermode); + expando_destroy("cumode", expando_cumode); + + signal_remove("event join", (SIGNAL_FUNC) event_join); +} diff --git a/src/irc/core/irc-masks.c b/src/irc/core/irc-masks.c new file mode 100644 index 0000000..e2c5aea --- /dev/null +++ b/src/irc/core/irc-masks.c @@ -0,0 +1,92 @@ +/* + masks.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/network.h> + +#include <irssi/src/irc/core/irc-masks.h> + +static char *get_domain_mask(char *host) +{ + char *ptr; + + if (strchr(host, '.') == NULL) { + /* no dots - toplevel domain or IPv6 address */ + ptr = strrchr(host, ':'); + if (ptr != NULL) { + /* IPv6 address, ban the last 64k addresses */ + if (ptr[1] != '\0') strcpy(ptr+1, "*"); + } + + return host; + } + + if (is_ipv4_address(host)) { + /* it's an IP address, change last digit to * */ + ptr = strrchr(host, '.'); + if (ptr != NULL && i_isdigit(ptr[1])) + strcpy(ptr+1, "*"); + } else { + /* if more than one dot, skip the first + (dyn123.blah.net -> *.blah.net) */ + ptr = strchr(host, '.'); + if (ptr != NULL && strchr(ptr+1, '.') != NULL) { + host = ptr-1; + host[0] = '*'; + } + } + + return host; +} + +char *irc_get_mask(const char *nick, const char *address, int flags) +{ + char *ret, *user, *host; + + /* strip -, ^ or ~ from start.. */ + user = g_strconcat("*", ishostflag(*address) ? + address+1 : address, NULL); + + /* split user and host */ + host = strchr(user, '@'); + if (host == NULL) { + g_free(user); + return NULL; + } + *host++ = '\0'; + + if (flags & IRC_MASK_HOST) { + /* we already have the host */ + } else if (flags & IRC_MASK_DOMAIN) { + /* domain - *.blah.org */ + host = get_domain_mask(host); + } else { + /* no domain/host */ + host = "*"; + } + + ret = g_strdup_printf("%s!%s@%s", + (flags & IRC_MASK_NICK) ? nick : "*", + (flags & IRC_MASK_USER) ? user : "*", + host); + g_free(user); + + return ret; +} diff --git a/src/irc/core/irc-masks.h b/src/irc/core/irc-masks.h new file mode 100644 index 0000000..5696bbb --- /dev/null +++ b/src/irc/core/irc-masks.h @@ -0,0 +1,13 @@ +#ifndef IRSSI_IRC_CORE_IRC_MASKS_H +#define IRSSI_IRC_CORE_IRC_MASKS_H + +#include <irssi/src/core/masks.h> + +#define IRC_MASK_NICK 0x01 +#define IRC_MASK_USER 0x02 +#define IRC_MASK_HOST 0x04 +#define IRC_MASK_DOMAIN 0x08 + +char *irc_get_mask(const char *nick, const char *address, int flags); + +#endif diff --git a/src/irc/core/irc-nicklist.c b/src/irc/core/irc-nicklist.c new file mode 100644 index 0000000..f62a96f --- /dev/null +++ b/src/irc/core/irc-nicklist.c @@ -0,0 +1,652 @@ +/* + irc-nicklist.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-masks.h> +#include <irssi/src/irc/core/irc-nicklist.h> +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/core/servers.h> + +static void nicklist_set_modes(IRC_CHANNEL_REC *channel, NICK_REC *rec, gboolean op, + gboolean halfop, gboolean voice, const char *prefixes, + gboolean send_changed) +{ + gboolean changed = FALSE; + if (rec->op != op) { + rec->op = op; + changed = TRUE; + } + if (rec->halfop != halfop) { + rec->halfop = halfop; + changed = TRUE; + } + if (rec->voice != voice) { + rec->voice = voice; + changed = TRUE; + } + + if (prefixes != NULL && g_strcmp0(rec->prefixes, prefixes) != 0) { + g_strlcpy(rec->prefixes, prefixes, sizeof(rec->prefixes)); + changed = TRUE; + } + + if (changed && send_changed) { + signal_emit("nicklist changed", 3, channel, rec, rec->nick); + } +} + +/* Add new nick to list */ +NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick, + int op, int halfop, int voice, int send_massjoin, + const char *prefixes) +{ + NICK_REC *rec; + + g_return_val_if_fail(IS_IRC_CHANNEL(channel), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new0(NICK_REC, 1); + rec->nick = g_strdup(nick); + + rec->send_massjoin = send_massjoin; + nicklist_set_modes(channel, rec, op, halfop, voice, prefixes, FALSE); + + nicklist_insert(CHANNEL(channel), rec); + return rec; +} + +int irc_nickcmp_rfc1459(const char *m, const char *n) +{ + while (*m != '\0' && *n != '\0') { + if (to_rfc1459(*m) != to_rfc1459(*n)) + return -1; + m++; n++; + } + return *m == *n ? 0 : 1; +} + +int irc_nickcmp_ascii(const char *m, const char *n) +{ + while (*m != '\0' && *n != '\0') { + if (to_ascii(*m) != to_ascii(*n)) + return -1; + m++; n++; + } + return *m == *n ? 0 : 1; +} + +static void event_names_list(IRC_SERVER_REC *server, const char *data) +{ + IRC_CHANNEL_REC *chanrec; + NICK_REC *rec; + char *params, *type, *channel, *names, *ptr, *host; + int op, halfop, voice; + char prefixes[MAX_USER_PREFIXES+1]; + const char *nick_flags, *nick_flag_cur, *nick_flag_op; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &type, &channel, &names); + + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL || chanrec->names_got) { + /* unknown channel / names list already read */ + g_free(params); + return; + } + nick_flags = server->get_nick_flags(SERVER(server)); + nick_flag_op = strchr(nick_flags, '@'); + + /* type = '=' = public, '*' = private, '@' = secret. + + This is actually pretty useless to check here, but at least we + get to know if the channel is +p or +s a few seconds before + we receive the MODE reply... + + If the channel key is set, assume the channel is +k also until + we know better, so parse_channel_modes() won't clear the key */ + if (*type == '*') { + parse_channel_modes(chanrec, NULL, + chanrec->key ? "+kp" : "+p", FALSE); + } else if (*type == '@') { + parse_channel_modes(chanrec, NULL, + chanrec->key ? "+ks" : "+s", FALSE); + } + + while (*names != '\0') { + while (*names == ' ') names++; + ptr = names; + while (*names != '\0' && *names != ' ') names++; + if (*names != '\0') *names++ = '\0'; + + /* some servers show ".@nick", there's also been talk about + showing "@+nick" and since none of these chars are valid + nick chars, just check them until a non-nickflag char is + found. */ + op = halfop = voice = FALSE; + prefixes[0] = '\0'; + while (isnickflag(server, *ptr)) { + prefix_add(prefixes, *ptr, (SERVER_REC *) server); + switch (*ptr) { + case '@': + op = TRUE; + break; + case '%': + halfop = TRUE; + break; + case '+': + voice = TRUE; + break; + default: + /* If this flag is listed higher than op (in the + * isupport PREFIX reply), then count this user + * as an op. */ + nick_flag_cur = strchr(nick_flags, *ptr); + if (nick_flag_cur && nick_flag_op && nick_flag_cur < nick_flag_op) { + op = TRUE; + } + break; + } + ptr++; + } + + host = strchr(ptr, '!'); + if (host != NULL) + *host++ = '\0'; + + rec = nicklist_find((CHANNEL_REC *) chanrec, ptr); + if (rec == NULL) { + rec = irc_nicklist_insert(chanrec, ptr, op, halfop, + voice, FALSE, prefixes); + if (host != NULL) + nicklist_set_host(CHANNEL(chanrec), rec, host); + } else { + nicklist_set_modes(chanrec, rec, op, halfop, voice, prefixes, TRUE); + } + } + + g_free(params); +} + +static void event_end_of_names(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel; + IRC_CHANNEL_REC *chanrec; + NICK_REC *ownnick; + int nicks; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &channel); + + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL && !chanrec->names_got) { + ownnick = nicklist_find(CHANNEL(chanrec), server->nick); + if (ownnick == NULL) { + /* stupid server - assume we have ops + if channel is empty */ + nicks = g_hash_table_size(chanrec->nicks); + ownnick = irc_nicklist_insert(chanrec, server->nick, + nicks == 0, FALSE, + FALSE, FALSE, NULL); + } + nicklist_set_own(CHANNEL(chanrec), ownnick); + chanrec->chanop = chanrec->ownnick->op; + chanrec->names_got = TRUE; + signal_emit("channel joined", 1, chanrec); + } + + g_free(params); +} + +static void fill_who(SERVER_REC *server, const char *channel, const char *user, const char *host, + const char *nick, const char *stat, const char *hops, const char *account, + const char *realname) +{ + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + /* update host, realname, hopcount */ + chanrec = channel_find(server, channel); + nickrec = chanrec == NULL ? NULL : + nicklist_find(chanrec, nick); + if (nickrec != NULL) { + if (nickrec->host == NULL) { + char *str = g_strdup_printf("%s@%s", user, host); + nicklist_set_host(chanrec, nickrec, str); + g_free(str); + } + if (nickrec->realname == NULL) { + nickrec->realname = g_strdup(realname); + } + if (nickrec->account == NULL && account != NULL) { + nicklist_set_account(chanrec, nickrec, + strcmp(account, "0") == 0 ? "*" : account); + } + sscanf(hops, "%d", &nickrec->hops); + } + + nicklist_update_flags(server, nick, + strchr(stat, 'G') != NULL, /* gone */ + strchr(stat, '*') != NULL); /* ircop */ +} + +static void event_who(SERVER_REC *server, const char *data) +{ + char *params, *nick, *channel, *user, *host, *stat, *realname, *hops; + + g_return_if_fail(data != NULL); + + params = + event_get_params(data, 8, NULL, &channel, &user, &host, NULL, &nick, &stat, &realname); + + /* get hop count */ + hops = realname; + while (*realname != '\0' && *realname != ' ') + realname++; + if (*realname == ' ') + *realname++ = '\0'; + + fill_who(server, channel, user, host, nick, stat, hops, NULL, realname); + + g_free(params); +} + +static void event_whox_channel_full(SERVER_REC *server, const char *data) +{ + char *params, *id, *nick, *channel, *user, *host, *stat, *hops, *account, *realname; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 10, NULL, &id, &channel, &user, &host, &nick, &stat, &hops, + &account, &realname); + + if (g_strcmp0(id, WHOX_CHANNEL_FULL_ID) != 0) { + g_free(params); + return; + } + + fill_who(server, channel, user, host, nick, stat, hops, account, realname); + + g_free(params); +} + +static void event_whox_useraccount(IRC_SERVER_REC *server, const char *data) +{ + char *params, *id, *nick, *account; + GSList *nicks, *tmp; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 4, NULL, &id, &nick, &account); + + if (g_strcmp0(id, WHOX_USERACCOUNT_ID) != 0) { + g_free(params); + return; + } + g_hash_table_remove(server->chanqueries->accountqueries, nick); + + if (strcmp(account, "0") == 0) { + account = "*"; + } + + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + if (rec->account == NULL || g_strcmp0(rec->account, account) != 0) { + nicklist_set_account(CHANNEL(tmp->data), rec, account); + } + } + g_slist_free(nicks); + g_free(params); +} + +static void event_whois(IRC_SERVER_REC *server, const char *data) +{ + char *params, *nick, *realname; + GSList *nicks, *tmp; + NICK_REC *rec; + + g_return_if_fail(data != NULL); + + /* first remove the gone-flag, if user is gone + it will be set later.. */ + params = event_get_params(data, 6, NULL, &nick, NULL, + NULL, NULL, &realname); + + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + rec = tmp->next->data; + + if (rec->realname == NULL) + rec->realname = g_strdup(realname); + } + g_slist_free(nicks); + + /* reset gone and ircop status, we'll handle them in the following + WHOIS replies */ + nicklist_update_flags(SERVER(server), nick, FALSE, FALSE); + g_free(params); +} + +static void event_whois_away(SERVER_REC *server, const char *data) +{ + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + nicklist_update_flags(server, nick, TRUE, -1); + g_free(params); +} + +static void event_own_away(SERVER_REC *server, const char *data) +{ + char *params, *nick; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 2, &nick, NULL); + nicklist_update_flags(server, nick, TRUE, -1); + g_free(params); +} + +static void event_own_unaway(SERVER_REC *server, const char *data) +{ + char *params, *nick; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 2, &nick, NULL); + nicklist_update_flags(server, nick, FALSE, -1); + g_free(params); +} + +static void event_whois_ircop(SERVER_REC *server, const char *data) +{ + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + nicklist_update_flags(server, nick, -1, TRUE); + g_free(params); +} + +static void event_nick_invalid(IRC_SERVER_REC *server, const char *data) +{ + if (!server->connected) + /* we used to call server_disconnect but that crashes + irssi because of undefined memory access. instead, + indicate that the connection should be dropped and + let the irc method to the clean-up. */ + server->connection_lost = server->no_reconnect = TRUE; +} + +static void event_nick_in_use(IRC_SERVER_REC *server, const char *data) +{ + char *str, *cmd, *params, *nick; + int n; + gboolean try_alternate_nick; + + g_return_if_fail(data != NULL); + + if (server->connected) { + /* Already connected, no need to handle this anymore. */ + return; + } + + try_alternate_nick = g_ascii_strcasecmp(server->nick, server->connrec->nick) == 0 && + server->connrec->alternate_nick != NULL && + g_ascii_strcasecmp(server->connrec->alternate_nick, server->nick) != 0; + + params = event_get_params(data, 2, NULL, &nick); + if (g_ascii_strcasecmp(server->nick, nick) != 0) { + /* the server uses a nick different from the one we send */ + g_free(server->nick); + server->nick = g_strdup(nick); + } + g_free(params); + + /* nick already in use - need to change it .. */ + if (try_alternate_nick) { + /* first try, so try the alternative nick.. */ + g_free(server->nick); + server->nick = g_strdup(server->connrec->alternate_nick); + } + else if (strlen(server->nick) < 9) { + /* keep adding '_' to end of nick.. */ + str = g_strdup_printf("%s_", server->nick); + g_free(server->nick); + server->nick = str; + } else { + /* nick full, keep adding number at the end */ + for (n = 8; n > 0; n--) { + if (server->nick[n] < '0' || server->nick[n] > '9') { + server->nick[n] = '1'; + break; + } + + if (server->nick[n] < '9') { + server->nick[n]++; + break; + } + server->nick[n] = '0'; + } + } + + cmd = g_strdup_printf("NICK %s", server->nick); + irc_send_cmd_now(server, cmd); + g_free(cmd); +} + +static void event_target_unavailable(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &channel); + if (!server_ischannel(SERVER(server), channel)) { + /* nick is unavailable. */ + event_nick_in_use(server, data); + } + + g_free(params); +} + +static void event_nick(IRC_SERVER_REC *server, const char *data, + const char *orignick) +{ + char *params, *nick; + + g_return_if_fail(data != NULL); + g_return_if_fail(orignick != NULL); + + params = event_get_params(data, 1, &nick); + + if (g_ascii_strcasecmp(orignick, server->nick) == 0) { + /* You changed your nick */ + if (server->last_nick != NULL && + g_ascii_strcasecmp(server->last_nick, nick) == 0) { + /* changed with /NICK - keep it as wanted nick */ + g_free(server->connrec->nick); + server->connrec->nick = g_strdup(nick); + } + + server_change_nick(SERVER(server), nick); + } + + /* invalidate any outstanding accountqueries for the old nick */ + irc_channels_query_purge_accountquery(server, orignick); + nicklist_rename(SERVER(server), orignick, nick); + g_free(params); +} + +static void event_userhost(SERVER_REC *server, const char *data) +{ + char *params, *hosts, **phosts, **pos, *ptr; + int oper; + + g_return_if_fail(data != NULL); + + /* set user's gone flag.. */ + params = event_get_params(data, 2, NULL, &hosts); + + phosts = g_strsplit(hosts, " ", -1); + for (pos = phosts; *pos != NULL; pos++) { + ptr = strchr(*pos, '='); + if (ptr == NULL || ptr == *pos) continue; + if (ptr[-1] == '*') { + ptr[-1] = '\0'; + oper = 1; + } else + oper = 0; + *ptr++ = '\0'; + + nicklist_update_flags(server, *pos, *ptr == '-', oper); + } + g_strfreev(phosts); + g_free(params); +} + +static void event_setname(SERVER_REC *server, const char *data, const char *nick, const char *address) +{ + GSList *nicks, *tmp; + NICK_REC *rec; + + if (!IS_IRC_SERVER(server)) + return; + + g_return_if_fail(nick != NULL); + g_return_if_fail(data != NULL); + if (*data == ':') data++; + + nicks = nicklist_get_same(server, nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + rec = tmp->next->data; + + g_free(rec->realname); + rec->realname = g_strdup(data); + } + g_slist_free(nicks); +} + +static void event_away_notify(IRC_SERVER_REC *server, const char *data, const char *nick, const char *add) +{ + char *params, *awaymsg; + + if (!IS_IRC_SERVER(server)) + return; + + g_return_if_fail(nick != NULL); + g_return_if_fail(data != NULL); + + params = event_get_params(data, 1 | PARAM_FLAG_GETREST, &awaymsg); + nicklist_update_flags(SERVER(server), nick, *awaymsg != '\0', -1); + g_free(params); +} + +static void sig_usermode(SERVER_REC *server) +{ + g_return_if_fail(IS_SERVER(server)); + + nicklist_update_flags(server, server->nick, server->usermode_away, -1); +} + +static const char *get_nick_flags(SERVER_REC *server) +{ + IRC_SERVER_REC *irc_server = (IRC_SERVER_REC *) server; + const char *prefix = + g_hash_table_lookup(irc_server->isupport, "PREFIX"); + + prefix = prefix == NULL ? NULL : strchr(prefix, ')'); + return prefix == NULL ? "" : prefix+1; +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + if (IS_IRC_SERVER(server)) + server->get_nick_flags = get_nick_flags; +} + +void irc_nicklist_init(void) +{ + signal_add_first("event nick", (SIGNAL_FUNC) event_nick); + signal_add_first("event 352", (SIGNAL_FUNC) event_who); + signal_add_first("event 354", (SIGNAL_FUNC) event_whox_channel_full); + signal_add("silent event who", (SIGNAL_FUNC) event_who); + signal_add("silent event whox", (SIGNAL_FUNC) event_whox_channel_full); + signal_add("silent event whox useraccount", (SIGNAL_FUNC) event_whox_useraccount); + signal_add("silent event whois", (SIGNAL_FUNC) event_whois); + signal_add_first("event 311", (SIGNAL_FUNC) event_whois); + signal_add_first("whois away", (SIGNAL_FUNC) event_whois_away); + signal_add_first("whois oper", (SIGNAL_FUNC) event_whois_ircop); + signal_add_first("event 306", (SIGNAL_FUNC) event_own_away); + signal_add_first("event 305", (SIGNAL_FUNC) event_own_unaway); + signal_add_first("event 353", (SIGNAL_FUNC) event_names_list); + signal_add_first("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_add_first("event 432", (SIGNAL_FUNC) event_nick_invalid); + signal_add_first("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_add_first("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_add_first("event 302", (SIGNAL_FUNC) event_userhost); + signal_add_first("event away", (SIGNAL_FUNC) event_away_notify); + signal_add("userhost event", (SIGNAL_FUNC) event_userhost); + signal_add("event setname", (SIGNAL_FUNC) event_setname); + signal_add("user mode changed", (SIGNAL_FUNC) sig_usermode); + signal_add("server connected", (SIGNAL_FUNC) sig_connected); +} + +void irc_nicklist_deinit(void) +{ + signal_remove("event nick", (SIGNAL_FUNC) event_nick); + signal_remove("event 352", (SIGNAL_FUNC) event_who); + signal_remove("event 354", (SIGNAL_FUNC) event_whox_channel_full); + signal_remove("silent event who", (SIGNAL_FUNC) event_who); + signal_remove("silent event whox", (SIGNAL_FUNC) event_whox_channel_full); + signal_remove("silent event whox useraccount", (SIGNAL_FUNC) event_whox_useraccount); + signal_remove("silent event whois", (SIGNAL_FUNC) event_whois); + signal_remove("event 311", (SIGNAL_FUNC) event_whois); + signal_remove("whois away", (SIGNAL_FUNC) event_whois_away); + signal_remove("whois oper", (SIGNAL_FUNC) event_whois_ircop); + signal_remove("event 306", (SIGNAL_FUNC) event_own_away); + signal_remove("event 305", (SIGNAL_FUNC) event_own_unaway); + signal_remove("event 353", (SIGNAL_FUNC) event_names_list); + signal_remove("event 366", (SIGNAL_FUNC) event_end_of_names); + signal_remove("event 432", (SIGNAL_FUNC) event_nick_invalid); + signal_remove("event 433", (SIGNAL_FUNC) event_nick_in_use); + signal_remove("event 437", (SIGNAL_FUNC) event_target_unavailable); + signal_remove("event 302", (SIGNAL_FUNC) event_userhost); + signal_remove("event away", (SIGNAL_FUNC) event_away_notify); + signal_remove("userhost event", (SIGNAL_FUNC) event_userhost); + signal_remove("event setname", (SIGNAL_FUNC) event_setname); + signal_remove("user mode changed", (SIGNAL_FUNC) sig_usermode); + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); +} diff --git a/src/irc/core/irc-nicklist.h b/src/irc/core/irc-nicklist.h new file mode 100644 index 0000000..7ac3176 --- /dev/null +++ b/src/irc/core/irc-nicklist.h @@ -0,0 +1,20 @@ +#ifndef IRSSI_IRC_CORE_IRC_NICKLIST_H +#define IRSSI_IRC_CORE_IRC_NICKLIST_H + +#include <irssi/src/core/nicklist.h> + +/* Add new nick to list */ +NICK_REC *irc_nicklist_insert(IRC_CHANNEL_REC *channel, const char *nick, + int op, int halfop, int voice, int send_massjoin, + const char *prefixes); + +int irc_nickcmp_rfc1459(const char *, const char *); +int irc_nickcmp_ascii(const char *, const char *); + +void irc_nicklist_init(void); +void irc_nicklist_deinit(void); + +#define to_rfc1459(x) ((x) >= 65 && (x) <= 94 ? (x) + 32 : (x)) +#define to_ascii(x) ((x) >= 65 && (x) <= 90 ? (x) + 32 : (x)) + +#endif diff --git a/src/irc/core/irc-queries.c b/src/irc/core/irc-queries.c new file mode 100644 index 0000000..9c7eb6a --- /dev/null +++ b/src/irc/core/irc-queries.c @@ -0,0 +1,116 @@ +/* + irc-queries.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-nicklist.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-queries.h> + +QUERY_REC *irc_query_create(const char *server_tag, + const char *nick, int automatic) +{ + QUERY_REC *rec; + + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new0(QUERY_REC, 1); + rec->chat_type = IRC_PROTOCOL; + rec->name = g_strdup(nick); + rec->server_tag = g_strdup(server_tag); + query_init(rec, automatic); + return rec; +} + +QUERY_REC *irc_query_find(IRC_SERVER_REC *server, const char *nick) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (server->nick_comp_func(rec->name, nick) == 0) + return rec; + } + + return NULL; +} + +static void check_query_changes(IRC_SERVER_REC *server, const char *nick, + const char *address, const char *target) +{ + QUERY_REC *query; + + if (server_ischannel(SERVER(server), target)) + return; + + query = irc_query_find(server, nick); + if (query == NULL) + return; + + if (g_strcmp0(query->name, nick) != 0) { + /* upper/lowercase chars in nick changed */ + query_change_nick(query, nick); + } + + if (address != NULL && (query->address == NULL || + g_strcmp0(query->address, address) != 0)) { + /* host changed */ + query_change_address(query, address); + } +} + +static void ctcp_action(IRC_SERVER_REC *server, const char *msg, + const char *nick, const char *address, + const char *target) +{ + check_query_changes(server, nick, address, target); +} + +static void event_nick(SERVER_REC *server, const char *data, + const char *orignick) +{ + QUERY_REC *query; + char *params, *nick; + + query = query_find(server, orignick); + if (query != NULL) { + params = event_get_params(data, 1, &nick); + if (g_strcmp0(query->name, nick) != 0) + query_change_nick(query, nick); + g_free(params); + } +} + +void irc_queries_init(void) +{ + signal_add_last("ctcp action", (SIGNAL_FUNC) ctcp_action); + signal_add("event nick", (SIGNAL_FUNC) event_nick); +} + +void irc_queries_deinit(void) +{ + signal_remove("ctcp action", (SIGNAL_FUNC) ctcp_action); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); +} diff --git a/src/irc/core/irc-queries.h b/src/irc/core/irc-queries.h new file mode 100644 index 0000000..f6680ed --- /dev/null +++ b/src/irc/core/irc-queries.h @@ -0,0 +1,22 @@ +#ifndef IRSSI_IRC_CORE_IRC_QUERIES_H +#define IRSSI_IRC_CORE_IRC_QUERIES_H + +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/queries.h> + +/* Returns IRC_QUERY_REC if it's IRC query, NULL if it isn't. */ +#define IRC_QUERY(query) \ + PROTO_CHECK_CAST(QUERY(query), QUERY_REC, chat_type, "IRC") + +#define IS_IRC_QUERY(query) \ + (IRC_QUERY(query) ? TRUE : FALSE) + +void irc_queries_init(void); +void irc_queries_deinit(void); + +QUERY_REC *irc_query_find(IRC_SERVER_REC *server, const char *nick); + +QUERY_REC *irc_query_create(const char *server_tag, + const char *nick, int automatic); + +#endif diff --git a/src/irc/core/irc-servers-reconnect.c b/src/irc/core/irc-servers-reconnect.c new file mode 100644 index 0000000..e18a5a9 --- /dev/null +++ b/src/irc/core/irc-servers-reconnect.c @@ -0,0 +1,128 @@ +/* + servers-reconnect.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/signals.h> + +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/core/settings.h> + +static void sig_server_connect_copy(SERVER_CONNECT_REC **dest, + IRC_SERVER_CONNECT_REC *src) +{ + IRC_SERVER_CONNECT_REC *rec; + + g_return_if_fail(dest != NULL); + if (!IS_IRC_SERVER_CONNECT(src)) + return; + + rec = g_new0(IRC_SERVER_CONNECT_REC, 1); + rec->chat_type = IRC_PROTOCOL; + rec->max_cmds_at_once = src->max_cmds_at_once; + rec->cmd_queue_speed = src->cmd_queue_speed; + rec->max_query_chans = src->max_query_chans; + rec->max_kicks = src->max_kicks; + rec->max_modes = src->max_modes; + rec->max_msgs = src->max_msgs; + rec->max_whois = src->max_whois; + rec->usermode = g_strdup(src->usermode); + rec->alternate_nick = g_strdup(src->alternate_nick); + rec->sasl_mechanism = src->sasl_mechanism; + rec->sasl_username = g_strdup(src->sasl_username); + rec->sasl_password = g_strdup(src->sasl_password); + rec->disallow_starttls = src->disallow_starttls; + rec->starttls = src->starttls; + rec->no_cap = src->no_cap; + *dest = (SERVER_CONNECT_REC *) rec; +} + +static void sig_server_reconnect_save_status(IRC_SERVER_CONNECT_REC *conn, + IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER_CONNECT(conn) || !IS_IRC_SERVER(server) || + !server->connected) + return; + + g_free_not_null(conn->channels); + conn->channels = + irc_server_get_channels(server, settings_get_choice("rejoin_channels_on_reconnect")); + + g_free_not_null(conn->usermode); + conn->usermode = g_strdup(server->wanted_usermode); +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server) || !server->connrec->reconnection) + return; + + if (server->connrec->away_reason != NULL) + irc_server_send_away(server, server->connrec->away_reason); +} + +static void event_nick_collision(IRC_SERVER_REC *server, const char *data) +{ + time_t new_connect; + + if (!IS_IRC_SERVER(server)) + return; + + /* after server kills us because of nick collision, we want to + connect back immediately. but no matter how hard they kill us, + don't connect to the server more than once in every 10 seconds. */ + + new_connect = server->connect_time+10 - + settings_get_time("server_reconnect_time")/1000; + if (server->connect_time > new_connect) + server->connect_time = new_connect; + + server->nick_collision = TRUE; +} + +static void event_kill(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + if (addr != NULL && !server->nick_collision) { + /* don't reconnect if we were killed by an oper (not server) */ + server->no_reconnect = TRUE; + } +} + +void irc_servers_reconnect_init(void) +{ + signal_add("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy); + signal_add("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status); + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + signal_add("event 436", (SIGNAL_FUNC) event_nick_collision); + signal_add("event kill", (SIGNAL_FUNC) event_kill); +} + +void irc_servers_reconnect_deinit(void) +{ + signal_remove("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy); + signal_remove("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status); + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + signal_remove("event 436", (SIGNAL_FUNC) event_nick_collision); + signal_remove("event kill", (SIGNAL_FUNC) event_kill); +} diff --git a/src/irc/core/irc-servers-setup.c b/src/irc/core/irc-servers-setup.c new file mode 100644 index 0000000..c1603a4 --- /dev/null +++ b/src/irc/core/irc-servers-setup.c @@ -0,0 +1,255 @@ +/* + irc-servers-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/servers-setup.h> +#include <irssi/src/lib-config/iconfig.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-chatnets.h> +#include <irssi/src/irc/core/irc-servers-setup.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/sasl.h> + +/* Fill information to connection from server setup record */ +static void sig_server_setup_fill_reconn(IRC_SERVER_CONNECT_REC *conn, + IRC_SERVER_SETUP_REC *sserver) +{ + if (!IS_IRC_SERVER_CONNECT(conn) || + !IS_IRC_SERVER_SETUP(sserver)) + return; + + if (sserver->cmd_queue_speed > 0) + conn->cmd_queue_speed = sserver->cmd_queue_speed; + if (sserver->max_cmds_at_once > 0) + conn->max_cmds_at_once = sserver->max_cmds_at_once; + if (sserver->max_query_chans > 0) + conn->max_query_chans = sserver->max_query_chans; + if (sserver->starttls == STARTTLS_DISALLOW) + conn->disallow_starttls = 1; + else if (sserver->starttls == STARTTLS_ENABLED) + conn->starttls = 1; + if (sserver->no_cap) + conn->no_cap = 1; +} + +static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn, GHashTable *optlist) +{ + const char *value; + + if (!IS_IRC_SERVER_CONNECT(conn)) + return; + + value = settings_get_str("alternate_nick"); + conn->alternate_nick = (value != NULL && *value != '\0') ? + g_strdup(value) : NULL; + + value = settings_get_str("usermode"); + conn->usermode = (value != NULL && *value != '\0') ? + g_strdup(value) : NULL; +} + +static void sig_server_setup_fill_optlist(IRC_SERVER_CONNECT_REC *conn, GHashTable *optlist) +{ + if (!IS_IRC_SERVER_CONNECT(conn)) + return; + + if (g_hash_table_lookup(optlist, "starttls") != NULL) { + conn->starttls = 1; + conn->use_tls = 0; + } else if (g_hash_table_lookup(optlist, "disallow_starttls") != NULL) { + conn->disallow_starttls = 1; + } + if (g_hash_table_lookup(optlist, "nocap")) + conn->no_cap = 1; + if (g_hash_table_lookup(optlist, "cap")) + conn->no_cap = 0; +} + +static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, + IRC_CHATNET_REC *ircnet) +{ + if (!IS_IRC_SERVER_CONNECT(conn)) + return; + g_return_if_fail(IS_IRCNET(ircnet)); + + if (ircnet->alternate_nick != NULL) { + g_free_and_null(conn->alternate_nick); + conn->alternate_nick = g_strdup(ircnet->alternate_nick); + } + if (ircnet->usermode != NULL) { + g_free_and_null(conn->usermode); + conn->usermode = g_strdup(ircnet->usermode); + } + + if (ircnet->max_kicks > 0) conn->max_kicks = ircnet->max_kicks; + if (ircnet->max_msgs > 0) conn->max_msgs = ircnet->max_msgs; + if (ircnet->max_modes > 0) conn->max_modes = ircnet->max_modes; + if (ircnet->max_whois > 0) conn->max_whois = ircnet->max_whois; + + if (ircnet->max_cmds_at_once > 0) + conn->max_cmds_at_once = ircnet->max_cmds_at_once; + if (ircnet->cmd_queue_speed > 0) + conn->cmd_queue_speed = ircnet->cmd_queue_speed; + if (ircnet->max_query_chans > 0) + conn->max_query_chans = ircnet->max_query_chans; + + /* Validate the SASL parameters filled by sig_chatnet_read() or cmd_network_add */ + conn->sasl_mechanism = SASL_MECHANISM_NONE; + conn->sasl_username = NULL; + conn->sasl_password = NULL; + + if (ircnet->sasl_mechanism != NULL) { + if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "plain")) { + /* The PLAIN method needs both the username and the password */ + if (ircnet->sasl_username != NULL && *ircnet->sasl_username && + ircnet->sasl_password != NULL && *ircnet->sasl_password) { + conn->sasl_mechanism = SASL_MECHANISM_PLAIN; + conn->sasl_username = g_strdup(ircnet->sasl_username); + conn->sasl_password = g_strdup(ircnet->sasl_password); + } else + g_warning("The fields sasl_username and sasl_password are either missing or empty"); + } + else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) { + conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL; + } + else + g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism); + } +} + +static void init_userinfo(void) +{ + unsigned int changed; + const char *set, *nick, *user_name, *str; + + changed = 0; + /* check if nick/username/realname wasn't read from setup.. */ + set = settings_get_str("real_name"); + if (set == NULL || *set == '\0') { + str = g_getenv("IRCNAME"); + settings_set_str("real_name", + str != NULL ? str : g_get_real_name()); + changed |= USER_SETTINGS_REAL_NAME; + } + + /* username */ + user_name = settings_get_str("user_name"); + if (user_name == NULL || *user_name == '\0') { + str = g_getenv("IRCUSER"); + settings_set_str("user_name", + str != NULL ? str : g_get_user_name()); + + user_name = settings_get_str("user_name"); + changed |= USER_SETTINGS_USER_NAME; + } + + /* nick */ + nick = settings_get_str("nick"); + if (nick == NULL || *nick == '\0') { + str = g_getenv("IRCNICK"); + settings_set_str("nick", str != NULL ? str : user_name); + + nick = settings_get_str("nick"); + changed |= USER_SETTINGS_NICK; + } + + /* host name */ + set = settings_get_str("hostname"); + if (set == NULL || *set == '\0') { + str = g_getenv("IRCHOST"); + if (str != NULL) { + settings_set_str("hostname", str); + changed |= USER_SETTINGS_HOSTNAME; + } + } + + signal_emit("irssi init userinfo changed", 1, GUINT_TO_POINTER(changed)); +} + +static void sig_server_setup_read(IRC_SERVER_SETUP_REC *rec, CONFIG_NODE *node) +{ + int starttls; + g_return_if_fail(rec != NULL); + g_return_if_fail(node != NULL); + + if (!IS_IRC_SERVER_SETUP(rec)) + return; + + rec->max_cmds_at_once = config_node_get_int(node, "cmds_max_at_once", 0); + rec->cmd_queue_speed = config_node_get_int(node, "cmd_queue_speed", 0); + rec->max_query_chans = config_node_get_int(node, "max_query_chans", 0); + starttls = config_node_get_bool(node, "starttls", -1); + rec->starttls = starttls == -1 ? STARTTLS_NOTSET : + starttls == 0 ? STARTTLS_DISALLOW : + STARTTLS_ENABLED; + if (rec->starttls == STARTTLS_ENABLED) { + rec->use_tls = 0; + } + rec->no_cap = config_node_get_bool(node, "no_cap", FALSE); +} + +static void sig_server_setup_saved(IRC_SERVER_SETUP_REC *rec, + CONFIG_NODE *node) +{ + if (!IS_IRC_SERVER_SETUP(rec)) + return; + + if (rec->max_cmds_at_once > 0) + iconfig_node_set_int(node, "cmds_max_at_once", rec->max_cmds_at_once); + if (rec->cmd_queue_speed > 0) + iconfig_node_set_int(node, "cmd_queue_speed", rec->cmd_queue_speed); + if (rec->max_query_chans > 0) + iconfig_node_set_int(node, "max_query_chans", rec->max_query_chans); + if (rec->starttls == STARTTLS_DISALLOW) + iconfig_node_set_bool(node, "starttls", FALSE); + else if (rec->starttls == STARTTLS_ENABLED) + iconfig_node_set_bool(node, "starttls", TRUE); + else if (rec->starttls == STARTTLS_NOTSET) + iconfig_node_set_str(node, "starttls", NULL); + if (rec->no_cap) + iconfig_node_set_bool(node, "no_cap", TRUE); +} + +void irc_servers_setup_init(void) +{ + settings_add_bool("server", "skip_motd", FALSE); + settings_add_str("server", "alternate_nick", ""); + + init_userinfo(); + signal_add("server setup fill reconn", (SIGNAL_FUNC) sig_server_setup_fill_reconn); + signal_add("server setup fill connect", (SIGNAL_FUNC) sig_server_setup_fill_connect); + signal_add("server setup fill chatnet", (SIGNAL_FUNC) sig_server_setup_fill_chatnet); + signal_add("server setup fill optlist", (SIGNAL_FUNC) sig_server_setup_fill_optlist); + signal_add("server setup read", (SIGNAL_FUNC) sig_server_setup_read); + signal_add("server setup saved", (SIGNAL_FUNC) sig_server_setup_saved); +} + +void irc_servers_setup_deinit(void) +{ + signal_remove("server setup fill reconn", (SIGNAL_FUNC) sig_server_setup_fill_reconn); + signal_remove("server setup fill connect", (SIGNAL_FUNC) sig_server_setup_fill_connect); + signal_remove("server setup fill chatnet", (SIGNAL_FUNC) sig_server_setup_fill_chatnet); + signal_remove("server setup fill optlist", (SIGNAL_FUNC) sig_server_setup_fill_optlist); + signal_remove("server setup read", (SIGNAL_FUNC) sig_server_setup_read); + signal_remove("server setup saved", (SIGNAL_FUNC) sig_server_setup_saved); +} diff --git a/src/irc/core/irc-servers-setup.h b/src/irc/core/irc-servers-setup.h new file mode 100644 index 0000000..7b11a2f --- /dev/null +++ b/src/irc/core/irc-servers-setup.h @@ -0,0 +1,34 @@ +#ifndef IRSSI_IRC_CORE_IRC_SERVERS_SETUP_H +#define IRSSI_IRC_CORE_IRC_SERVERS_SETUP_H + +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/servers-setup.h> + +#define IRC_SERVER_SETUP(server) \ + PROTO_CHECK_CAST(SERVER_SETUP(server), IRC_SERVER_SETUP_REC, \ + chat_type, "IRC") + +#define IS_IRC_SERVER_SETUP(server) \ + (IRC_SERVER_SETUP(server) ? TRUE : FALSE) + +enum { + STARTTLS_DISALLOW = -1, /* */ + STARTTLS_NOTSET = 0, + STARTTLS_ENABLED = 1 +}; + +typedef struct { +#include <irssi/src/core/server-setup-rec.h> + + /* override the default if > 0 */ + int max_cmds_at_once; + int cmd_queue_speed; + int max_query_chans; + int starttls; + int no_cap : 1; +} IRC_SERVER_SETUP_REC; + +void irc_servers_setup_init(void); +void irc_servers_setup_deinit(void); + +#endif diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c new file mode 100644 index 0000000..29f63c2 --- /dev/null +++ b/src/irc/core/irc-servers.c @@ -0,0 +1,1288 @@ +/* + irc-server.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" + +#include <irssi/src/core/misc.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/rawlog.h> +#include <irssi/src/core/signals.h> + +#include <irssi/src/core/channels.h> +#include <irssi/src/core/queries.h> + +#include <irssi/src/irc/core/irc-nicklist.h> +#include <irssi/src/irc/core/irc-queries.h> +#include <irssi/src/irc/core/irc-servers-setup.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-cap.h> +#include <irssi/src/irc/core/sasl.h> + +#include <irssi/src/core/channels-setup.h> +#include <irssi/src/irc/core/channel-rejoin.h> +#include <irssi/src/irc/core/servers-idle.h> +#include <irssi/src/core/servers-reconnect.h> +#include <irssi/src/irc/core/servers-redirect.h> +#include <irssi/src/irc/core/modes.h> + +#include <irssi/src/core/settings.h> +#include <irssi/src/core/recode.h> + +#define DEFAULT_MAX_KICKS 1 +#define DEFAULT_MAX_MODES 3 +#define DEFAULT_MAX_WHOIS 4 +#define DEFAULT_MAX_MSGS 1 + +#define DEFAULT_USER_MODE "+i" +#define DEFAULT_CMD_QUEUE_SPEED "2200msec" +#define DEFAULT_CMDS_MAX_AT_ONCE 5 +#define DEFAULT_MAX_QUERY_CHANS 1 /* more and more IRC networks are using stupid ircds.. */ + +void irc_servers_reconnect_init(void); +void irc_servers_reconnect_deinit(void); + +static int cmd_tag; + +static int isnickflag_func(SERVER_REC *server, char flag) +{ + IRC_SERVER_REC *irc_server = (IRC_SERVER_REC *) server; + + return isnickflag(irc_server, flag); +} + +static int ischannel_func(SERVER_REC *server, const char *data) +{ + IRC_SERVER_REC *irc_server = (IRC_SERVER_REC *) server; + char *chantypes, *statusmsg; + + g_return_val_if_fail(data != NULL, FALSE); + + /* empty string is no channel */ + if (*data == '\0') + return FALSE; + + chantypes = g_hash_table_lookup(irc_server->isupport, "CHANTYPES"); + if (chantypes == NULL) + chantypes = "#&!+"; /* normal, local, secure, modeless */ + + statusmsg = g_hash_table_lookup(irc_server->isupport, "STATUSMSG"); + if (statusmsg == NULL && strchr(chantypes, '@') == NULL) + statusmsg = "@"; + + if (statusmsg != NULL) + data += strspn(data, statusmsg); + + /* strchr(3) considers the trailing NUL as part of the string, make sure + * we didn't advance too much. */ + return *data != '\0' && strchr(chantypes, *data) != NULL; +} + +static char **split_line(const SERVER_REC *server, const char *line, + const char *target, int len) +{ + const char *start = settings_get_str("split_line_start"); + const char *end = settings_get_str("split_line_end"); + gboolean onspace = settings_get_bool("split_line_on_space"); + char *recoded_start = recode_out(server, start, target); + char *recoded_end = recode_out(server, end, target); + char **lines; + int i; + + /* + * Having the same length limit on all lines will make the first line + * shorter than necessary if `split_line_start' is set, but it makes + * the code much simpler. It's worth it. + */ + len -= strlen(recoded_start) + strlen(recoded_end); + g_warn_if_fail(len > 0); + if (len <= 0) { + /* There is no room for anything. */ + g_free(recoded_start); + g_free(recoded_end); + lines = g_new(char *, 1); + lines[0] = NULL; + return lines; + } + + lines = recode_split(server, line, target, len, onspace); + for (i = 0; lines[i] != NULL; i++) { + if (i != 0 && *start != '\0') { + /* Not the first line. */ + char *tmp = lines[i]; + lines[i] = g_strconcat(start, tmp, NULL); + g_free(tmp); + } + if (lines[i + 1] != NULL && *end != '\0') { + /* Not the last line. */ + char *tmp = lines[i]; + + if (lines[i + 2] == NULL) { + /* Next to last line. Check if we have room + * to append the last line to the current line, + * to avoid an unnecessary line break. + */ + char *recoded_l = recode_out(server, + lines[i+1], + target); + if (strlen(recoded_l) <= strlen(recoded_end)) { + lines[i] = g_strconcat(tmp, lines[i+1], + NULL); + g_free_and_null(lines[i+1]); + lines = g_renew(char *, lines, i + 2); + + g_free(recoded_l); + g_free(tmp); + break; + } + g_free(recoded_l); + } + + lines[i] = g_strconcat(tmp, end, NULL); + g_free(tmp); + } + } + + g_free(recoded_start); + g_free(recoded_end); + return lines; +} + +static void send_message(SERVER_REC *server, const char *target, + const char *msg, int target_type) +{ + IRC_SERVER_REC *ircserver; + CHANNEL_REC *channel; + char *str; + char *recoded; + + ircserver = IRC_SERVER(server); + g_return_if_fail(ircserver != NULL); + g_return_if_fail(target != NULL); + g_return_if_fail(msg != NULL); + + if (*target == '!') { + /* !chan -> !12345chan */ + channel = channel_find(server, target); + if (channel != NULL && + g_ascii_strcasecmp(channel->name, target) != 0) + target = channel->name; + } + + recoded = recode_out(SERVER(server), msg, target); + str = g_strdup_printf("PRIVMSG %s :%s", target, recoded); + irc_send_cmd_split(ircserver, str, 2, ircserver->max_msgs_in_cmd); + g_free(str); + g_free(recoded); +} + +static char **split_message(SERVER_REC *server, const char *target, + const char *msg) +{ + IRC_SERVER_REC *ircserver = IRC_SERVER(server); + + g_return_val_if_fail(ircserver != NULL, NULL); + g_return_val_if_fail(target != NULL, NULL); + g_return_val_if_fail(msg != NULL, NULL); + + /* length calculation shamelessly stolen from splitlong_safe.pl */ + return split_line(SERVER(server), msg, target, + ircserver->max_message_len - strlen(":! PRIVMSG :") - + strlen(ircserver->nick) - MAX_USERHOST_LEN - + strlen(target)); +} + +static void server_init_2(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + char *address, *ptr, *username, *cmd; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + if (conn->password != NULL && *conn->password != '\0') { + /* send password */ + cmd = g_strdup_printf("PASS %s", conn->password); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } + + /* send nick */ + cmd = g_strdup_printf("NICK %s", conn->nick); + irc_send_cmd_now(server, cmd); + g_free(cmd); + + /* send user/realname */ + address = server->connrec->address; + ptr = strrchr(address, ':'); + if (ptr != NULL) { + /* IPv6 address .. doesn't work here, use the string after + the last : char */ + address = ptr + 1; + if (*address == '\0') + address = "x"; + } + + username = g_strdup(conn->username); + ptr = strchr(username, ' '); + if (ptr != NULL) + *ptr = '\0'; + + cmd = g_strdup_printf("USER %s %s %s :%s", username, username, address, conn->realname); + irc_send_cmd_now(server, cmd); + g_free(cmd); + g_free(username); + + if (conn->proxy != NULL && conn->proxy_string_after != NULL) { + cmd = g_strdup_printf(conn->proxy_string_after, conn->address, conn->port); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } +} + +static void server_init_1(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + char *cmd; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + if (conn->proxy != NULL && conn->proxy_password != NULL && *conn->proxy_password != '\0') { + cmd = g_strdup_printf("PASS %s", conn->proxy_password); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } + + if (conn->proxy != NULL && conn->proxy_string != NULL) { + cmd = g_strdup_printf(conn->proxy_string, conn->address, conn->port); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } + + if (conn->sasl_mechanism != SASL_MECHANISM_NONE) { + irc_cap_toggle(server, CAP_SASL, TRUE); + } + + irc_cap_toggle(server, CAP_MULTI_PREFIX, TRUE); + irc_cap_toggle(server, CAP_EXTENDED_JOIN, TRUE); + irc_cap_toggle(server, CAP_SETNAME, TRUE); + irc_cap_toggle(server, CAP_INVITE_NOTIFY, TRUE); + irc_cap_toggle(server, CAP_AWAY_NOTIFY, TRUE); + irc_cap_toggle(server, CAP_CHGHOST, TRUE); + irc_cap_toggle(server, CAP_ACCOUNT_NOTIFY, TRUE); + irc_cap_toggle(server, CAP_SELF_MESSAGE, TRUE); + irc_cap_toggle(server, CAP_SERVER_TIME, TRUE); + if (!conn->use_tls && (conn->starttls || !conn->disallow_starttls)) { + irc_cap_toggle(server, CAP_STARTTLS, TRUE); + } + + /* set the standards */ + if (!g_hash_table_contains(server->isupport, "CHANMODES")) + g_hash_table_insert(server->isupport, g_strdup("CHANMODES"), + g_strdup("beI,k,l,imnpst")); + if (!g_hash_table_contains(server->isupport, "PREFIX")) + g_hash_table_insert(server->isupport, g_strdup("PREFIX"), g_strdup("(ohv)@%+")); + + server->cmdcount = 0; + + /* prevent the queue from sending too early, we have a max cut off of 120 secs */ + /* this will reset to 1 sec after we get the 001 event */ + server->wait_cmd = g_get_real_time(); + server->wait_cmd += 120 * G_USEC_PER_SEC; + + if (!conn->no_cap) { + signal_emit("server waiting cap ls", 2, server, CAP_LS_VERSION); + irc_send_cmd_now(server, "CAP LS " CAP_LS_VERSION); + /* to detect non-CAP servers, send this bogus join */ + /* the : will make INSPIRCD respond with 451 instead of 461, too */ + irc_send_cmd_now(server, "JOIN :"); + } + if (conn->starttls) + irc_server_send_starttls(server); + else if (conn->no_cap) + server_init_2(server); +} + +static void init_ssl_loop(IRC_SERVER_REC *server, GIOChannel *handle) +{ + int error; + server->connrec->starttls = 1; + + if (server->starttls_tag) { + g_source_remove(server->starttls_tag); + server->starttls_tag = 0; + } + + error = irssi_ssl_handshake(handle); + if (error == -1) { + server->connection_lost = TRUE; + server_disconnect((SERVER_REC *) server); + return; + } + if (error & 1) { /* wait */ + server->starttls_tag = + i_input_add(handle, error == 1 ? I_INPUT_READ : I_INPUT_WRITE, + (GInputFunction) init_ssl_loop, server); + return; + } + /* continue */ + rawlog_redirect(server->rawlog, "Now talking encrypted"); + signal_emit("server connection switched", 1, server); + if (!server->cap_supported) { + server_init_2(server); + } else { + signal_emit("server cap continue", 1, server); + } + + if (settings_get_bool("starttls_sts")) { + IRC_SERVER_SETUP_REC *ssetup = IRC_SERVER_SETUP(server_setup_find( + server->connrec->address, server->connrec->port, server->connrec->chatnet)); + if (ssetup != NULL) { + ssetup->starttls = STARTTLS_ENABLED; + server_setup_add((SERVER_SETUP_REC *) ssetup); + } + } +} + +#include <irssi/src/core/line-split.h> +void irc_server_send_starttls(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_warning("[%s] Now attempting STARTTLS", server->tag); + irc_send_cmd_now(server, "STARTTLS"); +} + +static void event_starttls(IRC_SERVER_REC *server, const char *data) +{ + GIOChannel *ssl_handle; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->handle->readbuffer != NULL && + !line_split_is_empty(server->handle->readbuffer)) { + char *str; + line_split("", -1, &str, &server->handle->readbuffer); + } + ssl_handle = net_start_ssl((SERVER_REC *) server); + if (ssl_handle != NULL) { + g_source_remove(server->readtag); + server->readtag = -1; + server->handle->handle = ssl_handle; + init_ssl_loop(server, server->handle->handle); + } else { + g_warning("net_start_ssl failed"); + } +} + +static void event_registerfirst(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->connected) + return; + + if (!server->cap_supported && !server->connrec->starttls) + server_init_2(server); +} + +static void event_capend(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->connected) + return; + + server_init_2(server); +} + +SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) +{ + IRC_SERVER_CONNECT_REC *ircconn; + IRC_SERVER_REC *server; + + g_return_val_if_fail(IS_IRC_SERVER_CONNECT(conn), NULL); + if (conn->address == NULL || *conn->address == '\0') return NULL; + if (conn->nick == NULL || *conn->nick == '\0') return NULL; + + server = g_new0(IRC_SERVER_REC, 1); + server->chat_type = IRC_PROTOCOL; + + ircconn = (IRC_SERVER_CONNECT_REC *) conn; + server->connrec = ircconn; + server_connect_ref(conn); + + if (server->connrec->port <= 0) { + server->connrec->port = + server->connrec->use_tls ? 6697 : 6667; + } + + server->max_message_len = MAX_IRC_MESSAGE_LEN; + + server->cmd_queue_speed = ircconn->cmd_queue_speed > 0 ? + ircconn->cmd_queue_speed : settings_get_time("cmd_queue_speed"); + server->max_cmds_at_once = ircconn->max_cmds_at_once > 0 ? + ircconn->max_cmds_at_once : settings_get_int("cmds_max_at_once"); + server->max_query_chans = ircconn->max_query_chans > 0 ? + ircconn->max_query_chans : DEFAULT_MAX_QUERY_CHANS; + + server->max_kicks_in_cmd = ircconn->max_kicks > 0 ? + ircconn->max_kicks : DEFAULT_MAX_KICKS; + server->max_modes_in_cmd = ircconn->max_modes > 0 ? + ircconn->max_modes : DEFAULT_MAX_MODES; + server->max_whois_in_cmd = ircconn->max_whois > 0 ? + ircconn->max_whois : DEFAULT_MAX_WHOIS; + server->max_msgs_in_cmd = ircconn->max_msgs > 0 ? + ircconn->max_msgs : DEFAULT_MAX_MSGS; + server->connrec->use_tls = conn->use_tls; + + modes_server_init(server); + + server->isupport = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); + + server->isnickflag = isnickflag_func; + server->ischannel = ischannel_func; + server->split_message = split_message; + server->send_message = send_message; + server->query_find_func = (QUERY_REC * (*) (SERVER_REC *, const char *) ) irc_query_find; + server->nick_comp_func = irc_nickcmp_rfc1459; + + server_connect_init((SERVER_REC *) server); + return (SERVER_REC *) server; +} + +void irc_server_connect(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (server->connrec->connect_handle != NULL) { + /* an existing handle from upgrade */ + IRC_SERVER_CONNECT_REC *conn; + int tls_disconnect; + + conn = ((IRC_SERVER_REC *) server)->connrec; + tls_disconnect = conn->use_tls || conn->starttls; + + if (tls_disconnect) { + /* we cannot use it, it is encrypted. force a reconnect */ + g_io_channel_unref(conn->connect_handle); + conn->connect_handle = NULL; + server->session_reconnect = FALSE; + server_connect_ref((SERVER_CONNECT_REC *) conn); + server_disconnect(server); + server_connect((SERVER_CONNECT_REC *) conn); + server_connect_unref((SERVER_CONNECT_REC *) conn); + return; + } + } + + if (!server_start_connect(server)) { + server_connect_unref(server->connrec); + g_free(server); + } +} + +/* Returns TRUE if `command' is sent to `target' */ +static int command_has_target(const char *cmd, const char *target) +{ + const char *p; + int len; + + /* just assume the command is in form "<command> <target> <data>" */ + p = strchr(cmd, ' '); + if (p == NULL) return FALSE; + p++; + + len = strlen(target); + return strncmp(p, target, len) == 0 && p[len] == ' '; +} + +/* Purge server output, either all or for specified target */ +void irc_server_purge_output(IRC_SERVER_REC *server, const char *target) +{ + GSList *tmp, *next, *link; + REDIRECT_REC *redirect; + char *cmd; + + if (target != NULL && *target == '\0') + target = NULL; + + for (tmp = server->cmdqueue; tmp != NULL; tmp = next) { + next = tmp->next->next; + cmd = tmp->data; + redirect = tmp->next->data; + + if ((target == NULL || command_has_target(cmd, target)) && + g_ascii_strncasecmp(cmd, "PONG ", 5) != 0) { + /* remove the redirection */ + link = tmp->next; + server->cmdqueue = + g_slist_remove_link(server->cmdqueue, link); + g_slist_free_1(link); + + if (redirect != NULL) + server_redirect_destroy(redirect); + + /* remove the command */ + server->cmdqueue = + g_slist_remove(server->cmdqueue, cmd); + g_free(cmd); + server->cmdcount--; + } + } +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + server->splits = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); + + if (!server->session_reconnect) + server_init_1(server); +} + +static void isupport_destroy_hash(void *key, void *value) +{ + g_free(key); + g_free(value); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + if (server->starttls_tag) { + g_source_remove(server->starttls_tag); + server->starttls_tag = 0; + } +} + +static void sig_destroyed(IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!IS_IRC_SERVER(server)) + return; + + for (tmp = server->cmdqueue; tmp != NULL; tmp = tmp->next->next) { + g_free(tmp->data); + if (tmp->next->data != NULL) + server_redirect_destroy(tmp->next->data); + } + g_slist_free(server->cmdqueue); + server->cmdqueue = NULL; + + i_slist_free_full(server->cap_active, (GDestroyNotify) g_free); + server->cap_active = NULL; + + if (server->cap_supported) { + g_hash_table_destroy(server->cap_supported); + server->cap_supported = NULL; + } + + i_slist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; + + /* was g_free_and_null, but can't use on a GString */ + if (server->sasl_buffer != NULL) { + g_string_free(server->sasl_buffer, TRUE); + server->sasl_buffer = NULL; + } + + /* these are dynamically allocated only if isupport was sent */ + g_hash_table_foreach(server->isupport, + (GHFunc) isupport_destroy_hash, server); + g_hash_table_destroy(server->isupport); + server->isupport = NULL; + + g_free_and_null(server->wanted_usermode); + g_free_and_null(server->real_address); + g_free_and_null(server->usermode); + g_free_and_null(server->userhost); + g_free_and_null(server->last_invite); +} + +static void sig_server_quit(IRC_SERVER_REC *server, const char *msg) +{ + char *str; + char *recoded; + + if (!IS_IRC_SERVER(server) || !server->connected) + return; + + recoded = recode_out(SERVER(server), msg, NULL); + str = g_strdup_printf("QUIT :%s", recoded); + irc_send_cmd_now(server, str); + g_free(str); + g_free(recoded); +} + +void irc_server_send_action(IRC_SERVER_REC *server, const char *target, const char *data) +{ + char *recoded; + + recoded = recode_out(SERVER(server), data, target); + irc_send_cmdv(server, "PRIVMSG %s :\001ACTION %s\001", target, recoded); + g_free(recoded); +} + +char **irc_server_split_action(IRC_SERVER_REC *server, const char *target, + const char *data) +{ + g_return_val_if_fail(server != NULL, NULL); + g_return_val_if_fail(target != NULL, NULL); + g_return_val_if_fail(data != NULL, NULL); + + return split_line(SERVER(server), data, target, + server->max_message_len - strlen(":! PRIVMSG :\001ACTION \001") - + strlen(server->nick) - MAX_USERHOST_LEN - + strlen(target)); +} + +void irc_server_send_away(IRC_SERVER_REC *server, const char *reason) +{ + char *recoded = NULL; + + if (!IS_IRC_SERVER(server)) + return; + + if (*reason != '\0' || server->usermode_away) { + g_free_and_null(server->away_reason); + if (*reason != '\0') { + server->away_reason = g_strdup(reason); + reason = recoded = recode_out(SERVER(server), reason, NULL); + irc_send_cmdv(server, "AWAY :%s", reason); + } else { + irc_send_cmdv(server, "AWAY"); + } + + } + g_free(recoded); +} + +void irc_server_send_data(IRC_SERVER_REC *server, const char *data, int len) +{ + if (net_sendbuffer_send(server->handle, data, len) == -1) { + /* something bad happened */ + server->connection_lost = TRUE; + return; + } + + server->last_cmd = g_get_real_time(); + + /* A bit kludgy way to do the flood protection. In ircnet, there + actually is 1sec / 100 bytes penalty, but we rather want to deal + with the max. 1000 bytes input buffer problem. If we send more + than that with the burst, we'll get excess flooded. */ + if (len < 100 || server->cmd_queue_speed <= 10) + server->wait_cmd = 0; + else { + server->wait_cmd = server->last_cmd; + server->wait_cmd += (2 + len / 100) * G_USEC_PER_SEC; + } +} + +void irc_server_send_and_redirect(IRC_SERVER_REC *server, GString *str, REDIRECT_REC *redirect) +{ + int crlf; + + g_return_if_fail(server != NULL); + g_return_if_fail(str != NULL); + + if (str->len > 2 && str->str[str->len - 2] == '\r') + crlf = 2; + else if (str->len > 1 && str->str[str->len - 1] == '\n') + crlf = 1; + else + crlf = 0; + + if (crlf) + g_string_truncate(str, str->len - crlf); + + signal_emit("server outgoing modify", 3, server, str, crlf); + if (str->len) { + if (crlf == 2) + g_string_append(str, "\r\n"); + else if (crlf == 1) + g_string_append(str, "\n"); + + irc_server_send_data(server, str->str, str->len); + + /* add to rawlog without [CR+]LF */ + if (crlf) + g_string_truncate(str, str->len - crlf); + rawlog_output(server->rawlog, str->str); + server_redirect_command(server, str->str, redirect); + } +} + +static int server_cmd_timeout(IRC_SERVER_REC *server, gint64 now) +{ + REDIRECT_REC *redirect; + GSList *link; + GString *str; + long usecs; + char *cmd; + + if (!IS_IRC_SERVER(server)) + return 0; + + if (server->cmdcount == 0 && server->cmdqueue == NULL) + return 0; + + if (now < server->wait_cmd) + return 1; + + usecs = (now - server->last_cmd) / G_TIME_SPAN_MILLISECOND; + if (usecs < server->cmd_queue_speed) + return 1; + + server->cmdcount--; + if (server->cmdqueue == NULL) + return 1; + + /* get command */ + cmd = server->cmdqueue->data; + redirect = server->cmdqueue->next->data; + + /* send command */ + str = g_string_new(cmd); + irc_server_send_and_redirect(server, str, redirect); + g_string_free(str, TRUE); + + /* remove from queue */ + server->cmdqueue = g_slist_remove(server->cmdqueue, cmd); + g_free(cmd); + + link = server->cmdqueue; + server->cmdqueue = g_slist_remove_link(server->cmdqueue, link); + g_slist_free_1(link); + return 1; +} + +/* check every now and then if there's data to be sent in command buffer */ +static int servers_cmd_timeout(void) +{ + gint64 now; + GSList *tmp; + int keep = 0; + + now = g_get_real_time(); + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + keep |= server_cmd_timeout(tmp->data, now); + } + if (keep) + return 1; + else { + cmd_tag = -1; + return 0; + } +} + +/* Start the timeout for sending data later and decreasing cmdcount again */ +void irc_servers_start_cmd_timeout(void) +{ + if (cmd_tag == -1) + cmd_tag = g_timeout_add(500, (GSourceFunc) servers_cmd_timeout, NULL); +} + +/* Return a string of all channels (and keys, if any have them) in server, + like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ +char *irc_server_get_channels(IRC_SERVER_REC *server, int rejoin_channels_mode) +{ + GSList *tmp; + GString *chans, *keys; + char *ret; + int use_keys; + + g_return_val_if_fail(server != NULL, FALSE); + + /* do we want to rejoin channels in the first place? */ + if (rejoin_channels_mode == REJOIN_CHANNELS_MODE_OFF) + return g_strdup(""); + + chans = g_string_new(NULL); + keys = g_string_new(NULL); + use_keys = FALSE; + + /* get currently joined channels */ + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + CHANNEL_SETUP_REC *setup = channel_setup_find(channel->name, channel->server->connrec->chatnet); + if ((setup != NULL && setup->autojoin && + rejoin_channels_mode == REJOIN_CHANNELS_MODE_AUTO) || + rejoin_channels_mode == REJOIN_CHANNELS_MODE_ON) { + g_string_append_printf(chans, "%s,", channel->name); + g_string_append_printf(keys, "%s,", channel->key == NULL ? "x" : channel->key); + if (channel->key != NULL) + use_keys = TRUE; + } + } + + /* get also the channels that are in rejoin list */ + for (tmp = server->rejoin_channels; tmp != NULL; tmp = tmp->next) { + REJOIN_REC *rec = tmp->data; + CHANNEL_SETUP_REC *setup = channel_setup_find(rec->channel, server->tag); + + if ((setup != NULL && setup->autojoin && + rejoin_channels_mode == REJOIN_CHANNELS_MODE_AUTO) || + rejoin_channels_mode == REJOIN_CHANNELS_MODE_ON) { + g_string_append_printf(chans, "%s,", rec->channel); + g_string_append_printf(keys, "%s,", rec->key == NULL ? "x" : + rec->key); + + if (rec->key != NULL) use_keys = TRUE; + } + } + + if (chans->len > 0) { + g_string_truncate(chans, chans->len-1); + g_string_truncate(keys, keys->len-1); + if (use_keys) g_string_append_printf(chans, " %s", keys->str); + } + + ret = chans->str; + g_string_free(chans, FALSE); + g_string_free(keys, TRUE); + + return ret; +} + +static void event_connected(IRC_SERVER_REC *server, const char *data, const char *from) +{ + char *params, *nick; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 1, &nick); + + if (g_strcmp0(server->nick, nick) != 0) { + /* nick changed unexpectedly .. connected via proxy, etc. */ + g_free(server->nick); + server->nick = g_strdup(nick); + } + + /* set the server address */ + g_free(server->real_address); + server->real_address = from == NULL ? + g_strdup(server->connrec->address) : /* shouldn't happen.. */ + g_strdup(from); + + /* last welcome message found - commands can be sent to server now. */ + server->connected = 1; + server->real_connect_time = time(NULL); + + /* let the queue send now that we are identified */ + server->wait_cmd = g_get_real_time(); + + if (server->connrec->usermode != NULL) { + /* Send the user mode, before the autosendcmd. + * Do not pass this through cmd_mode because it + * is not known whether the resulting MODE message + * (if any) is the initial umode or a reply to this. + */ + irc_send_cmdv(server, "MODE %s %s", server->nick, + server->connrec->usermode); + g_free_not_null(server->wanted_usermode); + server->wanted_usermode = g_strdup(server->connrec->usermode); + } + + signal_emit("event connected", 1, server); + g_free(params); +} + +static void event_server_info(IRC_SERVER_REC *server, const char *data) +{ + char *params, *ircd_version, *usermodes, *chanmodes; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 5, NULL, NULL, &ircd_version, &usermodes, &chanmodes); + + /* check if server understands I and e channel modes */ + if (strchr(chanmodes, 'I') && strchr(chanmodes, 'e')) + server->emode_known = TRUE; + + /* save server version */ + g_free_not_null(server->version); + server->version = g_strdup(ircd_version); + + g_free(params); +} + +static void parse_chanmodes(IRC_SERVER_REC *server, const char *sptr) +{ + mode_func_t *modefuncs[] = { + modes_type_a, + modes_type_b, + modes_type_c, + modes_type_d + }; + char **item, **chanmodes; + int i; + + chanmodes = g_strsplit(sptr, ",", 5); /* ignore extras */ + + for (i = 0, item = chanmodes; *item != NULL && i < 4; item++, i++) { + unsigned char *p = (unsigned char*) *item; + while (*p != '\0') { + server->modes[(int)*p].func = modefuncs[i]; + p++; + } + } + + g_strfreev(chanmodes); +} + +static void parse_prefix(IRC_SERVER_REC *server, const char *sptr) +{ + const char *eptr; + + if (*sptr++ != '(') + return; /* Unknown prefix format */ + + eptr = strchr(sptr, ')'); + if (eptr == NULL) + return; + + eptr++; + while (*sptr != '\0' && *eptr != '\0' && *sptr != ')' && *eptr != ' ') { + server->modes[(int)(unsigned char) *sptr].func = + modes_type_prefix; + server->modes[(int)(unsigned char) *sptr].prefix = *eptr; + server->prefix[(int)(unsigned char) *eptr] = *sptr; + sptr++; eptr++; + } +} + + +static void event_isupport(IRC_SERVER_REC *server, const char *data) +{ + char **item, *sptr, *eptr; + char **isupport; + gpointer key, value; + + g_return_if_fail(server != NULL); + + server->isupport_sent = TRUE; + + sptr = strchr(data, ' '); + if (sptr == NULL) + return; + sptr++; + + isupport = g_strsplit(sptr, " ", -1); + + for(item = isupport; *item != NULL; item++) { + int removed = FALSE; + + if (**item == '\0') + continue; + + if (**item == ':') + break; + + sptr = strchr(*item, '='); + if (sptr != NULL) { + *sptr = '\0'; + sptr++; + } + + eptr = *item; + if(*eptr == '-') { + removed = TRUE; + eptr++; + } + + key = value = NULL; + if (!g_hash_table_lookup_extended(server->isupport, eptr, + &key, &value) && removed) + continue; + + g_hash_table_remove(server->isupport, eptr); + if (!removed) { + g_hash_table_insert(server->isupport, g_strdup(eptr), + g_strdup(sptr != NULL ? sptr : "")); + } + + g_free(key); + g_free(value); + } + g_strfreev(isupport); + irc_server_init_isupport(server); + +} + +static void event_motd(IRC_SERVER_REC *server, const char *data, const char *from) +{ + if (server->connected) + return; + + /* Stupid broken piece of shit ircd didn't send us 001, + you'd think they could at least get that right?? + But no, then I'll have to go and add these idiotic kludges + to make them work. Maybe I should instead get the users of these + servers to complain about it to their admins. + + Oh, and looks like it also doesn't answer anything to PINGs, + disable lag checking. */ + server->disable_lag = TRUE; + event_connected(server, data, from); +} + +static void event_end_of_motd(IRC_SERVER_REC *server, const char *data) +{ + server->motd_got = TRUE; +} + +static void event_channels_formed(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channels; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &channels); + server->channels_formed = atoi(channels); + g_free(params); +} + +static void event_hosthidden(IRC_SERVER_REC *server, const char *data) +{ + char *params, *newhost, *p, *newuserhost; + + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &newhost); + /* do a sanity check */ + if (!strchr(newhost, '*') && !strchr(newhost, '?') && + !strchr(newhost, '!') && !strchr(newhost, '#') && + !strchr(newhost, '&') && !strchr(newhost, ' ') && + *newhost != '\0' && *newhost != '@' && + *newhost != ':' && *newhost != '-' && + newhost[strlen(newhost) - 1] != '-') { + if (strchr(newhost, '@')) { + newuserhost = g_strdup(newhost); + g_free(server->userhost); + server->userhost = newuserhost; + } else if (server->userhost != NULL) { + /* no user@, only process if we know the user@ + * already + */ + p = strchr(server->userhost, '@'); + if (p == NULL) + p = server->userhost; + newuserhost = g_strdup_printf("%.*s@%s", (int)(p - server->userhost), server->userhost, newhost); + g_free(server->userhost); + server->userhost = newuserhost; + } + } + g_free(params); +} + +static void event_server_banned(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + server->banned = TRUE; +} + +static void event_error(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + if (!server->connected && (stristr(data, "Unauthorized") != NULL || + stristr(data, "K-lined") != NULL || + stristr(data, "Banned") != NULL || + stristr(data, "Bad user info") != NULL)) + server->banned = TRUE; +} + +static void event_ping(IRC_SERVER_REC *server, const char *data) +{ + char *params, *origin, *target, *str; + + params = event_get_params(data, 2, &origin, &target); + str = *target == '\0' ? g_strconcat("PONG :", origin, NULL) : + g_strdup_printf("PONG %s :%s", target, origin); + irc_send_cmd_now(server, str); + g_free(str); + g_free(params); +} + +static void event_empty(void) +{ +} + +void irc_server_init_isupport(IRC_SERVER_REC *server) +{ + char *sptr; + gpointer key, value; + /* chanmodes/prefix will fully override defaults */ + memset(server->modes, 0, sizeof(server->modes)); + memset(server->prefix, 0, sizeof(server->prefix)); + + if ((sptr = g_hash_table_lookup(server->isupport, "CHANMODES"))) + parse_chanmodes(server, sptr); + + /* This is after chanmode because some servers define modes in both */ + if (g_hash_table_lookup_extended(server->isupport, "PREFIX", + &key, &value)) { + sptr = value; + if (*sptr != '(') { + /* server incompatible with isupport draft */ + g_hash_table_remove(server->isupport, key); + g_free(key); + g_free(value); + sptr = NULL; + } + } else { + sptr = NULL; + } + + if (sptr == NULL) { + sptr = g_strdup("(ohv)@%+"); + g_hash_table_insert(server->isupport, g_strdup("PREFIX"), sptr); + } + parse_prefix(server, sptr); + + if ((sptr = g_hash_table_lookup(server->isupport, "MODES"))) { + server->max_modes_in_cmd = atoi(sptr); + if (server->max_modes_in_cmd < 1) + server->max_modes_in_cmd = DEFAULT_MAX_MODES; + } + + if ((sptr = g_hash_table_lookup(server->isupport, "CASEMAPPING"))) { + if (strstr(sptr, "rfc1459") != NULL) + server->nick_comp_func = irc_nickcmp_rfc1459; + else + server->nick_comp_func = irc_nickcmp_ascii; + } + + if ((sptr = g_hash_table_lookup(server->isupport, "TARGMAX"))) { + char *p = sptr; + server->max_kicks_in_cmd = 1; + server->max_msgs_in_cmd = 1; + /* Not doing WHOIS here until it is clear what it means. */ + while (*p != '\0') { + if (!g_ascii_strncasecmp(p, "KICK:", 5)) { + server->max_kicks_in_cmd = atoi(p + 5); + if (server->max_kicks_in_cmd <= 0) + server->max_kicks_in_cmd = 30; + } else if (!g_ascii_strncasecmp(p, "PRIVMSG:", 8)) { + server->max_msgs_in_cmd = atoi(p + 8); + if (server->max_msgs_in_cmd <= 0) + server->max_msgs_in_cmd = 30; + } + p = strchr(p, ','); + if (p == NULL) + break; + p++; + } + } else if ((sptr = g_hash_table_lookup(server->isupport, "MAXTARGETS"))) { + server->max_msgs_in_cmd = atoi(sptr); + if (server->max_msgs_in_cmd <= 0) + server->max_msgs_in_cmd = 1; + } +} + +void irc_servers_init(void) +{ + settings_add_bool("servers", "starttls_sts", TRUE); + settings_add_choice("servers", "rejoin_channels_on_reconnect", 1, "off;on;auto"); + settings_add_str("misc", "usermode", DEFAULT_USER_MODE); + settings_add_str("misc", "split_line_start", ""); + settings_add_str("misc", "split_line_end", ""); + settings_add_bool("misc", "split_line_on_space", TRUE); + settings_add_time("flood", "cmd_queue_speed", DEFAULT_CMD_QUEUE_SPEED); + settings_add_int("flood", "cmds_max_at_once", DEFAULT_CMDS_MAX_AT_ONCE); + + cmd_tag = -1; + + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_first("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_last("server destroyed", (SIGNAL_FUNC) sig_destroyed); + signal_add_last("server quit", (SIGNAL_FUNC) sig_server_quit); + signal_add("event 670", (SIGNAL_FUNC) event_starttls); + signal_add("event 451", (SIGNAL_FUNC) event_registerfirst); + signal_add("server cap end", (SIGNAL_FUNC) event_capend); + signal_add("event 001", (SIGNAL_FUNC) event_connected); + signal_add("event 004", (SIGNAL_FUNC) event_server_info); + signal_add("event 005", (SIGNAL_FUNC) event_isupport); + signal_add("event 375", (SIGNAL_FUNC) event_motd); + signal_add_last("event 376", (SIGNAL_FUNC) event_end_of_motd); + signal_add_last("event 422", (SIGNAL_FUNC) event_end_of_motd); /* no motd */ + signal_add("event 254", (SIGNAL_FUNC) event_channels_formed); + signal_add("event 396", (SIGNAL_FUNC) event_hosthidden); + signal_add("event 465", (SIGNAL_FUNC) event_server_banned); + signal_add("event error", (SIGNAL_FUNC) event_error); + signal_add("event ping", (SIGNAL_FUNC) event_ping); + signal_add("event empty", (SIGNAL_FUNC) event_empty); + + irc_servers_setup_init(); + irc_servers_reconnect_init(); + servers_redirect_init(); + servers_idle_init(); +} + +void irc_servers_deinit(void) +{ + if (cmd_tag != -1) + g_source_remove(cmd_tag); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("server destroyed", (SIGNAL_FUNC) sig_destroyed); + signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit); + signal_remove("event 670", (SIGNAL_FUNC) event_starttls); + signal_remove("event 451", (SIGNAL_FUNC) event_registerfirst); + signal_remove("server cap end", (SIGNAL_FUNC) event_capend); + signal_remove("event 001", (SIGNAL_FUNC) event_connected); + signal_remove("event 004", (SIGNAL_FUNC) event_server_info); + signal_remove("event 005", (SIGNAL_FUNC) event_isupport); + signal_remove("event 375", (SIGNAL_FUNC) event_motd); + signal_remove("event 376", (SIGNAL_FUNC) event_end_of_motd); + signal_remove("event 422", (SIGNAL_FUNC) event_end_of_motd); /* no motd */ + signal_remove("event 254", (SIGNAL_FUNC) event_channels_formed); + signal_remove("event 396", (SIGNAL_FUNC) event_hosthidden); + signal_remove("event 465", (SIGNAL_FUNC) event_server_banned); + signal_remove("event error", (SIGNAL_FUNC) event_error); + signal_remove("event ping", (SIGNAL_FUNC) event_ping); + signal_remove("event empty", (SIGNAL_FUNC) event_empty); + + irc_servers_setup_deinit(); + irc_servers_reconnect_deinit(); + servers_redirect_deinit(); + servers_idle_deinit(); +} diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h new file mode 100644 index 0000000..6e78c4d --- /dev/null +++ b/src/irc/core/irc-servers.h @@ -0,0 +1,187 @@ +#ifndef IRSSI_IRC_CORE_IRC_SERVERS_H +#define IRSSI_IRC_CORE_IRC_SERVERS_H + +#include <irssi/src/core/chat-protocols.h> +#include <irssi/src/core/servers.h> +#include <irssi/src/irc/core/modes.h> + +/* + * 63 is the maximum hostname length defined by the protocol. 10 is a common + * username limit on many networks. 1 is for the `@'. + */ +#define MAX_USERHOST_LEN (63 + 10 + 1) +#define MAX_IRC_MESSAGE_LEN (512 - 2) /* (2 bytes for CR+LF) */ +#define MAX_IRC_TAGS_LEN (8191 - 2) /* (2 bytes for `@' and SPACE) */ +#define MAX_IRC_USER_TAGS_LEN 4094 + +#define CAP_LS_VERSION "302" +#define CAP_MESSAGE_TAGS "message-tags" +#define CAP_SASL "sasl" +#define CAP_MULTI_PREFIX "multi-prefix" +#define CAP_EXTENDED_JOIN "extended-join" +#define CAP_SETNAME "setname" +#define CAP_INVITE_NOTIFY "invite-notify" +#define CAP_AWAY_NOTIFY "away-notify" +#define CAP_CHGHOST "chghost" +#define CAP_ACCOUNT_NOTIFY "account-notify" +#define CAP_SELF_MESSAGE "znc.in/self-message" +#define CAP_SERVER_TIME "server-time" +#define CAP_STARTTLS "tls" + +/* returns IRC_SERVER_REC if it's IRC server, NULL if it isn't */ +#define IRC_SERVER(server) \ + PROTO_CHECK_CAST(SERVER(server), IRC_SERVER_REC, chat_type, "IRC") + +#define IRC_SERVER_CONNECT(conn) \ + PROTO_CHECK_CAST(SERVER_CONNECT(conn), IRC_SERVER_CONNECT_REC, \ + chat_type, "IRC") + +#define IS_IRC_SERVER(server) \ + (IRC_SERVER(server) ? TRUE : FALSE) + +#define IS_IRC_SERVER_CONNECT(conn) \ + (IRC_SERVER_CONNECT(conn) ? TRUE : FALSE) + +/* clang-format off */ +/* all strings should be either NULL or dynamically allocated */ +/* address and nick are mandatory, rest are optional */ +struct _IRC_SERVER_CONNECT_REC { +#include <irssi/src/core/server-connect-rec.h> + + char *usermode; + char *alternate_nick; + + int sasl_mechanism; + char *sasl_username; + char *sasl_password; + + int max_cmds_at_once; + int cmd_queue_speed; + int max_query_chans; + + int max_kicks, max_msgs, max_modes, max_whois; + int disallow_starttls:1; + int starttls:1; + int no_cap:1; +}; +/* clang-format on */ + +#define STRUCT_SERVER_CONNECT_REC IRC_SERVER_CONNECT_REC +struct _IRC_SERVER_REC { +#include <irssi/src/core/server-rec.h> + + int max_message_len; /* Maximum message length, default = 510 = 512 - 2 (for CR+LF) */ + + /* For deciding if event should be redirected */ + GSList *redirects; + GSList *redirect_queue; /* should be updated from redirect_next each time cmdqueue is updated */ + REDIRECT_REC *redirect_next; + GSList *redirect_active; /* redirects start event has been received for, must have unique prefix */ + + char *last_nick; /* last /NICK, kept even if it resulted as not valid change */ + + char *real_address; /* address the irc server gives */ + char *usermode; /* The whole mode string .. */ + char *wanted_usermode; /* The usermode we want to use, doesn't include the modes given us by the server (eg. +r) */ + char *userhost; /* /USERHOST <nick> - set when joined to first channel */ + int channels_formed; /* channels formed in irc network */ + + unsigned int whois_found:1; /* Did WHOIS return any entries? */ + unsigned int whowas_found:1; /* Did WHOWAS return any entries? */ + + unsigned int emode_known:1; /* Server understands ban exceptions and invite lists */ + unsigned int no_multi_mode:1; /* Server doesn't understand MODE #chan1,#chan2,... */ + unsigned int no_multi_who:1; /* Server doesn't understand WHO #chan1,#chan2,... */ + unsigned int one_endofwho:1; /* /WHO #a,#b,.. replies only with one End of WHO message */ + unsigned int disable_lag:1; /* Disable lag detection (PING command doesn't exist) */ + unsigned int nick_collision:1; /* We're just now being killed because of nick collision */ + unsigned int motd_got:1; /* We've received MOTD */ + unsigned int isupport_sent:1; /* Server has sent us an isupport reply */ + unsigned int cap_complete:1; /* We've done the initial CAP negotiation */ + unsigned int cap_in_multiline:1; /* We're waiting for the multiline response to end */ + unsigned int sasl_success:1; /* Did we authenticate successfully ? */ + + int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */ + int max_modes_in_cmd; /* max. number of mode changes in one /MODE command */ + int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */ + int max_msgs_in_cmd; /* max. number of targets in one /MSG */ + + GHashTable *cap_supported; /* A list of caps supported by the server */ + GSList *cap_active; /* A list of caps active for this session */ + GSList *cap_queue; /* A list of caps to request on connection */ + + GString *sasl_buffer; /* Buffer used to reassemble a fragmented SASL payload */ + guint sasl_timeout; /* Holds the source id of the running timeout */ + + /* Command sending queue */ + int cmdcount; /* number of commands in `cmdqueue'. Can be more than + there actually is, to make flood control remember + how many messages can be sent before starting the + flood control */ + int cmdlater; /* number of commands in queue to be sent later */ + GSList *cmdqueue; /* command, redirection, ... */ + gint64 wait_cmd; /* don't send anything to server before this */ + gint64 last_cmd; /* last time command was sent to server */ + + int max_cmds_at_once; /* How many messages can be sent immediately before timeouting starts */ + int cmd_queue_speed; /* Timeout between sending commands */ + int max_query_chans; /* when syncing, max. number of channels to + put in one MODE/WHO command */ + + GSList *idles; /* Idle queue - send these commands to server + if there's nothing else to do */ + + GSList *ctcpqueue; /* CTCP flood protection - list of tags in idle queue */ + + /* /knockout ban list */ + GSList *knockoutlist; + + GHashTable *splits; /* For keeping track of netsplits */ + GSList *split_servers; /* Servers that are currently in split */ + + GSList *rejoin_channels; /* try to join to these channels after a while - + channels go here if they're "temporarily unavailable" + because of netsplits */ + guint starttls_tag; /* Holds the source id of the running timeout */ + struct _SERVER_QUERY_REC *chanqueries; + + GHashTable *isupport; + struct modes_type modes[256]; /* Stores the modes sent by a server in an isupport reply */ + char prefix[256]; + + int (*nick_comp_func)(const char *, const char *); /* Function for comparing nicknames on this server */ +}; + +SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn); +void irc_server_connect(SERVER_REC *server); + +/* Purge server output, either all or for specified target */ +void irc_server_purge_output(IRC_SERVER_REC *server, const char *target); + +enum { + REJOIN_CHANNELS_MODE_OFF = 0, /* */ + REJOIN_CHANNELS_MODE_ON, + REJOIN_CHANNELS_MODE_AUTO +}; + +/* Return a string of all channels (and keys, if any have them) in server, + like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ +char *irc_server_get_channels(IRC_SERVER_REC *server, int rejoin_channels_mode); + +void irc_server_send_starttls(IRC_SERVER_REC *server); +/* INTERNAL: */ +void irc_server_send_action(IRC_SERVER_REC *server, const char *target, + const char *data); +char **irc_server_split_action(IRC_SERVER_REC *server, const char *target, + const char *data); +void irc_server_send_away(IRC_SERVER_REC *server, const char *reason); +void irc_server_send_data(IRC_SERVER_REC *server, const char *data, int len); +void irc_server_send_and_redirect(IRC_SERVER_REC *server, GString *str, REDIRECT_REC *redirect); +void irc_server_init_isupport(IRC_SERVER_REC *server); + +void irc_servers_start_cmd_timeout(void); + +void irc_servers_init(void); +void irc_servers_deinit(void); + +#endif diff --git a/src/irc/core/irc-session.c b/src/irc/core/irc-session.c new file mode 100644 index 0000000..49d9a3f --- /dev/null +++ b/src/irc/core/irc-session.c @@ -0,0 +1,251 @@ +/* + irc-session.c : irssi + + Copyright (C) 2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/lib-config/iconfig.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/network.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-servers-setup.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-nicklist.h> + +#include <irssi/src/irc/core/sasl.h> + +struct _isupport_data { CONFIG_REC *config; CONFIG_NODE *node; }; + +static void session_isupport_foreach(char *key, char *value, struct _isupport_data *data) +{ + config_node_set_str(data->config, data->node, key, value); +} + +static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, + CONFIG_NODE *node) +{ + GSList *tmp; + CONFIG_NODE *isupport; + struct _isupport_data isupport_data; + int tls_disconnect; + + if (!IS_IRC_SERVER(server)) + return; + + /* send all non-redirected commands to server immediately */ + for (tmp = server->cmdqueue; tmp != NULL; tmp = tmp->next->next) { + const char *cmd = tmp->data; + void *redirect = tmp->next->data; + + if (redirect == NULL) { + if (net_sendbuffer_send(server->handle, cmd, + strlen(cmd)) == -1) + break; + } + } + /* we cannot upgrade TLS (yet?) */ + tls_disconnect = server->connrec->use_tls || server->connrec->starttls; + if (tls_disconnect) { + config_node_set_str(config, node, "rejoin_channels", + irc_server_get_channels(server, REJOIN_CHANNELS_MODE_ON)); + irc_send_cmd_now(server, "QUIT :[TLS] Client upgrade"); + } + + net_sendbuffer_flush(server->handle); + + config_node_set_str(config, node, "real_address", server->real_address); + config_node_set_str(config, node, "userhost", server->userhost); + config_node_set_str(config, node, "usermode", server->usermode); + config_node_set_bool(config, node, "usermode_away", server->usermode_away); + config_node_set_str(config, node, "away_reason", server->away_reason); + config_node_set_bool(config, node, "emode_known", server->emode_known); + + config_node_set_int(config, node, "sasl_mechanism", server->connrec->sasl_mechanism); + config_node_set_str(config, node, "sasl_username", server->connrec->sasl_username); + config_node_set_str(config, node, "sasl_password", server->connrec->sasl_password); + + config_node_set_int(config, node, "starttls", + server->connrec->disallow_starttls ? STARTTLS_DISALLOW : + server->connrec->starttls ? STARTTLS_ENABLED : + STARTTLS_NOTSET); + + config_node_set_bool(config, node, "no_cap", server->connrec->no_cap); + config_node_set_bool(config, node, "isupport_sent", server->isupport_sent); + isupport = config_node_section(config, node, "isupport", NODE_TYPE_BLOCK); + isupport_data.config = config; + isupport_data.node = isupport; + + g_hash_table_foreach(server->isupport, (GHFunc) session_isupport_foreach, &isupport_data); + + /* we have to defer the disconnect to irc_server_connect */ +} + +static void sig_session_restore_server(IRC_SERVER_REC *server, + CONFIG_NODE *node) +{ + GSList *tmp; + int starttls_mode; + + if (!IS_IRC_SERVER(server)) + return; + + if (server->real_address == NULL) + server->real_address = g_strdup(config_node_get_str(node, "real_address", NULL)); + server->userhost = g_strdup(config_node_get_str(node, "userhost", NULL)); + server->usermode = g_strdup(config_node_get_str(node, "usermode", NULL)); + server->usermode_away = config_node_get_bool(node, "usermode_away", FALSE); + server->away_reason = g_strdup(config_node_get_str(node, "away_reason", NULL)); + server->emode_known = config_node_get_bool(node, "emode_known", FALSE); + server->isupport_sent = config_node_get_bool(node, "isupport_sent", FALSE); + + server->connrec->no_cap = config_node_get_bool(node, "no_cap", FALSE); + server->connrec->sasl_mechanism = config_node_get_int(node, "sasl_mechanism", SASL_MECHANISM_NONE); + /* The fields below might have been filled when loading the chatnet + * description from the config and we favor the content that's been saved + * in the session file over that. */ + g_free(server->connrec->sasl_username); + server->connrec->sasl_username = g_strdup(config_node_get_str(node, "sasl_username", NULL)); + g_free(server->connrec->sasl_password); + server->connrec->sasl_password = g_strdup(config_node_get_str(node, "sasl_password", NULL)); + + server->connrec->channels = g_strdup(config_node_get_str(node, "rejoin_channels", NULL)); + + starttls_mode = config_node_get_int(node, "starttls", STARTTLS_NOTSET); + if (starttls_mode == STARTTLS_DISALLOW) + server->connrec->disallow_starttls = 1; + if (starttls_mode == STARTTLS_ENABLED) { + server->connrec->starttls = 1; + server->connrec->use_tls = 0; + } + + if (server->isupport == NULL) { + server->isupport = + g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); + } + + node = config_node_section(NULL, node, "isupport", -1); + tmp = node == NULL ? NULL : config_node_first(node->value); + + for (; tmp != NULL; tmp = config_node_next(tmp)) { + node = tmp->data; + if (node == NULL) + break; + + g_hash_table_insert(server->isupport, g_strdup(node->key), + g_strdup(node->value)); + } + irc_server_init_isupport(server); + + /* we will reconnect in irc_server_connect if the connection was TLS */ +} + +static void sig_session_restore_nick(IRC_CHANNEL_REC *channel, + CONFIG_NODE *node) +{ + const char *nick, *prefixes; + int op, halfop, voice; + char newprefixes[MAX_USER_PREFIXES + 1]; + int i; + + if (!IS_IRC_CHANNEL(channel)) + return; + + nick = config_node_get_str(node, "nick", NULL); + if (nick == NULL) + return; + + op = config_node_get_bool(node, "op", FALSE); + voice = config_node_get_bool(node, "voice", FALSE); + halfop = config_node_get_bool(node, "halfop", FALSE); + prefixes = config_node_get_str(node, "prefixes", NULL); + if (prefixes == NULL || *prefixes == '\0') { + /* upgrading from old irssi or from an in-between + * version that did not imply non-present prefixes from + * op/voice/halfop, restore prefixes + */ + i = 0; + if (op) + newprefixes[i++] = '@'; + if (halfop) + newprefixes[i++] = '%'; + if (voice) + newprefixes[i++] = '+'; + newprefixes[i] = '\0'; + prefixes = newprefixes; + } + irc_nicklist_insert(channel, nick, op, halfop, voice, FALSE, prefixes); +} + +static void session_restore_channel(IRC_CHANNEL_REC *channel) +{ + char *data; + + signal_emit("event join", 4, channel->server, channel->name, + channel->server->nick, channel->server->userhost); + + data = g_strconcat(channel->server->nick, " ", channel->name, NULL); + signal_emit("event 366", 2, channel->server, data); + g_free(data); +} + +static void sig_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + char *str, *addr; + + if (!IS_IRC_SERVER(server) || !server->session_reconnect) + return; + + str = g_strdup_printf("%s :Restoring connection to %s", + server->nick, server->connrec->address); + /* addr needs to be strdup'd because the event_connected() handler + free()'s the server->real_address and then tries to strdup() the + given origin again */ + addr = g_strdup(server->real_address); + signal_emit("event 001", 3, server, str, addr); + g_free(addr); + g_free(str); + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + IRC_CHANNEL_REC *rec = tmp->data; + + if (rec->session_rejoin) + session_restore_channel(rec); + } +} + +void irc_session_init(void) +{ + signal_add("session save server", (SIGNAL_FUNC) sig_session_save_server); + signal_add("session restore server", (SIGNAL_FUNC) sig_session_restore_server); + signal_add("session restore nick", (SIGNAL_FUNC) sig_session_restore_nick); + + signal_add("server connected", (SIGNAL_FUNC) sig_connected); +} + +void irc_session_deinit(void) +{ + signal_remove("session save server", (SIGNAL_FUNC) sig_session_save_server); + signal_remove("session restore server", (SIGNAL_FUNC) sig_session_restore_server); + signal_remove("session restore nick", (SIGNAL_FUNC) sig_session_restore_nick); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); +} diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c new file mode 100644 index 0000000..a52e919 --- /dev/null +++ b/src/irc/core/irc.c @@ -0,0 +1,608 @@ +/* + irc.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/modules.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/rawlog.h> +#include <irssi/src/core/refstrings.h> + +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-redirect.h> + +char *current_server_event; +static int signal_default_event; +static int signal_server_event; +static int signal_server_event_tags; +static int signal_server_incoming; + +#ifdef BLOCKING_SOCKETS +# define MAX_SOCKET_READS 1 +#else +# define MAX_SOCKET_READS 5 +#endif + +static void strip_params_colon(char *const); + +/* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd' + won't be checked at all if it's 512 bytes or not, or if it contains + line feeds or not. Use with extreme caution! */ +void irc_send_cmd_full(IRC_SERVER_REC *server, const char *cmd, int irc_send_when, int raw) +{ + GString *str; + int len; + guint pos; + gboolean server_supports_tag; + + g_return_if_fail(server != NULL); + g_return_if_fail(cmd != NULL); + + if (server->connection_lost) + return; + + str = g_string_sized_new(MAX_IRC_USER_TAGS_LEN + 2 /* `@'+SPACE */ + + server->max_message_len + 2 /* CR+LF */ + 1 /* `\0' */); + + if (server->cmdcount == 0) + irc_servers_start_cmd_timeout(); + server->cmdcount++; + + pos = g_slist_length(server->cmdqueue); + if (server->cmdlater > pos / 2) { + server->cmdlater = pos / 2; + pos = 0; + } else { + pos -= 2 * server->cmdlater; + } + + if (!raw) { + const char *tmp = cmd; + + server_supports_tag = server->cap_supported != NULL && + g_hash_table_lookup_extended(server->cap_supported, CAP_MESSAGE_TAGS, NULL, NULL); + + if (*cmd == '@' && server_supports_tag) { + const char *end; + + while (*tmp != ' ' && *tmp != '\0') + tmp++; + + end = tmp; + + if (tmp - cmd > MAX_IRC_USER_TAGS_LEN) { + g_warning("irc_send_cmd_full(); tags too long(%ld)", tmp - cmd); + while (tmp - cmd > MAX_IRC_USER_TAGS_LEN && cmd != tmp - 1) tmp--; + while (*tmp != ',' && cmd != tmp - 1) tmp--; + } + if (cmd != tmp) + g_string_append_len(str, cmd, tmp - cmd); + + tmp = end; + while (*tmp == ' ') tmp++; + + if (*tmp != '\0' && str->len > 0) + g_string_append_c(str, ' '); + } + len = strlen(tmp); + + /* check that we don't send any longer commands + than 510 bytes (2 bytes for CR+LF) */ + g_string_append_len(str, tmp, len > server->max_message_len ? + server->max_message_len : len); + } else { + g_string_append(str, cmd); + } + + if (!raw) { + /* Add CR+LF to command */ + g_string_append(str, "\r\n"); + } + + if (irc_send_when == IRC_SEND_NOW) { + irc_server_send_and_redirect(server, str, server->redirect_next); + g_string_free(str, TRUE); + } else if (irc_send_when == IRC_SEND_NEXT) { + /* add to queue */ + server->cmdqueue = g_slist_prepend(server->cmdqueue, server->redirect_next); + server->cmdqueue = g_slist_prepend(server->cmdqueue, g_string_free(str, FALSE)); + } else if (irc_send_when == IRC_SEND_NORMAL) { + server->cmdqueue = g_slist_insert(server->cmdqueue, server->redirect_next, pos); + server->cmdqueue = g_slist_insert(server->cmdqueue, g_string_free(str, FALSE), pos); + } else if (irc_send_when == IRC_SEND_LATER) { + server->cmdqueue = g_slist_append(server->cmdqueue, g_string_free(str, FALSE)); + server->cmdqueue = g_slist_append(server->cmdqueue, server->redirect_next); + server->cmdlater++; + } else { + g_warn_if_reached(); + } + + server->redirect_next = NULL; +} + +/* Send command to IRC server */ +void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd) +{ + gint64 now; + int send_now; + + now = g_get_real_time(); + send_now = now >= server->wait_cmd && + (server->cmdcount < server->max_cmds_at_once || + server->cmd_queue_speed <= 0); + + irc_send_cmd_full(server, cmd, send_now ? IRC_SEND_NOW : IRC_SEND_NORMAL, FALSE); +} + +/* Send command to IRC server */ +void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) +{ + va_list args; + char *str; + + va_start(args, cmd); + + str = g_strdup_vprintf(cmd, args); + irc_send_cmd(server, str); + g_free(str); + + va_end(args); +} + +/* Send command to server immediately bypassing all flood protections + and queues. */ +void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd) +{ + g_return_if_fail(cmd != NULL); + + irc_send_cmd_full(server, cmd, IRC_SEND_NOW, FALSE); +} + +/* Send command to server putting it at the beginning of the queue of + commands to send -- it will go out as soon as possible in accordance + to the flood protection settings. */ +void irc_send_cmd_first(IRC_SERVER_REC *server, const char *cmd) +{ + g_return_if_fail(cmd != NULL); + + irc_send_cmd_full(server, cmd, IRC_SEND_NEXT, FALSE); +} + +/* Send command to server putting it at the end of the queue. */ +void irc_send_cmd_later(IRC_SERVER_REC *server, const char *cmd) +{ + g_return_if_fail(cmd != NULL); + + irc_send_cmd_full(server, cmd, IRC_SEND_LATER, FALSE); +} + +static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg) +{ + char *p; + + *pre = g_strdup(cmd); + *post = *nicks = NULL; + + if (**pre == '@') { + /* the message-tags "add" one space separated argument + in front of the non message-tagged IRC commands. So + the nicks are now off-set by one to the right. */ + arg++; + } + + for (p = *pre; *p != '\0'; p++) { + if (*p != ' ') + continue; + + if (arg == 1) { + /* text after nicks */ + *p++ = '\0'; + while (*p == ' ') p++; + *post = p; + break; + } + + /* find nicks */ + while (p[1] == ' ') p++; + if (--arg == 1) { + *p = '\0'; + *nicks = p+1; + } + } + + return *pre; +} + +void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, + int nickarg, int max_nicks) +{ + char *str, *pre, *post, *nicks; + char **nicklist, **tmp; + GString *nickstr; + int count; + + g_return_if_fail(server != NULL); + g_return_if_fail(cmd != NULL); + + str = split_nicks(cmd, &pre, &nicks, &post, nickarg); + if (nicks == NULL) { + /* no nicks given? */ + g_free(str); + return; + } + + /* split the nicks */ + nickstr = g_string_new(NULL); + nicklist = g_strsplit(nicks, ",", -1); count = 0; + + tmp = nicklist; + for (;; tmp++) { + if (*tmp != NULL) { + g_string_append_printf(nickstr, "%s,", *tmp); + if (++count < max_nicks) + continue; + } + + count = 0; + if (nickstr->len > 0) + g_string_truncate(nickstr, nickstr->len-1); + + if (post == NULL) + irc_send_cmdv(server, "%s %s", pre, nickstr->str); + else + irc_send_cmdv(server, "%s %s %s", pre, nickstr->str, post); + + g_string_truncate(nickstr, 0); + + if (*tmp == NULL || tmp[1] == NULL) + break; + } + g_strfreev(nicklist); + g_string_free(nickstr, TRUE); + + g_free(str); +} + +/* Get next parameter */ +char *event_get_param(char **data) +{ + char *pos; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + if (**data == ':') { + /* last parameter */ + pos = *data; + *data += strlen(*data); + return pos+1; + } + + pos = *data; + while (**data != '\0' && **data != ' ') (*data)++; + if (**data == ' ') *(*data)++ = '\0'; + + return pos; +} + +/* Get count parameters from data */ +char *event_get_params(const char *data, int count, ...) +{ + char **str, *tmp, *duprec, *datad; + gboolean rest; + va_list args; + + g_return_val_if_fail(data != NULL, NULL); + + va_start(args, count); + duprec = datad = g_strdup(data); + + rest = count & PARAM_FLAG_GETREST; + count = PARAM_WITHOUT_FLAGS(count); + + while (count-- > 0) { + str = (char **) va_arg(args, char **); + if (count == 0 && rest) { + /* Put the rest into the last parameter. */ + strip_params_colon(datad); + tmp = datad; + } else { + tmp = event_get_param(&datad); + } + if (str != NULL) *str = tmp; + } + va_end(args); + + return duprec; +} + +/* Given a string containing <params>, strip any colon prefixing <trailing>. */ +static void strip_params_colon(char *const params) +{ + char *s; + + if (params == NULL) { + return; + } + + s = params; + while (*s != '\0') { + if (*s == ':') { + memmove(s, s+1, strlen(s+1)+1); + return; + } + + s = strchr(s, ' '); + if (s == NULL) { + return; + } + + while (*s == ' ') { + s++; + } + } +} + +static void irc_server_event(IRC_SERVER_REC *server, const char *line, + const char *nick, const char *address) +{ + const char *signal; + char *event, *args; + + g_return_if_fail(line != NULL); + + /* split event / args */ + event = g_strconcat("event ", line, NULL); + args = strchr(event+6, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + while (*args == ' ') args++; + ascii_strdown(event); + + /* check if event needs to be redirected */ + signal = server_redirect_get_signal(server, nick, event, args); + if (signal == NULL) + signal = event; + else + rawlog_redirect(server->rawlog, signal); + + /* emit it */ + current_server_event = event+6; + if (!signal_emit(signal, 4, server, args, nick, address)) + signal_emit_id(signal_default_event, 4, server, line, nick, address); + current_server_event = NULL; + + g_free(event); +} + +static void unescape_tag(char *tag) +{ + char *tmp; + + if (tag == NULL) + return; + + tmp = tag; + for (; *tmp != '\0'; tmp++, tag++) { + if (*tmp == '\\') { + tmp++; + if (*tmp == '\0') + break; + switch (*tmp) { + case ':': + *tag = ';'; + break; + case 'n': + *tag = '\n'; + break; + case 'r': + *tag = '\r'; + break; + case 's': + *tag = ' '; + break; + default: + *tag = *tmp; + break; + } + } else { + *tag = *tmp; + } + } + *tag = '\0'; +} + +static gboolean i_str0_equal(const char *s1, const char *s2) +{ + return g_strcmp0(s1, s2) == 0; +} + +GHashTable *irc_parse_message_tags(const char *tags) +{ + char **split, **tmp, **kv; + GHashTable *hash; + + hash = g_hash_table_new_full(g_str_hash, (GEqualFunc) i_str0_equal, + (GDestroyNotify) i_refstr_release, (GDestroyNotify) g_free); + split = g_strsplit(tags, ";", -1); + for (tmp = split; *tmp != NULL; tmp++) { + if (*tmp[0] == '\0') + continue; + kv = g_strsplit(*tmp, "=", 2); + unescape_tag(kv[1]); + g_hash_table_replace(hash, i_refstr_intern(kv[0]), + g_strdup(kv[1] == NULL ? "" : kv[1])); + g_strfreev(kv); + } + g_strfreev(split); + return hash; +} + +static void irc_server_event_tags(IRC_SERVER_REC *server, const char *line, const char *nick, + const char *address, const char *tags) +{ + char *timestr; + GHashTable *tags_hash = NULL; + + if (tags != NULL && *tags != '\0') { + tags_hash = irc_parse_message_tags(tags); + if ((timestr = g_hash_table_lookup(tags_hash, "time")) != NULL) { + server_meta_stash(SERVER(server), "time", timestr); + } + } + + if (*line != '\0') + signal_emit_id(signal_server_event, 4, server, line, nick, address); + + if (tags_hash != NULL) + g_hash_table_destroy(tags_hash); +} + +static char *irc_parse_prefix(char *line, char **nick, char **address, char **tags) +{ + char *p; + + *nick = *address = *tags = NULL; + + /* ["@" <tags> SPACE] :<nick> [["!" <user>] "@" <host>] SPACE */ + + if (*line == '@') { + *tags = ++line; + while (*line != '\0' && *line != ' ') { + line++; + } + if (*line == ' ') { + *line++ = '\0'; + while (*line == ' ') line++; + } + } + + if (*line != ':') + return line; + + *nick = ++line; p = NULL; + while (*line != '\0' && *line != ' ') { + if (*line == '!' || *line == '@') { + p = line; + if (*line == '!') + break; + } + line++; + } + + if (p != NULL) { + line = p; + *line++ = '\0'; + *address = line; + while (*line != '\0' && *line != ' ') + line++; + } + + if (*line == ' ') { + *line++ = '\0'; + while (*line == ' ') line++; + } + + return line; +} + +/* Parse command line sent by server */ +static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line) +{ + char *nick, *address, *tags; + + g_return_if_fail(server != NULL); + g_return_if_fail(line != NULL); + + line = irc_parse_prefix(line, &nick, &address, &tags); + if (*line != '\0' || tags != NULL) + signal_emit_id(signal_server_event_tags, 5, server, line, nick, address, tags); + + server_meta_clear_all(SERVER(server)); +} + +/* input function: handle incoming server messages */ +static void irc_parse_incoming(SERVER_REC *server) +{ + char *str; + int count; + int ret; + + g_return_if_fail(server != NULL); + + /* Some commands can send huge replies and irssi might handle them + too slowly, so read only a few times from the socket before + letting other tasks to run. */ + count = 0; + ret = 0; + server_ref(server); + while (!server->disconnected && + (ret = net_sendbuffer_receive_line(server->handle, &str, count < MAX_SOCKET_READS)) > 0) { + rawlog_input(server->rawlog, str); + signal_emit_id(signal_server_incoming, 2, server, str); + + if (server->connection_lost) + server_disconnect(server); + + count++; + } + if (ret == -1) { + /* connection lost */ + server->connection_lost = TRUE; + server_disconnect(server); + } + server_unref(server); +} + +static void irc_init_server(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + server->readtag = i_input_add(net_sendbuffer_handle(server->handle), I_INPUT_READ, + (GInputFunction) irc_parse_incoming, server); +} + +void irc_irc_init(void) +{ + signal_add("server event", (SIGNAL_FUNC) irc_server_event); + signal_add("server event tags", (SIGNAL_FUNC) irc_server_event_tags); + signal_add("server connected", (SIGNAL_FUNC) irc_init_server); + signal_add("server connection switched", (SIGNAL_FUNC) irc_init_server); + signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); + + current_server_event = NULL; + signal_default_event = signal_get_uniq_id("default event"); + signal_server_event = signal_get_uniq_id("server event"); + signal_server_event_tags = signal_get_uniq_id("server event tags"); + signal_server_incoming = signal_get_uniq_id("server incoming"); +} + +void irc_irc_deinit(void) +{ + signal_remove("server event", (SIGNAL_FUNC) irc_server_event); + signal_remove("server event tags", (SIGNAL_FUNC) irc_server_event_tags); + signal_remove("server connected", (SIGNAL_FUNC) irc_init_server); + signal_remove("server connection switched", (SIGNAL_FUNC) irc_init_server); + signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); +} diff --git a/src/irc/core/irc.h b/src/irc/core/irc.h new file mode 100644 index 0000000..92fb4cc --- /dev/null +++ b/src/irc/core/irc.h @@ -0,0 +1,70 @@ +#ifndef IRSSI_IRC_CORE_IRC_H +#define IRSSI_IRC_CORE_IRC_H + +typedef struct _IRC_CHATNET_REC IRC_CHATNET_REC; +typedef struct _IRC_SERVER_CONNECT_REC IRC_SERVER_CONNECT_REC; +typedef struct _IRC_SERVER_REC IRC_SERVER_REC; +typedef struct _IRC_CHANNEL_REC IRC_CHANNEL_REC; +typedef struct _REDIRECT_REC REDIRECT_REC; + +/* From ircd 2.9.5: + none I line with ident + ^ I line with OTHER type ident + ~ I line, no ident + + i line with ident + = i line with OTHER type ident + - i line, no ident +*/ +#define ishostflag(a) \ + ((a) == '^' || (a) == '~' || \ + (a) == '+' || (a) == '=' || (a) == '-') + +#define isnickflag(server, a) \ + (server->prefix[(int)(unsigned char) a] != '\0') + +#define IS_IRC_ITEM(rec) (IS_IRC_CHANNEL(rec) || IS_IRC_QUERY(rec)) +#define IRC_PROTOCOL (chat_protocol_lookup("IRC")) + +extern char *current_server_event; /* current server event being processed */ + +enum { + IRC_SEND_NOW, /* */ + IRC_SEND_NEXT, + IRC_SEND_NORMAL, + IRC_SEND_LATER +}; + +/* Send command to IRC server */ +void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd); +void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) G_GNUC_PRINTF (2, 3); +/* Send command to IRC server, split to multiple commands if necessary so + that command will never have more target nicks than `max_nicks'. Nicks + are separated with commas. (works with /msg, /kick, ...) */ +void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, + int nickarg, int max_nicks); +/* Send command to server immediately bypassing all flood protections + and queues. */ +void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd); +/* Send command to server putting it at the beginning of the queue of + commands to send -- it will go out as soon as possible in accordance + to the flood protection settings. */ +void irc_send_cmd_first(IRC_SERVER_REC *server, const char *cmd); +/* Send command to server putting it at the end of the queue. */ +void irc_send_cmd_later(IRC_SERVER_REC *server, const char *cmd); +/* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd' + won't be checked at all if it's 512 bytes or not, or if it contains + line feeds or not. Use with extreme caution! */ +void irc_send_cmd_full(IRC_SERVER_REC *server, const char *cmd, int irc_send_when, int raw); + +/* Extract a tag value from tags */ +GHashTable *irc_parse_message_tags(const char *tags); + +/* Get count parameters from data */ +#include <irssi/src/core/commands.h> +char *event_get_param(char **data); +char *event_get_params(const char *data, int count, ...); + +void irc_irc_init(void); +void irc_irc_deinit(void); + +#endif diff --git a/src/irc/core/lag.c b/src/irc/core/lag.c new file mode 100644 index 0000000..aef5e61 --- /dev/null +++ b/src/irc/core/lag.c @@ -0,0 +1,140 @@ +/* + lag.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-redirect.h> + +static int timeout_tag; + +static void lag_get(IRC_SERVER_REC *server) +{ + server->lag_sent = g_get_real_time(); + server->lag_last_check = time(NULL); + + server_redirect_event(server, "ping", 1, NULL, FALSE, + "lag ping error", + "event pong", "lag pong", NULL); + irc_send_cmdv(server, "PING %s", server->real_address); +} + +/* we didn't receive PONG for some reason .. try again */ +static void lag_ping_error(IRC_SERVER_REC *server) +{ + lag_get(server); +} + +static void lag_event_pong(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + gint64 now; + + g_return_if_fail(data != NULL); + + if (server->lag_sent == 0) { + /* not expecting lag reply.. */ + return; + } + + now = g_get_real_time(); + server->lag = (now - server->lag_sent) / G_TIME_SPAN_MILLISECOND; + server->lag_sent = 0; + + signal_emit("server lag", 1, server); +} + +static void sig_unknown_command(IRC_SERVER_REC *server, const char *data) +{ + char *params, *cmd; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &cmd); + if (g_ascii_strcasecmp(cmd, "PING") == 0) { + /* some servers have disabled PING command, don't bother + trying alternative methods to detect lag with these + servers. */ + server->disable_lag = TRUE; + server->lag_sent = 0; + server->lag = 0; + } + g_free(params); +} + +static int sig_check_lag(void) +{ + GSList *tmp, *next; + time_t now; + int lag_check_time, max_lag; + + lag_check_time = settings_get_time("lag_check_time")/1000; + max_lag = settings_get_time("lag_max_before_disconnect")/1000; + + if (lag_check_time <= 0) + return 1; + + now = time(NULL); + for (tmp = servers; tmp != NULL; tmp = next) { + IRC_SERVER_REC *rec = tmp->data; + + next = tmp->next; + if (!IS_IRC_SERVER(rec) || rec->disable_lag) + continue; + + if (rec->lag_sent != 0) { + /* waiting for lag reply */ + if (max_lag > 1 && now - (rec->lag_sent / G_TIME_SPAN_SECOND) > max_lag) { + /* too much lag, disconnect */ + signal_emit("server lag disconnect", 1, rec); + rec->connection_lost = TRUE; + server_disconnect((SERVER_REC *) rec); + } + } else if (rec->lag_last_check + lag_check_time < now && rec->cmdcount == 0 && + rec->connected) { + /* no commands in buffer - get the lag */ + lag_get(rec); + } + } + + return 1; +} + +void lag_init(void) +{ + settings_add_time("misc", "lag_check_time", "1min"); + settings_add_time("misc", "lag_max_before_disconnect", "5min"); + + timeout_tag = g_timeout_add(1000, (GSourceFunc) sig_check_lag, NULL); + signal_add_first("lag pong", (SIGNAL_FUNC) lag_event_pong); + signal_add("lag ping error", (SIGNAL_FUNC) lag_ping_error); + signal_add("event 421", (SIGNAL_FUNC) sig_unknown_command); +} + +void lag_deinit(void) +{ + g_source_remove(timeout_tag); + signal_remove("lag pong", (SIGNAL_FUNC) lag_event_pong); + signal_remove("lag ping error", (SIGNAL_FUNC) lag_ping_error); + signal_remove("event 421", (SIGNAL_FUNC) sig_unknown_command); +} diff --git a/src/irc/core/massjoin.c b/src/irc/core/massjoin.c new file mode 100644 index 0000000..15a9a9a --- /dev/null +++ b/src/irc/core/massjoin.c @@ -0,0 +1,379 @@ +/* + massjoin.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/irc-nicklist.h> + +static int massjoin_tag; +static int massjoin_max_joins; + +/* Massjoin support - really useful when trying to do things (like op/deop) + to people after netjoins. It sends + "massjoin #channel nick!user@host nick2!user@host ..." signals */ +static void event_join(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + char *params, *channel, *account, *realname, *ptr; + IRC_CHANNEL_REC *chanrec; + NICK_REC *nickrec; + GSList *nicks, *tmp; + gboolean send_massjoin; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, &channel, &account, &realname); + + ptr = strchr(channel, 7); /* ^G does something weird.. */ + if (ptr != NULL) *ptr = '\0'; + + /* find channel */ + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) { + g_free(params); + return; + } + + if (g_ascii_strcasecmp(nick, server->nick) == 0) { + /* do not overwrite our /UPGRADEd ownnick */ + if (chanrec->session_rejoin) { + g_free(params); + return; + } else { + /* You joined, do not massjoin */ + send_massjoin = FALSE; + } + } else { + send_massjoin = TRUE; + } + + /* check that the nick isn't already in nicklist. seems to happen + sometimes (server desyncs or something?) */ + nickrec = nicklist_find(CHANNEL(chanrec), nick); + if (nickrec != NULL) { + /* destroy the old record */ + nicklist_remove(CHANNEL(chanrec), nickrec); + } + + /* add user to nicklist */ + nickrec = irc_nicklist_insert(chanrec, nick, FALSE, FALSE, FALSE, send_massjoin, NULL); + if (nickrec == NULL) { + /* invalid nick? */ + g_free(params); + return; + } + if (*account != '\0' && g_strcmp0(nickrec->account, account) != 0) { + nicklist_set_account(CHANNEL(chanrec), nickrec, account); + } + + nicklist_set_host(CHANNEL(chanrec), nickrec, address); + + if (send_massjoin && chanrec->massjoins == 0) { + /* no nicks waiting in massjoin queue */ + chanrec->massjoin_start = time(NULL); + chanrec->last_massjoins = 0; + } + + if (nickrec->realname == NULL) { + /* Check if user is already in some other channel, + get the realname and other stuff from there */ + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + if (rec->realname != NULL) { + nickrec->last_check = rec->last_check; + nickrec->realname = g_strdup(rec->realname); + nickrec->gone = rec->gone; + nickrec->serverop = rec->serverop; + break; + } + } + g_slist_free(nicks); + } + + if (*realname != '\0' && g_strcmp0(nickrec->realname, realname) != 0) { + g_free(nickrec->realname); + nickrec->realname = g_strdup(realname); + } + + if (send_massjoin) { + chanrec->massjoins++; + } + g_free(params); +} + +static void event_chghost(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *old_address) +{ + char *params, *user, *host, *address; + GSList *nicks, *tmp; + + g_return_if_fail(nick != NULL); + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, &user, &host); + + /* check that the nick isn't already in nicklist. seems to happen + sometimes (server desyncs or something?) */ + nicks = nicklist_get_same(SERVER(server), nick); + address = nicks != NULL ? g_strconcat(user, "@", host, NULL) : NULL; + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + nicklist_set_host(CHANNEL(tmp->data), rec, address); + } + g_free(address); + g_slist_free(nicks); + g_free(params); +} + +static void event_account(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + char *params, *account; + GSList *nicks, *tmp; + + g_return_if_fail(nick != NULL); + g_return_if_fail(data != NULL); + + params = event_get_params(data, 1, &account); + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + NICK_REC *rec = tmp->next->data; + + nicklist_set_account(CHANNEL(tmp->data), rec, account); + } + g_slist_free(nicks); + g_free(params); +} + +static void event_part(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *channel, *reason; + IRC_CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + if (g_ascii_strcasecmp(nick, server->nick) == 0) { + /* you left channel, no need to do anything here */ + return; + } + + params = event_get_params(data, 2, &channel, &reason); + + /* find channel */ + chanrec = irc_channel_find(server, channel); + if (chanrec == NULL) { + g_free(params); + return; + } + + /* remove user from nicklist */ + nickrec = nicklist_find(CHANNEL(chanrec), nick); + if (nickrec != NULL) { + if (nickrec->send_massjoin) { + /* quick join/part after which it's useless to send + nick in massjoin */ + chanrec->massjoins--; + } + nicklist_remove(CHANNEL(chanrec), nickrec); + } + g_free(params); +} + +static void event_quit(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + IRC_CHANNEL_REC *channel; + NICK_REC *nickrec; + GSList *nicks, *tmp; + + g_return_if_fail(data != NULL); + + if (g_ascii_strcasecmp(nick, server->nick) == 0) { + /* you quit, don't do anything here */ + return; + } + + /* Remove nick from all channels */ + nicks = nicklist_get_same(SERVER(server), nick); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + nickrec = tmp->next->data; + + if (nickrec->send_massjoin) { + /* quick join/quit after which it's useless to + send nick in massjoin */ + channel->massjoins--; + } + nicklist_remove(CHANNEL(channel), nickrec); + } + g_slist_free(nicks); + + /* invalidate any outstanding accountqueries for the nick */ + irc_channels_query_purge_accountquery(server, nick); +} + +static void event_kick(IRC_SERVER_REC *server, const char *data) +{ + char *params, *channel, *nick, *reason; + IRC_CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, &channel, &nick, &reason); + + if (g_ascii_strcasecmp(nick, server->nick) == 0) { + /* you were kicked, no need to do anything */ + g_free(params); + return; + } + + /* Remove user from nicklist */ + chanrec = irc_channel_find(server, channel); + nickrec = chanrec == NULL ? NULL : + nicklist_find(CHANNEL(chanrec), nick); + + if (chanrec != NULL && nickrec != NULL) { + if (nickrec->send_massjoin) { + /* quick join/kick after which it's useless to + send nick in massjoin */ + chanrec->massjoins--; + } + nicklist_remove(CHANNEL(chanrec), nickrec); + } + + g_free(params); +} + +static void massjoin_send_hash(gpointer key, NICK_REC *nick, GSList **list) +{ + if (nick->send_massjoin) { + nick->send_massjoin = FALSE; + *list = g_slist_append(*list, nick); + } +} + +/* Send channel's massjoin list signal */ +static void massjoin_send(IRC_CHANNEL_REC *channel) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(channel->nicks, (GHFunc) massjoin_send_hash, &list); + + channel->massjoins = 0; + signal_emit("massjoin", 2, channel, list); + g_slist_free(list); +} + +static void server_check_massjoins(IRC_SERVER_REC *server, time_t max) +{ + GSList *tmp; + + /* + 1) First time always save massjoin count to last_massjoins + 2) Next time check if there's been less than massjoin_max_joins + (yes, the name is misleading..) joins since previous check. + yes) send a massjoin signal and reset last_massjoin count + no) unless we've waited for massjoin_max_wait seconds already, + goto 2. + + So, with single joins the massjoin signal is sent 1-2 seconds after + the join. + */ + + /* Scan all channels through for massjoins */ + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + IRC_CHANNEL_REC *rec = tmp->data; + + if (!IS_IRC_CHANNEL(rec) || rec->massjoins <= 0) + continue; + + if (rec->massjoin_start < max || /* We've waited long enough */ + (rec->last_massjoins > 0 && + rec->massjoins-massjoin_max_joins < rec->last_massjoins)) { /* Less than x joins since last check */ + /* send them */ + massjoin_send(rec); + } else { + /* Wait for some more.. */ + rec->last_massjoins = rec->massjoins; + } + } + +} + +static int sig_massjoin_timeout(void) +{ + GSList *tmp; + time_t max; + + max = time(NULL)-settings_get_int("massjoin_max_wait"); + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *server = tmp->data; + + if (IS_IRC_SERVER(server)) + server_check_massjoins(server, max); + } + + return 1; +} + +static void read_settings(void) +{ + massjoin_max_joins = settings_get_int("massjoin_max_joins"); +} + +void massjoin_init(void) +{ + settings_add_int("misc", "massjoin_max_wait", 5000); + settings_add_int("misc", "massjoin_max_joins", 3); + massjoin_tag = g_timeout_add(1000, (GSourceFunc) sig_massjoin_timeout, NULL); + + read_settings(); + signal_add_first("event join", (SIGNAL_FUNC) event_join); + signal_add("event chghost", (SIGNAL_FUNC) event_chghost); + signal_add("event account", (SIGNAL_FUNC) event_account); + signal_add("event part", (SIGNAL_FUNC) event_part); + signal_add("event kick", (SIGNAL_FUNC) event_kick); + signal_add("event quit", (SIGNAL_FUNC) event_quit); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void massjoin_deinit(void) +{ + g_source_remove(massjoin_tag); + + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event chghost", (SIGNAL_FUNC) event_chghost); + signal_remove("event account", (SIGNAL_FUNC) event_account); + signal_remove("event part", (SIGNAL_FUNC) event_part); + signal_remove("event kick", (SIGNAL_FUNC) event_kick); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/irc/core/meson.build b/src/irc/core/meson.build new file mode 100644 index 0000000..e6cc89c --- /dev/null +++ b/src/irc/core/meson.build @@ -0,0 +1,66 @@ +# this file is part of irssi + +libirc_core_a = static_library('irc_core', + files( + 'bans.c', + 'channel-events.c', + 'channel-rejoin.c', + 'channels-query.c', + 'ctcp.c', + 'irc-cap.c', + 'irc-channels-setup.c', + 'irc-channels.c', + 'irc-chatnets.c', + 'irc-commands.c', + 'irc-core.c', + 'irc-expandos.c', + 'irc-masks.c', + 'irc-nicklist.c', + 'irc-queries.c', + 'irc-servers-reconnect.c', + 'irc-servers-setup.c', + 'irc-servers.c', + 'irc-session.c', + 'irc.c', + 'lag.c', + 'massjoin.c', + 'mode-lists.c', + 'modes.c', + 'netsplit.c', + 'sasl.c', + 'servers-idle.c', + 'servers-redirect.c', + ), + include_directories : rootinc, + implicit_include_directories : false, + c_args : [ + def_moduledir, + def_sysconfdir, + ], + dependencies : dep) + +install_headers( + files( + 'bans.h', + 'channel-events.h', + 'channel-rejoin.h', + 'ctcp.h', + 'irc-cap.h', + 'irc-channels.h', + 'irc-chatnets.h', + 'irc-commands.h', + 'irc-masks.h', + 'irc-nicklist.h', + 'irc-queries.h', + 'irc-servers-setup.h', + 'irc-servers.h', + 'irc.h', + 'mode-lists.h', + 'modes.h', + 'module.h', + 'netsplit.h', + 'sasl.h', + 'servers-idle.h', + 'servers-redirect.h', + ), + subdir : incdir / 'src' / 'irc' / 'core') diff --git a/src/irc/core/mode-lists.c b/src/irc/core/mode-lists.c new file mode 100644 index 0000000..d2d7234 --- /dev/null +++ b/src/irc/core/mode-lists.c @@ -0,0 +1,142 @@ +/* + mode-lists.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/signals.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/mode-lists.h> + +static void ban_free(GSList **list, BAN_REC *rec) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(rec != NULL); + + *list = g_slist_remove(*list, rec); + + g_free(rec->ban); + g_free_not_null(rec->setby); + g_free(rec); +} + +void banlist_free(GSList *banlist) +{ + while (banlist != NULL) + ban_free(&banlist, banlist->data); +} + +BAN_REC *banlist_find(GSList *list, const char *ban) +{ + GSList *tmp; + + g_return_val_if_fail(ban != NULL, NULL); + + for (tmp = list; tmp != NULL; tmp = tmp->next) { + BAN_REC *rec = tmp->data; + + if (g_ascii_strcasecmp(rec->ban, ban) == 0) + return rec; + } + + return NULL; +} + +BAN_REC *banlist_add(IRC_CHANNEL_REC *channel, const char *ban, + const char *nick, time_t time) +{ + BAN_REC *rec; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(ban != NULL, NULL); + + rec = banlist_find(channel->banlist, ban); + if (rec != NULL) { + /* duplicate - ignore. some servers send duplicates + for non-ops because they just replace the hostname with + eg. "localhost"... */ + return NULL; + } + + rec = g_new(BAN_REC, 1); + rec->ban = g_strdup(ban); + rec->setby = nick == NULL || *nick == '\0' ? NULL : + g_strdup(nick); + rec->time = time; + + channel->banlist = g_slist_append(channel->banlist, rec); + + signal_emit("ban new", 2, channel, rec); + return rec; +} + +void banlist_remove(IRC_CHANNEL_REC *channel, const char *ban, const char *nick) +{ + BAN_REC *rec; + + g_return_if_fail(channel != NULL); + g_return_if_fail(ban != NULL); + + rec = banlist_find(channel->banlist, ban); + if (rec != NULL) { + signal_emit("ban remove", 3, channel, rec, nick); + ban_free(&channel->banlist, rec); + } +} + +static void channel_destroyed(IRC_CHANNEL_REC *channel) +{ + if (!IS_IRC_CHANNEL(channel)) + return; + + banlist_free(channel->banlist); +} + +static void event_banlist(IRC_SERVER_REC *server, const char *data) +{ + IRC_CHANNEL_REC *chanrec; + char *params, *channel, *ban, *setby, *tims; + time_t tim; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 5, NULL, &channel, &ban, &setby, &tims); + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL) { + tim = (time_t) atol(tims); + banlist_add(chanrec, ban, setby, tim); + } + g_free(params); +} + +void mode_lists_init(void) +{ + signal_add("channel destroyed", (SIGNAL_FUNC) channel_destroyed); + + signal_add("chanquery ban", (SIGNAL_FUNC) event_banlist); +} + +void mode_lists_deinit(void) +{ + signal_remove("channel destroyed", (SIGNAL_FUNC) channel_destroyed); + + signal_remove("chanquery ban", (SIGNAL_FUNC) event_banlist); +} diff --git a/src/irc/core/mode-lists.h b/src/irc/core/mode-lists.h new file mode 100644 index 0000000..2c9f20b --- /dev/null +++ b/src/irc/core/mode-lists.h @@ -0,0 +1,24 @@ +#ifndef IRSSI_IRC_CORE_MODE_LISTS_H +#define IRSSI_IRC_CORE_MODE_LISTS_H + +typedef struct { + char *ban; + char *setby; + time_t time; +} BAN_REC; + +BAN_REC *banlist_find(GSList *list, const char *ban); + +BAN_REC *banlist_add(IRC_CHANNEL_REC *channel, const char *ban, const char *nick, time_t time); +void banlist_remove(IRC_CHANNEL_REC *channel, const char *ban, const char *nick); + +BAN_REC *banlist_exception_add(IRC_CHANNEL_REC *channel, const char *ban, const char *nick, time_t time); +void banlist_exception_remove(IRC_CHANNEL_REC *channel, const char *ban); + +void invitelist_add(IRC_CHANNEL_REC *channel, const char *mask); +void invitelist_remove(IRC_CHANNEL_REC *channel, const char *mask); + +void mode_lists_init(void); +void mode_lists_deinit(void); + +#endif diff --git a/src/irc/core/modes.c b/src/irc/core/modes.c new file mode 100644 index 0000000..b0de2f1 --- /dev/null +++ b/src/irc/core/modes.c @@ -0,0 +1,932 @@ +/* + modes.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-commands.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/servers-redirect.h> +#include <irssi/src/irc/core/modes.h> +#include <irssi/src/irc/core/mode-lists.h> +#include <irssi/src/core/nicklist.h> + +/* Change nick's mode in channel */ +static void nick_mode_change(IRC_CHANNEL_REC *channel, const char *nick, + char mode, int type, const char *setby) +{ + NICK_REC *nickrec; + char modestr[2], typestr[2]; + + g_return_if_fail(IS_IRC_CHANNEL(channel)); + g_return_if_fail(nick != NULL); + + nickrec = nicklist_find(CHANNEL(channel), nick); + if (nickrec == NULL) return; /* No /names list got yet */ + + if (mode == '@') nickrec->op = type == '+'; + else if (mode == '+') nickrec->voice = type == '+'; + else if (mode == '%') nickrec->halfop = type == '+'; + if (channel->server->prefix[(unsigned char) mode] != '\0') { + if (type == '+') + prefix_add(nickrec->prefixes, mode, (SERVER_REC *) channel->server); + else + prefix_del(nickrec->prefixes, mode); + } + + modestr[0] = mode; modestr[1] = '\0'; + typestr[0] = type; typestr[1] = '\0'; + signal_emit("nick mode changed", 5, + channel, nickrec, setby, modestr, typestr); +} + +void prefix_add(char prefixes[MAX_USER_PREFIXES+1], char newprefix, SERVER_REC *server) +{ + const char *prefixlst; + char newprefixes[MAX_USER_PREFIXES+1]; /* to hold the new prefixes */ + unsigned int newpos = 0; /* to hold our position in the new prefixes */ + unsigned int oldpos = 0; /* to hold our position in the old prefixes */ + + prefixlst = server->get_nick_flags(server); + + /* go through the possible prefixes, copy higher ones, and find this one's place + * always leave room for the current prefix to be added, though. + */ + while (*prefixlst != '\0' && prefixes[oldpos] != '\0' && + newpos < MAX_USER_PREFIXES - 1) { + if (prefixes[oldpos] == newprefix) + return; /* already inserted. why are we here? */ + + if (*prefixlst == newprefix) + break; /* insert the new prefix here */ + + if (*prefixlst == prefixes[oldpos]) { + /* this prefix is present. + * the one we are inserting goes after it. + * copy it over, and continue searching. + */ + newprefixes[newpos++] = prefixes[oldpos++]; + } + prefixlst++; + } + + /* newpos is now the position in which we wish to insert the prefix */ + newprefixes[newpos++] = newprefix; + + /* finish copying the remaining prefixes */ + while (prefixes[oldpos] != '\0' && newpos < MAX_USER_PREFIXES) + newprefixes[newpos++] = prefixes[oldpos++]; + + newprefixes[newpos] = '\0'; + + strcpy(prefixes, newprefixes); +} + +void prefix_del(char prefixes[MAX_USER_PREFIXES+1], char oldprefix) +{ + char *todel; + + todel = strchr(prefixes, oldprefix); + if (todel) + memmove(todel, todel+1, strlen(todel)); +} + +static int mode_is_set(const char *str, char mode) +{ + char *end, *pos; + + g_return_val_if_fail(str != NULL, FALSE); + + end = strchr(str, ' '); + pos = strchr(str, mode); + return pos != NULL && (end == NULL || pos < end); +} + +/* add argument to specified position */ +static void mode_add_arg(GString *str, int pos, int updating, const char *arg) +{ + char *p; + + for (p = str->str; *p != '\0'; p++) { + if (*p != ' ') + continue; + + if (pos == 0) + break; + pos--; + } + + pos = (int) (p-str->str); + if (updating && *p != '\0') { + /* remove the old argument */ + p++; + while (*p != '\0' && *p != ' ') p++; + g_string_erase(str, pos, (int) (p-str->str)-pos); + } + + g_string_insert_c(str, pos, ' '); + g_string_insert(str, pos+1, arg); +} + +/* Add mode character to list sorted alphabetically */ +static void mode_add_sorted(IRC_SERVER_REC *server, GString *str, + char mode, const char *arg, int user) +{ + char *p; + int updating, argpos = 0; + + /* check that mode isn't already set */ + if ((!user && !HAS_MODE_ARG_SET(server, mode)) && + mode_is_set(str->str, mode)) + return; + + updating = FALSE; + for (p = str->str; *p != '\0' && *p != ' '; p++) { + if (mode < *p) + break; + if (mode == *p) { + updating = TRUE; + break; + } + if (!user && HAS_MODE_ARG_SET(server, *p)) + argpos++; + } + + /* .. GLib shouldn't fail when inserting at the end of the string */ + if (!updating) { + if (*p == '\0') + g_string_append_c(str, mode); + else + g_string_insert_c(str, (int) (p-str->str), mode); + } + if (arg != NULL) + mode_add_arg(str, argpos, updating, arg); +} + +/* remove the n'th argument */ +static void node_remove_arg(GString *str, int pos) +{ + char *p; + int startpos; + + startpos = -1; + for (p = str->str; *p != '\0'; p++) { + if (*p != ' ') + continue; + + if (pos < 0) + break; + if (pos == 0) + startpos = (int) (p-str->str); + pos--; + } + + if (startpos == -1) + return; /* not found */ + + g_string_erase(str, startpos, (int) (p-str->str)-startpos); +} + +/* remove mode (and it's argument) from string */ +static void mode_remove(IRC_SERVER_REC *server, GString *str, char mode, int user) +{ + char *p; + int argpos = 0; + + for (p = str->str; *p != '\0' && *p != ' '; p++) { + if (mode == *p) { + g_string_erase(str, (int) (p-str->str), 1); + if (!user && HAS_MODE_ARG_SET(server, mode)) + node_remove_arg(str, argpos); + break; + } + if (!user && HAS_MODE_ARG_SET(server, *p)) + argpos++; + } +} + +static void mode_set(IRC_SERVER_REC *server, GString *str, + char type, char mode, int user) +{ + g_return_if_fail(str != NULL); + + if (type == '-') + mode_remove(server, str, mode, user); + else + mode_add_sorted(server, str, mode, NULL, user); +} + +static void mode_set_arg(IRC_SERVER_REC *server, GString *str, + char type, char mode, const char *arg, int user) +{ + g_return_if_fail(str != NULL); + g_return_if_fail(type == '-' || arg != NULL); + + if (type == '-') + mode_remove(server, str, mode, user); + else + mode_add_sorted(server, str, mode, arg, user); +} + +/* Mode that needs a parameter of a mask for both setting and removing + (eg: bans) */ +void modes_type_a(IRC_CHANNEL_REC *channel, const char *setby, char type, + char mode, char *arg, GString *newmode) +{ + if (mode == 'b') { + if (type == '+') + banlist_add(channel, arg, setby, time(NULL)); + else + banlist_remove(channel, arg, setby); + } +} + +/* Mode that needs parameter for both setting and removing (eg: +k) */ +void modes_type_b(IRC_CHANNEL_REC *channel, const char *setby, char type, + char mode, char *arg, GString *newmode) +{ + if (mode == 'k') { + if (*arg == '\0' && type == '+') + arg = channel->key != NULL ? channel->key : "???"; + + if (arg != channel->key) { + g_free_and_null(channel->key); + if (type == '+') + channel->key = g_strdup(arg); + } + } + + mode_set_arg(channel->server, newmode, type, mode, arg, FALSE); +} + +/* Mode that needs parameter only for adding */ +void modes_type_c(IRC_CHANNEL_REC *channel, const char *setby, + char type, char mode, char *arg, GString *newmode) +{ + if (mode == 'l') { + channel->limit = type == '-' ? 0 : atoi(arg); + } + + mode_set_arg(channel->server, newmode, type, mode, arg, FALSE); +} + +/* Mode that takes no parameter */ +void modes_type_d(IRC_CHANNEL_REC *channel, const char *setby, + char type, char mode, char *arg, GString *newmode) +{ + mode_set(channel->server, newmode, type, mode, FALSE); +} + +void modes_type_prefix(IRC_CHANNEL_REC *channel, const char *setby, + char type, char mode, char *arg, GString *newmode) +{ + int umode = (unsigned char) mode; + + if (g_ascii_strcasecmp(channel->server->nick, arg) == 0) { + /* see if we need to update channel->chanop */ + const char *prefix = + g_hash_table_lookup(channel->server->isupport, "PREFIX"); + if (prefix != NULL && *prefix == '(') { + prefix++; + while (*prefix != ')' && *prefix != '\0') { + if (*prefix == mode) { + channel->chanop = type == '+'; + break; + } + if (*prefix == 'o') + break; + prefix++; + } + } else { + if (mode == 'o' || mode == 'O') + channel->chanop = type == '+'; + } + } + + nick_mode_change(channel, arg, channel->server->modes[umode].prefix, + type, setby); +} + +int channel_mode_is_set(IRC_CHANNEL_REC *channel, char mode) +{ + g_return_val_if_fail(IS_IRC_CHANNEL(channel), FALSE); + + return channel->mode == NULL ? FALSE : + mode_is_set(channel->mode, mode); +} + +/* Parse channel mode string */ +void parse_channel_modes(IRC_CHANNEL_REC *channel, const char *setby, + const char *mode, int update_key) +{ + IRC_SERVER_REC *server = channel->server; + GString *newmode; + char *dup, *modestr, *arg, *curmode, type, *old_key; + int umode; + + g_return_if_fail(IS_IRC_CHANNEL(channel)); + g_return_if_fail(mode != NULL); + + type = '+'; + newmode = g_string_new(channel->mode); + old_key = update_key ? NULL : g_strdup(channel->key); + + dup = modestr = g_strdup(mode); + curmode = cmd_get_param(&modestr); + while (*curmode != '\0') { + if (HAS_MODE_ARG(server, type, *curmode)) { + /* get the argument for the mode. NOTE: We don't + get the +k's argument when joining to channel. */ + arg = cmd_get_param(&modestr); + } else { + arg = NULL; + } + + switch (*curmode) { + case '+': + case '-': + type = *curmode; + break; + default: + umode = (unsigned char) *curmode; + if (server->modes[umode].func != NULL) { + server->modes[umode].func(channel, setby, + type, *curmode, arg, + newmode); + } else { + /* Treat unknown modes as ones without params */ + modes_type_d(channel, setby, type, *curmode, + arg, newmode); + } + } + + curmode++; + } + g_free(dup); + + if (channel->key != NULL && + strchr(channel->mode, 'k') == NULL && + strchr(newmode->str, 'k') == NULL) { + /* join was used with key but there's no key set + in channel modes.. */ + g_free(channel->key); + channel->key = NULL; + } else if (!update_key && old_key != NULL) { + /* get the old one back, just in case it was replaced */ + g_free(channel->key); + channel->key = old_key; + mode_set_arg(channel->server, newmode, '+', 'k', old_key, FALSE); + old_key = NULL; + } + + if (g_strcmp0(newmode->str, channel->mode) != 0) { + g_free(channel->mode); + channel->mode = g_strdup(newmode->str); + + signal_emit("channel mode changed", 2, channel, setby); + } + + g_string_free(newmode, TRUE); + g_free(old_key); +} + +/* add `mode' to `old' - return newly allocated mode. + `channel' specifies if we're parsing channel mode and we should try + to join mode arguments too. */ +char *modes_join(IRC_SERVER_REC *server, const char *old, + const char *mode, int channel) +{ + GString *newmode; + char *dup, *modestr, *curmode, type; + + g_return_val_if_fail(mode != NULL, NULL); + + type = '+'; + newmode = g_string_new(old); + + dup = modestr = g_strdup(mode); + curmode = cmd_get_param(&modestr); + while (*curmode != '\0' && *curmode != ' ') { + if (*curmode == '+' || *curmode == '-') { + type = *curmode; + curmode++; + continue; + } + + if (!channel || !HAS_MODE_ARG(server, type, *curmode)) + mode_set(server, newmode, type, *curmode, !channel); + else { + mode_set_arg(server, newmode, type, *curmode, + cmd_get_param(&modestr), !channel); + } + + curmode++; + } + g_free(dup); + + modestr = newmode->str; + g_string_free(newmode, FALSE); + return modestr; +} + +/* Parse user mode string */ +static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr) +{ + char *newmode, *oldmode; + + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(modestr != NULL); + + newmode = modes_join(NULL, server->usermode, modestr, FALSE); + oldmode = server->usermode; + server->usermode = newmode; + server->server_operator = ((strchr(newmode, 'o') != NULL) || (strchr(newmode, 'O') != NULL)); + + signal_emit("user mode changed", 2, server, oldmode); + g_free_not_null(oldmode); +} + +static void event_user_mode(IRC_SERVER_REC *server, const char *data) +{ + char *params, *nick, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &mode); + parse_user_mode(server, mode); + + g_free(params); +} + +static void event_mode(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + IRC_CHANNEL_REC *chanrec; + char *params, *channel, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, + &channel, &mode); + + if (!server_ischannel(SERVER(server), channel)) { + /* user mode change */ + parse_user_mode(server, mode); + } else { + /* channel mode change */ + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL) + parse_channel_modes(chanrec, nick, mode, TRUE); + } + + g_free(params); +} + +static void event_oper(IRC_SERVER_REC *server, const char *data) +{ + const char *opermode; + + opermode = settings_get_str("opermode"); + if (*opermode != '\0') + irc_send_cmdv(server, "MODE %s %s", server->nick, opermode); +} + +static void event_away(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + server->usermode_away = TRUE; + signal_emit("away mode changed", 1, server); +} + +static void event_unaway(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + server->usermode_away = FALSE; + g_free_and_null(server->away_reason); + signal_emit("away mode changed", 1, server); +} + +static void sig_req_usermode_change(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *target, *mode; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2 | PARAM_FLAG_GETREST, + &target, &mode); + if (!server_ischannel(SERVER(server), target)) { + /* we requested a user mode change, save this */ + mode = modes_join(NULL, server->wanted_usermode, mode, FALSE); + g_free_not_null(server->wanted_usermode); + server->wanted_usermode = mode; + } + + g_free(params); + + signal_emit("event mode", 4, server, data, nick, addr); +} + +void channel_set_singlemode(IRC_CHANNEL_REC *channel, const char *nicks, + const char *mode) +{ + GString *str; + int num, modepos; + char **nick, **nicklist; + + g_return_if_fail(IS_IRC_CHANNEL(channel)); + g_return_if_fail(nicks != NULL && mode != NULL); + if (*nicks == '\0') return; + + num = modepos = 0; + str = g_string_new(NULL); + + nicklist = g_strsplit(nicks, " ", -1); + for (nick = nicklist; *nick != NULL; nick++) { + if (**nick == '\0') + continue; + + if (num == 0) + { + g_string_printf(str, "MODE %s %s", + channel->name, mode); + modepos = str->len; + } else { + /* insert the mode string */ + g_string_insert(str, modepos, mode); + } + + g_string_append_printf(str, " %s", *nick); + + if (++num == channel->server->max_modes_in_cmd) { + /* max. modes / command reached, send to server */ + irc_send_cmd(channel->server, str->str); + num = 0; + } + } + if (num > 0) irc_send_cmd(channel->server, str->str); + + g_strfreev(nicklist); + g_string_free(str, TRUE); +} + +void channel_set_mode(IRC_SERVER_REC *server, const char *channel, + const char *mode) +{ + IRC_CHANNEL_REC *chanrec; + GString *tmode, *targs; + char *modestr, *curmode, *orig, type, prevtype; + int count; + + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(channel != NULL && mode != NULL); + + tmode = g_string_new(NULL); + targs = g_string_new(NULL); + count = 0; + + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL) + channel = chanrec->name; + + orig = modestr = g_strdup(mode); + + type = '+'; prevtype = '\0'; + curmode = cmd_get_param(&modestr); + for (;; curmode++) { + if (*curmode == '\0') { + /* support for +o nick +o nick2 */ + curmode = cmd_get_param(&modestr); + if (*curmode == '\0') + break; + } + + if (*curmode == '+' || *curmode == '-') { + type = *curmode; + continue; + } + + if (count == server->max_modes_in_cmd && + HAS_MODE_ARG(server, type, *curmode)) { + irc_send_cmdv(server, "MODE %s %s%s", + channel, tmode->str, targs->str); + + count = 0; prevtype = '\0'; + g_string_truncate(tmode, 0); + g_string_truncate(targs, 0); + } + + if (type != prevtype) { + prevtype = type; + g_string_append_c(tmode, type); + } + g_string_append_c(tmode, *curmode); + + if (HAS_MODE_ARG(server, type, *curmode)) { + char *arg; + + count++; + arg = cmd_get_param(&modestr); + if (*arg == '\0' && type == '-' && *curmode == 'k') { + /* "/mode #channel -k" - no reason why it + shouldn't work really, so append the key */ + IRC_CHANNEL_REC *chanrec; + + chanrec = irc_channel_find(server, channel); + if (chanrec != NULL && chanrec->key != NULL) + arg = chanrec->key; + } + + if (*arg != '\0') + g_string_append_printf(targs, " %s", arg); + } + } + + if (tmode->len > 0) { + irc_send_cmdv(server, "MODE %s %s%s", + channel, tmode->str, targs->str); + } + + g_string_free(tmode, TRUE); + g_string_free(targs, TRUE); + g_free(orig); +} + +static int get_wildcard_nicks(GString *output, const char *mask, + IRC_CHANNEL_REC *channel, int op, int voice) +{ + GSList *nicks, *tmp; + int count; + + g_return_val_if_fail(output != NULL, 0); + g_return_val_if_fail(mask != NULL, 0); + g_return_val_if_fail(IS_IRC_CHANNEL(channel), 0); + + count = 0; + nicks = nicklist_find_multiple(CHANNEL(channel), mask); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + if ((op == 1 && !rec->op) || (op == 0 && rec->op) || + (voice == 1 && !rec->voice) || (voice == 0 && rec->voice)) + continue; + + if (g_ascii_strcasecmp(rec->nick, channel->server->nick) == 0) + continue; + + g_string_append_printf(output, "%s ", rec->nick); + count++; + } + g_slist_free(nicks); + + return count; +} + +static char *get_nicks(IRC_SERVER_REC *server, WI_ITEM_REC *item, + const char *data, int op, int voice, + IRC_CHANNEL_REC **ret_channel) +{ + IRC_CHANNEL_REC *channel; + GString *str; + GHashTable *optlist; + char **matches, **match, *ret, *channame, *nicks; + void *free_arg; + int count, max_modes; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_OPTIONS | PARAM_FLAG_OPTCHAN_NAME, + item, "op", &optlist, &channame, &nicks)) + return NULL; + + if (*nicks == '\0') + return NULL; + + channel = irc_channel_find(server, channame); + if (channel == NULL) { + cmd_params_free(free_arg); + return NULL; + } + + str = g_string_new(NULL); + matches = g_strsplit(nicks, " ", -1); + for (match = matches; *match != NULL; match++) { + if (strchr(*match, '*') == NULL && + strchr(*match, '?') == NULL) { + /* no wildcards */ + g_string_append_printf(str, "%s ", *match); + } else { + count = get_wildcard_nicks(str, *match, channel, + op, voice); + max_modes = settings_get_int("max_wildcard_modes"); + if (max_modes > 0 && count > max_modes && + g_hash_table_lookup(optlist, "yes") == NULL) { + /* too many matches */ + g_string_free(str, TRUE); + g_strfreev(matches); + cmd_params_free(free_arg); + + signal_emit("error command", 1, + GINT_TO_POINTER(CMDERR_NOT_GOOD_IDEA)); + signal_stop(); + return NULL; + } + } + } + + if (str->len > 0) g_string_truncate(str, str->len-1); + ret = str->str; + g_string_free(str, FALSE); + g_strfreev(matches); + cmd_params_free(free_arg); + + *ret_channel = channel; + return ret; +} + +/* SYNTAX: OP <nicks> */ +static void cmd_op(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + IRC_CHANNEL_REC *channel; + char *nicks; + + CMD_IRC_SERVER(server); + + nicks = get_nicks(server, item, data, 0, -1, &channel); + if (nicks != NULL && *nicks != '\0') + channel_set_singlemode(channel, nicks, "+o"); + g_free_not_null(nicks); +} + +/* SYNTAX: DEOP <nicks> */ +static void cmd_deop(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + IRC_CHANNEL_REC *channel; + char *nicks; + + CMD_IRC_SERVER(server); + + nicks = get_nicks(server, item, data, 1, -1, &channel); + if (nicks != NULL && *nicks != '\0') + channel_set_singlemode(channel, nicks, "-o"); + g_free_not_null(nicks); +} + +/* SYNTAX: VOICE <nicks> */ +static void cmd_voice(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + IRC_CHANNEL_REC *channel; + char *nicks; + + CMD_IRC_SERVER(server); + + nicks = get_nicks(server, item, data, 0, 0, &channel); + if (nicks != NULL && *nicks != '\0') + channel_set_singlemode(channel, nicks, "+v"); + g_free_not_null(nicks); +} + +/* SYNTAX: DEVOICE <nicks> */ +static void cmd_devoice(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + IRC_CHANNEL_REC *channel; + char *nicks; + + CMD_IRC_SERVER(server); + + nicks = get_nicks(server, item, data, -1, 1, &channel); + if (nicks != NULL && *nicks != '\0') + channel_set_singlemode(channel, nicks, "-v"); + g_free_not_null(nicks); +} + +/* SYNTAX: MODE <your nick>|<channel> [<mode> [<mode parameters>]] */ +static void cmd_mode(const char *data, IRC_SERVER_REC *server, + IRC_CHANNEL_REC *channel) +{ + IRC_CHANNEL_REC *chanrec; + char *target, *mode; + void *free_arg; + + CMD_IRC_SERVER(server); + + if (*data == '+' || *data == '-') { + target = "*"; + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &mode)) + return; + } else { + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, &target, &mode)) + return; + } + + if (g_strcmp0(target, "*") == 0) { + if (!IS_IRC_CHANNEL(channel)) + cmd_param_error(CMDERR_NOT_JOINED); + + target = channel->name; + } + if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*mode == '\0') { + chanrec = irc_channel_find(server, target); + if (chanrec != NULL) + target = chanrec->name; + + irc_send_cmdv(server, "MODE %s", target); + } else if (server_ischannel(SERVER(server), target)) + channel_set_mode(server, target, mode); + else { + if (g_ascii_strcasecmp(target, server->nick) == 0) { + server_redirect_event(server, "mode user", 1, target, -1, NULL, + "event mode", "requested usermode change", NULL); + } + + irc_send_cmdv(server, "MODE %s %s", target, mode); + } + + cmd_params_free(free_arg); +} + +void modes_server_init(IRC_SERVER_REC *server) +{ + server->modes['b'].func = modes_type_a; + server->modes['e'].func = modes_type_a; + server->modes['I'].func = modes_type_a; + + server->modes['h'].func = modes_type_prefix; + server->modes['h'].prefix = '%'; + server->modes['o'].func = modes_type_prefix; + server->modes['o'].prefix = '@'; + server->modes['O'].func = modes_type_prefix; + server->modes['O'].prefix = '@'; + server->modes['v'].func = modes_type_prefix; + server->modes['v'].prefix = '+'; + + server->prefix['%'] = 'h'; + server->prefix['@'] = 'o'; + server->prefix['+'] = 'v'; + + server->modes['k'].func = modes_type_b; + server->modes['l'].func = modes_type_c; +} + +void modes_init(void) +{ + settings_add_str("misc", "opermode", ""); + settings_add_int("misc", "max_wildcard_modes", 6); + + signal_add("event 221", (SIGNAL_FUNC) event_user_mode); + signal_add("event 305", (SIGNAL_FUNC) event_unaway); + signal_add("event 306", (SIGNAL_FUNC) event_away); + signal_add("event 381", (SIGNAL_FUNC) event_oper); + signal_add("event mode", (SIGNAL_FUNC) event_mode); + signal_add("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change); + + command_bind_irc("op", NULL, (SIGNAL_FUNC) cmd_op); + command_bind_irc("deop", NULL, (SIGNAL_FUNC) cmd_deop); + command_bind_irc("voice", NULL, (SIGNAL_FUNC) cmd_voice); + command_bind_irc("devoice", NULL, (SIGNAL_FUNC) cmd_devoice); + command_bind_irc("mode", NULL, (SIGNAL_FUNC) cmd_mode); + + command_set_options("op", "yes"); +} + +void modes_deinit(void) +{ + signal_remove("event 221", (SIGNAL_FUNC) event_user_mode); + signal_remove("event 305", (SIGNAL_FUNC) event_unaway); + signal_remove("event 306", (SIGNAL_FUNC) event_away); + signal_remove("event 381", (SIGNAL_FUNC) event_oper); + signal_remove("event mode", (SIGNAL_FUNC) event_mode); + signal_remove("requested usermode change", (SIGNAL_FUNC) sig_req_usermode_change); + + command_unbind("op", (SIGNAL_FUNC) cmd_op); + command_unbind("deop", (SIGNAL_FUNC) cmd_deop); + command_unbind("voice", (SIGNAL_FUNC) cmd_voice); + command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice); + command_unbind("mode", (SIGNAL_FUNC) cmd_mode); +} diff --git a/src/irc/core/modes.h b/src/irc/core/modes.h new file mode 100644 index 0000000..f899a06 --- /dev/null +++ b/src/irc/core/modes.h @@ -0,0 +1,67 @@ +#ifndef IRSSI_IRC_CORE_MODES_H +#define IRSSI_IRC_CORE_MODES_H + +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/core/nicklist.h> /* MAX_USER_PREFIXES */ + +typedef void mode_func_t(IRC_CHANNEL_REC *, const char *, char, char, + char *, GString *); + +struct modes_type { + mode_func_t *func; + char prefix; +}; + +/* modes that have argument always */ +#define HAS_MODE_ARG_ALWAYS(server, mode) \ + (server->modes[(int)(unsigned char) mode].func == modes_type_a || \ + server->modes[(int)(unsigned char) mode].func == modes_type_b || \ + server->modes[(int)(unsigned char) mode].func == modes_type_prefix) + +/* modes that have argument when being set (+) */ +#define HAS_MODE_ARG_SET(server, mode) \ + (HAS_MODE_ARG_ALWAYS(server, mode) || \ + server->modes[(int)(unsigned char) mode].func == modes_type_c) + +/* modes that have argument when being unset (-) */ +#define HAS_MODE_ARG_UNSET(server, mode) \ + HAS_MODE_ARG_ALWAYS(server, mode) + +#define HAS_MODE_ARG(server, type, mode) \ + ((type) == '+' ? HAS_MODE_ARG_SET(server,mode) : \ + HAS_MODE_ARG_UNSET(server, mode)) + +#define GET_MODE_PREFIX(server, c) \ + ((server)->modes[(int)(unsigned char)c].prefix) +#define GET_PREFIX_MODE(server, c) \ + ((server)->prefix[(int)(unsigned char)c]) + +void modes_init(void); +void modes_deinit(void); +void modes_server_init(IRC_SERVER_REC *); + +/* add `mode' to `old' - return newly allocated mode. + `channel' specifies if we're parsing channel mode and we should try + to join mode arguments too. */ +char *modes_join(IRC_SERVER_REC *server, const char *old, const char *mode, int channel); + +int channel_mode_is_set(IRC_CHANNEL_REC *channel, char mode); + +void parse_channel_modes(IRC_CHANNEL_REC *channel, const char *setby, + const char *modestr, int update_key); + +void channel_set_singlemode(IRC_CHANNEL_REC *channel, const char *nicks, + const char *mode); +void channel_set_mode(IRC_SERVER_REC *server, const char *channel, + const char *mode); + +void prefix_add(char prefixes[MAX_USER_PREFIXES+1], char newprefix, SERVER_REC *server); +void prefix_del(char prefixes[MAX_USER_PREFIXES+1], char oldprefix); + +mode_func_t modes_type_a; +mode_func_t modes_type_b; +mode_func_t modes_type_c; +mode_func_t modes_type_d; +mode_func_t modes_type_prefix; + +#endif diff --git a/src/irc/core/module.h b/src/irc/core/module.h new file mode 100644 index 0000000..4180003 --- /dev/null +++ b/src/irc/core/module.h @@ -0,0 +1,4 @@ +#include <irssi/src/common.h> +#include <irssi/src/irc/core/irc.h> + +#define MODULE_NAME "irc/core" diff --git a/src/irc/core/netsplit.c b/src/irc/core/netsplit.c new file mode 100644 index 0000000..4a902c9 --- /dev/null +++ b/src/irc/core/netsplit.c @@ -0,0 +1,441 @@ +/* + netsplit.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/netsplit.h> + +/* How long to keep netsplits in memory (seconds) */ +#define NETSPLIT_MAX_REMEMBER (60*60) + +static int split_tag; + +static NETSPLIT_SERVER_REC *netsplit_server_find(IRC_SERVER_REC *server, + const char *servername, + const char *destserver) +{ + GSList *tmp; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + + for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) { + NETSPLIT_SERVER_REC *rec = tmp->data; + + if (g_ascii_strcasecmp(rec->server, servername) == 0 && + g_ascii_strcasecmp(rec->destserver, destserver) == 0) + return rec; + } + + return NULL; +} + +static NETSPLIT_SERVER_REC *netsplit_server_create(IRC_SERVER_REC *server, + const char *servername, + const char *destserver) +{ + NETSPLIT_SERVER_REC *rec; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + + rec = netsplit_server_find(server, servername, destserver); + if (rec != NULL) { + rec->last = time(NULL); + return rec; + } + + rec = g_new0(NETSPLIT_SERVER_REC, 1); + rec->last = time(NULL); + rec->server = g_strdup(servername); + rec->destserver = g_strdup(destserver); + + server->split_servers = g_slist_append(server->split_servers, rec); + signal_emit("netsplit server new", 2, server, rec); + + return rec; +} + +static void netsplit_server_destroy(IRC_SERVER_REC *server, + NETSPLIT_SERVER_REC *rec) +{ + g_return_if_fail(IS_IRC_SERVER(server)); + + server->split_servers = g_slist_remove(server->split_servers, rec); + + signal_emit("netsplit server remove", 2, server, rec); + + g_free(rec->server); + g_free(rec->destserver); + g_free(rec); +} + +static NETSPLIT_REC *netsplit_add(IRC_SERVER_REC *server, const char *nick, + const char *address, const char *servers) +{ + NETSPLIT_REC *rec; + NETSPLIT_CHAN_REC *splitchan; + NICK_REC *nickrec; + GSList *tmp; + char *p, *dupservers; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(address != NULL, NULL); + + /* get splitted servers */ + dupservers = g_strdup(servers); + p = strchr(dupservers, ' '); + if (p == NULL) { + g_free(dupservers); + g_warning("netsplit_add() : only one server found"); + return NULL; + } + *p++ = '\0'; + + rec = g_new0(NETSPLIT_REC, 1); + rec->nick = g_strdup(nick); + rec->address = g_strdup(address); + rec->destroy = time(NULL)+NETSPLIT_MAX_REMEMBER; + + rec->server = netsplit_server_create(server, dupservers, p); + rec->server->count++; + g_free(dupservers); + + /* copy the channel nick records.. */ + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + nickrec = nicklist_find(channel, nick); + if (nickrec == NULL) + continue; + + splitchan = g_new0(NETSPLIT_CHAN_REC, 1); + splitchan->name = g_strdup(channel->visible_name); + splitchan->op = nickrec->op; + splitchan->halfop = nickrec->halfop; + splitchan->voice = nickrec->voice; + memcpy(splitchan->prefixes, nickrec->prefixes, sizeof(splitchan->prefixes)); + + rec->channels = g_slist_append(rec->channels, splitchan); + } + + if (rec->channels == NULL) + g_warning("netsplit_add(): nick '%s' not in any channels", nick); + + g_hash_table_insert(server->splits, rec->nick, rec); + + signal_emit("netsplit new", 1, rec); + return rec; +} + +static void netsplit_destroy(IRC_SERVER_REC *server, NETSPLIT_REC *rec) +{ + GSList *tmp; + + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(rec != NULL); + + signal_emit("netsplit remove", 1, rec); + for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { + NETSPLIT_CHAN_REC *rec = tmp->data; + + g_free(rec->name); + g_free(rec); + } + g_slist_free(rec->channels); + + if (--rec->server->count == 0) + netsplit_server_destroy(server, rec->server); + + g_free(rec->nick); + g_free(rec->address); + g_free(rec); +} + +static void netsplit_destroy_hash(void *key, NETSPLIT_REC *rec, + IRC_SERVER_REC *server) +{ + netsplit_destroy(server, rec); +} + +NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, + const char *address) +{ + NETSPLIT_REC *rec; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_hash_table_lookup(server->splits, nick); + if (rec == NULL) return NULL; + + return (address == NULL || + g_ascii_strcasecmp(rec->address, address) == 0) ? rec : NULL; +} + +NETSPLIT_CHAN_REC *netsplit_find_channel(IRC_SERVER_REC *server, + const char *nick, const char *address, + const char *channel) +{ + NETSPLIT_REC *rec; + GSList *tmp; + + g_return_val_if_fail(IS_IRC_SERVER(server), NULL); + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(channel != NULL, NULL); + + rec = netsplit_find(server, nick, address); + if (rec == NULL) return NULL; + + for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) { + NETSPLIT_CHAN_REC *rec = tmp->data; + + if (g_ascii_strcasecmp(rec->name, channel) == 0) + return rec; + } + + return NULL; +} + +/* check if quit message is a netsplit message */ +int quitmsg_is_split(const char *msg) +{ + const char *host2, *p; + int prev, host1_dot, host2_dot; + + g_return_val_if_fail(msg != NULL, FALSE); + + /* NOTE: there used to be some paranoia checks (some older IRC + clients have even more), but they're pretty useless nowadays, + since IRC server prefixes the quit message with a space if it + looks like a netsplit message. + + So, the check is currently just: + - host1.domain1 host2.domain2 + - top-level domains have to be 2+ characters long, + containing only alphabets + - only 1 space + - no double-dots (".." - probably useless check) + - hosts/domains can't start or end with a dot + - the two hosts can't be identical (probably useless check) + - can't contain ':' or '/' chars (some servers allow URLs) + */ + host2 = NULL; + prev = '\0'; + host1_dot = host2_dot = 0; + while (*msg != '\0') { + if (*msg == ' ') { + if (prev == '.' || prev == '\0') { + /* domains can't end with '.', space can't + be the first character in msg. */ + return FALSE; + } + if (host2 != NULL) + return FALSE; /* only one space allowed */ + if (!host1_dot) + return FALSE; /* host1 didn't have domain */ + host2 = msg + 1; + } else if (*msg == '.') { + if (prev == '\0' || prev == ' ' || prev == '.') { + /* domains can't start with '.' + and can't have ".." */ + return FALSE; + } + + if (host2 != NULL) + host2_dot = TRUE; + else + host1_dot = TRUE; + } else if (*msg == ':' || *msg == '/') + return FALSE; + + prev = *msg; + msg++; + } + + if (!host2_dot || prev == '.') + return FALSE; + + /* top-domain1 must be 2+ chars long and contain only alphabets */ + p = host2-1; + while (p[-1] != '.') { + if (!i_isalpha(p[-1])) + return FALSE; + p--; + } + if (host2-p-1 < 2) return FALSE; + + /* top-domain2 must be 2+ chars long and contain only alphabets */ + p = host2+strlen(host2); + while (p[-1] != '.') { + if (!i_isalpha(p[-1])) + return FALSE; + p--; + } + if (strlen(p) < 2) return FALSE; + + return TRUE; +} + +static void split_set_timeout(void *key, NETSPLIT_REC *rec, NETSPLIT_REC *orig) +{ + /* same servers -> split over -> destroy old records sooner.. */ + if (rec->server == orig->server) + rec->destroy = time(NULL)+60; +} + +static void event_join(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + NETSPLIT_REC *rec; + + if (nick == NULL) + return; + + /* check if split is over */ + rec = g_hash_table_lookup(server->splits, nick); + + if (rec != NULL && g_ascii_strcasecmp(rec->address, address) == 0) { + /* yep, looks like it is. for same people that had the same + splitted servers set the timeout to one minute. + + .. if the user just changed server, she can't use the + same nick (unless the server is broken) so don't bother + checking that the nick's server matches the split. */ + g_hash_table_foreach(server->splits, + (GHFunc) split_set_timeout, rec); + } +} + +/* remove the nick from netsplit, but do it last so that other "event join" + signal handlers can check if the join was a netjoin */ +static void event_join_last(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + NETSPLIT_REC *rec; + + if (nick == NULL) + return; + + rec = g_hash_table_lookup(server->splits, nick); + if (rec != NULL) { + g_hash_table_remove(server->splits, rec->nick); + netsplit_destroy(server, rec); + } +} + +static void event_quit(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + g_return_if_fail(data != NULL); + + if (*data == ':') data++; + if (g_ascii_strcasecmp(nick, server->nick) != 0 && quitmsg_is_split(data)) { + /* netsplit! */ + netsplit_add(server, nick, address, data); + } +} + +static void event_nick(IRC_SERVER_REC *server, const char *data) +{ + NETSPLIT_REC *rec; + char *params, *nick; + + params = event_get_params(data, 1, &nick); + + /* remove nick from split list when somebody changed + nick to this one during split */ + rec = g_hash_table_lookup(server->splits, nick); + if (rec != NULL) { + g_hash_table_remove(server->splits, rec->nick); + netsplit_destroy(server, rec); + } + + g_free(params); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->splits == NULL) + return; + + g_hash_table_foreach(server->splits, + (GHFunc) netsplit_destroy_hash, server); + g_hash_table_destroy(server->splits); + server->splits = NULL; +} + +static int split_server_check(void *key, NETSPLIT_REC *rec, + IRC_SERVER_REC *server) +{ + /* Check if this split record is too old.. */ + if (rec->destroy > time(NULL)) + return FALSE; + + netsplit_destroy(server, rec); + return TRUE; +} + +static int split_check_old(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *server = tmp->data; + + if (!IS_IRC_SERVER(server)) + continue; + + g_hash_table_foreach_remove(server->splits, + (GHRFunc) split_server_check, + server); + } + + return 1; +} + +void netsplit_init(void) +{ + split_tag = g_timeout_add(1000, (GSourceFunc) split_check_old, NULL); + signal_add_first("event join", (SIGNAL_FUNC) event_join); + signal_add_last("event join", (SIGNAL_FUNC) event_join_last); + signal_add_first("event quit", (SIGNAL_FUNC) event_quit); + signal_add("event nick", (SIGNAL_FUNC) event_nick); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void netsplit_deinit(void) +{ + g_source_remove(split_tag); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("event join", (SIGNAL_FUNC) event_join_last); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/netsplit.h b/src/irc/core/netsplit.h new file mode 100644 index 0000000..50edf63 --- /dev/null +++ b/src/irc/core/netsplit.h @@ -0,0 +1,43 @@ +#ifndef IRSSI_IRC_CORE_NETSPLIT_H +#define IRSSI_IRC_CORE_NETSPLIT_H + +#include <irssi/src/core/nicklist.h> + +typedef struct { + char *server; + char *destserver; + int count; + int prints; /* temp variable */ + + time_t last; /* last time we received a QUIT msg here */ +} NETSPLIT_SERVER_REC; + +typedef struct { + NETSPLIT_SERVER_REC *server; + + char *nick; + char *address; + GSList *channels; + + unsigned int printed:1; + time_t destroy; +} NETSPLIT_REC; + +typedef struct { + char *name; + unsigned int op:1; + unsigned int halfop:1; + unsigned int voice:1; + char prefixes[MAX_USER_PREFIXES+1]; +} NETSPLIT_CHAN_REC; + +void netsplit_init(void); +void netsplit_deinit(void); + +NETSPLIT_REC *netsplit_find(IRC_SERVER_REC *server, const char *nick, const char *address); +NETSPLIT_CHAN_REC *netsplit_find_channel(IRC_SERVER_REC *server, const char *nick, const char *address, const char *channel); + +/* check if quit message is a netsplit message */ +int quitmsg_is_split(const char *msg); + +#endif diff --git a/src/irc/core/sasl.c b/src/irc/core/sasl.c new file mode 100644 index 0000000..9bc40ff --- /dev/null +++ b/src/irc/core/sasl.c @@ -0,0 +1,365 @@ +/* + fe-sasl.c : irssi + + Copyright (C) 2015 The Lemon Man + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-cap.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/sasl.h> + +/* + * Based on IRCv3 SASL Extension Specification: + * http://ircv3.net/specs/extensions/sasl-3.1.html + */ +#define AUTHENTICATE_CHUNK_SIZE 400 /* bytes */ + +/* + * Maximum size to allow the buffer to grow to before the next fragment comes in. Note that + * due to the way fragmentation works, the maximum message size will actually be: + * floor(AUTHENTICATE_MAX_SIZE / AUTHENTICATE_CHUNK_SIZE) + AUTHENTICATE_CHUNK_SIZE - 1 + */ +#define AUTHENTICATE_MAX_SIZE 8192 /* bytes */ + +#define SASL_TIMEOUT (20 * 1000) /* ms */ + +static gboolean sasl_timeout(IRC_SERVER_REC *server) +{ + /* The authentication timed out, we can't do much beside terminating it */ + irc_send_cmd_now(server, "AUTHENTICATE *"); + irc_cap_finish_negotiation(server); + + server->sasl_timeout = 0; + server->sasl_success = FALSE; + + signal_emit("server sasl failure", 2, server, "The authentication timed out"); + + return FALSE; +} + +static void sasl_timeout_stop(IRC_SERVER_REC *server) +{ + /* Stop any pending timeout, if any */ + if (server->sasl_timeout != 0) { + g_source_remove(server->sasl_timeout); + server->sasl_timeout = 0; + } +} + +static void sasl_start(IRC_SERVER_REC *server, const char *data, const char *from) +{ + IRC_SERVER_CONNECT_REC *conn; + + sasl_timeout_stop(server); + + conn = server->connrec; + + switch (conn->sasl_mechanism) { + case SASL_MECHANISM_PLAIN: + irc_send_cmd_now(server, "AUTHENTICATE PLAIN"); + break; + + case SASL_MECHANISM_EXTERNAL: + irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL"); + break; + } + server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); +} + +static void sasl_fail(IRC_SERVER_REC *server, const char *data, const char *from) +{ + char *params, *error; + + + params = event_get_params(data, 2, NULL, &error); + + server->sasl_success = FALSE; + + signal_emit("server sasl failure", 2, server, error); + + /* Terminate the negotiation */ + irc_cap_finish_negotiation(server); + + g_free(params); +} + +static void sasl_already(IRC_SERVER_REC *server, const char *data, const char *from) +{ + sasl_timeout_stop(server); + + server->sasl_success = TRUE; + + signal_emit("server sasl success", 1, server); + + /* We're already authenticated, do nothing */ + irc_cap_finish_negotiation(server); +} + +static void sasl_success(IRC_SERVER_REC *server, const char *data, const char *from) +{ + sasl_timeout_stop(server); + + server->sasl_success = TRUE; + + signal_emit("server sasl success", 1, server); + + /* The authentication succeeded, time to finish the CAP negotiation */ + irc_cap_finish_negotiation(server); +} + +/* + * Responsible for reassembling incoming SASL requests. SASL requests must be split + * into 400 byte requests to stay below the IRC command length limit of 512 bytes. + * The spec says that if there is 400 bytes, then there is expected to be a + * continuation in the next chunk. If a message is exactly a multiple of 400 bytes, + * there must be a blank message of "AUTHENTICATE +" to indicate the end. + * + * This function returns the fully reassembled and decoded AUTHENTICATION message if + * completed or NULL if there are more messages expected. + */ +static gboolean sasl_reassemble_incoming(IRC_SERVER_REC *server, const char *fragment, GString **decoded) +{ + GString *enc_req; + gsize fragment_len; + + fragment_len = strlen(fragment); + + /* Check if there is an existing fragment to prepend. */ + if (server->sasl_buffer != NULL) { + if (g_strcmp0("+", fragment) == 0) { + enc_req = server->sasl_buffer; + } else { + enc_req = g_string_append_len(server->sasl_buffer, fragment, fragment_len); + } + server->sasl_buffer = NULL; + } else { + enc_req = g_string_new_len(fragment, fragment_len); + } + + /* + * Fail authentication with this server. They have sent too much data. + */ + if (enc_req->len > AUTHENTICATE_MAX_SIZE) { + g_string_free(enc_req, TRUE); + return FALSE; + } + + /* + * If the the request is exactly the chunk size, this is a fragment + * and more data is expected. + */ + if (fragment_len == AUTHENTICATE_CHUNK_SIZE) { + server->sasl_buffer = enc_req; + return TRUE; + } + + if (enc_req->len == 1 && *enc_req->str == '+') { + *decoded = g_string_new_len("", 0); + } else { + gsize dec_len; + gint state = 0; + guint save = 0; + + /* Since we're not going to use the enc_req GString anymore we + * can perform the decoding in place. */ + dec_len = g_base64_decode_step(enc_req->str, enc_req->len, + (guchar *)enc_req->str, + &state, &save); + /* A copy of the data is made when the GString is created. */ + *decoded = g_string_new_len(enc_req->str, dec_len); + } + + g_string_free(enc_req, TRUE); + return TRUE; +} + +/* + * Splits the response into appropriately sized chunks for the AUTHENTICATION + * command to be sent to the IRC server. If |response| is NULL, then the empty + * response is sent to the server. + */ +void sasl_send_response(IRC_SERVER_REC *server, GString *response) +{ + char *enc; + size_t offset, enc_len, chunk_len; + + if (response == NULL) { + irc_send_cmdv(server, "AUTHENTICATE +"); + return; + } + + enc = g_base64_encode((guchar *) response->str, response->len); + enc_len = strlen(enc); + + for (offset = 0; offset < enc_len; offset += AUTHENTICATE_CHUNK_SIZE) { + chunk_len = enc_len - offset; + if (chunk_len > AUTHENTICATE_CHUNK_SIZE) + chunk_len = AUTHENTICATE_CHUNK_SIZE; + + irc_send_cmdv(server, "AUTHENTICATE %.*s", (int) chunk_len, enc + offset); + } + + if (offset == enc_len) { + irc_send_cmdv(server, "AUTHENTICATE +"); + } + g_free(enc); +} + +/* + * Called when the incoming SASL request is completely received. + */ +static void sasl_step_complete(IRC_SERVER_REC *server, GString *data) +{ + IRC_SERVER_CONNECT_REC *conn; + GString *resp; + + conn = server->connrec; + + switch (conn->sasl_mechanism) { + case SASL_MECHANISM_PLAIN: + /* At this point we assume that conn->sasl_{username, password} are non-NULL. + * The PLAIN mechanism expects a NULL-separated string composed by the authorization identity, the + * authentication identity and the password. + * The authorization identity field is explicitly set to the user provided username. + */ + + resp = g_string_new(NULL); + + g_string_append(resp, conn->sasl_username); + g_string_append_c(resp, '\0'); + g_string_append(resp, conn->sasl_username); + g_string_append_c(resp, '\0'); + g_string_append(resp, conn->sasl_password); + + sasl_send_response(server, resp); + g_string_free(resp, TRUE); + + break; + + case SASL_MECHANISM_EXTERNAL: + /* Empty response */ + sasl_send_response(server, NULL); + break; + } +} + +static void sasl_step_fail(IRC_SERVER_REC *server) +{ + irc_send_cmd_now(server, "AUTHENTICATE *"); + irc_cap_finish_negotiation(server); + + sasl_timeout_stop(server); + + signal_emit("server sasl failure", 2, server, "The server sent an invalid payload"); +} + +static void sasl_step(IRC_SERVER_REC *server, const char *data, const char *from) +{ + GString *req = NULL; + + sasl_timeout_stop(server); + + if (!sasl_reassemble_incoming(server, data, &req)) { + sasl_step_fail(server); + return; + } + + if (req != NULL) { + sasl_step_complete(server, req); + g_string_free(req, TRUE); + } + + /* We expect a response within a reasonable time */ + server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); +} + +static void sasl_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) { + return; + } + + sasl_timeout_stop(server); +} + +static void sig_sasl_over(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + /* The negotiation has now been terminated, if we didn't manage to + * authenticate successfully with the server just disconnect. */ + if (!server->sasl_success && + server->connrec->sasl_mechanism != SASL_MECHANISM_NONE) { + if (server->cap_supported == NULL || + !g_hash_table_lookup_extended(server->cap_supported, "sasl", NULL, NULL)) { + signal_emit("server sasl failure", 2, server, "The server did not offer SASL"); + } + + if (settings_get_bool("sasl_disconnect_on_failure")) { + /* We can't use server_disconnect() here because we'd end up + * freeing the 'server' object and be guilty of a slew of UaF. */ + server->connection_lost = TRUE; + /* By setting connection_lost we make sure the communication is + * halted and when the control goes back to irc_parse_incoming + * the server object is safely destroyed. */ + signal_stop(); + } + } + +} + +void sasl_init(void) +{ + settings_add_bool("server", "sasl_disconnect_on_failure", TRUE); + + signal_add_first("event 001", (SIGNAL_FUNC) sig_sasl_over); + /* this event can get us connected on broken ircds, see irc-servers.c */ + signal_add_first("event 375", (SIGNAL_FUNC) sig_sasl_over); + signal_add_first("server cap ack sasl", (SIGNAL_FUNC) sasl_start); + signal_add_first("server cap end", (SIGNAL_FUNC) sig_sasl_over); + signal_add_first("event authenticate", (SIGNAL_FUNC) sasl_step); + signal_add_first("event 903", (SIGNAL_FUNC) sasl_success); + signal_add_first("event 902", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 904", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 905", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 906", (SIGNAL_FUNC) sasl_fail); + signal_add_first("event 907", (SIGNAL_FUNC) sasl_already); + signal_add_first("server disconnected", (SIGNAL_FUNC) sasl_disconnected); +} + +void sasl_deinit(void) +{ + signal_remove("event 001", (SIGNAL_FUNC) sig_sasl_over); + signal_remove("event 375", (SIGNAL_FUNC) sig_sasl_over); + signal_remove("server cap ack sasl", (SIGNAL_FUNC) sasl_start); + signal_remove("server cap end", (SIGNAL_FUNC) sig_sasl_over); + signal_remove("event authenticate", (SIGNAL_FUNC) sasl_step); + signal_remove("event 903", (SIGNAL_FUNC) sasl_success); + signal_remove("event 902", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 904", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 905", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 906", (SIGNAL_FUNC) sasl_fail); + signal_remove("event 907", (SIGNAL_FUNC) sasl_already); + signal_remove("server disconnected", (SIGNAL_FUNC) sasl_disconnected); +} diff --git a/src/irc/core/sasl.h b/src/irc/core/sasl.h new file mode 100644 index 0000000..04d0cd9 --- /dev/null +++ b/src/irc/core/sasl.h @@ -0,0 +1,34 @@ +/* + fe-sasl.c : irssi + + Copyright (C) 2015 The Lemon Man + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef IRSSI_IRC_CORE_SASL_H +#define IRSSI_IRC_CORE_SASL_H + +enum { + SASL_MECHANISM_NONE = 0, + SASL_MECHANISM_PLAIN, + SASL_MECHANISM_EXTERNAL, + SASL_MECHANISM_MAX +}; + +void sasl_init(void); +void sasl_deinit(void); + +#endif diff --git a/src/irc/core/servers-idle.c b/src/irc/core/servers-idle.c new file mode 100644 index 0000000..93c8f05 --- /dev/null +++ b/src/irc/core/servers-idle.c @@ -0,0 +1,264 @@ +/* + server-idle.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-idle.h> +#include <irssi/src/irc/core/servers-redirect.h> + +typedef struct { + char *cmd; + char *arg; + int tag; + + char *redirect_cmd; + int count; + int remote; + char *failure_signal; + GSList *redirects; +} SERVER_IDLE_REC; + +static int idle_tag, idlepos; + +/* Add new idle command to queue */ +static SERVER_IDLE_REC * +server_idle_create(const char *cmd, const char *redirect_cmd, int count, + const char *arg, int remote, const char *failure_signal, + va_list va) +{ + SERVER_IDLE_REC *rec; + const char *event, *signal; + + g_return_val_if_fail(cmd != NULL, FALSE); + + rec = g_new0(SERVER_IDLE_REC, 1); + rec->cmd = g_strdup(cmd); + rec->arg = g_strdup(arg); + rec->tag = ++idlepos; + + rec->redirect_cmd = g_strdup(redirect_cmd); + rec->count = count; + rec->remote = remote; + rec->failure_signal = g_strdup(failure_signal); + + while ((event = va_arg(va, const char *)) != NULL) { + signal = va_arg(va, const char *); + if (signal == NULL) { + g_warning("server_idle_create(%s): " + "signal not specified for event", + redirect_cmd); + break; + } + + rec->redirects = + g_slist_append(rec->redirects, g_strdup(event)); + rec->redirects = + g_slist_append(rec->redirects, g_strdup(signal)); + } + + return rec; +} + +static SERVER_IDLE_REC *server_idle_find_rec(IRC_SERVER_REC *server, int tag) +{ + GSList *tmp; + + g_return_val_if_fail(server != NULL, FALSE); + + for (tmp = server->idles; tmp != NULL; tmp = tmp->next) { + SERVER_IDLE_REC *rec = tmp->data; + + if (rec->tag == tag) + return rec; + } + + return NULL; +} + +/* Add new idle command to queue */ +int server_idle_add_redir(IRC_SERVER_REC *server, const char *cmd, + const char *redirect_cmd, int count, const char *arg, + int remote, const char *failure_signal, ...) +{ + va_list va; + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, -1); + + va_start(va, failure_signal); + rec = server_idle_create(cmd, redirect_cmd, count, arg, remote, + failure_signal, va); + server->idles = g_slist_append(server->idles, rec); + va_end(va); + + return rec->tag; +} + +/* Add new idle command to first of queue */ +int server_idle_add_first_redir(IRC_SERVER_REC *server, const char *cmd, + const char *redirect_cmd, int count, + const char *arg, int remote, + const char *failure_signal, ...) +{ + va_list va; + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, -1); + + va_start(va, failure_signal); + rec = server_idle_create(cmd, redirect_cmd, count, arg, remote, + failure_signal, va); + server->idles = g_slist_prepend(server->idles, rec); + va_end(va); + + return rec->tag; +} + +/* Add new idle command to specified position of queue */ +int server_idle_insert_redir(IRC_SERVER_REC *server, const char *cmd, int tag, + const char *redirect_cmd, int count, + const char *arg, int remote, + const char *failure_signal, ...) +{ + va_list va; + SERVER_IDLE_REC *rec; + int pos; + + g_return_val_if_fail(server != NULL, -1); + + va_start(va, failure_signal); + + /* find the position of tag in idle list */ + rec = server_idle_find_rec(server, tag); + pos = g_slist_index(server->idles, rec); + + rec = server_idle_create(cmd, redirect_cmd, count, arg, remote, + failure_signal, va); + server->idles = pos < 0 ? + g_slist_append(server->idles, rec) : + g_slist_insert(server->idles, rec, pos); + va_end(va); + + return rec->tag; +} + +static void server_idle_destroy(IRC_SERVER_REC *server, SERVER_IDLE_REC *rec) +{ + g_return_if_fail(server != NULL); + + server->idles = g_slist_remove(server->idles, rec); + + g_slist_foreach(rec->redirects, (GFunc) g_free, NULL); + g_slist_free(rec->redirects); + + g_free_not_null(rec->arg); + g_free_not_null(rec->redirect_cmd); + g_free_not_null(rec->failure_signal); + g_free(rec->cmd); + g_free(rec); +} + +/* Check if record is still in queue */ +int server_idle_find(IRC_SERVER_REC *server, int tag) +{ + return server_idle_find_rec(server, tag) != NULL; +} + +/* Remove record from idle queue */ +int server_idle_remove(IRC_SERVER_REC *server, int tag) +{ + SERVER_IDLE_REC *rec; + + g_return_val_if_fail(server != NULL, FALSE); + + rec = server_idle_find_rec(server, tag); + if (rec == NULL) + return FALSE; + + server_idle_destroy(server, rec); + return TRUE; +} + +/* Execute next idle command */ +static void server_idle_next(IRC_SERVER_REC *server) +{ + SERVER_IDLE_REC *rec; + + g_return_if_fail(server != NULL); + + if (server->idles == NULL) + return; + rec = server->idles->data; + + /* Send command */ + if (rec->redirect_cmd != NULL) { + server_redirect_event_list(server, rec->redirect_cmd, + rec->count, rec->arg, + rec->remote, rec->failure_signal, + rec->redirects); + } + irc_send_cmd(server, rec->cmd); + + server_idle_destroy(server, rec); +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + while (server->idles != NULL) + server_idle_destroy(server, server->idles->data); +} + +static int sig_idle_timeout(void) +{ + GSList *tmp; + + /* Scan through every server */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + if (IS_IRC_SERVER(rec) && + rec->idles != NULL && rec->cmdcount == 0) { + /* We're idling and we have idle commands to run! */ + server_idle_next(rec); + } + } + return 1; +} + +void servers_idle_init(void) +{ + idlepos = 0; + idle_tag = g_timeout_add(1000, (GSourceFunc) sig_idle_timeout, NULL); + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void servers_idle_deinit(void) +{ + g_source_remove(idle_tag); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/servers-idle.h b/src/irc/core/servers-idle.h new file mode 100644 index 0000000..b0aed72 --- /dev/null +++ b/src/irc/core/servers-idle.h @@ -0,0 +1,37 @@ +#ifndef IRSSI_IRC_CORE_SERVERS_IDLE_H +#define IRSSI_IRC_CORE_SERVERS_IDLE_H + +/* Add new idle command to queue */ +int server_idle_add_redir(IRC_SERVER_REC *server, const char *cmd, + const char *redirect_cmd, int count, + const char *arg, int remote, + const char *failure_signal, ...); +#define server_idle_add(server, cmd) \ + server_idle_add_redir(server, cmd, NULL, 0, NULL, 0, NULL, NULL) + +/* Add new idle command to first of queue */ +int server_idle_add_first_redir(IRC_SERVER_REC *server, const char *cmd, + const char *redirect_cmd, int count, + const char *arg, int remote, + const char *failure_signal, ...); +#define server_idle_add_first(server, cmd) \ + server_idle_add_first_redir(server, cmd, NULL, 0, NULL, 0, NULL, NULL) + +/* Add new idle command to specified position of queue */ +int server_idle_insert_redir(IRC_SERVER_REC *server, const char *cmd, int tag, + const char *redirect_cmd, int count, + const char *arg, int remote, + const char *failure_signal, ...); +#define server_idle_insert(server, cmd, tag) \ + server_idle_insert_redir(server, cmd, tag, NULL, 0, NULL, 0, NULL, NULL) + +/* Check if record is still in queue */ +int server_idle_find(IRC_SERVER_REC *server, int tag); + +/* Remove record from idle queue */ +int server_idle_remove(IRC_SERVER_REC *server, int tag); + +void servers_idle_init(void); +void servers_idle_deinit(void); + +#endif diff --git a/src/irc/core/servers-redirect.c b/src/irc/core/servers-redirect.c new file mode 100644 index 0000000..5056088 --- /dev/null +++ b/src/irc/core/servers-redirect.c @@ -0,0 +1,809 @@ +/* + server-redirect.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/rawlog.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-redirect.h> + +#define DEFAULT_REDIRECT_TIMEOUT 60 + +/* Allow one non-expected redirections to come before the expected one + before aborting it. Some IRC bouncers/proxies reply to eg. PINGs + immediately. */ +#define MAX_FAILURE_COUNT 1 + +typedef struct { + char *name; + int refcount; + + int remote; + int timeout; + int pos; + GSList *start, *stop, *opt; /* char *event, int argpos, ... */ +} REDIRECT_CMD_REC; + +struct _REDIRECT_REC { + REDIRECT_CMD_REC *cmd; + time_t created; + int failures; + char *prefix; + + unsigned int destroyed:1; + unsigned int aborted:1; + unsigned int remote:1; + unsigned int first_signal_sent:1; + + char *arg; + int count; + char *failure_signal, *default_signal, *first_signal, *last_signal; + GSList *signals; /* event, signal, ... */ +}; + +static GHashTable *command_redirects; /* "command xxx" : REDIRECT_CMD_REC* */ + +/* Find redirection command record for specified command line. */ +static REDIRECT_CMD_REC *redirect_cmd_find(const char *command) +{ + REDIRECT_CMD_REC *rec; + const char *p; + char *cmd; + + p = strchr(command, ' '); + if (p == NULL) + rec = g_hash_table_lookup(command_redirects, command); + else { + cmd = g_strndup(command, (int) (p-command)); + rec = g_hash_table_lookup(command_redirects, cmd); + g_free(cmd); + } + return rec; +} + +static void redirect_cmd_destroy(REDIRECT_CMD_REC *rec) +{ + GSList *tmp; + + for (tmp = rec->start; tmp != NULL; tmp = tmp->next->next) + g_free(tmp->data); + for (tmp = rec->stop; tmp != NULL; tmp = tmp->next->next) + g_free(tmp->data); + for (tmp = rec->opt; tmp != NULL; tmp = tmp->next->next) + g_free(tmp->data); + g_slist_free(rec->start); + g_slist_free(rec->stop); + g_slist_free(rec->opt); + g_free(rec->name); + g_free(rec); +} + +static void redirect_cmd_ref(REDIRECT_CMD_REC *rec) +{ + rec->refcount++; +} + +static void redirect_cmd_unref(REDIRECT_CMD_REC *rec) +{ + if (--rec->refcount <= 0) + redirect_cmd_destroy(rec); +} + +void server_redirect_destroy(REDIRECT_REC *rec) +{ + redirect_cmd_unref(rec->cmd); + + g_free_not_null(rec->prefix); + g_free_not_null(rec->arg); + g_free_not_null(rec->failure_signal); + g_free_not_null(rec->default_signal); + g_free_not_null(rec->first_signal); + g_free_not_null(rec->last_signal); + g_slist_foreach(rec->signals, (GFunc) g_free, NULL); + g_slist_free(rec->signals); + g_free(rec); +} + +void server_redirect_register(const char *command, + int remote, int timeout, ...) +{ + va_list va; + GSList *start, *stop, *opt, **list; + const char *event; + int argpos; + + va_start(va, timeout); + start = stop = opt = NULL; list = &start; + for (;;) { + event = va_arg(va, const char *); + if (event == NULL) { + if (list == &start) + list = &stop; + else if (list == &stop) + list = &opt; + else + break; + continue; + } + + argpos = va_arg(va, int); + *list = g_slist_append(*list, g_strdup(event)); + *list = g_slist_append(*list, GINT_TO_POINTER(argpos)); + } + + va_end(va); + + server_redirect_register_list(command, remote, timeout, start, stop, opt, 0); +} + +void server_redirect_register_list(const char *command, int remote, int timeout, GSList *start, + GSList *stop, GSList *opt, int pos) +{ + REDIRECT_CMD_REC *rec; + gpointer key, value; + + g_return_if_fail(command != NULL); + g_return_if_fail(stop != NULL); + + if (g_hash_table_lookup_extended(command_redirects, command, + &key, &value)) { + /* Already registered - might have changed so destroy + the old one */ + g_hash_table_remove(command_redirects, command); + redirect_cmd_unref(value); + } + + rec = g_new0(REDIRECT_CMD_REC, 1); + redirect_cmd_ref(rec); + rec->name = g_strdup(command); + rec->remote = remote; + rec->timeout = timeout > 0 ? timeout : DEFAULT_REDIRECT_TIMEOUT; + rec->start = start; + rec->stop = stop; + rec->opt = opt; + rec->pos = pos; + g_hash_table_insert(command_redirects, rec->name, rec); +} + +void server_redirect_event(IRC_SERVER_REC *server, const char *command, + int count, const char *arg, int remote, + const char *failure_signal, ...) +{ + GSList *signals; + const char *event, *signal; + va_list va; + + va_start(va, failure_signal); + signals = NULL; + while ((event = va_arg(va, const char *)) != NULL) { + signal = va_arg(va, const char *); + if (signal == NULL) { + g_warning("server_redirect_event(%s): " + "signal not specified for event", command); + break; + } + + signals = g_slist_append(signals, g_strdup(event)); + signals = g_slist_append(signals, g_strdup(signal)); + } + + va_end(va); + + server_redirect_event_list(server, command, count, arg, remote, + failure_signal, signals); +} + +/* Find specified event from signals list. If it's found, remove it from the + list and return it's target signal. */ +static char *signal_list_move(GSList **signals, const char *event) +{ + GSList *link; + char *linkevent, *linksignal; + + link = i_slist_find_string(*signals, event); + if (link == NULL) + return NULL; + + linkevent = link->data; + linksignal = link->next->data; + + *signals = g_slist_remove(*signals, linkevent); + *signals = g_slist_remove(*signals, linksignal); + + g_free(linkevent); + return linksignal; +} + +void server_redirect_event_list(IRC_SERVER_REC *server, const char *command, + int count, const char *arg, int remote, + const char *failure_signal, GSList *signals) +{ + REDIRECT_CMD_REC *cmdrec; + REDIRECT_REC *rec; + + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(command != NULL); + g_return_if_fail((g_slist_length(signals) & 1) == 0); + + cmdrec = g_hash_table_lookup(command_redirects, command); + if (cmdrec == NULL) { + g_warning("Unknown redirection command: %s", command); + return; + } + + redirect_cmd_ref(cmdrec); + + rec = g_new0(REDIRECT_REC, 1); + rec->created = time(NULL); + rec->cmd = cmdrec; + rec->arg = g_strdup(arg); + rec->count = count; + rec->remote = remote != -1 ? remote : cmdrec->remote; + rec->failure_signal = g_strdup(failure_signal); + + rec->default_signal = signal_list_move(&signals, ""); + rec->first_signal = signal_list_move(&signals, "redirect first"); + rec->last_signal = signal_list_move(&signals, "redirect last"); + rec->signals = signals; + + if (server->redirect_next != NULL) + server_redirect_destroy(server->redirect_next); + server->redirect_next = rec; +} + +void server_redirect_command(IRC_SERVER_REC *server, const char *command, + REDIRECT_REC *redirect) +{ + REDIRECT_CMD_REC *cmdrec; + + g_return_if_fail(IS_IRC_SERVER(server)); + g_return_if_fail(command != NULL); + + if (redirect == NULL) { + /* no redirection wanted, but still register the command + so future redirections wont get messed up. */ + cmdrec = redirect_cmd_find(command); + if (cmdrec == NULL) + return; + + redirect_cmd_ref(cmdrec); + + redirect = g_new0(REDIRECT_REC, 1); + redirect->created = time(NULL); + redirect->cmd = cmdrec; + redirect->remote = cmdrec->remote; + } + + server->redirects = g_slist_append(server->redirects, redirect); +} + +static int redirect_args_match(const char *event_args, + const char *arg, int pos) +{ + const char *start; + + if (pos == -1) + return TRUE; + + /* skip to the start of the wanted argument */ + while (pos > 0 && *event_args != '\0') { + while (*event_args != ' ' && *event_args != '\0') event_args++; + while (*event_args == ' ') event_args++; + pos--; + } + + /* now compare the arguments */ + start = event_args; + while (*arg != '\0') { + while (*arg != '\0' && *arg != ' ' && *event_args != '\0') { + if (i_toupper(*arg) != i_toupper(*event_args)) + break; + arg++; event_args++; + } + + if ((*arg == '\0' || *arg == ' ') && + (*event_args == '\0' || *event_args == ' ')) + return TRUE; + + /* compare the next argument */ + while (*arg != ' ' && *arg != '\0') arg++; + while (*arg == ' ') arg++; + + event_args = start; + } + + return FALSE; +} + +static GSList *redirect_cmd_list_find(GSList *list, const char *event) +{ + while (list != NULL) { + const char *str = list->data; + + if (g_strcmp0(str, event) == 0) + break; + list = list->next->next; + } + + return list; +} + +#define MATCH_NONE 0 +#define MATCH_START 1 +#define MATCH_STOP 2 + +static const char *redirect_match(REDIRECT_REC *redirect, const char *event, + const char *args, int *match) +{ + GSList *tmp, *cmdpos; + const char *signal; + int match_list; + + if (redirect->aborted) + return NULL; + + /* get the signal for redirection event - if it's not found we'll + use the default signal */ + signal = NULL; + for (tmp = redirect->signals; tmp != NULL; tmp = tmp->next->next) { + if (g_strcmp0(tmp->data, event) == 0) { + signal = tmp->next->data; + break; + } + } + + /* find the argument position */ + if (redirect->destroyed) { + /* stop event is already found for this redirection, but + we'll still want to look for optional events */ + cmdpos = redirect_cmd_list_find(redirect->cmd->opt, event); + if (cmdpos == NULL) + return NULL; + + match_list = MATCH_STOP; + } else { + /* look from start/stop lists */ + cmdpos = redirect_cmd_list_find(redirect->cmd->start, event); + if (cmdpos != NULL) + match_list = MATCH_START; + else { + cmdpos = redirect_cmd_list_find(redirect->cmd->stop, + event); + if (cmdpos != NULL) + match_list = MATCH_STOP; + else if (redirect->default_signal != NULL && + args == NULL && + strncmp(event, "event ", 6) == 0 && + i_isdigit(event[6])) { + /* If there is a default signal, the + * redirection has already started and + * this is a numeric, use it */ + /* XXX this should depend on the + * REDIRECT_CMD_REC, not REDIRECT_REC */ + if (signal == NULL) + signal = redirect->default_signal; + match_list = MATCH_START; + } else { + match_list = MATCH_NONE; + } + } + } + + if (signal == NULL && cmdpos == NULL) { + /* event not found from specified redirection events nor + registered command events, and no default signal */ + return NULL; + } + + /* check that arguments match */ + if (args != NULL && redirect->arg != NULL && cmdpos != NULL && + !redirect_args_match(args, redirect->arg, + GPOINTER_TO_INT(cmdpos->next->data))) + return NULL; + + *match = match_list; + return signal != NULL ? signal : redirect->default_signal; +} + +static void redirect_abort(IRC_SERVER_REC *server, REDIRECT_REC *rec) +{ + char *str; + + server->redirects = + g_slist_remove(server->redirects, rec); + + if (rec->aborted || !rec->destroyed) { + /* emit the failure signal */ + if (rec->failure_signal != NULL) + str = g_strdup_printf("FAILED %s: %s", rec->cmd->name, rec->failure_signal); + else + str = g_strdup_printf("FAILED %s", rec->cmd->name); + + rawlog_redirect(server->rawlog, str); + g_free(str); + + if (rec->failure_signal != NULL) + signal_emit(rec->failure_signal, 3, server, rec->cmd->name, rec->arg); + } else if (rec->last_signal != NULL) { + /* emit the last signal */ + signal_emit(rec->last_signal, 1, server); + } + + server->redirect_active = g_slist_remove(server->redirect_active, rec); + + server_redirect_destroy(rec); +} + +#define REDIRECT_IS_TIMEOUTED(rec) \ + ((now-(rec)->created) > (rec)->cmd->timeout) + + +static REDIRECT_REC *redirect_find(IRC_SERVER_REC *server, const char *event, + const char *args, const char **signal, + int *match) +{ + REDIRECT_REC *redirect; + GSList *tmp, *next; + time_t now; + const char *match_signal; + + /* find the redirection */ + *signal = NULL; redirect = NULL; + for (tmp = server->redirects; tmp != NULL; tmp = tmp->next) { + REDIRECT_REC *rec = tmp->data; + + /* already active, don't try to start it again */ + if (g_slist_find(server->redirect_active, rec) != NULL) + continue; + + match_signal = redirect_match(rec, event, args, match); + if (match_signal != NULL && *match != MATCH_NONE) { + redirect = rec; + *signal = match_signal; + break; + } + } + + if (g_strcmp0("event 263", event) == 0) { /* RPL_TRYAGAIN */ + char *params, *command; + params = event_get_params(args, 3, NULL, &command, NULL); + + for (tmp = server->redirects; tmp != NULL; tmp = next) { + REDIRECT_REC *rec = tmp->data; + + next = tmp->next; + + if (rec == redirect) + break; + + if (g_slist_find(server->redirect_active, rec) != NULL) + continue; + + if (redirect_args_match(rec->cmd->name, command, rec->cmd->pos)) { + /* the server crashed our command with RPL_TRYAGAIN, send the + failure */ + rec->aborted = TRUE; + redirect_abort(server, rec); + break; + } + } + g_free(params); + } + + /* remove the destroyed, non-remote and timeouted remote + redirections that should have happened before this redirection */ + now = time(NULL); + for (tmp = server->redirects; tmp != NULL; tmp = next) { + REDIRECT_REC *rec = tmp->data; + + if (rec == redirect) + break; + + next = tmp->next; + if (rec->destroyed) { + /* redirection is finished, destroy it */ + redirect_abort(server, rec); + } else if (redirect != NULL) { + /* check if redirection failed */ + if (rec->aborted || + rec->failures++ >= MAX_FAILURE_COUNT) { + /* enough failures, abort it now */ + if (!rec->remote || REDIRECT_IS_TIMEOUTED(rec)) + redirect_abort(server, rec); + } + } + } + + return redirect; +} + +static const char * +server_redirect_get(IRC_SERVER_REC *server, const char *prefix, + const char *event, const char *args, + REDIRECT_REC **redirect, int *match) +{ + const char *signal = NULL; + GSList *ptr, *next; + REDIRECT_REC *r; + + *redirect = NULL; + *match = MATCH_NONE; + + if (server->redirects == NULL) + return NULL; + + for (ptr = server->redirect_active; ptr != NULL; ptr = next) { + next = ptr->next; + r = ptr->data; + if (prefix != NULL && r->prefix != NULL && + g_strcmp0(prefix, r->prefix)) { + /* not from this server */ + continue; + } + /* redirection is already started, now we'll just need to + keep redirecting until stop-event is found. */ + *redirect = r; + signal = redirect_match(*redirect, event, NULL, match); + if (signal == NULL) { + /* not a numeric, so we've lost the + stop event.. */ + (*redirect)->aborted = TRUE; + redirect_abort(server, *redirect); + + *redirect = NULL; + } + if (*redirect != NULL) + break; + } + + if (*redirect == NULL) { + /* find the redirection */ + *redirect = redirect_find(server, event, args, &signal, match); + } + + /* remember which server is replying to our request */ + if (*redirect != NULL && prefix != NULL && (*redirect)->prefix == NULL) + (*redirect)->prefix = g_strdup(prefix); + + if (*redirect != NULL && (*redirect)->first_signal != NULL && + !(*redirect)->first_signal_sent) { + /* emit the first_signal */ + (*redirect)->first_signal_sent = TRUE; + signal_emit((*redirect)->first_signal, 1, server); + } + + return signal; +} + +const char *server_redirect_get_signal(IRC_SERVER_REC *server, + const char *prefix, + const char *event, + const char *args) +{ + REDIRECT_REC *redirect; + const char *signal; + int match; + + signal = server_redirect_get(server, prefix, event, args, &redirect, &match); + if (redirect == NULL) + ; + else if (match != MATCH_STOP) { + if (g_slist_find(server->redirect_active, redirect) == NULL) + server->redirect_active = g_slist_prepend(server->redirect_active, redirect); + } else { + /* stop event - remove this redirection next time this + function is called (can't destroy now or our return + value would be corrupted) */ + if (--redirect->count <= 0) + redirect->destroyed = TRUE; + server->redirect_active = g_slist_remove(server->redirect_active, redirect); + } + + return signal; +} + +const char *server_redirect_peek_signal(IRC_SERVER_REC *server, + const char *prefix, + const char *event, + const char *args, + int *redirected) +{ + REDIRECT_REC *redirect; + const char *signal; + int match; + + signal = server_redirect_get(server, prefix, event, args, &redirect, &match); + *redirected = match != MATCH_NONE; + return signal; +} + +static void sig_disconnected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + g_slist_free(server->redirect_active); + server->redirect_active = NULL; + g_slist_foreach(server->redirects, + (GFunc) server_redirect_destroy, NULL); + g_slist_free(server->redirects); + server->redirects = NULL; + + if (server->redirect_next != NULL) { + server_redirect_destroy(server->redirect_next); + server->redirect_next = NULL; + } +} + +static void cmd_redirect_destroy(char *key, REDIRECT_CMD_REC *cmd) +{ + redirect_cmd_unref(cmd); +} + +void servers_redirect_init(void) +{ + command_redirects = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal); + + /* WHOIS - register as remote command by default + with a default timeout */ + server_redirect_register("whois", TRUE, 0, + "event 311", 1, /* Begins the WHOIS */ + NULL, + "event 401", 1, /* No such nick */ + "event 318", 1, /* End of WHOIS */ + "event 402", 1, /* No such server */ + "event 431", 1, /* No nickname given */ + "event 461", 1, /* Not enough parameters */ + NULL, + "event 318", 1, /* After 401, we should get 318, but in OPN we don't.. */ + NULL); + + /* WHOWAS */ + server_redirect_register("whowas", FALSE, 0, + "event 314", 1, /* Begins the WHOWAS */ + "event 406", 1, /* There was no such nick */ + NULL, + "event 369", 1, /* End of WHOWAS */ + NULL, + NULL); + + /* WHO */ + server_redirect_register("who", FALSE, 0, + "event 352", 1, /* An element of the WHO */ + "event 354", -1, /* WHOX element */ + "event 401", 1, /* No such nick/channel */ + NULL, + "event 315", 1, /* End of WHO */ + "event 403", 1, /* no such channel */ + NULL, + NULL); + + /* WHO user */ + server_redirect_register("who user", FALSE, 0, /* */ + "event 352", 5, /* An element of the WHO */ + "event 354", -1, /* WHOX element */ + NULL, /* */ + "event 315", 1, /* End of WHO */ + NULL, /* */ + NULL); + + /* LIST */ + server_redirect_register("list", FALSE, 0, + "event 321", 1, /* Begins the LIST */ + NULL, + "event 323", 1, /* End of LIST */ + NULL, + NULL); + + /* ISON */ + server_redirect_register("ison", FALSE, 0, + NULL, + "event 303", -1, /* ISON */ + NULL, + NULL); + + /* USERHOST */ + server_redirect_register("userhost", FALSE, 0, + "event 401", 1, /* no such nick */ + NULL, + "event 302", -1, /* Userhost */ + "event 461", -1, /* Not enough parameters */ + NULL, + NULL); + + /* MODE user */ + server_redirect_register("mode user", FALSE, 0, + NULL, + "event mode", 0, /* MODE-reply */ + "event 501", -1, /* Uknown MODE flag */ + "event 502", -1, /* Can't change mode for other users */ + "event 403", 1, /* That channel doesn't exist (tried to change mode to others) */ + NULL, + NULL); + + /* MODE #channel */ + server_redirect_register("mode channel", FALSE, 0, + NULL, + "event 324", 1, /* MODE-reply */ + "event 403", 1, /* no such channel */ + "event 442", 1, /* "you're not on that channel" */ + "event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */ + NULL, + "event 329", 1, /* Channel create time */ + NULL); + + /* MODE #channel b */ + server_redirect_register("mode b", FALSE, 0, + "event 367", 1, + NULL, + "event 368", 1, /* End of Channel ban List */ + "event 403", 1, /* no such channel */ + "event 442", 1, /* "you're not on that channel" */ + "event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */ + NULL, + NULL); + + /* MODE #channel e */ + server_redirect_register("mode e", FALSE, 0, + "event 348", 1, + NULL, + "event 349", 1, /* End of ban exceptions */ + "event 482", 1, /* not channel operator - OPN's ircd doesn't want non-ops to see ban exceptions */ + "event 403", 1, /* no such channel */ + "event 442", 1, /* "you're not on that channel" */ + "event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */ + "event 472", -1, /* unknown mode (you should check e-mode's existence from 004 event instead of relying on this) */ + NULL, + NULL); + + /* MODE #channel I */ + server_redirect_register("mode I", FALSE, 0, + "event 346", 1, + NULL, + "event 347", 1, /* End of invite list */ + "event 482", 1, /* not channel operator - OPN's ircd doesn't want non-ops to see ban exceptions */ + "event 403", 1, /* no such channel */ + "event 442", 1, /* "you're not on that channel" */ + "event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */ + "event 472", -1, /* unknown mode (you should check I-mode's existence from 004 event instead of relying on this) */ + NULL, + NULL); + + /* PING - use default timeout */ + server_redirect_register("ping", TRUE, 0, + NULL, + "event 402", -1, /* no such server */ + "event pong", -1, /* PONG */ + NULL, + NULL); + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void servers_redirect_deinit(void) +{ + g_hash_table_foreach(command_redirects, + (GHFunc) cmd_redirect_destroy, NULL); + g_hash_table_destroy(command_redirects); + + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/src/irc/core/servers-redirect.h b/src/irc/core/servers-redirect.h new file mode 100644 index 0000000..0d8b63f --- /dev/null +++ b/src/irc/core/servers-redirect.h @@ -0,0 +1,86 @@ +#ifndef IRSSI_IRC_CORE_SERVERS_REDIRECT_H +#define IRSSI_IRC_CORE_SERVERS_REDIRECT_H + +/* Register new redirection command. + + remote - Specifies if the command is by default a remote command + (eg. sent to another server). server_redirect_event() may override this. + + timeout - If remote is TRUE, specifies how many seconds to wait for + reply before aborting. + + ... - char *start, int argpos, char *start, int argpos, ..., NULL, + char *stop, int argpos, char *stop, int argpos, ..., NULL, + char *optional, int argpos, ..., NULL + List of events that start and stop this redirection. + Start event list may be just NULL, but there must be at least one + stop event. Optional events are checked only if they are received + immediately after one of the stop-events. `argpos' specifies the + word number in event string which is compared to wanted argument, + -1 = don't compare, TRUE always. */ +void server_redirect_register(const char *command, + int remote, int timeout, ...); +/* start/stop/opt lists shouldn't be free'd after, and their strings + should be dynamically allocated */ +void server_redirect_register_list(const char *command, int remote, int timeout, GSList *start, + GSList *stop, GSList *opt, int pos); + +/* Specify that the next command sent to server will be redirected. + NOTE: This command MUST be called before irc_send_cmd(). + + command - Specifies the registered command that should be used for this + redirection. + + count - How many times to execute the redirection. Some commands may send + multiple stop events, like MODE #a,#b. + + arg - The argument to be compared in event strings. You can give multiple + arguments separated with space. + + remote - Specifies if the command is a remote command, -1 = use default. + + failure_signal - If irssi can't find the stop signal for the redirection, + this signal is called. + + ... - char *event, char *redirect_signal, ..., NULL + If the `event' is "", all the events belonging to the redirection but not + specified here, will be sent there. */ +void server_redirect_event(IRC_SERVER_REC *server, const char *command, + int count, const char *arg, int remote, + const char *failure_signal, ...); +/* Signals list shouldn't be free'd after, and it's strings should be + dynamically allocated */ +void server_redirect_event_list(IRC_SERVER_REC *server, const char *command, + int count, const char *arg, int remote, + const char *failure_signal, GSList *signals); + +/* INTERNAL: */ + +/* irc_send_cmd() calls this to make sure redirecting knows + what's sent to server */ +void server_redirect_command(IRC_SERVER_REC *server, const char *command, + REDIRECT_REC *redirect); +/* Returns the redirection signal for specified event. + This is the function that contains the real redirecting logic. */ +const char *server_redirect_get_signal(IRC_SERVER_REC *server, + const char *prefix, + const char *event, + const char *args); +/* Returns the redirection signal for specified event. + Doesn't change the server state in any way, so if you really wish to + use the signal, call server_redirect_get_signal() after this. + `redirected' is set to TRUE, if this event belongs to redirection even + while there might be no redirection signal. */ +const char *server_redirect_peek_signal(IRC_SERVER_REC *server, + const char *prefix, + const char *event, + const char *args, + int *redirected); + +/* Destroy redirection record */ +void server_redirect_destroy(REDIRECT_REC *rec); + +void servers_redirect_init(void); +void servers_redirect_deinit(void); + +#endif |