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 | |
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 '')
102 files changed, 23782 insertions, 0 deletions
diff --git a/src/irc/Makefile.am b/src/irc/Makefile.am new file mode 100644 index 0000000..a271a89 --- /dev/null +++ b/src/irc/Makefile.am @@ -0,0 +1,11 @@ +if BUILD_IRSSIPROXY +PROXY=proxy +endif + +SUBDIRS = core dcc flood notifylist $(PROXY) + +noinst_LIBRARIES = libirc.a + +libirc_a_SOURCES = irc.c + +EXTRA_DIST = meson.build diff --git a/src/irc/Makefile.in b/src/irc/Makefile.in new file mode 100644 index 0000000..2ab5391 --- /dev/null +++ b/src/irc/Makefile.in @@ -0,0 +1,767 @@ +# 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 +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 $(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_a_AR = $(AR) $(ARFLAGS) +libirc_a_LIBADD = +am_libirc_a_OBJECTS = irc.$(OBJEXT) +libirc_a_OBJECTS = $(am_libirc_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)/irc.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_a_SOURCES) +DIST_SOURCES = $(libirc_a_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +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)` +DIST_SUBDIRS = core dcc flood notifylist proxy +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +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@ +@BUILD_IRSSIPROXY_TRUE@PROXY = proxy +SUBDIRS = core dcc flood notifylist $(PROXY) +noinst_LIBRARIES = libirc.a +libirc_a_SOURCES = irc.c +EXTRA_DIST = meson.build +all: all-recursive + +.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/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/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.a: $(libirc_a_OBJECTS) $(libirc_a_DEPENDENCIES) $(EXTRA_libirc_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc.a + $(AM_V_AR)$(libirc_a_AR) libirc.a $(libirc_a_OBJECTS) $(libirc_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irc.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 + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(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-recursive + +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-recursive + +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 + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(LIBRARIES) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +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-recursive + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/irc.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/irc.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) 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-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am + +.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/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 diff --git a/src/irc/dcc/Makefile.am b/src/irc/dcc/Makefile.am new file mode 100644 index 0000000..c5592f7 --- /dev/null +++ b/src/irc/dcc/Makefile.am @@ -0,0 +1,30 @@ +noinst_LIBRARIES = libirc_dcc.a + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_dcc_a_SOURCES = \ + dcc.c \ + dcc-chat.c \ + dcc-get.c \ + dcc-send.c \ + dcc-resume.c \ + dcc-autoget.c \ + dcc-queue.c \ + dcc-server.c + +pkginc_irc_dccdir=$(pkgincludedir)/src/irc/dcc +pkginc_irc_dcc_HEADERS = \ + dcc-rec.h \ + dcc-file-rec.h \ + dcc.h \ + dcc-file.h \ + dcc-chat.h \ + dcc-get.h \ + dcc-send.h \ + dcc-queue.h \ + module.h \ + dcc-server.h + +EXTRA_DIST = meson.build diff --git a/src/irc/dcc/Makefile.in b/src/irc/dcc/Makefile.in new file mode 100644 index 0000000..0a0bfa4 --- /dev/null +++ b/src/irc/dcc/Makefile.in @@ -0,0 +1,758 @@ +# 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/dcc +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_dcc_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_dcc_a_AR = $(AR) $(ARFLAGS) +libirc_dcc_a_LIBADD = +am_libirc_dcc_a_OBJECTS = dcc.$(OBJEXT) dcc-chat.$(OBJEXT) \ + dcc-get.$(OBJEXT) dcc-send.$(OBJEXT) dcc-resume.$(OBJEXT) \ + dcc-autoget.$(OBJEXT) dcc-queue.$(OBJEXT) dcc-server.$(OBJEXT) +libirc_dcc_a_OBJECTS = $(am_libirc_dcc_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)/dcc-autoget.Po \ + ./$(DEPDIR)/dcc-chat.Po ./$(DEPDIR)/dcc-get.Po \ + ./$(DEPDIR)/dcc-queue.Po ./$(DEPDIR)/dcc-resume.Po \ + ./$(DEPDIR)/dcc-send.Po ./$(DEPDIR)/dcc-server.Po \ + ./$(DEPDIR)/dcc.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_dcc_a_SOURCES) +DIST_SOURCES = $(libirc_dcc_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_dccdir)" +HEADERS = $(pkginc_irc_dcc_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_dcc.a +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_dcc_a_SOURCES = \ + dcc.c \ + dcc-chat.c \ + dcc-get.c \ + dcc-send.c \ + dcc-resume.c \ + dcc-autoget.c \ + dcc-queue.c \ + dcc-server.c + +pkginc_irc_dccdir = $(pkgincludedir)/src/irc/dcc +pkginc_irc_dcc_HEADERS = \ + dcc-rec.h \ + dcc-file-rec.h \ + dcc.h \ + dcc-file.h \ + dcc-chat.h \ + dcc-get.h \ + dcc-send.h \ + dcc-queue.h \ + module.h \ + dcc-server.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/dcc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/dcc/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_dcc.a: $(libirc_dcc_a_OBJECTS) $(libirc_dcc_a_DEPENDENCIES) $(EXTRA_libirc_dcc_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc_dcc.a + $(AM_V_AR)$(libirc_dcc_a_AR) libirc_dcc.a $(libirc_dcc_a_OBJECTS) $(libirc_dcc_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc_dcc.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-autoget.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-chat.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-get.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-resume.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-send.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc.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_dccHEADERS: $(pkginc_irc_dcc_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_irc_dcc_HEADERS)'; test -n "$(pkginc_irc_dccdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_irc_dccdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_irc_dccdir)" || 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_dccdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_irc_dccdir)" || exit $$?; \ + done + +uninstall-pkginc_irc_dccHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_irc_dcc_HEADERS)'; test -n "$(pkginc_irc_dccdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_irc_dccdir)'; $(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_dccdir)"; 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)/dcc-autoget.Po + -rm -f ./$(DEPDIR)/dcc-chat.Po + -rm -f ./$(DEPDIR)/dcc-get.Po + -rm -f ./$(DEPDIR)/dcc-queue.Po + -rm -f ./$(DEPDIR)/dcc-resume.Po + -rm -f ./$(DEPDIR)/dcc-send.Po + -rm -f ./$(DEPDIR)/dcc-server.Po + -rm -f ./$(DEPDIR)/dcc.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_dccHEADERS + +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)/dcc-autoget.Po + -rm -f ./$(DEPDIR)/dcc-chat.Po + -rm -f ./$(DEPDIR)/dcc-get.Po + -rm -f ./$(DEPDIR)/dcc-queue.Po + -rm -f ./$(DEPDIR)/dcc-resume.Po + -rm -f ./$(DEPDIR)/dcc-send.Po + -rm -f ./$(DEPDIR)/dcc-server.Po + -rm -f ./$(DEPDIR)/dcc.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_dccHEADERS + +.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_dccHEADERS 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_dccHEADERS + +.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/dcc/dcc-autoget.c b/src/irc/dcc/dcc-autoget.c new file mode 100644 index 0000000..ba0bed0 --- /dev/null +++ b/src/irc/dcc/dcc-autoget.c @@ -0,0 +1,97 @@ +/* + dcc-autoget.c : irssi + + Copyright (C) 1999-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/masks.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/servers.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/dcc/dcc-get.h> + +static void sig_dcc_request(GET_DCC_REC *dcc, const char *nickaddr) +{ + struct stat statbuf; + const char *masks; + char *str, *file, *esc_arg; + int max_size; + + if (!IS_DCC_GET(dcc)) return; + + /* check if we want to autoget file offer */ + if (!settings_get_bool("dcc_autoget")) + return; + + /* check for lowports */ + if (dcc->port < 1024 && !settings_get_bool("dcc_autoaccept_lowports")) + return; + + /* check that autoget masks match */ + masks = settings_get_str("dcc_autoget_masks"); + if (*masks != '\0' && + !masks_match(SERVER(dcc->server), masks, dcc->nick, nickaddr)) + return; + + /* Unless specifically said in dcc_autoget_masks, don't do autogets + sent to channels. */ + if (*masks == '\0' && dcc->target != NULL && server_ischannel(SERVER(dcc->server), dcc->target)) + return; + + /* don't autoget files beginning with a dot, if download dir is + our home dir (stupid kludge for stupid people) */ + if (*dcc->arg == '.' && + g_strcmp0(settings_get_str("dcc_download_path"), "~") == 0) + return; + + /* check file size limit, NOTE: it's still possible to send a + bogus file size and then just send what ever sized file.. */ + max_size = settings_get_size("dcc_autoget_max_size"); + if (max_size > 0 && (uoff_t)max_size < dcc->size) + return; + + /* ok. but do we want/need to resume? */ + file = dcc_get_download_path(dcc->arg); + esc_arg = escape_string(dcc->arg); + str = g_strdup_printf(settings_get_bool("dcc_autoresume") && + stat(file, &statbuf) == 0 ? + "RESUME %s \"%s\"" : "GET %s \"%s\"", + dcc->nick, esc_arg); + signal_emit("command dcc", 2, str, dcc->server); + g_free(esc_arg); + g_free(file); + g_free(str); +} + +void dcc_autoget_init(void) +{ + settings_add_bool("dcc", "dcc_autoget", FALSE); + settings_add_bool("dcc", "dcc_autoaccept_lowports", FALSE); + settings_add_bool("dcc", "dcc_autoresume", FALSE); + settings_add_size("dcc", "dcc_autoget_max_size", "0k"); + settings_add_str("dcc", "dcc_autoget_masks", ""); + + signal_add_last("dcc request", (SIGNAL_FUNC) sig_dcc_request); +} + +void dcc_autoget_deinit(void) +{ + signal_remove("dcc request", (SIGNAL_FUNC) sig_dcc_request); +} diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c new file mode 100644 index 0000000..68da0e2 --- /dev/null +++ b/src/irc/dcc/dcc-chat.c @@ -0,0 +1,873 @@ +/* + dcc-chat.c : irssi + + Copyright (C) 1999-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/commands.h> +#include <irssi/src/core/recode.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.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-queries.h> +#include <irssi/src/core/masks.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> + +static char *dcc_chat_get_new_id(const char *nick) +{ + char *id; + int num; + + g_return_val_if_fail(nick != NULL, NULL); + + if (dcc_chat_find_id(nick) == NULL) { + /* same as nick, good */ + return g_strdup(nick); + } + + /* keep adding numbers after nick until some of them isn't found */ + for (num = 2;; num++) { + id = g_strdup_printf("%s%d", nick, num); + if (dcc_chat_find_id(id) == NULL) + return id; + g_free(id); + } +} + +CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, + CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + CHAT_DCC_REC *dcc; + + dcc = g_new0(CHAT_DCC_REC, 1); + dcc->orig_type = dcc->type = DCC_CHAT_TYPE; + dcc->mirc_ctcp = settings_get_bool("dcc_mirc_ctcp"); + dcc->id = dcc_chat_get_new_id(nick); + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change init_rec API */ + g_free(dcc->id); + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void dcc_remove_chat_refs(CHAT_DCC_REC *dcc) +{ + GSList *tmp; + + g_return_if_fail(dcc != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *rec = tmp->data; + + if (rec->chat == dcc) + rec->chat = NULL; + } +} + +static void sig_dcc_destroyed(CHAT_DCC_REC *dcc) +{ + if (!IS_DCC_CHAT(dcc)) return; + + dcc_remove_chat_refs(dcc); + + if (dcc->sendbuf != NULL) net_sendbuffer_destroy(dcc->sendbuf, FALSE); + g_free(dcc->id); +} + +CHAT_DCC_REC *dcc_chat_find_id(const char *id) +{ + GSList *tmp; + + g_return_val_if_fail(id != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + CHAT_DCC_REC *dcc = tmp->data; + + if (IS_DCC_CHAT(dcc) && dcc->id != NULL && + g_ascii_strcasecmp(dcc->id, id) == 0) + return dcc; + } + + return NULL; +} + +static CHAT_DCC_REC *dcc_chat_find_nick(IRC_SERVER_REC *server, + const char *nick) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + CHAT_DCC_REC *dcc = tmp->data; + + if (IS_DCC_CHAT(dcc) && dcc->server == server && + g_ascii_strcasecmp(dcc->nick, nick) == 0) + return dcc; + } + + return NULL; +} + +/* Send `data' to dcc chat. */ +void dcc_chat_send(CHAT_DCC_REC *dcc, const char *data) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + g_return_if_fail(dcc->sendbuf != NULL); + g_return_if_fail(data != NULL); + + net_sendbuffer_send(dcc->sendbuf, data, strlen(data)); + net_sendbuffer_send(dcc->sendbuf, "\n", 1); +} + +/* Send a CTCP message/notify to target. + Send the CTCP via DCC chat if `chat' is specified. */ +void dcc_ctcp_message(IRC_SERVER_REC *server, const char *target, + CHAT_DCC_REC *chat, int notice, const char *msg) +{ + char *str; + char *recoded; + + if (chat != NULL && chat->sendbuf != NULL) { + /* send it via open DCC chat */ + recoded = recode_out(SERVER(server), msg, chat->nick); + str = g_strdup_printf("%s\001%s\001", chat->mirc_ctcp ? "" : + notice ? "CTCP_REPLY " : + "CTCP_MESSAGE ", recoded); + dcc_chat_send(chat, str); + g_free(str); + } else { + recoded = recode_out(SERVER(server), msg, target); + irc_send_cmdv(server, "%s %s :\001%s\001", + notice ? "NOTICE" : "PRIVMSG", target, recoded); + } + g_free(recoded); +} + +/* If `item' is a query of a =nick, return DCC chat record of nick */ +CHAT_DCC_REC *item_get_dcc(WI_ITEM_REC *item) +{ + QUERY_REC *query; + + query = IRC_QUERY(item); + if (query == NULL || *query->name != '=') + return NULL; + + return dcc_chat_find_id(query->name+1); +} + +/* Send text to DCC chat */ +static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + GHashTable *optlist; + char *text, *target; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_UNKNOWN_OPTIONS | + PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "msg", + &optlist, &target, &text)) + return; + + /* handle only DCC messages */ + if (g_strcmp0(target, "*") == 0) + dcc = item_get_dcc(item); + else if (*target == '=') + dcc = dcc_chat_find_id(target+1); + else + dcc = NULL; + + if (dcc != NULL && dcc->sendbuf != NULL) { + char *recoded; + + recoded = recode_out(server, text, dcc->nick); + dcc_chat_send(dcc, recoded); + g_free(recoded); + } + + if (dcc != NULL || *target == '=') + signal_stop(); + + cmd_params_free(free_arg); +} + +static void cmd_me(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + char *str; + + g_return_if_fail(data != NULL); + + dcc = item_get_dcc(item); + if (dcc == NULL) return; + + str = g_strconcat("ACTION ", data, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + + signal_stop(); +} + +static void cmd_action(const char *data, IRC_SERVER_REC *server) +{ + CHAT_DCC_REC *dcc; + char *target, *text, *str; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (*data != '=') { + /* handle only DCC actions */ + return; + } + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, + &target, &text)) + return; + if (*target == '\0' || *text == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = dcc_chat_find_id(target+1); + if (dcc != NULL) { + str = g_strconcat("ACTION ", text, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + } + + cmd_params_free(free_arg); + signal_stop(); +} + +static void cmd_ctcp(const char *data, IRC_SERVER_REC *server) +{ + CHAT_DCC_REC *dcc; + char *target, *ctcpcmd, *ctcpdata, *str; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &target, &ctcpcmd, &ctcpdata)) + return; + if (*target == '\0' || *ctcpcmd == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target != '=') { + /* handle only DCC CTCPs */ + cmd_params_free(free_arg); + return; + } + + dcc = dcc_chat_find_id(target+1); + if (dcc != NULL) { + ascii_strup(ctcpcmd); + + str = g_strconcat(ctcpcmd, " ", ctcpdata, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + } + + cmd_params_free(free_arg); + signal_stop(); +} + +/* input function: DCC CHAT received some data.. */ +void dcc_chat_input(CHAT_DCC_REC *dcc) +{ + char *str; + int ret; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + do { + ret = net_sendbuffer_receive_line(dcc->sendbuf, &str, 1); + + if (ret == -1) { + /* connection lost */ + dcc->connection_lost = TRUE; + dcc_close(DCC(dcc)); + break; + } + + if (ret > 0) { + SERVER_REC *server; + char *recoded; + + dcc->transfd += ret; + + server = SERVER(dcc->server); + recoded = recode_in(server, str, dcc->nick); + signal_emit("dcc chat message", 2, dcc, recoded); + g_free(recoded); + if (server != NULL) + server_meta_clear_all(server); + } + } while (ret > 0); +} + +/* input function: DCC CHAT - someone tried to connect to our socket */ +static void dcc_chat_listen(CHAT_DCC_REC *dcc) +{ + IPADDR ip; + GIOChannel *handle; + int port; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + /* accept connection */ + handle = net_accept(dcc->handle, &ip, &port); + if (handle == NULL) + return; + + /* TODO: add paranoia check - see dcc-files.c */ + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + dcc->sendbuf = net_sendbuffer_create(handle, 0); + memcpy(&dcc->addr, &ip, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + dcc->tagread = i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* callback: DCC CHAT - connect finished */ +static void sig_chat_connected(CHAT_DCC_REC *dcc) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (net_geterror(dcc->handle) != 0) { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + return; + } + + /* connect ok. */ + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->sendbuf = net_sendbuffer_create(dcc->handle, 0); + dcc->tagread = i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +static void dcc_chat_connect(CHAT_DCC_REC *dcc) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (dcc->addrstr[0] == '\0' || + dcc->starttime != 0 || dcc->handle != NULL) { + /* already sent a chat request / already chatting */ + return; + } + + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + if (dcc->handle != NULL) { + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_WRITE | I_INPUT_READ, + (GInputFunction) sig_chat_connected, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static void dcc_chat_passive(CHAT_DCC_REC *dcc) +{ + IPADDR own_ip; + int port; + GIOChannel *handle; + char host[MAX_IP_LEN]; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (dcc->addrstr[0] == '\0' || + dcc->starttime != 0 || dcc->handle != NULL) { + /* already sent a chat request / already chatting */ + return; + } + + handle = dcc_listen(net_sendbuffer_handle(dcc->server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_return_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_listen, dcc); + + /* Let's send the reply to the other client! */ + dcc_ip2str(&own_ip, host); + irc_send_cmdv(dcc->server, "PRIVMSG %s :\001DCC CHAT CHAT %s %d %d\001", + dcc->nick, host, port, dcc->pasv_id); + +} + +/* SYNTAX: DCC CHAT [-passive] [<nick>] */ +static void cmd_dcc_chat(const char *data, IRC_SERVER_REC *server) +{ + void *free_arg; + CHAT_DCC_REC *dcc; + IPADDR own_ip; + GIOChannel *handle; + GHashTable *optlist; + int p_id; + char *nick, host[MAX_IP_LEN]; + int port; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "dcc chat", &optlist, &nick)) + return; + + if (*nick == '\0') { + dcc = DCC_CHAT(dcc_find_request_latest(DCC_CHAT_TYPE)); + if (dcc != NULL) { + if (!dcc_is_passive(dcc)) + dcc_chat_connect(dcc); + else + dcc_chat_passive(dcc); + } + cmd_params_free(free_arg); + return; + } + + dcc = dcc_chat_find_id(nick); + if (dcc != NULL && dcc_is_waiting_user(dcc)) { + if (!dcc_is_passive(dcc)) { + /* found from dcc chat requests, + we're the connecting side */ + dcc_chat_connect(dcc); + } else { + /* We are accepting a passive DCC CHAT. */ + dcc_chat_passive(dcc); + } + cmd_params_free(free_arg); + return; + } + + if (dcc != NULL && dcc_is_listening(dcc) && + dcc->server == server) { + /* sending request again even while old request is + still waiting, remove it. */ + dcc_destroy(DCC(dcc)); + } + + if (!IS_IRC_SERVER(server) || !server->connected) + cmd_param_error(CMDERR_NOT_CONNECTED); + + dcc = dcc_chat_create(server, NULL, nick, "chat"); + if (dcc == NULL) { + cmd_params_free(free_arg); + g_warn_if_reached(); + return; + } + + if (g_hash_table_lookup(optlist, "passive") == NULL) { + /* Standard DCC CHAT... let's listen for incoming connections */ + handle = dcc_listen(net_sendbuffer_handle(server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_param_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_listen, dcc); + + /* send the chat request */ + signal_emit("dcc request send", 1, dcc); + + dcc_ip2str(&own_ip, host); + irc_send_cmdv(server, "PRIVMSG %s :\001DCC CHAT CHAT %s %d\001", + nick, host, port); + } else { + /* Passive protocol... we want the other side to listen */ + /* send the chat request */ + dcc->port = 0; + signal_emit("dcc request send", 1, dcc); + + /* generate a random id */ + p_id = rand() % 64; + dcc->pasv_id = p_id; + + /* 16843009 is the long format of 1.1.1.1, we use a fake IP + since the other side shouldn't care of it: they will send + the address for us to connect to in the reply */ + irc_send_cmdv(server, + "PRIVMSG %s :\001DCC CHAT CHAT 16843009 0 %d\001", + nick, p_id); + } + cmd_params_free(free_arg); +} + +/* SYNTAX: MIRCDCC ON|OFF */ +static void cmd_mircdcc(const char *data, SERVER_REC *server, + QUERY_REC *item) +{ + CHAT_DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + dcc = item_get_dcc((WI_ITEM_REC *) item); + if (dcc == NULL) return; + + dcc->mirc_ctcp = i_toupper(*data) != 'N' && + g_ascii_strncasecmp(data, "OF", 2) != 0; +} + +/* DCC CLOSE CHAT <nick> - check only from chat_ids in open DCC chats, + the default handler will check from DCC chat requests */ +static void cmd_dcc_close(char *data, SERVER_REC *server) +{ + GSList *tmp, *next; + char *nick; + void *free_arg; + int found; + + g_return_if_fail(data != NULL); + + if (g_ascii_strncasecmp(data, "CHAT ", 5) != 0 || + !cmd_get_params(data, &free_arg, 2, NULL, &nick)) + return; + + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + CHAT_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_CHAT(dcc) && dcc->id != NULL && + g_ascii_strcasecmp(dcc->id, nick) == 0) { + found = TRUE; + if (!dcc_is_connected(dcc) && IS_IRC_SERVER(server)) + dcc_reject(DCC(dcc), IRC_SERVER(server)); + else { + /* don't send DCC REJECT after DCC chat + is already open */ + dcc_close(DCC(dcc)); + } + } + } + + if (found) signal_stop(); + + cmd_params_free(free_arg); +} + +static void cmd_whois(const char *data, SERVER_REC *server, + WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + /* /WHOIS without target in DCC CHAT query? */ + if (*data == '\0') { + dcc = item_get_dcc(item); + if (dcc != NULL) { + signal_emit("command whois", 3, + dcc->nick, server, item); + signal_stop(); + } + } +} + +#define DCC_AUTOACCEPT_PORT(dcc) \ + ((dcc)->port >= 1024 || settings_get_bool("dcc_autoaccept_lowports")) + +#define DCC_CHAT_AUTOACCEPT(dcc, server, nick, addr) \ + (DCC_AUTOACCEPT_PORT(dcc) && \ + masks_match(SERVER(server), \ + settings_get_str("dcc_autochat_masks"), (nick), (addr))) + + +/* CTCP: DCC CHAT */ +static void ctcp_msg_dcc_chat(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, CHAT_DCC_REC *chat) +{ + CHAT_DCC_REC *dcc; + char **params; + int paramcount; + int passive, autoallow = FALSE; + + /* CHAT <unused> <address> <port> */ + /* CHAT <unused> <address> 0 <id> (DCC CHAT passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + return; + } + passive = paramcount == 4 && g_strcmp0(params[2], "0") == 0; + + if (nick == NULL) + nick = ""; + + dcc = DCC_CHAT(dcc_find_request(DCC_CHAT_TYPE, nick, NULL)); + if (dcc != NULL) { + if (dcc_is_listening(dcc)) { + /* we requested dcc chat, they requested + dcc chat from us .. allow it. */ + dcc_destroy(DCC(dcc)); + autoallow = TRUE; + } else if (!dcc_is_passive(dcc)) { + /* we already have one dcc chat request + from this nick, remove it. */ + dcc_destroy(DCC(dcc)); + } else if (passive) { + if (dcc->pasv_id != atoi(params[3])) { + /* IDs don't match! */ + dcc_destroy(DCC(dcc)); + } else { + /* IDs are ok! Update address and port and + connect! */ + dcc->target = g_strdup(target); + dcc->port = atoi(params[2]); + dcc_str2ip(params[1], &dcc->addr); + net_ip2host(&dcc->addr, dcc->addrstr); + + dcc_chat_connect(dcc); + g_strfreev(params); + return; + } + } + } + + dcc = dcc_chat_create(server, chat, nick, params[0]); + if (dcc == NULL) { + g_strfreev(params); + g_warn_if_reached(); + return; + } + dcc->target = g_strdup(target); + dcc->port = atoi(params[2]); + + if (passive) + dcc->pasv_id = atoi(params[3]); + + dcc_str2ip(params[1], &dcc->addr); + net_ip2host(&dcc->addr, dcc->addrstr); + + signal_emit("dcc request", 2, dcc, addr); + + if (autoallow || DCC_CHAT_AUTOACCEPT(dcc, server, nick, addr)) { + if (passive) { + /* Passive DCC... let's set up a listening socket + and send reply back */ + dcc_chat_passive(dcc); + } else { + dcc_chat_connect(dcc); + } + } + g_strfreev(params); +} + +/* DCC CHAT: text received */ +static void dcc_chat_msg(CHAT_DCC_REC *dcc, const char *msg) +{ + char *event, *cmd, *ptr; + int reply; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + g_return_if_fail(msg != NULL); + + reply = FALSE; + if (g_ascii_strncasecmp(msg, "CTCP_MESSAGE ", 13) == 0) { + /* bitchx (and ircii?) sends this */ + msg += 13; + dcc->mirc_ctcp = FALSE; + } else if (g_ascii_strncasecmp(msg, "CTCP_REPLY ", 11) == 0) { + /* bitchx (and ircii?) sends this */ + msg += 11; + reply = TRUE; + dcc->mirc_ctcp = FALSE; + } else if (*msg == 1) { + /* Use the mirc style CTCPs from now on.. */ + dcc->mirc_ctcp = TRUE; + } + + /* Handle only DCC CTCPs */ + if (*msg != 1) + return; + + /* get ctcp command, remove \001 chars */ + event = g_strconcat(reply ? "dcc reply " : "dcc ctcp ", msg+1, NULL); + if (event[strlen(event)-1] == 1) event[strlen(event)-1] = '\0'; + + cmd = event + (reply ? 10 : 9); + ptr = strchr(cmd, ' '); + if (ptr != NULL) *ptr++ = '\0'; else ptr = ""; + + cmd = g_ascii_strup(cmd, -1); + + ascii_strdown(event+9); + if (!signal_emit(event, 2, dcc, ptr)) { + signal_emit(reply ? "default dcc reply" : + "default dcc ctcp", 3, dcc, cmd, ptr); + } + + g_free(cmd); + g_free(event); + + signal_stop(); +} + +static void dcc_ctcp_redirect(CHAT_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(IS_DCC_CHAT(dcc)); + + signal_emit("ctcp msg dcc", 6, dcc->server, msg, + dcc->nick, "dcc", dcc->mynick, dcc); +} + +static void dcc_ctcp_reply_redirect(CHAT_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(IS_DCC_CHAT(dcc)); + + signal_emit("ctcp reply dcc", 6, dcc->server, msg, + dcc->nick, "dcc", dcc->mynick, dcc); +} + +/* CTCP REPLY: REJECT */ +static void ctcp_reply_dcc_reject(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + DCC_REC *chat) +{ + DCC_REC *dcc; + + /* default REJECT handler checks args too - + we don't care about it in DCC chats. */ + if (g_ascii_strncasecmp(data, "CHAT", 4) == 0 && + (data[4] == '\0' || data[4] == ' ')) { + dcc = dcc_find_request(DCC_CHAT_TYPE, nick, NULL); + if (dcc != NULL) dcc_close(dcc); + signal_stop(); + } +} + +static void event_nick(IRC_SERVER_REC *server, const char *data, + const char *orignick) +{ + QUERY_REC *query; + CHAT_DCC_REC *dcc; + char *params, *nick, *tag; + + g_return_if_fail(data != NULL); + g_return_if_fail(orignick != NULL); + + params = event_get_params(data, 1, &nick); + if (g_ascii_strcasecmp(nick, orignick) == 0) { + /* shouldn't happen, but just to be sure irssi doesn't + get into infinite loop */ + g_free(params); + return; + } + + while ((dcc = dcc_chat_find_nick(server, orignick)) != NULL) { + g_free(dcc->nick); + dcc->nick = g_strdup(nick); + + tag = g_strconcat("=", dcc->id, NULL); + query = irc_query_find(server, tag); + g_free(tag); + + /* change the id too */ + g_free(dcc->id); + dcc->id = NULL; + dcc->id = dcc_chat_get_new_id(nick); + + if (query != NULL) { + tag = g_strconcat("=", dcc->id, NULL); + query_change_nick(query, tag); + g_free(tag); + } + } + + g_free(params); +} + +void dcc_chat_init(void) +{ + dcc_register_type("CHAT"); + settings_add_bool("dcc", "dcc_mirc_ctcp", FALSE); + settings_add_str("dcc", "dcc_autochat_masks", ""); + + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + command_bind("me", NULL, (SIGNAL_FUNC) cmd_me); + command_bind("action", NULL, (SIGNAL_FUNC) cmd_action); + command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); + command_bind("dcc chat", NULL, (SIGNAL_FUNC) cmd_dcc_chat); + command_set_options("dcc chat", "passive"); + command_bind("mircdcc", NULL, (SIGNAL_FUNC) cmd_mircdcc); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + command_bind("whois", NULL, (SIGNAL_FUNC) cmd_whois); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("ctcp msg dcc chat", (SIGNAL_FUNC) ctcp_msg_dcc_chat); + signal_add_first("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_add("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); + signal_add("dcc reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply_redirect); + signal_add("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_add("event nick", (SIGNAL_FUNC) event_nick); +} + +void dcc_chat_deinit(void) +{ + dcc_unregister_type("CHAT"); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); + command_unbind("me", (SIGNAL_FUNC) cmd_me); + command_unbind("action", (SIGNAL_FUNC) cmd_action); + command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); + command_unbind("dcc chat", (SIGNAL_FUNC) cmd_dcc_chat); + command_unbind("mircdcc", (SIGNAL_FUNC) cmd_mircdcc); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + command_unbind("whois", (SIGNAL_FUNC) cmd_whois); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("ctcp msg dcc chat", (SIGNAL_FUNC) ctcp_msg_dcc_chat); + signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_remove("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); + signal_remove("dcc reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply_redirect); + signal_remove("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); +} diff --git a/src/irc/dcc/dcc-chat.h b/src/irc/dcc/dcc-chat.h new file mode 100644 index 0000000..ef992bb --- /dev/null +++ b/src/irc/dcc/dcc-chat.h @@ -0,0 +1,40 @@ +#ifndef IRSSI_IRC_DCC_DCC_CHAT_H +#define IRSSI_IRC_DCC_DCC_CHAT_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_CHAT(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, CHAT_DCC_REC, type, "DCC", "CHAT") + +#define IS_DCC_CHAT(dcc) \ + (DCC_CHAT(dcc) ? TRUE : FALSE) + +struct CHAT_DCC_REC { +#include <irssi/src/irc/dcc/dcc-rec.h> + + char *id; /* unique identifier - usually same as nick. */ + NET_SENDBUF_REC *sendbuf; + + unsigned int mirc_ctcp:1; /* Send CTCPs without the CTCP_MESSAGE prefix */ + unsigned int connection_lost:1; /* other side closed connection */ +}; + +#define DCC_CHAT_TYPE module_get_uniq_id_str("DCC", "CHAT") + +CHAT_DCC_REC *dcc_chat_find_id(const char *id); + +/* Send `data' to dcc chat. */ +void dcc_chat_send(CHAT_DCC_REC *dcc, const char *data); + +/* Send a CTCP message/notify to target. + Send the CTCP via DCC chat if `chat' is specified. */ +void dcc_ctcp_message(IRC_SERVER_REC *server, const char *target, + CHAT_DCC_REC *chat, int notice, const char *msg); + +/* If `item' is a query of a =nick, return DCC chat record of nick */ +CHAT_DCC_REC *item_get_dcc(WI_ITEM_REC *item); + +void dcc_chat_init(void); +void dcc_chat_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-file-rec.h b/src/irc/dcc/dcc-file-rec.h new file mode 100644 index 0000000..28a488e --- /dev/null +++ b/src/irc/dcc/dcc-file-rec.h @@ -0,0 +1,9 @@ +#include <irssi/src/irc/dcc/dcc-rec.h> + +uoff_t size, skipped; /* file size / skipped at start */ +int fhandle; /* file handle */ +int queue; /* queue number */ + +/* counter buffer */ +char count_buf[4]; +int count_pos; diff --git a/src/irc/dcc/dcc-file.h b/src/irc/dcc/dcc-file.h new file mode 100644 index 0000000..3898635 --- /dev/null +++ b/src/irc/dcc/dcc-file.h @@ -0,0 +1,10 @@ +#ifndef IRSSI_IRC_DCC_DCC_FILE_H +#define IRSSI_IRC_DCC_DCC_FILE_H + +#include <irssi/src/irc/dcc/dcc.h> + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> +} FILE_DCC_REC; + +#endif diff --git a/src/irc/dcc/dcc-get.c b/src/irc/dcc/dcc-get.c new file mode 100644 index 0000000..0214387 --- /dev/null +++ b/src/irc/dcc/dcc-get.c @@ -0,0 +1,627 @@ +/* + dcc-get.c : irssi + + Copyright (C) 1999-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/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> + +static char *dcc_get_recv_buffer; + +GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + GET_DCC_REC *dcc; + + dcc = g_new0(GET_DCC_REC, 1); + dcc->orig_type = module_get_uniq_id_str("DCC", "SEND"); + dcc->type = module_get_uniq_id_str("DCC", "GET"); + dcc->fhandle = -1; + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void sig_dcc_destroyed(GET_DCC_REC *dcc) +{ + if (!IS_DCC_GET(dcc)) return; + + g_free_not_null(dcc->file); + if (dcc->fhandle != -1) close(dcc->fhandle); +} + +char *dcc_get_download_path(const char *fname) +{ + char *str, *downpath; + char *base; + + base = g_path_get_basename(fname); + downpath = convert_home(settings_get_str("dcc_download_path")); + str = g_strconcat(downpath, G_DIR_SEPARATOR_S, base, NULL); + g_free(downpath); + g_free(base); + + return str; +} + +static char *dcc_get_rename_file(const char *fname) +{ + GString *newname; + struct stat statbuf; + char *ret; + int num; + + newname = g_string_new(NULL); + num = 1; + do { + g_string_printf(newname, "%s.%d", fname, num); + num++; + } while (stat(newname->str, &statbuf) == 0); + + ret = newname->str; + g_string_free(newname, FALSE); + return ret; +} + +static void sig_dccget_send(GET_DCC_REC *dcc); + +void dcc_get_send_received(GET_DCC_REC *dcc) +{ + guint32 recd; + + recd = (guint32) htonl(dcc->transfd & 0xffffffff); + memcpy(dcc->count_buf, &recd, 4); + + dcc->count_pos = + net_transmit(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + if (dcc->count_pos == 4) dcc->count_pos = 0; + + /* count_pos might be -1 here. if this happens, the + count_buf should be re-sent.. also, if it's 1, 2 or 3, the + last 1-3 bytes should be sent later. these happen probably + never, but I just want to do it right.. :) */ + if (dcc->tagwrite == -1) { + dcc->tagwrite = + i_input_add(dcc->handle, I_INPUT_WRITE, (GInputFunction) sig_dccget_send, dcc); + } +} + +/* input function: DCC GET is free to send data */ +static void sig_dccget_send(GET_DCC_REC *dcc) +{ + guint32 recd; + int ret; + + if (dcc->count_pos != 0) { + ret = net_transmit(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + + if (dcc->count_pos <= 0) + dcc->count_pos = ret; + else if (ret > 0) + dcc->count_pos += ret; + + if (dcc->count_pos == 4) dcc->count_pos = 0; + + } + + if (dcc->count_pos == 0) { + g_source_remove(dcc->tagwrite); + dcc->tagwrite = -1; + } + + memcpy(&recd, dcc->count_buf, 4); + if (recd != (guint32) htonl(dcc->transfd & 0xffffffff)) + dcc_get_send_received(dcc); +} + +#define DCC_GET_RECV_BUFFER_SIZE 32768 + +/* input function: DCC GET received data */ +static void sig_dccget_receive(GET_DCC_REC *dcc) +{ + int ret; + + if (dcc_get_recv_buffer == NULL) { + dcc_get_recv_buffer = g_malloc(DCC_GET_RECV_BUFFER_SIZE); + } + + for (;;) { + ret = net_receive(dcc->handle, dcc_get_recv_buffer, + DCC_GET_RECV_BUFFER_SIZE); + if (ret == 0) break; + + if (ret < 0) { + /* socket closed - transmit complete, + or other side died.. */ + dcc_close(DCC(dcc)); + return; + } + + if (write(dcc->fhandle, dcc_get_recv_buffer, ret) != ret) { + /* most probably out of disk space */ + signal_emit("dcc error write", 2, + dcc, g_strerror(errno)); + dcc_close(DCC(dcc)); + return; + } + dcc->transfd += ret; + break; + } + + /* send number of total bytes received */ + if (dcc->count_pos <= 0) + dcc_get_send_received(dcc); + + signal_emit("dcc transfer update", 1, dcc); +} + +/* callback: net_connect() finished for DCC GET */ +void sig_dccget_connected(GET_DCC_REC *dcc) +{ + struct stat statbuf; + char *fname, *tempfname, *str; + int ret, ret_errno, temphandle, old_umask; + + if (!dcc->from_dccserver) { + if (net_geterror(dcc->handle) != 0) { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + return; + } + + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + } + + g_free_not_null(dcc->file); + dcc->file = dcc_get_download_path(dcc->arg); + + /* if some plugin wants to change the file name/path here.. */ + signal_emit("dcc get receive", 1, dcc); + + if (stat(dcc->file, &statbuf) == 0 && + dcc->get_type == DCC_GET_RENAME) { + /* file exists, rename.. */ + fname = dcc_get_rename_file(dcc->file); + g_free(dcc->file); + dcc->file = fname; + } + + if (dcc->get_type != DCC_GET_RESUME) { + int dcc_file_create_mode = octal2dec(settings_get_int("dcc_file_create_mode")); + + /* we want to overwrite the file, remove it here. + if it gets created after this, we'll fail. */ + unlink(dcc->file); + + /* just to make sure we won't run into race conditions + if download_path is in some global temp directory */ + tempfname = g_strconcat(dcc->file, ".XXXXXX", NULL); + + old_umask = umask(0077); + temphandle = mkstemp(tempfname); + umask(old_umask); + + if (temphandle == -1) + ret = -1; + else { + if (fchmod(temphandle, dcc_file_create_mode) != 0) + g_warning("fchmod(3) failed: %s", strerror(errno)); + /* proceed even if chmod fails */ + ret = 0; + } + + close(temphandle); + + if (ret != -1) { + ret = link(tempfname, dcc->file); + + if (ret == -1 && + /* Linux */ + (errno == EPERM || + /* FUSE */ + errno == ENOSYS || errno == EACCES || + /* BSD */ + errno == EOPNOTSUPP)) { + /* hard links aren't supported - some people + want to download stuff to FAT/NTFS/etc + partitions, so fallback to rename() */ + ret = rename(tempfname, dcc->file); + } + } + + /* if ret = 0, we're the file owner now */ + dcc->fhandle = ret == -1 ? -1 : + open(dcc->file, O_WRONLY | O_TRUNC); + + /* close/remove the temp file */ + ret_errno = errno; + unlink(tempfname); + g_free(tempfname); + + if (dcc->fhandle == -1) { + signal_emit("dcc error file create", 3, + dcc, dcc->file, g_strerror(ret_errno)); + dcc_destroy(DCC(dcc)); + return; + } + } + + dcc->starttime = time(NULL); + if (dcc->size == 0) { + dcc_close(DCC(dcc)); + return; + } + dcc->tagread = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) sig_dccget_receive, dcc); + signal_emit("dcc connected", 1, dcc); + + if (dcc->from_dccserver) { + str = g_strdup_printf("121 %s %d\n", + dcc->server ? dcc->server->nick : "??", 0); + net_transmit(dcc->handle, str, strlen(str)); + } +} + +void dcc_get_connect(GET_DCC_REC *dcc) +{ + if (dcc->get_type == DCC_GET_DEFAULT) { + dcc->get_type = settings_get_bool("dcc_autorename") ? + DCC_GET_RENAME : DCC_GET_OVERWRITE; + } + + if (dcc->from_dccserver) { + sig_dccget_connected(dcc); + return; + } + + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + + if (dcc->handle != NULL) { + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_WRITE | I_INPUT_READ, + (GInputFunction) sig_dccget_connected, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static void dcc_get_listen(GET_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR addr; + int port; + + /* accept connection */ + handle = net_accept(dcc->handle, &addr, &port); + if (handle == NULL) + return; + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + memcpy(&dcc->addr, &addr, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + + dcc->tagconn = i_input_add(handle, I_INPUT_READ | I_INPUT_WRITE, + (GInputFunction) sig_dccget_connected, dcc); +} + +void dcc_get_passive(GET_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR own_ip; + int port; + char host[MAX_IP_LEN]; + + handle = dcc_listen(net_sendbuffer_handle(dcc->server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_return_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_get_listen, dcc); + + /* Let's send the reply to the other client! */ + dcc_ip2str(&own_ip, host); + irc_send_cmdv(dcc->server, + "PRIVMSG %s :\001DCC SEND %s %s %d %"PRIuUOFF_T" %d\001", + dcc->nick, dcc->arg, host, port, dcc->size, dcc->pasv_id); +} + +#define get_params_match(params, pos) \ + ((is_numeric(params[pos], '\0') || is_ipv6_address(params[pos])) && \ + is_numeric(params[(pos)+1], '\0') && atol(params[(pos)+1]) < 65536 && \ + is_numeric(params[(pos)+2], '\0')) + +/* Return number of parameters in `params' that belong to file name. + Normally it's paramcount-3, but I don't think anything forbids of + adding some extension where there could be more parameters after + file size. + + MIRC sends filenames with spaces quoted ("file name"), but I'd rather + not trust that entirely either. At least some clients that don't really + understand the problem with spaces in file names sends the file name + without any quotes. */ +int get_file_params_count(char **params, int paramcount) +{ + int pos, best; + + if (*params[0] == '"') { + /* quoted file name? */ + for (pos = 0; pos < paramcount-3; pos++) { + if (strlen(params[pos]) == 0) + continue; + if (params[pos][strlen(params[pos])-1] == '"' && + get_params_match(params, pos+1)) + return pos+1; + } + } + + best = paramcount-3; + for (pos = paramcount-3; pos > 0; pos--) { + if (get_params_match(params, pos)) + best = pos; + } + + return best; +} + +char *get_file_name(char **params, int fileparams) +{ + GString *out = g_string_new(params[0]); + char *ret; + int pos; + + for (pos = 1; pos < fileparams; pos++) { + out = g_string_append(out, " "); + out = g_string_append(out, params[pos]); + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +/* CTCP: DCC SEND */ +static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, CHAT_DCC_REC *chat) +{ + GET_DCC_REC *dcc; + SEND_DCC_REC *temp_dcc; + IPADDR ip; + char *address, **params, *fname; + int paramcount, fileparams; + int port, len, quoted = FALSE; + uoff_t size; + int p_id = -1; + int passive = FALSE; + + if (addr == NULL) + addr = ""; + if (nick == NULL) + nick = ""; + + /* SEND <file name> <address> <port> <size> [...] */ + /* SEND <file name> <address> 0 <size> <id> (DCC SEND passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 4) { + signal_emit("dcc error ctcp", 5, "SEND", data, + nick, addr, target); + g_strfreev(params); + return; + } + + fileparams = get_file_params_count(params, paramcount); + + address = g_strdup(params[fileparams]); + dcc_str2ip(address, &ip); + port = atoi(params[fileparams+1]); + size = str_to_uofft(params[fileparams+2]); + + /* If this DCC uses passive protocol then store the id for later use. */ + if (paramcount == fileparams + 4) { + p_id = atoi(params[fileparams+3]); + passive = TRUE; + } + + fname = get_file_name(params, fileparams); + g_strfreev(params); + + len = strlen(fname); + if (len > 1 && *fname == '"' && fname[len-1] == '"') { + /* "file name" - MIRC sends filenames with spaces like this */ + fname[len-1] = '\0'; + memmove(fname, fname+1, len); + quoted = TRUE; + } + + if (passive && port != 0) { + /* This is NOT a DCC SEND request! This is a reply to our + passive request. We MUST check the IDs and then connect to + the remote host. */ + + temp_dcc = DCC_SEND(dcc_find_request(DCC_SEND_TYPE, nick, fname)); + if (temp_dcc != NULL && p_id == temp_dcc->pasv_id) { + temp_dcc->target = g_strdup(target); + temp_dcc->port = port; + temp_dcc->size = size; + temp_dcc->file_quoted = quoted; + + memcpy(&temp_dcc->addr, &ip, sizeof(IPADDR)); + if (temp_dcc->addr.family == AF_INET) + net_ip2host(&temp_dcc->addr, temp_dcc->addrstr); + else { + /* with IPv6, show it to us as it was sent */ + g_strlcpy(temp_dcc->addrstr, address, + sizeof(temp_dcc->addrstr)); + } + + /* This new signal is added to let us invoke + dcc_send_connect() which is found in dcc-send.c */ + signal_emit("dcc reply send pasv", 1, temp_dcc); + g_free(address); + g_free(fname); + return; + } else if (temp_dcc != NULL && p_id != temp_dcc->pasv_id) { + /* IDs don't match... remove the old DCC SEND and + return */ + dcc_destroy(DCC(temp_dcc)); + g_free(address); + g_free(fname); + return; + } + } + + dcc = DCC_GET(dcc_find_request(DCC_GET_TYPE, nick, fname)); + if (dcc != NULL) + dcc_destroy(DCC(dcc)); /* remove the old DCC */ + + dcc = dcc_get_create(server, chat, nick, fname); + if (dcc == NULL) { + g_free(address); + g_free(fname); + g_warn_if_reached(); + return; + } + dcc->target = g_strdup(target); + + if (passive && port == 0) + dcc->pasv_id = p_id; /* Assign the ID to the DCC */ + + memcpy(&dcc->addr, &ip, sizeof(ip)); + if (dcc->addr.family == AF_INET) + net_ip2host(&dcc->addr, dcc->addrstr); + else { + /* with IPv6, show it to us as it was sent */ + g_strlcpy(dcc->addrstr, address, sizeof(dcc->addrstr)); + } + dcc->port = port; + dcc->size = size; + dcc->file_quoted = quoted; + + signal_emit("dcc request", 2, dcc, addr); + + g_free(address); + g_free(fname); +} + +/* handle receiving DCC - GET/RESUME. */ +void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, + DCC_GET_FUNC pasv_accept_func) +{ + GET_DCC_REC *dcc; + GSList *tmp, *next; + char *nick, *arg, *fname; + void *free_arg; + int found; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, &nick, &arg)) + return; + + if (*nick == '\0') { + dcc = DCC_GET(dcc_find_request_latest(DCC_GET_TYPE)); + if (dcc != NULL) { + if (!dcc_is_passive(dcc)) + accept_func(dcc); + else + pasv_accept_func(dcc); + } + cmd_params_free(free_arg); + return; + } + + fname = cmd_get_quoted_param(&arg); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + GET_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_GET(dcc) && g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (dcc_is_waiting_user(dcc) || dcc->from_dccserver) && + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { + found = TRUE; + if (!dcc_is_passive(dcc)) + accept_func(dcc); + else + pasv_accept_func(dcc); + } + } + + if (!found) + signal_emit("dcc error get not found", 1, nick); + + cmd_params_free(free_arg); +} + +/* SYNTAX: DCC GET [<nick> [<file>]] */ +static void cmd_dcc_get(const char *data) +{ + cmd_dcc_receive(data, dcc_get_connect, dcc_get_passive); +} + +void dcc_get_init(void) +{ + dcc_register_type("GET"); + settings_add_bool("dcc", "dcc_autorename", FALSE); + settings_add_str("dcc", "dcc_download_path", "~"); + settings_add_int("dcc", "dcc_file_create_mode", 644); + + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("ctcp msg dcc send", (SIGNAL_FUNC) ctcp_msg_dcc_send); + command_bind("dcc get", NULL, (SIGNAL_FUNC) cmd_dcc_get); +} + +void dcc_get_deinit(void) +{ + dcc_unregister_type("GET"); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("ctcp msg dcc send", (SIGNAL_FUNC) ctcp_msg_dcc_send); + command_unbind("dcc get", (SIGNAL_FUNC) cmd_dcc_get); + g_free_and_null(dcc_get_recv_buffer); +} diff --git a/src/irc/dcc/dcc-get.h b/src/irc/dcc/dcc-get.h new file mode 100644 index 0000000..3624f2c --- /dev/null +++ b/src/irc/dcc/dcc-get.h @@ -0,0 +1,45 @@ +#ifndef IRSSI_IRC_DCC_DCC_GET_H +#define IRSSI_IRC_DCC_DCC_GET_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_GET(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, GET_DCC_REC, type, "DCC", "GET") + +#define IS_DCC_GET(dcc) \ + (DCC_GET(dcc) ? TRUE : FALSE) + +enum { + DCC_GET_DEFAULT, + + DCC_GET_RENAME, + DCC_GET_OVERWRITE, + DCC_GET_RESUME +}; + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> + + int get_type; /* what to do if file exists? */ + char *file; /* file name we're really moving, arg is just the reference */ + + unsigned int file_quoted:1; /* file name was received quoted ("file name") */ + unsigned int from_dccserver:1; /* get is using dccserver method */ +} GET_DCC_REC; + +#define DCC_GET_TYPE module_get_uniq_id_str("DCC", "GET") + +typedef void (*DCC_GET_FUNC) (GET_DCC_REC *); + +/* handle receiving DCC - GET/RESUME. */ +void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, + DCC_GET_FUNC pasv_accept_func); + +void dcc_get_passive(GET_DCC_REC *dcc); +void dcc_get_connect(GET_DCC_REC *dcc); +char *dcc_get_download_path(const char *fname); + +void dcc_get_init(void); +void dcc_get_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-queue.c b/src/irc/dcc/dcc-queue.c new file mode 100644 index 0000000..0253be4 --- /dev/null +++ b/src/irc/dcc/dcc-queue.c @@ -0,0 +1,227 @@ +/* + dcc-queue.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + DCC queue by Heikki Orsila <heikki.orsila@tut.fi> (no copyrights claimed) + + 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/network.h> +#include <irssi/src/core/net-sendbuffer.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/dcc/dcc-queue.h> + +static GPtrArray *queuelist; + +/* dcc_queue_old finds an old queue (if it exists) */ +int dcc_queue_old(const char *nick, const char *servertag) +{ + int i; + + for (i = 0; i < queuelist->len; i++) { + GSList *qlist = g_ptr_array_index(queuelist, i); + + for (; qlist != NULL; qlist = qlist->next) { + DCC_QUEUE_REC *rec = qlist->data; + + if (rec == NULL) + continue; + + if (*nick != '\0' && + g_ascii_strcasecmp(nick, rec->nick) != 0) + continue; + + if (*servertag != '\0' && + g_ascii_strcasecmp(servertag, rec->servertag) != 0) + continue; + + /* found a queue matching nick/server! */ + return i; + } + } + + return -1; +} + + +int dcc_queue_new(void) +{ + int i; + + for (i = 0; i < queuelist->len; i++) { + if (g_ptr_array_index(queuelist, i) == NULL) + break; + } + + if (i == queuelist->len) + g_ptr_array_set_size(queuelist, (i + 1) * 2); + + /* create stub */ + g_ptr_array_index(queuelist, i) = g_slist_append(NULL, NULL); + return i; +} + +static void dcc_queue_free_rec(DCC_QUEUE_REC *rec) +{ + if (rec != NULL) { + g_free(rec->servertag); + g_free(rec->nick); + g_free(rec->file); + g_free(rec); + } +} + +void dcc_queue_free(int queue) +{ + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + while (*qlist != NULL) { + DCC_QUEUE_REC *rec = (*qlist)->data; + + *qlist = (*qlist)->next; + dcc_queue_free_rec(rec); + } +} + +/* add an element to queue. element will have nick/servertag/fname/chat as data. + mode specifies how the element should be added (append or prepend) +*/ + +void dcc_queue_add(int queue, int mode, const char *nick, const char *fname, + const char *servertag, CHAT_DCC_REC *chat, int passive) +{ + DCC_QUEUE_REC *rec; + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + rec = g_new0(DCC_QUEUE_REC, 1); + rec->chat = chat; + rec->servertag = g_strdup(servertag); + rec->nick = g_strdup(nick); + rec->file = g_strdup(fname); + rec->passive = passive; + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + if (mode == DCC_QUEUE_PREPEND) + *qlist = g_slist_insert(*qlist, rec, 1); + else + *qlist = g_slist_append(*qlist, rec); +} + +/* removes the head or the tail from the queue. returns the number of + elements removed from the queue (0 or 1). if remove_head is non-zero, + the head is removed (or actually stub is removed and the current head + becomes the stub), otherwise the tail is removed. */ +static int dcc_queue_remove_entry(int queue, int remove_head) +{ + DCC_QUEUE_REC *rec; + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + if (*qlist == NULL || (*qlist)->next == NULL) + return 0; + + rec = remove_head ? (*qlist)->data : g_slist_last(*qlist)->data; + *qlist = g_slist_remove(*qlist, rec); + + dcc_queue_free_rec(rec); + return 1; +} + +/* removes the head, but not stub from the queue. returns number of elements + removed from the queue (0 or 1) */ +int dcc_queue_remove_head(int queue) +{ + return dcc_queue_remove_entry(queue, 1); +} + +/* removes the tail, but not stub from the queue. returns number of elements + removed from the queue (0 or 1) */ +int dcc_queue_remove_tail(int queue) +{ + return dcc_queue_remove_entry(queue, 0); +} + +DCC_QUEUE_REC *dcc_queue_get_next(int queue) +{ + GSList *qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = g_ptr_array_index(queuelist, queue); + return qlist == NULL || qlist->next == NULL ? NULL : qlist->next->data; +} + +GSList *dcc_queue_get_queue(int queue) +{ + GSList *qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = g_ptr_array_index(queuelist, queue); + return qlist == NULL ? NULL : qlist->next; +} + +static void sig_dcc_destroyed(CHAT_DCC_REC *dcc) +{ + int i; + + if (!IS_DCC_CHAT(dcc)) + return; + + for (i = 0; i < queuelist->len; i++) { + GSList *qlist = g_ptr_array_index(queuelist, i); + + for (; qlist != NULL; qlist = qlist->next) { + DCC_QUEUE_REC *rec = qlist->data; + + if (rec != NULL && rec->chat == dcc) + rec->chat = NULL; + } + } +} + +void dcc_queue_init(void) +{ + queuelist = g_ptr_array_new(); + + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); +} + +void dcc_queue_deinit(void) +{ + int i; + + for (i = 0; i < queuelist->len; i++) + dcc_queue_free(i); + + g_ptr_array_free(queuelist, TRUE); + + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); +} diff --git a/src/irc/dcc/dcc-queue.h b/src/irc/dcc/dcc-queue.h new file mode 100644 index 0000000..1ab4ecb --- /dev/null +++ b/src/irc/dcc/dcc-queue.h @@ -0,0 +1,45 @@ +#ifndef IRSSI_IRC_DCC_DCC_QUEUE_H_ +#define IRSSI_IRC_DCC_DCC_QUEUE_H_ + +#include <irssi/src/irc/dcc/dcc-chat.h> + +enum { + DCC_QUEUE_NORMAL, + DCC_QUEUE_PREPEND, + DCC_QUEUE_APPEND +}; + +typedef struct { + CHAT_DCC_REC *chat; + char *servertag; + char *nick; + char *file; + int passive; /* for passive DCCs */ +} DCC_QUEUE_REC; + +/* create a new queue. returns it's designation number (int) */ +int dcc_queue_new(void); + +void dcc_queue_free(int queue); + +/* finds an old queue and returns it's designation number (int). if not + found return -1 */ +int dcc_queue_old(const char *nick, const char *servertag); + +/* adds nick/fname/servertag triplet into queue */ +void dcc_queue_add(int queue, int mode, const char *nick, const char *fname, + const char *servertag, CHAT_DCC_REC *chat, int passive); + +int dcc_queue_remove_head(int queue); + +int dcc_queue_remove_tail(int queue); + +/* return the first entry from queue */ +DCC_QUEUE_REC *dcc_queue_get_next(int queue); + +GSList *dcc_queue_get_queue(int queue); + +void dcc_queue_init(void); +void dcc_queue_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-rec.h b/src/irc/dcc/dcc-rec.h new file mode 100644 index 0000000..afb029d --- /dev/null +++ b/src/irc/dcc/dcc-rec.h @@ -0,0 +1,27 @@ +int type; +int orig_type; /* original DCC type that was sent to us - same as type except GET and SEND are swapped */ +time_t created; + +IRC_SERVER_REC *server; +char *servertag; /* for resetting server later if we get disconnected */ +char *mynick; /* my current nick */ +char *nick; + +CHAT_DCC_REC *chat; /* if the request came through DCC chat */ +char *target; /* who the request was sent to - your nick, channel or NULL if you sent the request */ +char *arg; + +IPADDR addr; /* address we're connected in */ +char addrstr[MAX_IP_LEN]; /* in readable form */ +int port; /* port we're connected in */ + +GIOChannel *handle; /* socket handle */ +int tagconn, tagread, tagwrite; +time_t starttime; /* transfer start time */ +uoff_t transfd; /* bytes transferred */ + +int pasv_id; /* DCC Id for passive DCCs. <0 means a passive DCC, >=0 means a standard DCC */ + +unsigned int destroyed:1; /* We're about to destroy this DCC recond */ + +GHashTable *module_data; diff --git a/src/irc/dcc/dcc-resume.c b/src/irc/dcc/dcc-resume.c new file mode 100644 index 0000000..dd62ff9 --- /dev/null +++ b/src/irc/dcc/dcc-resume.c @@ -0,0 +1,248 @@ +/* + dcc-resume.c : irssi + + Copyright (C) 1999-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/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/dcc/dcc-file.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-chat.h> + +static FILE_DCC_REC *dcc_resume_find(int type, const char *nick, int port) +{ + GSList *tmp; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + FILE_DCC_REC *dcc = tmp->data; + + if (dcc->type == type && !dcc_is_connected(dcc) && + dcc->port == port && + g_ascii_strcasecmp(dcc->nick, nick) == 0) + return dcc; + } + + return NULL; +} + +#define get_params_match_resume(params, pos) \ + (is_numeric(params[pos], '\0') && atol(params[pos]) < 65536 && \ + is_numeric(params[(pos)+1], '\0')) + +/* Based on get_file_params_count() found in dcc-get.c. The main difference + is represented by the number of params expected after the filename (2 at + least). I've added this new routine to avoid possible troubles connected + to relaxing the old checks done on DCC GET params to suite the ACCEPT/RESUME + needs. + */ +int get_file_params_count_resume(char **params, int paramcount) +{ + int pos, best; + + if (*params[0] == '"') { + /* quoted file name? */ + for (pos = 0; pos < paramcount-2; pos++) { + if (strlen(params[pos]) == 0) + continue; + if (params[pos][strlen(params[pos])-1] == '"' && + get_params_match_resume(params, pos+1)) + return pos+1; + } + } + + best = paramcount-2; + for (pos = paramcount-2; pos > 0; pos--) { + if (get_params_match_resume(params, pos)) + best = pos; + } + + return best; +} + + +static int dcc_ctcp_resume_parse(int type, const char *data, const char *nick, + FILE_DCC_REC **dcc, uoff_t *size, int *pasv_id) +{ + char **params; + int paramcount, fileparams; + int port; + + /* RESUME|ACCEPT <file name> <port> <size> */ + /* RESUME|ACCEPT <file name> 0 <size> <id> (passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + return 0; + } + + fileparams = get_file_params_count_resume(params, paramcount); + + if (paramcount >= fileparams + 2) { + port = atoi(params[fileparams]); + *size = str_to_uofft(params[fileparams+1]); + *pasv_id = ((port == 0) && (paramcount == fileparams + 3)) ? atoi(params[fileparams+2]) : -1; + *dcc = dcc_resume_find(type, nick, port); + g_strfreev(params); + + /* If the ID is different then the DCC cannot be resumed */ + return ((*dcc != NULL) && ((*dcc)->pasv_id == *pasv_id)); + } + g_strfreev(params); + return FALSE; +} + +static int dcc_resume_file_check(FILE_DCC_REC *dcc, IRC_SERVER_REC *server, + uoff_t size) +{ + if (size >= dcc->size) { + /* whole file sent */ + dcc->starttime = time(NULL); + dcc_reject(DCC(dcc), server); + } else if (lseek(dcc->fhandle, (off_t)size, SEEK_SET) != (off_t)size) { + /* error */ + dcc_reject(DCC(dcc), server); + } else { + dcc->transfd = dcc->skipped = size; + return TRUE; + } + + return FALSE; +} + +/* CTCP: DCC RESUME - requesting to resume DCC SEND */ +static void ctcp_msg_dcc_resume(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + FILE_DCC_REC *dcc; + char *str; + uoff_t size; + int pasv_id = -1; + + if (!dcc_ctcp_resume_parse(DCC_SEND_TYPE, data, nick, &dcc, &size, &pasv_id)) { + signal_emit("dcc error ctcp", 5, "RESUME", data, + nick, addr, target); + } else if (dcc != NULL && dcc_resume_file_check(dcc, server, size)) { + if (!dcc_is_passive(dcc)) { + str = g_strdup_printf(DCC_SEND(dcc)->file_quoted ? + "DCC ACCEPT \"%s\" %d %"PRIuUOFF_T : + "DCC ACCEPT %s %d %"PRIuUOFF_T, + dcc->arg, dcc->port, dcc->transfd); + } else { + str = g_strdup_printf(DCC_SEND(dcc)->file_quoted ? + "DCC ACCEPT \"%s\" 0 %"PRIuUOFF_T" %d" : + "DCC ACCEPT %s 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->transfd, dcc->pasv_id); + } + dcc_ctcp_message(dcc->server, dcc->nick, + dcc->chat, FALSE, str); + g_free(str); + } +} + +/* CTCP: DCC ACCEPT - accept resuming DCC GET */ +static void ctcp_msg_dcc_accept(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + FILE_DCC_REC *dcc; + uoff_t size; + int pasv_id; + + if (!dcc_ctcp_resume_parse(DCC_GET_TYPE, data, nick, &dcc, &size, &pasv_id) || + (dcc != NULL && DCC_GET(dcc)->get_type != DCC_GET_RESUME)) { + signal_emit("dcc error ctcp", 5, "ACCEPT", data, + nick, addr, target); + } else if (dcc != NULL && dcc_resume_file_check(dcc, server, size)) { + if (!dcc_is_passive(dcc)) + dcc_get_connect(DCC_GET(dcc)); + else + dcc_get_passive(DCC_GET(dcc)); + } +} + +/* Resume a DCC GET */ +static void dcc_send_resume(GET_DCC_REC *dcc) +{ + off_t pos; + char *str; + + g_return_if_fail(dcc != NULL); + + dcc->file = dcc_get_download_path(dcc->arg); + dcc->fhandle = open(dcc->file, O_WRONLY); + if (dcc->fhandle == -1) { + signal_emit("dcc error file open", 3, dcc->nick, dcc->file, + GINT_TO_POINTER(errno)); + return; + } + + dcc->get_type = DCC_GET_RESUME; + + pos = lseek(dcc->fhandle, 0, SEEK_END); + dcc->transfd = pos < 0 ? 0 : (uoff_t)pos; + dcc->skipped = dcc->transfd; + + if (dcc->skipped == dcc->size) { + /* already received whole file */ + dcc->starttime = time(NULL); + dcc_reject(DCC(dcc), NULL); + } else { + if (!dcc_is_passive(dcc)) { + str = g_strdup_printf(dcc->file_quoted ? + "DCC RESUME \"%s\" %d %"PRIuUOFF_T : + "DCC RESUME %s %d %"PRIuUOFF_T, + dcc->arg, dcc->port, dcc->transfd); + } else { + str = g_strdup_printf(dcc->file_quoted ? + "DCC RESUME \"%s\" 0 %"PRIuUOFF_T" %d" : + "DCC RESUME %s 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->transfd, dcc->pasv_id); + } + dcc_ctcp_message(dcc->server, dcc->nick, + dcc->chat, FALSE, str); + g_free(str); + } +} + +/* SYNTAX: DCC RESUME [<nick> [<file>]] */ +static void cmd_dcc_resume(const char *data) +{ + cmd_dcc_receive(data, dcc_send_resume, dcc_send_resume); +} + +void dcc_resume_init(void) +{ + signal_add("ctcp msg dcc resume", (SIGNAL_FUNC) ctcp_msg_dcc_resume); + signal_add("ctcp msg dcc accept", (SIGNAL_FUNC) ctcp_msg_dcc_accept); + command_bind("dcc resume", NULL, (SIGNAL_FUNC) cmd_dcc_resume); +} + +void dcc_resume_deinit(void) +{ + signal_remove("ctcp msg dcc resume", (SIGNAL_FUNC) ctcp_msg_dcc_resume); + signal_remove("ctcp msg dcc accept", (SIGNAL_FUNC) ctcp_msg_dcc_accept); + command_unbind("dcc resume", (SIGNAL_FUNC) cmd_dcc_resume); +} diff --git a/src/irc/dcc/dcc-send.c b/src/irc/dcc/dcc-send.c new file mode 100644 index 0000000..eccb8d3 --- /dev/null +++ b/src/irc/dcc/dcc-send.c @@ -0,0 +1,487 @@ +/* + dcc-send.c : irssi + + Copyright (C) 1999-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/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.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/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-queue.h> + +#include <glob.h> + +#ifndef GLOB_TILDE +# define GLOB_TILDE 0 /* unsupported */ +#endif + +static int dcc_send_one_file(int queue, const char *target, const char *fname, + IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + int passive); + +static void dcc_queue_send_next(int queue) +{ + IRC_SERVER_REC *server; + DCC_QUEUE_REC *qrec; + int send_started = FALSE; + + while ((qrec = dcc_queue_get_next(queue)) != NULL && !send_started) { + server = qrec->servertag == NULL ? NULL : + IRC_SERVER(server_find_tag(qrec->servertag)); + + if (server == NULL && qrec->chat == NULL) { + /* no way to send this request */ + signal_emit("dcc error send no route", 2, + qrec->nick, qrec->file); + } else { + send_started = dcc_send_one_file(queue, qrec->nick, + qrec->file, server, + qrec->chat, + qrec->passive); + } + dcc_queue_remove_head(queue); + } + + if (!send_started) { + /* no files in queue anymore, remove it */ + dcc_queue_free(queue); + } +} + +static char *dcc_send_get_file(const char *fname) +{ + char *str, *path; + + str = convert_home(fname); + if (!g_path_is_absolute(str)) { + /* full path not given to file, use dcc_upload_path */ + g_free(str); + + path = convert_home(settings_get_str("dcc_upload_path")); + str = *path == '\0' ? g_strdup(fname) : + g_strconcat(path, G_DIR_SEPARATOR_S, fname, NULL); + g_free(path); + } + + return str; +} + +static void dcc_send_add(const char *servertag, CHAT_DCC_REC *chat, + const char *nick, char *fileargs, int add_mode, + int passive) +{ + struct stat st; + glob_t globbuf; + char *fname; + int i, ret, files, flags, queue, start_new_transfer; + + memset(&globbuf, 0, sizeof(globbuf)); + flags = GLOB_NOCHECK | GLOB_TILDE; + + /* this loop parses all <file> parameters and adds them to glubbuf */ + for (;;) { + fname = cmd_get_quoted_param(&fileargs); + if (*fname == '\0') + break; + + if (glob(fname, flags, 0, &globbuf) < 0) + break; + + /* this flag must not be set before first call to glob! + (man glob) */ + flags |= GLOB_APPEND; + } + + files = 0; queue = -1; start_new_transfer = 0; + + /* add all globbed files to a proper queue */ + for (i = 0; i < globbuf.gl_pathc; i++) { + char *fname = dcc_send_get_file(globbuf.gl_pathv[i]); + + ret = stat(fname, &st); + if (ret == 0 && S_ISDIR(st.st_mode)) { + /* we don't want directories */ + errno = EISDIR; + ret = -1; + } + + if (ret < 0) { + signal_emit("dcc error file open", 3, + nick, fname, errno); + g_free(fname); + continue; + } + + if (queue < 0) { + /* in append and prepend mode try to find an + old queue. if an old queue is not found + create a new queue. if not in append or + prepend mode, create a new queue */ + if (add_mode != DCC_QUEUE_NORMAL) + queue = dcc_queue_old(nick, servertag); + start_new_transfer = 0; + if (queue < 0) { + queue = dcc_queue_new(); + start_new_transfer = 1; + } + } + + dcc_queue_add(queue, add_mode, nick, + fname, servertag, chat, passive); + files++; + g_free(fname); + } + + if (files > 0 && start_new_transfer) + dcc_queue_send_next(queue); + + globfree(&globbuf); +} + +/* DCC SEND [-append | -prepend | -flush | -rmtail | -rmhead | -passive] + <nick> <file> [<file> ...] */ +static void cmd_dcc_send(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + const char *servertag; + char *nick, *fileargs; + void *free_arg; + CHAT_DCC_REC *chat; + GHashTable *optlist; + int queue, mode, passive; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "dcc send", &optlist, &nick, &fileargs)) + return; + + chat = item_get_dcc(item); + if (chat != NULL && + (chat->mirc_ctcp || g_ascii_strcasecmp(nick, chat->nick) != 0)) + chat = NULL; + + if (IS_IRC_SERVER(server) && server->connected) + servertag = server->tag; + else if (chat != NULL) + servertag = chat->servertag; + else + servertag = NULL; + + if (servertag == NULL && chat == NULL) + cmd_param_error(CMDERR_NOT_CONNECTED); + + passive = g_hash_table_lookup(optlist, "passive") != NULL; + + if (g_hash_table_lookup(optlist, "rmhead") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_remove_head(queue); + } else if (g_hash_table_lookup(optlist, "rmtail") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_remove_tail(queue); + } else if (g_hash_table_lookup(optlist, "flush") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_free(queue); + } else { + if (g_hash_table_lookup(optlist, "append") != NULL) + mode = DCC_QUEUE_APPEND; + else if (g_hash_table_lookup(optlist, "prepend") != NULL) + mode = DCC_QUEUE_PREPEND; + else + mode = DCC_QUEUE_NORMAL; + + if (*fileargs == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc_send_add(servertag, chat, nick, fileargs, mode, passive); + } + + cmd_params_free(free_arg); +} + +static SEND_DCC_REC *dcc_send_create(IRC_SERVER_REC *server, + CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + SEND_DCC_REC *dcc; + + dcc = g_new0(SEND_DCC_REC, 1); + dcc->orig_type = module_get_uniq_id_str("DCC", "GET"); + dcc->type = module_get_uniq_id_str("DCC", "SEND"); + dcc->fhandle = -1; + dcc->queue = -1; + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void sig_dcc_destroyed(SEND_DCC_REC *dcc) +{ + if (!IS_DCC_SEND(dcc)) return; + + if (dcc->fhandle != -1) + close(dcc->fhandle); + + dcc_queue_send_next(dcc->queue); +} + +/* input function: DCC SEND - we're ready to send more data */ +static void dcc_send_data(SEND_DCC_REC *dcc) +{ + char buffer[512]; + int ret; + + ret = read(dcc->fhandle, buffer, sizeof(buffer)); + if (ret <= 0) { + /* no need to call this function anymore.. + in fact it just eats all the cpu.. */ + dcc->waitforend = TRUE; + g_source_remove(dcc->tagwrite); + dcc->tagwrite = -1; + return; + } + + ret = net_transmit(dcc->handle, buffer, ret); + if (ret > 0) dcc->transfd += ret; + dcc->gotalldata = FALSE; + + lseek(dcc->fhandle, dcc->transfd, SEEK_SET); + + signal_emit("dcc transfer update", 1, dcc); +} + +/* input function: DCC SEND - received some data */ +static void dcc_send_read_size(SEND_DCC_REC *dcc) +{ + guint32 bytes; + int ret; + + ret = net_receive(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + if (ret == -1) { + dcc_close(DCC(dcc)); + return; + } + + dcc->count_pos += ret; + + if (dcc->count_pos != 4) + return; + + memcpy(&bytes, dcc->count_buf, sizeof(bytes)); + bytes = ntohl(bytes); + dcc->count_pos = 0; + + if (dcc->waitforend && bytes == (dcc->transfd & 0xffffffff)) { + /* file is sent */ + dcc->gotalldata = TRUE; + dcc_close(DCC(dcc)); + } +} + +/* input function: DCC SEND - someone tried to connect to our socket */ +static void dcc_send_connected(SEND_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR addr; + int port; + + /* accept connection */ + handle = net_accept(dcc->handle, &addr, &port); + if (handle == NULL) + return; + + /* TODO: some kind of paranoia check would be nice. it would check + that the host of the nick who we sent the request matches the + address who connected us. */ + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + memcpy(&dcc->addr, &addr, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + + dcc->tagread = i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_send_read_size, dcc); + dcc->tagwrite = i_input_add(handle, I_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* input function: DCC SEND - connect to the receiver (passive protocol) */ +static void dcc_send_connect(SEND_DCC_REC *dcc) +{ + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + + if (dcc->handle != NULL) { + dcc->starttime = time(NULL); + + dcc->tagread = i_input_add(dcc->handle, I_INPUT_READ, + (GInputFunction) dcc_send_read_size, dcc); + dcc->tagwrite = + i_input_add(dcc->handle, I_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc); + signal_emit("dcc connected", 1, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static int dcc_send_one_file(int queue, const char *target, const char *fname, + IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + int passive) +{ + struct stat st; + char *str; + char host[MAX_IP_LEN]; + int hfile, port = 0; + SEND_DCC_REC *dcc; + IPADDR own_ip; + GIOChannel *handle; + + if (dcc_find_request(DCC_SEND_TYPE, target, fname)) { + signal_emit("dcc error send exists", 2, target, fname); + return FALSE; + } + + str = dcc_send_get_file(fname); + hfile = open(str, O_RDONLY); + g_free(str); + + if (hfile == -1) { + signal_emit("dcc error file open", 3, target, fname, + GINT_TO_POINTER(errno)); + return FALSE; + } + + if (fstat(hfile, &st) < 0) { + g_warning("fstat() failed: %s", strerror(errno)); + close(hfile); + return FALSE; + } + + /* start listening (only if passive == FALSE )*/ + + if (passive == FALSE) { + handle = dcc_listen(chat != NULL ? chat->handle : + net_sendbuffer_handle(server->handle), + &own_ip, &port); + if (handle == NULL) { + close(hfile); + g_warning("dcc_listen() failed: %s", strerror(errno)); + return FALSE; + } + } else { + handle = NULL; + } + + str = g_path_get_basename(fname); + + /* Replace all the spaces with underscore so that lesser + intelligent clients can communicate.. */ + if (settings_get_bool("dcc_send_replace_space_with_underscore")) + g_strdelimit(str, " ", '_'); + + dcc = dcc_send_create(server, chat, target, str); + g_free(str); + if (dcc == NULL) { + g_warn_if_reached(); + close(hfile); + return FALSE; + } + + dcc->handle = handle; + dcc->port = port; + dcc->size = st.st_size; + dcc->fhandle = hfile; + dcc->queue = queue; + dcc->file_quoted = strchr(fname, ' ') != NULL; + if (!passive) { + dcc->tagconn = + i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_send_connected, dcc); + } + + /* Generate an ID for this send if using passive protocol */ + if (passive) { + dcc->pasv_id = rand() % 64; + } + + /* send DCC request */ + signal_emit("dcc request send", 1, dcc); + + + dcc_ip2str(&own_ip, host); + if (passive == FALSE) { + str = g_strdup_printf(dcc->file_quoted ? + "DCC SEND \"%s\" %s %d %"PRIuUOFF_T : + "DCC SEND %s %s %d %"PRIuUOFF_T, + dcc->arg, host, port, dcc->size); + } else { + str = g_strdup_printf(dcc->file_quoted ? + "DCC SEND \"%s\" 16843009 0 %"PRIuUOFF_T" %d" : + "DCC SEND %s 16843009 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->size, dcc->pasv_id); + } + dcc_ctcp_message(server, target, chat, FALSE, str); + + g_free(str); + return TRUE; +} + +void dcc_send_init(void) +{ + dcc_register_type("SEND"); + settings_add_str("dcc", "dcc_upload_path", "~"); + settings_add_bool("dcc", "dcc_send_replace_space_with_underscore", FALSE); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("dcc reply send pasv", (SIGNAL_FUNC) dcc_send_connect); + command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send); + command_set_options("dcc send", "append flush prepend rmhead rmtail passive"); + + dcc_queue_init(); +} + +void dcc_send_deinit(void) +{ + dcc_queue_deinit(); + + dcc_unregister_type("SEND"); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("dcc reply send pasv", (SIGNAL_FUNC) dcc_send_connect); + command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send); +} diff --git a/src/irc/dcc/dcc-send.h b/src/irc/dcc/dcc-send.h new file mode 100644 index 0000000..881ef64 --- /dev/null +++ b/src/irc/dcc/dcc-send.h @@ -0,0 +1,27 @@ +#ifndef IRSSI_IRC_DCC_DCC_SEND_H +#define IRSSI_IRC_DCC_DCC_SEND_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_SEND(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, SEND_DCC_REC, type, "DCC", "SEND") + +#define IS_DCC_SEND(dcc) \ + (DCC_SEND(dcc) ? TRUE : FALSE) + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> + + unsigned int file_quoted:1; /* file name was received quoted ("file name") */ + + /* fastsending: */ + unsigned int waitforend:1; /* file is sent, just wait for the replies from the other side */ + unsigned int gotalldata:1; /* got all acks from the other end (needed to make sure the end of transfer works right) */ +} SEND_DCC_REC; + +#define DCC_SEND_TYPE module_get_uniq_id_str("DCC", "SEND") + +void dcc_send_init(void); +void dcc_send_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-server.c b/src/irc/dcc/dcc-server.c new file mode 100644 index 0000000..4d1e94a --- /dev/null +++ b/src/irc/dcc/dcc-server.c @@ -0,0 +1,413 @@ +/* + dcc-server.c : irssi + + Copyright (C) 2003 Mark Trumbull + + 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/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-server.h> + +void sig_dccget_connected(GET_DCC_REC *dcc); +GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); + +void dcc_chat_input(CHAT_DCC_REC *dcc); +CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); + +static void sig_dcc_destroyed(SERVER_DCC_REC *dcc) +{ + if (!IS_DCC_SERVER(dcc)) + return; + + if (dcc->sendbuf != NULL) + net_sendbuffer_destroy(dcc->sendbuf, FALSE); +} + +/* Start listening for incoming connections */ +static GIOChannel *dcc_listen_port(GIOChannel *iface, IPADDR *ip, int port) +{ + if (net_getsockname(iface, ip, NULL) == -1) + return NULL; + + if (IPADDR_IS_V6(ip)) + return net_listen(NULL, &port); + else + return net_listen(&ip4_any, &port); +} + +/* input function: DCC SERVER received some data.. */ +static void dcc_server_input(SERVER_DCC_REC *dcc) +{ + char *str; + int ret; + + g_return_if_fail(IS_DCC_SERVER(dcc)); + + do { + ret = net_sendbuffer_receive_line(dcc->sendbuf, &str, 1); + + if (ret == -1) { + /* connection lost */ + dcc_close(DCC(dcc)); + break; + } + + if (ret > 0) { + dcc->transfd += ret; + signal_emit("dcc server message", 2, dcc, str); + } + + if (dcc->connection_established) { + /* We set handle to NULL first because the new (chat/get) is using the same */ + /* handle and we don't want dcc_close to disconnect it.*/ + dcc->handle = NULL; + dcc_close(DCC(dcc)); + break; + } + } while (ret > 0); +} + +static void dcc_server_update_flags(SERVER_DCC_REC *dcc, const char *flags) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(IS_DCC_SERVER(dcc)); + + if (*flags == '+' || *flags == '-') { + const char *ptr = flags + 1; + unsigned int value = (*flags == '+') ? 1 : 0; + + while (*ptr) { + if (*ptr == 's' || *ptr == 'S') { dcc->accept_send = value; } + else if (*ptr == 'c' || *ptr == 'C') { dcc->accept_chat = value; } + else if (*ptr == 'f' || *ptr == 'F') { dcc->accept_fserve = value; } + ptr++; + } + } +} + +/* Initialize DCC record */ +static void dcc_init_server_rec(SERVER_DCC_REC *dcc, IRC_SERVER_REC *server, + const char *mynick, const char *servertag) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(IS_DCC_SERVER(dcc)); + + MODULE_DATA_INIT(dcc); + dcc->created = time(NULL); + dcc->chat = NULL; + dcc->arg = NULL; + dcc->nick = NULL; + dcc->tagconn = dcc->tagread = dcc->tagwrite = -1; + dcc->server = server; + dcc->mynick = g_strdup(mynick); + dcc->servertag = g_strdup(servertag); + + dcc_conns = g_slist_append(dcc_conns, dcc); + signal_emit("dcc created", 1, dcc); +} + +static SERVER_DCC_REC *dcc_server_create(IRC_SERVER_REC *server, const char *flags) +{ + SERVER_DCC_REC *dcc; + + dcc = g_new0(SERVER_DCC_REC, 1); + dcc->orig_type = dcc->type = DCC_SERVER_TYPE; + dcc_server_update_flags(dcc, flags); + + dcc_init_server_rec(dcc, server, dcc->mynick, dcc->servertag); + return dcc; +} + +static SERVER_DCC_REC *dcc_server_clone(SERVER_DCC_REC *dcc) +{ + SERVER_DCC_REC *newdcc; + + g_return_val_if_fail(IS_DCC_SERVER(dcc), NULL); + + newdcc = g_new0(SERVER_DCC_REC, 1); + newdcc->orig_type = newdcc->type = DCC_SERVER_TYPE; + newdcc->accept_send = dcc->accept_send; + newdcc->accept_chat = dcc->accept_chat; + newdcc->accept_fserve = dcc->accept_fserve; + + dcc_init_server_rec(newdcc, dcc->server, dcc->mynick, dcc->servertag); + return newdcc; +} + +/* input function: DCC SERVER - someone tried to connect to our socket */ +static void dcc_server_listen(SERVER_DCC_REC *dcc) +{ + SERVER_DCC_REC *newdcc; + IPADDR ip; + GIOChannel *handle; + int port; + + g_return_if_fail(IS_DCC_SERVER(dcc)); + + /* accept connection */ + handle = net_accept(dcc->handle, &ip, &port); + if (handle == NULL) + return; + + /* Create a new DCC SERVER to handle this connection */ + newdcc = dcc_server_clone(dcc); + + newdcc->starttime = time(NULL); + newdcc->handle = handle; + newdcc->sendbuf = net_sendbuffer_create(handle, 0); + memcpy(&newdcc->addr, &ip, sizeof(IPADDR)); + net_ip2host(&newdcc->addr, newdcc->addrstr); + newdcc->port = port; + newdcc->tagread = + i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_server_input, newdcc); + + signal_emit("dcc connected", 1, newdcc); +} + +/* DCC SERVER: text received */ +static void dcc_server_msg(SERVER_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(IS_DCC_SERVER(dcc)); + g_return_if_fail(msg != NULL); + + /* Check for CHAT protocol */ + if (g_ascii_strncasecmp(msg, "100 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting chat requests.*/ + if (dcc->accept_chat) { + /* Connect and start DCC Chat */ + char *str; + CHAT_DCC_REC *dccchat = dcc_chat_create(dcc->server, NULL, msg, "chat"); + + dccchat->starttime = time(NULL); + dccchat->handle = dcc->handle; + dccchat->sendbuf = net_sendbuffer_create(dccchat->handle, 0); + memcpy(&dccchat->addr, &dcc->addr, sizeof(IPADDR)); + net_ip2host(&dccchat->addr, dccchat->addrstr); + dccchat->port = dcc->port; + dccchat->tagread = i_input_add(dccchat->handle, I_INPUT_READ, + (GInputFunction) dcc_chat_input, dccchat); + + dcc->connection_established = 1; + signal_emit("dcc connected", 1, dccchat); + + str = g_strdup_printf("101 %s\n", + (dccchat->server) ? dccchat->server->nick : "??"); + net_sendbuffer_send(dccchat->sendbuf, str, strlen(str)); + g_free(str); + } + } + + /* Check for FSERVE protocol */ + if (g_ascii_strncasecmp(msg, "110 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting fserve requests.*/ + if (dcc->accept_fserve) { + /* TODO - Connect and start DCC Fserve */ + } + } + + /* Check for SEND protocol */ + if (g_ascii_strncasecmp(msg, "120 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting send requests.*/ + if (dcc->accept_send) { + /* Connect and start DCC Send */ + GET_DCC_REC *dccget; + char **params, *fname, *nick; + int paramcount, len, quoted = FALSE; + uoff_t size; + + /* 120 clientnickname filesize filename */ + params = g_strsplit(msg, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + signal_stop(); + return; + } + + nick = params[0]; + size = str_to_uofft(params[1]); + fname = g_strjoinv(" ", ¶ms[2]); + + len = strlen(fname); + if (len > 1 && *fname == '"' && fname[len-1] == '"') { + /* "file name" - MIRC sends filenames with spaces like this */ + fname[len-1] = '\0'; + memmove(fname, fname+1, len); + quoted = TRUE; + } + + dccget = dcc_get_create(dcc->server, NULL, nick, fname); + dccget->handle = dcc->handle; + dccget->target = g_strdup(dcc->server ? dcc->server->nick : "??"); + memcpy(&dccget->addr, &dcc->addr, sizeof(dcc->addr)); + if (dccget->addr.family == AF_INET) { + net_ip2host(&dccget->addr, dccget->addrstr); + } else { + /* with IPv6, show it to us as it was sent */ + memcpy(dccget->addrstr, dcc->addrstr, sizeof(dccget->addrstr)); + } + dccget->port = dcc->port; + dccget->size = size; + dccget->file_quoted = quoted; + dccget->from_dccserver = 1; + + dcc->connection_established = 1; + signal_emit("dcc request", 2, dccget, dccget->addrstr); + + g_strfreev(params); + g_free(fname); + } + } + + signal_stop(); +} + +SERVER_DCC_REC *dcc_server_find_port(const char *port_str) +{ + GSList *tmp; + unsigned int port = 0; + + g_return_val_if_fail(port_str != NULL, NULL); + + port = atoi(port_str); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + SERVER_DCC_REC *dcc = tmp->data; + + if (IS_DCC_SERVER(dcc) && dcc->port == port) + return dcc; + } + + return NULL; +} + +/* SYNTAX: DCC SERVER [+|-scf] [port] */ +static void cmd_dcc_server(const char *data, IRC_SERVER_REC *server) +{ + void *free_arg; + GIOChannel *handle; + SERVER_DCC_REC *dcc; + IPADDR own_ip; + char *flags, *port; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2, &flags, &port)) + return; + + dcc = dcc_server_find_port(port); + if (dcc != NULL) { + /* Server is already running, update it */ + dcc_server_update_flags(dcc, flags); + cmd_params_free(free_arg); + return; + } + + /* start listening */ + if (!IS_IRC_SERVER(server) || !server->connected) { + cmd_param_error(CMDERR_NOT_CONNECTED); + } + + handle = dcc_listen_port(net_sendbuffer_handle(server->handle), + &own_ip, atoi(port)); + + if (handle == NULL) { + cmd_param_error(CMDERR_ERRNO); + } + + dcc = dcc_server_create(server, flags); + dcc->handle = handle; + dcc->port = atoi(port); + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_server_listen, dcc); + + signal_emit("dcc server started", 1, dcc); + + cmd_params_free(free_arg); +} + +/* DCC CLOSE SERVER <port> */ +static void cmd_dcc_close(char *data, SERVER_REC *server) +{ + GSList *tmp, *next; + char *port_str; + void *free_arg; + int found, port; + + g_return_if_fail(data != NULL); + + if (g_ascii_strncasecmp(data, "SERVER ", 7) != 0 || + !cmd_get_params(data, &free_arg, 2, NULL, &port_str)) { + return; + } + + if (*port_str == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + } + + port = atoi(port_str); + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + SERVER_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_SERVER(dcc) && dcc->port == port) { + found = TRUE; + dcc_close(DCC(dcc)); + } + } + + if (found) { + signal_stop(); + } + + cmd_params_free(free_arg); +} + +void dcc_server_init(void) +{ + dcc_register_type("SERVER"); + command_bind("dcc server", NULL, (SIGNAL_FUNC) cmd_dcc_server); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add_first("dcc server message", (SIGNAL_FUNC) dcc_server_msg); +} + +void dcc_server_deinit(void) +{ + dcc_unregister_type("SERVER"); + command_unbind("dcc server", (SIGNAL_FUNC) cmd_dcc_server); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("dcc server message", (SIGNAL_FUNC) dcc_server_msg); +} + diff --git a/src/irc/dcc/dcc-server.h b/src/irc/dcc/dcc-server.h new file mode 100644 index 0000000..1a4449d --- /dev/null +++ b/src/irc/dcc/dcc-server.h @@ -0,0 +1,29 @@ +#ifndef IRSSI_IRC_DCC_DCC_SERVER_H +#define IRSSI_IRC_DCC_DCC_SERVER_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_SERVER(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, SERVER_DCC_REC, type, "DCC", "SERVER") + +#define IS_DCC_SERVER(dcc) \ + (DCC_SERVER(dcc) ? TRUE : FALSE) + +struct SERVER_DCC_REC { +#include <irssi/src/irc/dcc/dcc-rec.h> + NET_SENDBUF_REC *sendbuf; + + unsigned int accept_send:1; /* Accept SEND connections */ + unsigned int accept_chat:1; /* Accept CHAT connections */ + unsigned int accept_fserve:1; /* Accept FSERVE connections */ + unsigned int connection_established:1; /* We have made a connection */ +}; + +#define DCC_SERVER_TYPE module_get_uniq_id_str("DCC", "SERVER") + +typedef struct SERVER_DCC_REC SERVER_DCC_REC; + +void dcc_server_init(void); +void dcc_server_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c new file mode 100644 index 0000000..bb1c6c2 --- /dev/null +++ b/src/irc/dcc/dcc.c @@ -0,0 +1,600 @@ +/* + dcc.c : irssi + + Copyright (C) 1999-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/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/ignore.h> +#include <irssi/src/core/levels.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/core/servers-setup.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-server.h> + +void dcc_resume_init(void); +void dcc_resume_deinit(void); + +void dcc_autoget_init(void); +void dcc_autoget_deinit(void); + +GSList *dcc_conns; + +static GSList *dcc_types; +static int dcc_timeouttag; + +void dcc_register_type(const char *type) +{ + dcc_types = g_slist_append(dcc_types, g_strdup(type)); +} + +void dcc_unregister_type(const char *type) +{ + GSList *pos; + + pos = i_slist_find_string(dcc_types, type); + if (pos != NULL) { + void *tmp = pos->data; + dcc_types = g_slist_remove(dcc_types, pos->data); + g_free(tmp); + } +} + +int dcc_str2type(const char *str) +{ + if (i_slist_find_string(dcc_types, str) == NULL) + return -1; + + return module_get_uniq_id_str("DCC", str); +} + +/* Initialize DCC record */ +void dcc_init_rec(DCC_REC *dcc, IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(nick != NULL); + g_return_if_fail(arg != NULL); + + MODULE_DATA_INIT(dcc); + dcc->created = time(NULL); + dcc->chat = chat; + dcc->arg = g_strdup(arg); + dcc->nick = g_strdup(nick); + dcc->tagconn = dcc->tagread = dcc->tagwrite = -1; + dcc->server = server; + dcc->mynick = g_strdup(server != NULL ? server->nick : + chat != NULL ? chat->nick : "??"); + + dcc->servertag = server != NULL ? g_strdup(server->tag) : + (chat == NULL ? NULL : g_strdup(chat->servertag)); + + dcc->pasv_id = -1; /* Not a passive DCC */ + + dcc_conns = g_slist_append(dcc_conns, dcc); + signal_emit("dcc created", 1, dcc); +} + +/* Destroy DCC record */ +void dcc_destroy(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + if (dcc->destroyed) return; + + dcc_conns = g_slist_remove(dcc_conns, dcc); + + dcc->destroyed = TRUE; + signal_emit("dcc destroyed", 1, dcc); + + if (dcc->handle != NULL) net_disconnect(dcc->handle); + if (dcc->tagconn != -1) g_source_remove(dcc->tagconn); + if (dcc->tagread != -1) g_source_remove(dcc->tagread); + if (dcc->tagwrite != -1) g_source_remove(dcc->tagwrite); + + MODULE_DATA_DEINIT(dcc); + g_free_not_null(dcc->servertag); + g_free_not_null(dcc->target); + g_free(dcc->mynick); + g_free(dcc->nick); + g_free(dcc->arg); + g_free(dcc); +} + +DCC_REC *dcc_find_request_latest(int type) +{ + DCC_REC *latest; + GSList *tmp; + + latest = NULL; + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->type == type && dcc_is_waiting_user(dcc)) + latest = dcc; + } + + return latest; +} + +DCC_REC *dcc_find_request(int type, const char *nick, const char *arg) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->type == type && !dcc_is_connected(dcc) && + g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (arg == NULL || g_strcmp0(dcc->arg, arg) == 0)) + return dcc; + } + + return NULL; +} + +void dcc_ip2str(IPADDR *ip, char *host) +{ + IPADDR temp_ip; + guint32 addr; + + if (*settings_get_str("dcc_own_ip") != '\0') { + /* overridden IP address */ + net_host2ip(settings_get_str("dcc_own_ip"), &temp_ip); + ip = &temp_ip; + } + + if (IPADDR_IS_V6(ip)) { + /* IPv6 */ + net_ip2host(ip, host); + } else { + memcpy(&addr, &ip->ip, sizeof(addr)); + g_snprintf(host, MAX_IP_LEN, "%lu", + (unsigned long) htonl(addr)); + } +} + +void dcc_str2ip(const char *str, IPADDR *ip) +{ + guint32 addr; + + if (strchr(str, ':') == NULL) { + /* normal IPv4 address in 32bit number form */ + addr = strtoul(str, NULL, 10); + ip->family = AF_INET; + addr = (guint32) ntohl(addr); + memcpy(&ip->ip, &addr, sizeof(addr)); + } else { + /* IPv6 - in standard form */ + net_host2ip(str, ip); + } +} + +/* Start listening for incoming connections */ +GIOChannel *dcc_listen(GIOChannel *iface, IPADDR *ip, int *port) +{ + GIOChannel *handle; + IPADDR *listen_ip = NULL; + const char *dcc_port, *p, *own_ip; + int first, last; + + if (net_getsockname(iface, ip, NULL) == -1) + return NULL; + + /* figure out if we want to listen in IPv4 address or in "any" address, + which may mean IPv4+IPv6 or just IPv6 depending on OS. */ + own_ip = settings_get_str("dcc_own_ip"); + if (*own_ip != '\0') { + if (is_ipv4_address(own_ip)) + listen_ip = &ip4_any; + } else { + if (!IPADDR_IS_V6(ip)) + listen_ip = &ip4_any; + } + + /* get first port */ + dcc_port = settings_get_str("dcc_port"); + first = atoi(dcc_port); + if (first == 0) { + /* random port */ + *port = 0; + return net_listen(listen_ip, port); + } + + /* get last port */ + p = strchr(dcc_port, ' '); + if (p == NULL) p = strchr(dcc_port, '-'); + + dcc_port = p; + if (dcc_port == NULL) + last = first; + else { + last = atoi(dcc_port+1); + if (last == 0) + last = first; + } + + /* use the first available port */ + for (*port = first; *port <= last; (*port)++) { + handle = net_listen(listen_ip, port); + if (handle != NULL) + return handle; + } + + return NULL; +} + +/* Connect to specified IP address using the correct own_ip. */ +GIOChannel *dcc_connect_ip(IPADDR *ip, int port) +{ + IPADDR *own_ip, temp_ip; + const char *own_ip_str; + GIOChannel *handle; + + own_ip_str = settings_get_str("dcc_own_ip"); + own_ip = NULL; + if (*own_ip_str != '\0') { + /* use the specified interface for connecting */ + net_host2ip(own_ip_str, &temp_ip); + if (IPADDR_IS_V6(ip) == IPADDR_IS_V6(&temp_ip)) + own_ip = &temp_ip; + } + + if (own_ip == NULL) + own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4; + + handle = net_connect_ip(ip, port, own_ip); + if (handle == NULL && errno == EADDRNOTAVAIL && own_ip != NULL) { + /* dcc_own_ip is external address */ + own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4; + handle = net_connect_ip(ip, port, own_ip); + } + return handle; +} + +/* Server connected - update server for DCC records that have + the same server tag */ +static void sig_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == NULL && dcc->servertag != NULL && + g_ascii_strcasecmp(dcc->servertag, server->tag) == 0) { + dcc->server = server; + g_free(dcc->mynick); + dcc->mynick = g_strdup(server->nick); + } + } +} + +/* Server disconnected, remove it from all DCC records */ +static void sig_server_disconnected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == server) + dcc->server = NULL; + } +} + +/* Your nick changed, change nick in all DCC records */ +static void sig_server_nick_changed(IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!IS_IRC_SERVER(server)) return; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == server) { + g_free(dcc->mynick); + dcc->mynick = g_strdup(server->nick); + } + } +} + +/* handle emitting "ctcp msg dcc" signal - don't use it directly because + with /IGNORE * CTCPS we'd be ignored */ +static void ctcp_msg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + if (g_ascii_strncasecmp(data, "dcc ", 4) != 0) + return; + data += 4; + + signal_emit("ctcp msg dcc", 5, server, data, nick, addr, target); + signal_stop(); +} + +/* handle emitting "ctcp reply dcc" signal - don't use it directly because + with /IGNORE * CTCPS we'd be ignored */ +static void ctcp_reply(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + if (g_ascii_strncasecmp(data, "dcc ", 4) != 0) + return; + data += 4; + + signal_emit("ctcp reply dcc", 5, server, data, nick, addr, target); + signal_stop(); +} + +/* Handle incoming DCC CTCP messages - either from IRC server or DCC chat */ +static void ctcp_msg_dcc(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + char *args, *str; + + if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC)) + return; + + str = g_strconcat("ctcp msg dcc ", data, NULL); + args = strchr(str+13, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+13); + if (!signal_emit(str, 6, server, args, nick, addr, target, chat)) { + signal_emit("default ctcp msg dcc", 6, + server, data, nick, addr, target, chat); + } + g_free(str); +} + +/* Handle incoming DCC CTCP replies - either from IRC server or DCC chat */ +static void ctcp_reply_dcc(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_DCC)) + return; + + str = g_strconcat("ctcp reply dcc ", data, NULL); + args = strchr(str+15, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+15); + if (!signal_emit(str, 5, server, args, nick, addr, target)) { + signal_emit("default ctcp reply dcc", 5, + server, data, nick, addr, target); + } + g_free(str); +} + +/* CTCP REPLY: REJECT */ +static void ctcp_reply_dcc_reject(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + DCC_REC *chat) +{ + DCC_REC *dcc; + char *type, *args; + + type = g_strdup(data); + args = strchr(type, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + dcc = dcc_find_request(dcc_str2type(type), nick, args); + if (dcc != NULL) dcc_close(dcc); + + g_free(type); +} + +void dcc_close(DCC_REC *dcc) +{ + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); +} + +/* Reject a DCC request */ +void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server) +{ + g_return_if_fail(dcc != NULL); + + signal_emit("dcc rejected", 1, dcc); + + if (dcc->server != NULL) + server = dcc->server; + + if (server != NULL && !dcc_is_connected(dcc)) { + irc_send_cmdv(server, "NOTICE %s :\001DCC REJECT %s %s\001", + dcc->nick, dcc_type2str(dcc->orig_type), + dcc->arg); + } + + dcc_close(dcc); +} + +static int dcc_timeout_func(void) +{ + GSList *tmp, *next; + time_t now; + + now = time(NULL)-settings_get_time("dcc_timeout")/1000; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (dcc->tagread == -1 && now > dcc->created && !IS_DCC_SERVER(dcc)) { + /* Timed out - don't send DCC REJECT CTCP so CTCP + flooders won't affect us and it really doesn't + matter that much anyway if the other side doen't + get it.. + + We don't want dcc servers to time out. */ + dcc_close(dcc); + } + } + + return 1; +} + +static void event_no_such_nick(IRC_SERVER_REC *server, char *data) +{ + char *params, *nick; + GSList *tmp, *next; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + + /* check if we've send any dcc requests to this nick.. */ + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (!dcc_is_connected(dcc) && dcc->server == server && + dcc->nick != NULL && g_ascii_strcasecmp(dcc->nick, nick) == 0) + dcc_close(dcc); + } + + g_free(params); +} + +/* SYNTAX: DCC CLOSE <type> <nick> [<file>] */ +static void cmd_dcc_close(char *data, IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + char *typestr, *nick, *arg, *fname; + void *free_arg; + int found, type; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &typestr, &nick, &arg)) + return; + + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + ascii_strup(typestr); + type = dcc_str2type(typestr); + if (type == -1) { + signal_emit("dcc error unknown type", 1, typestr); + cmd_params_free(free_arg); + return; + } + + fname = cmd_get_quoted_param(&arg); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (dcc->type == type && g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { + dcc_reject(dcc, server); + found = TRUE; + } + } + + if (!found) { + signal_emit("dcc error close not found", 3, + typestr, nick, arg); + } + + cmd_params_free(free_arg); +} + +static void cmd_dcc(const char *data, IRC_SERVER_REC *server, void *item) +{ + command_runsub("dcc", data, server, item); +} + +void irc_dcc_init(void) +{ + dcc_conns = NULL; + dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL); + + settings_add_str("dcc", "dcc_port", "0"); + settings_add_time("dcc", "dcc_timeout", "5min"); + settings_add_str("dcc", "dcc_own_ip", ""); + + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed); + signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_add("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc); + signal_add("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc); + signal_add("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick); + command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + + dcc_chat_init(); + dcc_get_init(); + dcc_send_init(); + dcc_resume_init(); + dcc_autoget_init(); + dcc_server_init(); + + settings_check(); + module_register("dcc", "irc"); +} + +void irc_dcc_deinit(void) +{ + while (dcc_conns != NULL) + dcc_destroy(dcc_conns->data); + + dcc_chat_deinit(); + dcc_get_deinit(); + dcc_send_deinit(); + dcc_resume_deinit(); + dcc_autoget_deinit(); + dcc_server_deinit(); + + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed); + signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_remove("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc); + signal_remove("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc); + signal_remove("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick); + command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + + g_source_remove(dcc_timeouttag); +} + +MODULE_ABICHECK(irc_dcc) diff --git a/src/irc/dcc/dcc.h b/src/irc/dcc/dcc.h new file mode 100644 index 0000000..258d339 --- /dev/null +++ b/src/irc/dcc/dcc.h @@ -0,0 +1,67 @@ +#ifndef IRSSI_IRC_DCC_DCC_H +#define IRSSI_IRC_DCC_DCC_H + +#include <irssi/src/core/modules.h> +#include <irssi/src/core/network.h> + +#define DCC(dcc) ((DCC_REC *) (dcc)) + +typedef struct CHAT_DCC_REC CHAT_DCC_REC; + +typedef struct { +#include <irssi/src/irc/dcc/dcc-rec.h> +} DCC_REC; + +/* fully connected? */ +#define dcc_is_connected(dcc) \ + ((dcc)->starttime != 0) + +/* not connected, we're waiting for other side to connect */ +#define dcc_is_listening(dcc) \ + ((dcc)->handle != NULL && (dcc)->starttime == 0) + +/* not connected, waiting for user to accept it */ +#define dcc_is_waiting_user(dcc) \ + ((dcc)->handle == NULL) + +/* passive DCC */ +#define dcc_is_passive(dcc) \ + ((dcc)->pasv_id >= 0) + +extern GSList *dcc_conns; + +void dcc_register_type(const char *type); +void dcc_unregister_type(const char *type); + +int dcc_str2type(const char *str); +#define dcc_type2str(type) (module_find_id_str("DCC", type)) + +/* Initialize DCC record */ +void dcc_init_rec(DCC_REC *dcc, IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); +void dcc_destroy(DCC_REC *dcc); + +/* Find waiting DCC requests (non-connected) */ +DCC_REC *dcc_find_request_latest(int type); +DCC_REC *dcc_find_request(int type, const char *nick, const char *arg); + +/* IP <-> string for DCC CTCP messages. + `str' must be at least MAX_IP_LEN bytes. + If /SET dcc_own_ip is set, dcc_ip2str() always returns it. */ +void dcc_ip2str(IPADDR *ip, char *str); +void dcc_str2ip(const char *str, IPADDR *ip); + +/* Start listening for incoming connections */ +GIOChannel *dcc_listen(GIOChannel *iface, IPADDR *ip, int *port); +/* Connect to specified IP address using the correct own_ip. */ +GIOChannel *dcc_connect_ip(IPADDR *ip, int port); + +/* Close DCC - sends "dcc closed" signal and calls dcc_destroy() */ +void dcc_close(DCC_REC *dcc); +/* Reject a DCC request */ +void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server); + +void dcc_init(void); +void dcc_deinit(void); + +#endif diff --git a/src/irc/dcc/meson.build b/src/irc/dcc/meson.build new file mode 100644 index 0000000..0c3d706 --- /dev/null +++ b/src/irc/dcc/meson.build @@ -0,0 +1,31 @@ +# this file is part of irssi + +libirc_dcc_a = static_library('irc_dcc', + files( + 'dcc-autoget.c', + 'dcc-chat.c', + 'dcc-get.c', + 'dcc-queue.c', + 'dcc-resume.c', + 'dcc-send.c', + 'dcc-server.c', + 'dcc.c', + ), + include_directories : rootinc, + implicit_include_directories : false, + dependencies : dep) + +install_headers( + files( + 'dcc-chat.h', + 'dcc-file-rec.h', + 'dcc-file.h', + 'dcc-get.h', + 'dcc-queue.h', + 'dcc-rec.h', + 'dcc-send.h', + 'dcc-server.h', + 'dcc.h', + 'module.h', + ), + subdir : incdir / 'src' / 'irc' / 'dcc') diff --git a/src/irc/dcc/module.h b/src/irc/dcc/module.h new file mode 100644 index 0000000..4330d0a --- /dev/null +++ b/src/irc/dcc/module.h @@ -0,0 +1,4 @@ +#include <irssi/src/common.h> +#include <irssi/src/irc/core/irc.h> + +#define MODULE_NAME "irc/dcc" diff --git a/src/irc/flood/Makefile.am b/src/irc/flood/Makefile.am new file mode 100644 index 0000000..4b37ced --- /dev/null +++ b/src/irc/flood/Makefile.am @@ -0,0 +1,15 @@ +noinst_LIBRARIES = libirc_flood.a + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_flood_a_SOURCES = \ + autoignore.c \ + flood.c + +pkginc_irc_flooddir=$(pkgincludedir)/src/irc/flood +pkginc_irc_flood_HEADERS = \ + module.h + +EXTRA_DIST = meson.build diff --git a/src/irc/flood/Makefile.in b/src/irc/flood/Makefile.in new file mode 100644 index 0000000..c72d06c --- /dev/null +++ b/src/irc/flood/Makefile.in @@ -0,0 +1,719 @@ +# 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/flood +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_flood_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_flood_a_AR = $(AR) $(ARFLAGS) +libirc_flood_a_LIBADD = +am_libirc_flood_a_OBJECTS = autoignore.$(OBJEXT) flood.$(OBJEXT) +libirc_flood_a_OBJECTS = $(am_libirc_flood_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)/autoignore.Po ./$(DEPDIR)/flood.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_flood_a_SOURCES) +DIST_SOURCES = $(libirc_flood_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_flooddir)" +HEADERS = $(pkginc_irc_flood_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_flood.a +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_flood_a_SOURCES = \ + autoignore.c \ + flood.c + +pkginc_irc_flooddir = $(pkgincludedir)/src/irc/flood +pkginc_irc_flood_HEADERS = \ + module.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/flood/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/flood/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_flood.a: $(libirc_flood_a_OBJECTS) $(libirc_flood_a_DEPENDENCIES) $(EXTRA_libirc_flood_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc_flood.a + $(AM_V_AR)$(libirc_flood_a_AR) libirc_flood.a $(libirc_flood_a_OBJECTS) $(libirc_flood_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc_flood.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/autoignore.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flood.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_floodHEADERS: $(pkginc_irc_flood_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_irc_flood_HEADERS)'; test -n "$(pkginc_irc_flooddir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_irc_flooddir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_irc_flooddir)" || 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_flooddir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_irc_flooddir)" || exit $$?; \ + done + +uninstall-pkginc_irc_floodHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_irc_flood_HEADERS)'; test -n "$(pkginc_irc_flooddir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_irc_flooddir)'; $(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_flooddir)"; 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)/autoignore.Po + -rm -f ./$(DEPDIR)/flood.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_floodHEADERS + +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)/autoignore.Po + -rm -f ./$(DEPDIR)/flood.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_floodHEADERS + +.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_floodHEADERS 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_floodHEADERS + +.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/flood/autoignore.c b/src/irc/flood/autoignore.c new file mode 100644 index 0000000..33bd91f --- /dev/null +++ b/src/irc/flood/autoignore.c @@ -0,0 +1,89 @@ +/* + autoignore.c : irssi + + Copyright (C) 1999-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/modules.h> +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/core/ignore.h> + +void autoignore_update(IGNORE_REC *rec, int level) +{ + rec->level |= level; + rec->unignore_time = time(NULL) + + settings_get_time("autoignore_time")/1000; + + ignore_update_rec(rec); +} + +void autoignore_add(IRC_SERVER_REC *server, char *mask, int level) +{ + IGNORE_REC *rec; + + rec = g_new0(IGNORE_REC, 1); + + rec->mask = g_strdup(mask); + rec->servertag = g_strdup(server->tag); + rec->level = level; + rec->unignore_time = time(NULL) + + settings_get_time("autoignore_time")/1000; + + ignore_add_rec(rec); +} + +static void sig_flood(IRC_SERVER_REC *server, const char *nick, const char *host, gpointer levelp) +{ + IGNORE_REC *rec; + char *mask; + int level, check_level; + + g_return_if_fail(IS_IRC_SERVER(server)); + + level = GPOINTER_TO_INT(levelp); + check_level = settings_get_level("autoignore_level"); + + mask = g_strdup_printf("%s!%s", nick, host); + if (level & check_level) { + rec = ignore_find_full(server->tag, mask, NULL, NULL, 0); + if (rec == NULL) + autoignore_add(server, mask, level); + else + autoignore_update(rec, level); + } + g_free(mask); +} + +void autoignore_init(void) +{ + settings_add_time("flood", "autoignore_time", "5min"); + settings_add_level("flood", "autoignore_level", ""); + + signal_add("flood", (SIGNAL_FUNC) sig_flood); +} + +void autoignore_deinit(void) +{ + signal_remove("flood", (SIGNAL_FUNC) sig_flood); +} diff --git a/src/irc/flood/flood.c b/src/irc/flood/flood.c new file mode 100644 index 0000000..a147264 --- /dev/null +++ b/src/irc/flood/flood.c @@ -0,0 +1,349 @@ +/* + flood.c : Flood protection + + 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/modules.h> +#include <irssi/src/core/signals.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/core/ignore.h> + +void autoignore_init(void); +void autoignore_deinit(void); + +typedef struct { + char *target; + int level; + + GSList *msgtimes; +} FLOOD_ITEM_REC; + +typedef struct { + char *nick; + GSList *items; +} FLOOD_REC; + +static int flood_tag; +static int flood_max_msgs, flood_timecheck; + +static int flood_hash_check_remove(const char *key, FLOOD_REC *flood, + time_t *now) +{ + GSList *tmp, *next, *times, *tnext; + + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(flood != NULL, FALSE); + + for (tmp = flood->items; tmp != NULL; tmp = next) { + FLOOD_ITEM_REC *rec = tmp->data; + + next = tmp->next; + /* remove old time entries for current rec item */ + for (times = rec->msgtimes; times != NULL; times = tnext) { + time_t *data = times->data; + tnext = times->next; + + if (*now-*((time_t *) times->data) >= flood_timecheck) { + rec->msgtimes = g_slist_remove(rec->msgtimes, data); + g_free(data); + } + } + /* if no more time entries remove rec item */ + if (rec->msgtimes == NULL) { + flood->items = g_slist_remove(flood->items, rec); + g_free(rec->target); + g_free(rec); + } + } + + if (flood->items != NULL) + return FALSE; + + g_free(flood->nick); + g_free(flood); + return TRUE; +} + +static int flood_timeout(void) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp; + time_t now; + + /* remove the old people from flood lists */ + now = time(NULL); + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *rec = tmp->data; + + if (!IS_IRC_SERVER(rec)) + continue; + + mserver = MODULE_DATA(rec); + g_hash_table_foreach_remove(mserver->floodlist, + (GHRFunc) flood_hash_check_remove, + &now); + } + return 1; +} + +/* Initialize flood protection */ +static void flood_init_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *rec; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + rec = g_new0(MODULE_SERVER_REC, 1); + MODULE_DATA_SET(server, rec); + + rec->floodlist = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); +} + +static void flood_hash_destroy(const char *key, FLOOD_REC *flood) +{ + while (flood->items != NULL) { + FLOOD_ITEM_REC *rec = flood->items->data; + + flood->items = g_slist_remove(flood->items, rec); + + g_slist_foreach(rec->msgtimes, (GFunc) g_free, NULL); + g_slist_free(rec->msgtimes); + g_free(rec->target); + g_free(rec); + } + + g_free(flood->nick); + g_free(flood); +} + +/* Deinitialize flood protection */ +static void flood_deinit_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + mserver = MODULE_DATA(server); + if (mserver != NULL && mserver->floodlist != NULL) { + flood_timecheck = 0; + + g_hash_table_foreach(mserver->floodlist, + (GHFunc) flood_hash_destroy, NULL); + g_hash_table_destroy(mserver->floodlist); + } + g_free(mserver); + MODULE_DATA_UNSET(server); +} + +static FLOOD_ITEM_REC *flood_find(FLOOD_REC *flood, int level, + const char *target) +{ + GSList *tmp; + + for (tmp = flood->items; tmp != NULL; tmp = tmp->next) { + FLOOD_ITEM_REC *rec = tmp->data; + + if (rec->level == level && + g_ascii_strcasecmp(rec->target, target) == 0) + return rec; + } + + return NULL; +} + +/* All messages should go through here.. */ +static void flood_newmsg(IRC_SERVER_REC *server, int level, const char *nick, + const char *host, const char *target) +{ + MODULE_SERVER_REC *mserver; + FLOOD_REC *flood; + FLOOD_ITEM_REC *rec; + time_t now, *ttime; + GSList *times, *tnext; + + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + mserver = MODULE_DATA(server); + flood = g_hash_table_lookup(mserver->floodlist, nick); + + rec = flood == NULL ? NULL : flood_find(flood, level, target); + if (rec != NULL) { + now = time(NULL); + for (times = rec->msgtimes; times != NULL; times = tnext) { + time_t *data = times->data; + tnext = times->next; + + if (now - *data >= flood_timecheck) { + rec->msgtimes = g_slist_remove(rec->msgtimes, data); + g_free(data); + } else + break; + } + ttime = g_new(time_t, 1); + *ttime = now; + rec->msgtimes = g_slist_append(rec->msgtimes, ttime); + if (g_slist_length(rec->msgtimes) > flood_max_msgs) { + /* flooding! */ + signal_emit("flood", 5, server, nick, host, + GINT_TO_POINTER(rec->level), target); + } + return; + } + + if (flood == NULL) { + flood = g_new0(FLOOD_REC, 1); + flood->nick = g_strdup(nick); + g_hash_table_insert(mserver->floodlist, flood->nick, flood); + } + + rec = g_new0(FLOOD_ITEM_REC, 1); + rec->level = level; + rec->msgtimes = NULL; + ttime = g_new(time_t, 1); + *ttime = time(NULL); + rec->msgtimes = g_slist_append(rec->msgtimes, ttime); + rec->target = g_strdup(target); + + flood->items = g_slist_append(flood->items, rec); +} + +static void flood_privmsg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *target, *text; + int level; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + if (addr == NULL || g_ascii_strcasecmp(nick, server->nick) == 0) + return; + + params = event_get_params(data, 2, &target, &text); + + level = server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; + if (addr != NULL && !ignore_check(SERVER(server), nick, addr, target, text, level)) + flood_newmsg(server, level, nick, addr, target); + + g_free(params); +} + +static void flood_notice(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr) +{ + char *params, *target, *text; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + if (addr == NULL || g_ascii_strcasecmp(nick, server->nick) == 0) + return; + + params = event_get_params(data, 2, &target, &text); + if (!ignore_check(SERVER(server), nick, addr, target, text, MSGLEVEL_NOTICES)) + flood_newmsg(server, MSGLEVEL_NOTICES, nick, addr, target); + + g_free(params); +} + +static void flood_ctcp(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + int level; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + if (addr == NULL || g_ascii_strcasecmp(nick, server->nick) == 0) + return; + + level = g_ascii_strncasecmp(data, "ACTION ", 7) != 0 ? MSGLEVEL_CTCPS : + (server_ischannel(SERVER(server), target) ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS); + if (!ignore_check(SERVER(server), nick, addr, target, data, level)) + flood_newmsg(server, level, nick, addr, target); +} + +static void read_settings(void) +{ + flood_timecheck = settings_get_int("flood_timecheck"); + flood_max_msgs = settings_get_int("flood_max_msgs"); + + if (flood_timecheck > 0 && flood_max_msgs > 0) { + if (flood_tag == -1) { + flood_tag = g_timeout_add(5000, (GSourceFunc) flood_timeout, NULL); + + signal_add("event privmsg", (SIGNAL_FUNC) flood_privmsg); + signal_add("event notice", (SIGNAL_FUNC) flood_notice); + signal_add("ctcp msg", (SIGNAL_FUNC) flood_ctcp); + } + } else if (flood_tag != -1) { + g_source_remove(flood_tag); + flood_tag = -1; + + signal_remove("event privmsg", (SIGNAL_FUNC) flood_privmsg); + signal_remove("event notice", (SIGNAL_FUNC) flood_notice); + signal_remove("ctcp msg", (SIGNAL_FUNC) flood_ctcp); + } +} + +void irc_flood_init(void) +{ + settings_add_int("flood", "flood_timecheck", 8); + settings_add_int("flood", "flood_max_msgs", 4); + + flood_tag = -1; + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add_first("server connected", (SIGNAL_FUNC) flood_init_server); + signal_add("server destroyed", (SIGNAL_FUNC) flood_deinit_server); + + autoignore_init(); + settings_check(); + module_register("flood", "irc"); +} + +void irc_flood_deinit(void) +{ + autoignore_deinit(); + + if (flood_tag != -1) { + g_source_remove(flood_tag); + signal_remove("event privmsg", (SIGNAL_FUNC) flood_privmsg); + signal_remove("event notice", (SIGNAL_FUNC) flood_notice); + signal_remove("ctcp msg", (SIGNAL_FUNC) flood_ctcp); + } + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("server connected", (SIGNAL_FUNC) flood_init_server); + signal_remove("server destroyed", (SIGNAL_FUNC) flood_deinit_server); +} + +MODULE_ABICHECK(irc_flood) diff --git a/src/irc/flood/meson.build b/src/irc/flood/meson.build new file mode 100644 index 0000000..75107db --- /dev/null +++ b/src/irc/flood/meson.build @@ -0,0 +1,14 @@ +# this file is part of irssi + +libirc_flood_a = static_library('irc_flood', + files( + 'autoignore.c', + 'flood.c', + ), + include_directories : rootinc, + implicit_include_directories : false, + dependencies : dep) + +install_headers( + files('module.h'), + subdir : incdir / 'src' / 'irc' / 'flood') diff --git a/src/irc/flood/module.h b/src/irc/flood/module.h new file mode 100644 index 0000000..a80c46f --- /dev/null +++ b/src/irc/flood/module.h @@ -0,0 +1,13 @@ +#include <irssi/src/common.h> +#include <irssi/src/irc/core/irc.h> + +typedef struct { + /* Flood protection */ + GHashTable *floodlist; + + /* Auto ignore list */ + GSList *ignorelist; + time_t ignore_lastcheck; +} MODULE_SERVER_REC; + +#define MODULE_NAME "irc/flood" diff --git a/src/irc/irc.c b/src/irc/irc.c new file mode 100644 index 0000000..250a6fb --- /dev/null +++ b/src/irc/irc.c @@ -0,0 +1,5 @@ +void irc_core_init(void); void irc_core_deinit(void); +void irc_dcc_init(void);void irc_flood_init(void);void irc_notifylist_init(void); +void irc_notifylist_deinit(void);void irc_flood_deinit(void);void irc_dcc_deinit(void); +void irc_init(void) { irc_core_init(); irc_dcc_init(); irc_flood_init(); irc_notifylist_init(); } +void irc_deinit(void) { irc_notifylist_deinit(); irc_flood_deinit(); irc_dcc_deinit(); irc_core_deinit(); } diff --git a/src/irc/meson.build b/src/irc/meson.build new file mode 100644 index 0000000..72abc3c --- /dev/null +++ b/src/irc/meson.build @@ -0,0 +1,24 @@ +# this file is part of irssi + +subdir('core') +subdir('dcc') +subdir('flood') +subdir('notifylist') + +libirc_a = static_library('irc', + files( + 'irc.c', + ), + link_with : [ + libirc_core_a, + libirc_dcc_a, + libirc_flood_a, + libirc_notifylist_a, + ], + include_directories : rootinc, + implicit_include_directories : false, +) + +if want_proxy + subdir('proxy') +endif diff --git a/src/irc/notifylist/Makefile.am b/src/irc/notifylist/Makefile.am new file mode 100644 index 0000000..3f913d5 --- /dev/null +++ b/src/irc/notifylist/Makefile.am @@ -0,0 +1,20 @@ +noinst_LIBRARIES = libirc_notifylist.a + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_notifylist_a_SOURCES = \ + notifylist.c \ + notify-commands.c \ + notify-ison.c \ + notify-setup.c \ + notify-whois.c + +pkginc_irc_notifylistdir=$(pkgincludedir)/src/irc/notifylist +pkginc_irc_notifylist_HEADERS = \ + notifylist.h \ + notify-setup.h \ + module.h + +EXTRA_DIST = meson.build diff --git a/src/irc/notifylist/Makefile.in b/src/irc/notifylist/Makefile.in new file mode 100644 index 0000000..f99c634 --- /dev/null +++ b/src/irc/notifylist/Makefile.in @@ -0,0 +1,737 @@ +# 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/notifylist +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_notifylist_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_notifylist_a_AR = $(AR) $(ARFLAGS) +libirc_notifylist_a_LIBADD = +am_libirc_notifylist_a_OBJECTS = notifylist.$(OBJEXT) \ + notify-commands.$(OBJEXT) notify-ison.$(OBJEXT) \ + notify-setup.$(OBJEXT) notify-whois.$(OBJEXT) +libirc_notifylist_a_OBJECTS = $(am_libirc_notifylist_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)/notify-commands.Po \ + ./$(DEPDIR)/notify-ison.Po ./$(DEPDIR)/notify-setup.Po \ + ./$(DEPDIR)/notify-whois.Po ./$(DEPDIR)/notifylist.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_notifylist_a_SOURCES) +DIST_SOURCES = $(libirc_notifylist_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_notifylistdir)" +HEADERS = $(pkginc_irc_notifylist_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_notifylist.a +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_notifylist_a_SOURCES = \ + notifylist.c \ + notify-commands.c \ + notify-ison.c \ + notify-setup.c \ + notify-whois.c + +pkginc_irc_notifylistdir = $(pkgincludedir)/src/irc/notifylist +pkginc_irc_notifylist_HEADERS = \ + notifylist.h \ + notify-setup.h \ + module.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/notifylist/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/notifylist/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_notifylist.a: $(libirc_notifylist_a_OBJECTS) $(libirc_notifylist_a_DEPENDENCIES) $(EXTRA_libirc_notifylist_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc_notifylist.a + $(AM_V_AR)$(libirc_notifylist_a_AR) libirc_notifylist.a $(libirc_notifylist_a_OBJECTS) $(libirc_notifylist_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc_notifylist.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-commands.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-ison.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-setup.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notify-whois.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/notifylist.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_notifylistHEADERS: $(pkginc_irc_notifylist_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_irc_notifylist_HEADERS)'; test -n "$(pkginc_irc_notifylistdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_irc_notifylistdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_irc_notifylistdir)" || 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_notifylistdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_irc_notifylistdir)" || exit $$?; \ + done + +uninstall-pkginc_irc_notifylistHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_irc_notifylist_HEADERS)'; test -n "$(pkginc_irc_notifylistdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_irc_notifylistdir)'; $(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_notifylistdir)"; 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)/notify-commands.Po + -rm -f ./$(DEPDIR)/notify-ison.Po + -rm -f ./$(DEPDIR)/notify-setup.Po + -rm -f ./$(DEPDIR)/notify-whois.Po + -rm -f ./$(DEPDIR)/notifylist.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_notifylistHEADERS + +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)/notify-commands.Po + -rm -f ./$(DEPDIR)/notify-ison.Po + -rm -f ./$(DEPDIR)/notify-setup.Po + -rm -f ./$(DEPDIR)/notify-whois.Po + -rm -f ./$(DEPDIR)/notifylist.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_notifylistHEADERS + +.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_notifylistHEADERS 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_notifylistHEADERS + +.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/notifylist/meson.build b/src/irc/notifylist/meson.build new file mode 100644 index 0000000..f00ea32 --- /dev/null +++ b/src/irc/notifylist/meson.build @@ -0,0 +1,21 @@ +# this file is part of irssi + +libirc_notifylist_a = static_library('irc_notifylist', + files( + 'notify-commands.c', + 'notify-ison.c', + 'notify-setup.c', + 'notify-whois.c', + 'notifylist.c', + ), + include_directories : rootinc, + implicit_include_directories : false, + dependencies : dep) + +install_headers( + files( + 'module.h', + 'notify-setup.h', + 'notifylist.h', + ), + subdir : incdir / 'src' / 'irc' / 'notifylist') diff --git a/src/irc/notifylist/module.h b/src/irc/notifylist/module.h new file mode 100644 index 0000000..f109aff --- /dev/null +++ b/src/irc/notifylist/module.h @@ -0,0 +1,42 @@ +#include <irssi/src/common.h> +#include <irssi/src/irc/core/irc.h> + +#define MODULE_NAME "irc/notifylist" + +typedef struct { + char *nick; + char *user, *host, *realname, *awaymsg; + + unsigned int host_ok:1; /* host matches the one in notifylist = this is the right person*/ + unsigned int away_ok:1; /* not away, or we don't care about it */ + + unsigned int away:1; /* nick is away */ + unsigned int join_announced:1; /* join to IRC has been announced */ + + time_t last_whois; +} NOTIFY_NICK_REC; + +typedef struct { + int ison_count; /* number of ISON requests sent */ + + GSList *notify_users; /* NOTIFY_NICK_REC's of notifylist people who are in IRC */ + GSList *ison_tempusers; /* Temporary list for saving /ISON events.. */ +} MODULE_SERVER_REC; + +#include <irssi/src/irc/core/irc-servers.h> + +NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick); +void notify_nick_destroy(NOTIFY_NICK_REC *rec); +NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick); + +void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec); +void notifylist_destroy_all(void); + +void notifylist_commands_init(void); +void notifylist_commands_deinit(void); + +void notifylist_whois_init(void); +void notifylist_whois_deinit(void); + +void notifylist_ison_init(void); +void notifylist_ison_deinit(void); diff --git a/src/irc/notifylist/notify-commands.c b/src/irc/notifylist/notify-commands.c new file mode 100644 index 0000000..e7bceaa --- /dev/null +++ b/src/irc/notifylist/notify-commands.c @@ -0,0 +1,81 @@ +/* + notify-commands.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/commands.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/notifylist/notifylist.h> + +/* SYNTAX: NOTIFY [-away] <mask> [<ircnets>] */ +static void cmd_notify(gchar *data) +{ + GHashTable *optlist; + char *mask, *ircnets; + void *free_arg; + int away_check; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, + 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "notify", &optlist, &mask, &ircnets)) + return; + if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + away_check = g_hash_table_lookup(optlist, "away") != NULL; + notifylist_remove(mask); + notifylist_add(mask, ircnets, away_check); + + cmd_params_free(free_arg); +} + +/* SYNTAX: UNNOTIFY <mask> */ +static void cmd_unnotify(const char *data) +{ + char *mask; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 1, &mask)) + return; + if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + notifylist_remove(mask); + + cmd_params_free(free_arg); +} + +void notifylist_commands_init(void) +{ + command_bind("notify", NULL, (SIGNAL_FUNC) cmd_notify); + command_bind("unnotify", NULL, (SIGNAL_FUNC) cmd_unnotify); + + command_set_options("notify", "away"); +} + +void notifylist_commands_deinit(void) +{ + command_unbind("notify", (SIGNAL_FUNC) cmd_notify); + command_unbind("unnotify", (SIGNAL_FUNC) cmd_unnotify); +} diff --git a/src/irc/notifylist/notify-ison.c b/src/irc/notifylist/notify-ison.c new file mode 100644 index 0000000..809394c --- /dev/null +++ b/src/irc/notifylist/notify-ison.c @@ -0,0 +1,348 @@ +/* + notify-ison.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.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/servers-redirect.h> + +#include <irssi/src/irc/notifylist/notifylist.h> + +#define DEFAULT_NOTIFY_CHECK_TIME "1min" +#define DEFAULT_NOTIFY_WHOIS_TIME "5min" + +static int notify_tag; +static int notify_whois_time; + +NOTIFY_NICK_REC *notify_nick_create(IRC_SERVER_REC *server, const char *nick) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + + mserver = MODULE_DATA(server); + + rec = g_new0(NOTIFY_NICK_REC, 1); + rec->nick = g_strdup(nick); + + mserver->notify_users = g_slist_append(mserver->notify_users, rec); + return rec; +} + +void notify_nick_destroy(NOTIFY_NICK_REC *rec) +{ + g_free(rec->nick); + g_free_not_null(rec->user); + g_free_not_null(rec->host); + g_free_not_null(rec->realname); + g_free_not_null(rec->awaymsg); + g_free(rec); +} + +NOTIFY_NICK_REC *notify_nick_find(IRC_SERVER_REC *server, const char *nick) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + GSList *tmp; + + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (g_ascii_strcasecmp(rec->nick, nick) == 0) + return rec; + } + + return NULL; +} + +static void ison_send(IRC_SERVER_REC *server, GString *cmd) +{ + MODULE_SERVER_REC *mserver; + + if (!server->connected) { + return; + } + + mserver = MODULE_DATA(server); + mserver->ison_count++; + + g_string_truncate(cmd, cmd->len-1); + g_string_prepend(cmd, "ISON :"); + + server_redirect_event(server, "ison", 1, NULL, -1, NULL, + "event 303", "notifylist event", NULL); + irc_send_cmd_later(server, cmd->str); + + g_string_truncate(cmd, 0); +} + +/* timeout function: send /ISON commands to server to check if someone in + notify list is in IRC */ +static void notifylist_timeout_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp; + GString *cmd; + char *nick, *ptr; + int len; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + mserver = MODULE_DATA(server); + if (mserver->ison_count > 0) { + /* still not received all replies to previous /ISON commands.. */ + return; + } + + cmd = g_string_new(NULL); + for (tmp = notifies; tmp != NULL; tmp = tmp->next) { + NOTIFYLIST_REC *rec = tmp->data; + + if (!notifylist_ircnets_match(rec, server->connrec->chatnet)) + continue; + + nick = g_strdup(rec->mask); + ptr = strchr(nick, '!'); + if (ptr != NULL) *ptr = '\0'; + + len = strlen(nick); + + if (cmd->len+len+1 > server->max_message_len) + ison_send(server, cmd); + + g_string_append_printf(cmd, "%s ", nick); + g_free(nick); + } + + if (cmd->len > 0) + ison_send(server, cmd); + g_string_free(cmd, TRUE); +} + +static int notifylist_timeout_func(void) +{ + g_slist_foreach(servers, (GFunc) notifylist_timeout_server, NULL); + return 1; +} + +static void ison_save_users(MODULE_SERVER_REC *mserver, char *online) +{ + char *ptr; + + while (online != NULL && *online != '\0') { + ptr = strchr(online, ' '); + if (ptr != NULL) *ptr++ = '\0'; + + mserver->ison_tempusers = + g_slist_append(mserver->ison_tempusers, g_strdup(online)); + online = ptr; + } +} + +static void whois_send(IRC_SERVER_REC *server, const char *nicks, + const char *whois_request) +{ + char *p, *str; + + /* "nick1,nick2" -> "nick1,nick2 nick1 nick2" because + End of WHOIS give nick1,nick2 while other whois events give + nick1 or nick2 */ + str = g_strconcat(nicks, " ", nicks, NULL); + for (p = str+strlen(nicks)+1; *p != '\0'; p++) + if (*p == ',') *p = ' '; + + server_redirect_event(server, "whois", 1, str, TRUE, + "notifylist event whois end", + "event 318", "notifylist event whois end", + "event 311", "notifylist event whois", + "event 301", "notifylist event whois away", + "", "event empty", NULL); + g_free(str); + + str = g_strdup_printf("WHOIS %s", whois_request); + irc_send_cmd_later(server, str); + g_free(str); +} + +static void whois_send_server(IRC_SERVER_REC *server, char *nick) +{ + char *str; + + str = g_strdup_printf("%s %s", nick, nick); + whois_send(server, nick, str); + g_free(str); +} + +/* try to send as many nicks in one WHOIS as possible */ +static void whois_list_send(IRC_SERVER_REC *server, GSList *nicks) +{ + GSList *tmp; + GString *str; + char *nick; + int count; + + str = g_string_new(NULL); + count = 0; + + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + nick = tmp->data; + + count++; + g_string_append_printf(str, "%s,", nick); + + if (count >= server->max_whois_in_cmd) { + g_string_truncate(str, str->len-1); + whois_send(server, str->str, str->str); + g_string_truncate(str, 0); + count = 0; + } + } + + if (str->len > 0) { + g_string_truncate(str, str->len-1); + whois_send(server, str->str, str->str); + } + + g_string_free(str, TRUE); +} + +static void ison_check_joins(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + GSList *tmp, *newnicks; + int send_whois; + time_t now; + + mserver = MODULE_DATA(server); + + now = time(NULL); + newnicks = NULL; + for (tmp = mserver->ison_tempusers; tmp != NULL; tmp = tmp->next) { + char *nick = tmp->data; + + notify = notifylist_find(nick, server->connrec->chatnet); + send_whois = notify != NULL && notify->away_check; + + rec = notify_nick_find(server, nick); + if (rec != NULL) { + /* check if we want to send WHOIS yet.. */ + if (now-rec->last_whois < notify_whois_time) + continue; + } else { + rec = notify_nick_create(server, nick); + if (!send_whois) newnicks = g_slist_append(newnicks, nick); + } + + if (send_whois) { + /* we need away message - + send the WHOIS reply to the nick's server */ + rec->last_whois = now; + whois_send_server(server, nick); + } + } + + whois_list_send(server, newnicks); + g_slist_free(newnicks); +} + +static void ison_check_parts(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + GSList *tmp, *next; + + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = next) { + NOTIFY_NICK_REC *rec = tmp->data; + next = tmp->next; + + if (i_slist_find_icase_string(mserver->ison_tempusers, rec->nick) != NULL) + continue; + + notifylist_left(server, rec); + } +} + +static void event_ison(IRC_SERVER_REC *server, const char *data) +{ + MODULE_SERVER_REC *mserver; + char *params, *online; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 2, NULL, &online); + + mserver = MODULE_DATA(server); + ison_save_users(mserver, online); + + if (--mserver->ison_count > 0) { + /* wait for the rest of the /ISON replies */ + g_free(params); + return; + } + + ison_check_joins(server); + ison_check_parts(server); + + /* free memory used by temp list */ + g_slist_foreach(mserver->ison_tempusers, (GFunc) g_free, NULL); + g_slist_free(mserver->ison_tempusers); + mserver->ison_tempusers = NULL; + + g_free(params); +} + +static void read_settings(void) +{ + if (notify_tag != -1) g_source_remove(notify_tag); + notify_tag = g_timeout_add(settings_get_time("notify_check_time"), + (GSourceFunc) notifylist_timeout_func, NULL); + + notify_whois_time = settings_get_time("notify_whois_time")/1000; +} + +void notifylist_ison_init(void) +{ + settings_add_time("misc", "notify_check_time", DEFAULT_NOTIFY_CHECK_TIME); + settings_add_time("misc", "notify_whois_time", DEFAULT_NOTIFY_WHOIS_TIME); + + notify_tag = -1; + read_settings(); + + signal_add("notifylist event", (SIGNAL_FUNC) event_ison); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void notifylist_ison_deinit(void) +{ + g_source_remove(notify_tag); + + signal_remove("notifylist event", (SIGNAL_FUNC) event_ison); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/src/irc/notifylist/notify-setup.c b/src/irc/notifylist/notify-setup.c new file mode 100644 index 0000000..62f983e --- /dev/null +++ b/src/irc/notifylist/notify-setup.c @@ -0,0 +1,79 @@ +/* + notify-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/lib-config/iconfig.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/notifylist/notifylist.h> + +void notifylist_add_config(NOTIFYLIST_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("notifies", TRUE); + node = iconfig_node_section(node, rec->mask, NODE_TYPE_BLOCK); + + if (rec->away_check) + iconfig_node_set_bool(node, "away_check", TRUE); + else + iconfig_node_set_str(node, "away_check", NULL); + + iconfig_node_set_str(node, "ircnets", NULL); + if (rec->ircnets != NULL && *rec->ircnets != NULL) { + node = iconfig_node_section(node, "ircnets", NODE_TYPE_LIST); + iconfig_node_add_list(node, rec->ircnets); + } +} + +void notifylist_remove_config(NOTIFYLIST_REC *rec) +{ + iconfig_set_str("notifies", rec->mask, NULL); +} + +void notifylist_read_config(void) +{ + CONFIG_NODE *node; + NOTIFYLIST_REC *rec; + GSList *tmp; + + notifylist_destroy_all(); + + node = iconfig_node_traverse("notifies", FALSE); + if (node == NULL) return; + + tmp = config_node_first(node->value); + for (; tmp != NULL; tmp = config_node_next(tmp)) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + rec = g_new0(NOTIFYLIST_REC, 1); + notifies = g_slist_append(notifies, rec); + + rec->mask = g_strdup(node->key); + rec->away_check = config_node_get_bool(node, "away_check", FALSE); + + node = iconfig_node_section(node, "ircnets", -1); + if (node != NULL) rec->ircnets = config_node_get_list(node); + } +} diff --git a/src/irc/notifylist/notify-setup.h b/src/irc/notifylist/notify-setup.h new file mode 100644 index 0000000..8540d1c --- /dev/null +++ b/src/irc/notifylist/notify-setup.h @@ -0,0 +1,9 @@ +#ifndef IRSSI_IRC_NOTIFYLIST_NOTIFY_SETUP_H +#define IRSSI_IRC_NOTIFYLIST_NOTIFY_SETUP_H + +void notifylist_add_config(NOTIFYLIST_REC *rec); +void notifylist_remove_config(NOTIFYLIST_REC *rec); + +void notifylist_read_config(void); + +#endif diff --git a/src/irc/notifylist/notify-whois.c b/src/irc/notifylist/notify-whois.c new file mode 100644 index 0000000..bdfd72b --- /dev/null +++ b/src/irc/notifylist/notify-whois.c @@ -0,0 +1,153 @@ +/* + notify-whois.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/expandos.h> + +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/core/masks.h> + +#include <irssi/src/irc/notifylist/notifylist.h> + +static char *last_notify_nick; + +static void event_whois(IRC_SERVER_REC *server, const char *data) +{ + char *params, *nick, *user, *host, *realname; + NOTIFY_NICK_REC *nickrec; + NOTIFYLIST_REC *notify; + + g_return_if_fail(data != NULL); + g_return_if_fail(server != NULL); + + params = event_get_params(data, 6, NULL, &nick, &user, &host, NULL, &realname); + + notify = notifylist_find(nick, server->connrec->chatnet); + if (notify != NULL && !mask_match(SERVER(server), notify->mask, nick, user, host)) { + /* user or host didn't match */ + g_free(params); + return; + } + + nickrec = notify_nick_find(server, nick); + if (nickrec != NULL) { + g_free_not_null(last_notify_nick); + last_notify_nick = g_strdup(nick); + + g_free_not_null(nickrec->user); + g_free_not_null(nickrec->host); + g_free_not_null(nickrec->realname); + g_free_and_null(nickrec->awaymsg); + nickrec->user = g_strdup(user); + nickrec->host = g_strdup(host); + nickrec->realname = g_strdup(realname); + + nickrec->away = FALSE; + nickrec->host_ok = TRUE; + } + g_free(params); +} + +static void event_whois_away(IRC_SERVER_REC *server, const char *data) +{ + NOTIFY_NICK_REC *nickrec; + char *params, *nick, *awaymsg; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 3, NULL, &nick, &awaymsg); + + nickrec = notify_nick_find(server, nick); + if (nickrec != NULL) { + nickrec->awaymsg = g_strdup(awaymsg); + nickrec->away = TRUE; + } + + g_free(params); +} + +/* All WHOIS replies got, now announce all the changes at once. */ +static void event_whois_end(IRC_SERVER_REC *server, const char *data) +{ + MODULE_SERVER_REC *mserver; + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + GSList *tmp; + const char *event; + int away_ok; + + mserver = MODULE_DATA(server); + for (tmp = mserver->notify_users; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (rec->realname == NULL) + continue; + + notify = notifylist_find(rec->nick, server->connrec->chatnet); + if (notify == NULL) continue; + + away_ok = !notify->away_check || !rec->away; + + event = NULL; + if (!rec->join_announced) { + rec->join_announced = TRUE; + if (away_ok) event = "notifylist joined"; + } else if (notify->away_check && rec->away_ok == rec->away) + event = "notifylist away changed"; + + if (event != NULL) { + signal_emit(event, 6, server, rec->nick, + rec->user != NULL ? rec->user : "??", + rec->host != NULL ? rec->host : "??", + rec->realname != NULL ? rec->realname : "??", + rec->awaymsg); + } + rec->away_ok = away_ok; + } +} + +/* last person that NOTIFY detected a signon for */ +static char *expando_lastnotify(SERVER_REC *server, void *item, int *free_ret) +{ + return last_notify_nick; +} + +void notifylist_whois_init(void) +{ + last_notify_nick = NULL; + + signal_add("notifylist event whois", (SIGNAL_FUNC) event_whois); + signal_add("notifylist event whois away", (SIGNAL_FUNC) event_whois_away); + signal_add("notifylist event whois end", (SIGNAL_FUNC) event_whois_end); + expando_create("D", expando_lastnotify, + "notifylist event whois", EXPANDO_ARG_SERVER, NULL); +} + +void notifylist_whois_deinit(void) +{ + g_free_not_null(last_notify_nick); + + signal_remove("notifylist event whois", (SIGNAL_FUNC) event_whois); + signal_remove("notifylist event whois away", (SIGNAL_FUNC) event_whois_away); + signal_remove("notifylist event whois end", (SIGNAL_FUNC) event_whois_end); + expando_destroy("D", expando_lastnotify); +} diff --git a/src/irc/notifylist/notifylist.c b/src/irc/notifylist/notifylist.c new file mode 100644 index 0000000..73737b3 --- /dev/null +++ b/src/irc/notifylist/notifylist.c @@ -0,0 +1,365 @@ +/* + notifylist.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/modules.h> +#include <irssi/src/core/signals.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-channels.h> +#include <irssi/src/irc/core/servers-redirect.h> +#include <irssi/src/core/masks.h> +#include <irssi/src/core/nicklist.h> + +#include <irssi/src/irc/notifylist/notifylist.h> +#include <irssi/src/irc/notifylist/notify-setup.h> + +GSList *notifies; + +NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets, + int away_check) +{ + NOTIFYLIST_REC *rec; + + g_return_val_if_fail(mask != NULL, NULL); + + rec = g_new0(NOTIFYLIST_REC, 1); + rec->mask = g_strdup(mask); + rec->ircnets = ircnets == NULL || *ircnets == '\0' ? NULL : + g_strsplit(ircnets, " ", -1); + rec->away_check = away_check; + + notifylist_add_config(rec); + + notifies = g_slist_append(notifies, rec); + signal_emit("notifylist new", 1, rec); + return rec; +} + +static void notify_destroy(NOTIFYLIST_REC *rec) +{ + if (rec->ircnets != NULL) g_strfreev(rec->ircnets); + g_free(rec->mask); + g_free(rec); +} + +void notifylist_destroy_all(void) +{ + g_slist_foreach(notifies, (GFunc) notify_destroy, NULL); + g_slist_free(notifies); + + notifies = NULL; +} + +void notifylist_remove(const char *mask) +{ + NOTIFYLIST_REC *rec; + + g_return_if_fail(mask != NULL); + + rec = notifylist_find(mask, "*"); + if (rec == NULL) return; + + notifylist_remove_config(rec); + notifies = g_slist_remove(notifies, rec); + signal_emit("notifylist remove", 1, rec); + + notify_destroy(rec); +} + +int notifylist_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet) +{ + char **tmp; + + if (rec->ircnets == NULL) return TRUE; + if (ircnet == NULL) return FALSE; + if (g_strcmp0(ircnet, "*") == 0) return TRUE; + + for (tmp = rec->ircnets; *tmp != NULL; tmp++) { + if (g_ascii_strcasecmp(*tmp, ircnet) == 0) + return TRUE; + } + + return FALSE; +} + +NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet) +{ + NOTIFYLIST_REC *best; + GSList *tmp; + int len; + + best = NULL; + len = strlen(mask); + for (tmp = notifies; tmp != NULL; tmp = tmp->next) { + NOTIFYLIST_REC *rec = tmp->data; + + /* check mask */ + if (g_ascii_strncasecmp(rec->mask, mask, len) != 0 || + (rec->mask[len] != '\0' && rec->mask[len] != '!')) continue; + + /* check ircnet */ + if (rec->ircnets == NULL) { + best = rec; + continue; + } + + if (notifylist_ircnets_match(rec, ircnet)) + return rec; + } + + return best; +} + +int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick) +{ + NOTIFY_NICK_REC *rec; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(IS_IRC_SERVER(server), FALSE); + + rec = notify_nick_find(server, nick); + return rec != NULL && rec->host_ok && rec->away_ok; +} + +static IRC_SERVER_REC *notifylist_ison_serverlist(const char *nick, const char *taglist) +{ + IRC_SERVER_REC *server; + char **list, **tmp; + + g_return_val_if_fail(nick != NULL, NULL); + g_return_val_if_fail(taglist != NULL, NULL); + + list = g_strsplit(taglist, " ", -1); + + server = NULL; + for (tmp = list; *tmp != NULL; tmp++) { + server = (IRC_SERVER_REC *) server_find_chatnet(*tmp); + + if (IS_IRC_SERVER(server) && + notifylist_ison_server(server, nick)) + break; + } + g_strfreev(list); + + return tmp == NULL ? NULL : server; +} + +IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(serverlist != NULL, FALSE); + + if (*serverlist != '\0') + return notifylist_ison_serverlist(nick, serverlist); + + /* any server.. */ + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + IRC_SERVER_REC *server = tmp->data; + + if (IS_IRC_SERVER(server) && + notifylist_ison_server(server, nick)) + return tmp->data; + } + + return NULL; +} + +static void notifylist_init_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *rec; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + rec = g_new0(MODULE_SERVER_REC,1 ); + MODULE_DATA_SET(server, rec); +} + +static void notifylist_deinit_server(IRC_SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + NOTIFY_NICK_REC *rec; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + mserver = MODULE_DATA(server); + if (!mserver) + return; + + while (mserver->notify_users != NULL) { + rec = mserver->notify_users->data; + + mserver->notify_users = g_slist_remove(mserver->notify_users, rec); + notify_nick_destroy(rec); + } + g_free(mserver); + MODULE_DATA_UNSET(server); +} + +void notifylist_left(IRC_SERVER_REC *server, NOTIFY_NICK_REC *rec) +{ + MODULE_SERVER_REC *mserver; + + mserver = MODULE_DATA(server); + mserver->notify_users = g_slist_remove(mserver->notify_users, rec); + + if (rec->host_ok && rec->away_ok) { + signal_emit("notifylist left", 6, + server, rec->nick, + rec->user, rec->host, + rec->realname, rec->awaymsg); + } + + notify_nick_destroy(rec); +} + +static void event_quit(IRC_SERVER_REC *server, const char *data, + const char *nick) +{ + NOTIFY_NICK_REC *rec; + + if (*data == ':') data++; /* quit message */ + + rec = notify_nick_find(server, nick); + if (rec != NULL) notifylist_left(server, rec); +} + +static void notifylist_check_join(IRC_SERVER_REC *server, const char *nick, + const char *userhost, const char *realname, int away) +{ + NOTIFYLIST_REC *notify; + NOTIFY_NICK_REC *rec; + char *user, *host; + + if (nick == NULL) + return; + + notify = notifylist_find(nick, server->connrec->chatnet); + if (notify == NULL) return; + + rec = notify_nick_find(server, nick); + if (rec != NULL && rec->join_announced) return; + if (rec == NULL) rec = notify_nick_create(server, nick); + + user = g_strdup(userhost == NULL ? "" : userhost); + host = strchr(user, '@'); + if (host != NULL) *host++ = '\0'; else host = ""; + + if (!mask_match(SERVER(server), notify->mask, nick, user, host)) { + g_free(user); + return; + } + + if (notify->away_check && away == -1) { + /* we need to know if the nick is away */ + g_free(user); + return; + } + + g_free_not_null(rec->user); + g_free_not_null(rec->host); + g_free_not_null(rec->realname); + rec->user = g_strdup(user); + rec->host = g_strdup(host); + rec->realname = realname == NULL || *realname == '\0' ? NULL : g_strdup(realname); + + if (away != -1) rec->away = away; + rec->host_ok = TRUE; + rec->join_announced = TRUE; + rec->away_ok = !notify->away_check || !rec->away; + + signal_emit("notifylist joined", 6, + server, rec->nick, rec->user, rec->host, realname, NULL); + g_free(user); +} + +static void event_privmsg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + if (nick != NULL) { + notifylist_check_join(server, nick, address, "", -1); + } +} + +static void event_join(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *address) +{ + notifylist_check_join(server, nick, address, "", -1); +} + +static void sig_channel_wholist(IRC_CHANNEL_REC *channel) +{ + GSList *nicks, *tmp; + + nicks = nicklist_getnicks(CHANNEL(channel)); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + notifylist_check_join(channel->server, rec->nick, rec->host, rec->realname, rec->gone); + } + g_slist_free(nicks); +} + +void irc_notifylist_init(void) +{ + notifylist_read_config(); + + notifylist_commands_init(); + notifylist_ison_init(); + notifylist_whois_init(); + signal_add("server connected", (SIGNAL_FUNC) notifylist_init_server); + signal_add("server destroyed", (SIGNAL_FUNC) notifylist_deinit_server); + signal_add("event quit", (SIGNAL_FUNC) event_quit); + signal_add("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_add("event join", (SIGNAL_FUNC) event_join); + signal_add("channel wholist", (SIGNAL_FUNC) sig_channel_wholist); + signal_add("setup reread", (SIGNAL_FUNC) notifylist_read_config); + + settings_check(); + module_register("notifylist", "irc"); +} + +void irc_notifylist_deinit(void) +{ + notifylist_commands_deinit(); + notifylist_ison_deinit(); + notifylist_whois_deinit(); + + signal_remove("server connected", (SIGNAL_FUNC) notifylist_init_server); + signal_remove("server destroyed", (SIGNAL_FUNC) notifylist_deinit_server); + signal_remove("event quit", (SIGNAL_FUNC) event_quit); + signal_remove("event privmsg", (SIGNAL_FUNC) event_privmsg); + signal_remove("event join", (SIGNAL_FUNC) event_join); + signal_remove("channel wholist", (SIGNAL_FUNC) sig_channel_wholist); + signal_remove("setup reread", (SIGNAL_FUNC) notifylist_read_config); + + notifylist_destroy_all(); +} + +MODULE_ABICHECK(irc_notifylist) diff --git a/src/irc/notifylist/notifylist.h b/src/irc/notifylist/notifylist.h new file mode 100644 index 0000000..fe2a1aa --- /dev/null +++ b/src/irc/notifylist/notifylist.h @@ -0,0 +1,29 @@ +#ifndef IRSSI_IRC_NOTIFYLIST_NOTIFYLIST_H +#define IRSSI_IRC_NOTIFYLIST_NOTIFYLIST_H + +typedef struct { + char *mask; /* nick part must not contain wildcards */ + char **ircnets; /* if non-NULL, check only from these irc networks */ + + /* notify when AWAY status changes (uses /USERHOST) */ + unsigned int away_check:1; +} NOTIFYLIST_REC; + +extern GSList *notifies; + +void notifylist_init(void); +void notifylist_deinit(void); + +NOTIFYLIST_REC *notifylist_add(const char *mask, const char *ircnets, + int away_check); +void notifylist_remove(const char *mask); + +IRC_SERVER_REC *notifylist_ison(const char *nick, const char *serverlist); +int notifylist_ison_server(IRC_SERVER_REC *server, const char *nick); + +/* If `ircnet' is "*", it doesn't matter at all. */ +NOTIFYLIST_REC *notifylist_find(const char *mask, const char *ircnet); + +int notifylist_ircnets_match(NOTIFYLIST_REC *rec, const char *ircnet); + +#endif diff --git a/src/irc/proxy/Makefile.am b/src/irc/proxy/Makefile.am new file mode 100644 index 0000000..437a6dc --- /dev/null +++ b/src/irc/proxy/Makefile.am @@ -0,0 +1,28 @@ +moduledir = $(libdir)/irssi/modules +module_LTLIBRARIES = libirc_proxy.la + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_proxy.a: + rm -f libirc_proxy.a + $(LN_S) .libs/libirc_proxy.a libirc_proxy.a + +libirc_proxy_la_LDFLAGS = -module -avoid-version + +libirc_proxy_la_DEPENDENCIES = libirc_proxy.a + +libirc_proxy_la_SOURCES = \ + proxy.c \ + dump.c \ + listen.c + +noinst_HEADERS = \ + module.h \ + proxy.h + +clean-generic: + rm -f libirc_proxy.a + +EXTRA_DIST = meson.build diff --git a/src/irc/proxy/Makefile.in b/src/irc/proxy/Makefile.in new file mode 100644 index 0000000..06e7ee7 --- /dev/null +++ b/src/irc/proxy/Makefile.in @@ -0,0 +1,740 @@ +# 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/proxy +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 $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/irssi-config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +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)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +libirc_proxy_la_LIBADD = +am_libirc_proxy_la_OBJECTS = proxy.lo dump.lo listen.lo +libirc_proxy_la_OBJECTS = $(am_libirc_proxy_la_OBJECTS) +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 = +libirc_proxy_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(libirc_proxy_la_LDFLAGS) $(LDFLAGS) \ + -o $@ +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)/dump.Plo ./$(DEPDIR)/listen.Plo \ + ./$(DEPDIR)/proxy.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +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_proxy_la_SOURCES) +DIST_SOURCES = $(libirc_proxy_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_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@ +moduledir = $(libdir)/irssi/modules +module_LTLIBRARIES = libirc_proxy.la +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_proxy_la_LDFLAGS = -module -avoid-version +libirc_proxy_la_DEPENDENCIES = libirc_proxy.a +libirc_proxy_la_SOURCES = \ + proxy.c \ + dump.c \ + listen.c + +noinst_HEADERS = \ + module.h \ + proxy.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/proxy/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/proxy/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): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libirc_proxy.la: $(libirc_proxy_la_OBJECTS) $(libirc_proxy_la_DEPENDENCIES) $(EXTRA_libirc_proxy_la_DEPENDENCIES) + $(AM_V_CCLD)$(libirc_proxy_la_LINK) -rpath $(moduledir) $(libirc_proxy_la_OBJECTS) $(libirc_proxy_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listen.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxy.Plo@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 + +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 $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(moduledir)"; 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: + +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-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dump.Plo + -rm -f ./$(DEPDIR)/listen.Plo + -rm -f ./$(DEPDIR)/proxy.Plo + -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-moduleLTLIBRARIES + +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)/dump.Plo + -rm -f ./$(DEPDIR)/listen.Plo + -rm -f ./$(DEPDIR)/proxy.Plo + -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-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + 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-moduleLTLIBRARIES install-pdf install-pdf-am \ + 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-moduleLTLIBRARIES + +.PRECIOUS: Makefile + + +libirc_proxy.a: + rm -f libirc_proxy.a + $(LN_S) .libs/libirc_proxy.a libirc_proxy.a + +clean-generic: + rm -f libirc_proxy.a + +# 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/proxy/dump.c b/src/irc/proxy/dump.c new file mode 100644 index 0000000..82dc1fe --- /dev/null +++ b/src/irc/proxy/dump.c @@ -0,0 +1,303 @@ +/* + dump.c : proxy plugin - output all information about irc session + + Copyright (C) 1999-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/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/settings.h> +#include <irssi/irssi-version.h> +#include <irssi/src/core/recode.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/modes.h> + +void proxy_outdata(CLIENT_REC *client, const char *data, ...) +{ + va_list args; + char *str; + + g_return_if_fail(client != NULL); + g_return_if_fail(data != NULL); + + va_start(args, data); + + str = g_strdup_vprintf(data, args); + net_sendbuffer_send(client->handle, str, strlen(str)); + g_free(str); + + va_end(args); +} + +void proxy_outdata_all(IRC_SERVER_REC *server, const char *data, ...) +{ + va_list args; + GSList *tmp; + char *str; + int len; + + g_return_if_fail(server != NULL); + g_return_if_fail(data != NULL); + + va_start(args, data); + + str = g_strdup_vprintf(data, args); + len = strlen(str); + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec->server == server) + net_sendbuffer_send(rec->handle, str, len); + } + g_free(str); + + va_end(args); +} + +void proxy_outserver(CLIENT_REC *client, const char *data, ...) +{ + va_list args; + char *str; + + g_return_if_fail(client != NULL); + g_return_if_fail(data != NULL); + + va_start(args, data); + + str = g_strdup_vprintf(data, args); + proxy_outdata(client, ":%s!%s@proxy %s\r\n", client->nick, + settings_get_str("user_name"), str); + g_free(str); + + va_end(args); +} + +void proxy_outserver_all(IRC_SERVER_REC *server, const char *data, ...) +{ + va_list args; + GSList *tmp; + char *str; + + g_return_if_fail(server != NULL); + g_return_if_fail(data != NULL); + + va_start(args, data); + + str = g_strdup_vprintf(data, args); + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec->server == server) { + proxy_outdata(rec, ":%s!%s@proxy %s\r\n", rec->nick, + settings_get_str("user_name"), str); + } + } + g_free(str); + + va_end(args); +} + +void proxy_outserver_all_except(CLIENT_REC *client, const char *data, ...) +{ + va_list args; + GSList *tmp; + char *str; + + g_return_if_fail(client != NULL); + g_return_if_fail(data != NULL); + + va_start(args, data); + + str = g_strdup_vprintf(data, args); + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec != client && + rec->server == client->server) { + proxy_outdata(rec, ":%s!%s@proxy %s\r\n", rec->nick, + settings_get_str("user_name"), str); + } + } + g_free(str); + + va_end(args); +} + +static void create_names_start(GString *str, IRC_CHANNEL_REC *channel, + CLIENT_REC *client) +{ + g_string_printf(str, ":%s 353 %s %c %s :", + client->proxy_address, client->nick, + channel_mode_is_set(channel, 'p') ? '*' : + channel_mode_is_set(channel, 's') ? '@' : '=', + channel->name); +} + +static void dump_join(IRC_CHANNEL_REC *channel, CLIENT_REC *client) +{ + GSList *tmp, *nicks; + GString *str; + int first; + char *recoded; + + proxy_outserver(client, "JOIN %s", channel->name); + + str = g_string_new(NULL); + create_names_start(str, channel, client); + + first = TRUE; + nicks = nicklist_getnicks(CHANNEL(channel)); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *nick = tmp->data; + + if (str->len >= 500) { + g_string_append(str, "\r\n"); + proxy_outdata(client, "%s", str->str); + create_names_start(str, channel, client); + first = TRUE; + } + + if (first) + first = FALSE; + else + g_string_append_c(str, ' '); + + if (nick->prefixes[0]) + g_string_append_c(str, nick->prefixes[0]); + g_string_append(str, nick->nick); + } + g_slist_free(nicks); + + g_string_append(str, "\r\n"); + proxy_outdata(client, "%s", str->str); + g_string_free(str, TRUE); + + proxy_outdata(client, ":%s 366 %s %s :End of /NAMES list.\r\n", + client->proxy_address, client->nick, channel->name); + if (channel->topic != NULL) { + /* this is needed because the topic may be encoded into other charsets internaly */ + recoded = recode_out(SERVER(client->server), channel->topic, channel->name); + proxy_outdata(client, ":%s 332 %s %s :%s\r\n", + client->proxy_address, client->nick, + channel->name, recoded); + g_free(recoded); + if (channel->topic_time > 0) + proxy_outdata(client, ":%s 333 %s %s %s %d\r\n", + client->proxy_address, client->nick, + channel->name, channel->topic_by, channel->topic_time); + } +} + +void proxy_client_reset_nick(CLIENT_REC *client) +{ + if (client->server == NULL || + g_strcmp0(client->nick, client->server->nick) == 0) + return; + + proxy_outdata(client, ":%s!proxy NICK :%s\r\n", + client->nick, client->server->nick); + + g_free(client->nick); + client->nick = g_strdup(client->server->nick); +} + +static void proxy_dump_data_005(gpointer key, gpointer value, gpointer context) +{ + if (*(char *)value != '\0') + g_string_append_printf(context, "%s=%s ", (char *)key, (char *)value); + else + g_string_append_printf(context, "%s ", (char *)key); +} + +void proxy_dump_data(CLIENT_REC *client) +{ + GString *isupport_out, *paramstr; + char **paramlist, **tmp; + int count; + + proxy_client_reset_nick(client); + + /* welcome info */ + proxy_outdata(client, ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@proxy\r\n", client->proxy_address, client->nick, client->nick, settings_get_str("user_name")); + proxy_outdata(client, ":%s 002 %s :Your host is irssi-proxy, running version %s\r\n", client->proxy_address, client->nick, PACKAGE_VERSION); + proxy_outdata(client, ":%s 003 %s :This server was created ...\r\n", client->proxy_address, client->nick); + if (client->server == NULL || !client->server->emode_known) + proxy_outdata(client, ":%s 004 %s %s %s oirw abiklmnopqstv\r\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); + else + proxy_outdata(client, ":%s 004 %s %s %s oirw abeIiklmnopqstv\r\n", client->proxy_address, client->nick, client->proxy_address, PACKAGE_VERSION); + + if (client->server != NULL && client->server->isupport_sent) { + isupport_out = g_string_new(NULL); + g_hash_table_foreach(client->server->isupport, proxy_dump_data_005, isupport_out); + if (isupport_out->len > 0) + g_string_truncate(isupport_out, isupport_out->len-1); + + proxy_outdata(client, ":%s 005 %s ", client->proxy_address, client->nick); + + paramstr = g_string_new(NULL); + paramlist = g_strsplit(isupport_out->str, " ", -1); + count = 0; + tmp = paramlist; + + for (;; tmp++) { + if (*tmp != NULL) { + g_string_append_printf(paramstr, "%s ", *tmp); + if (++count < 15) + continue; + } + + count = 0; + if (paramstr->len > 0) + g_string_truncate(paramstr, paramstr->len-1); + g_string_append_printf(paramstr, " :are supported by this server\r\n"); + proxy_outdata(client, "%s", paramstr->str); + g_string_truncate(paramstr, 0); + g_string_printf(paramstr, ":%s 005 %s ", client->proxy_address, client->nick); + + if (*tmp == NULL || tmp[1] == NULL) + break; + } + + g_string_free(isupport_out, TRUE); + g_string_free(paramstr, TRUE); + g_strfreev(paramlist); + } + + proxy_outdata(client, ":%s 251 %s :There are 0 users and 0 invisible on 1 servers\r\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 255 %s :I have 0 clients, 0 services and 0 servers\r\n", client->proxy_address, client->nick); + proxy_outdata(client, ":%s 422 %s :MOTD File is missing\r\n", client->proxy_address, client->nick); + + /* user mode / away status */ + if (client->server != NULL) { + if (client->server->usermode != NULL) { + proxy_outserver(client, "MODE %s :+%s", + client->server->nick, + client->server->usermode); + } + if (client->server->usermode_away) { + proxy_outdata(client, ":%s 306 %s :You have been marked as being away\r\n", + client->proxy_address, client->nick); + } + + /* Send channel joins */ + g_slist_foreach(client->server->channels, (GFunc) dump_join, client); + } +} diff --git a/src/irc/proxy/listen.c b/src/irc/proxy/listen.c new file mode 100644 index 0000000..19aba87 --- /dev/null +++ b/src/irc/proxy/listen.c @@ -0,0 +1,874 @@ +/* + listen.c : irc proxy + + Copyright (C) 1999-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/irc/core/servers-redirect.h> +#include <irssi/src/core/levels.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-chatnets.h> +#include <irssi/src/irc/core/irc-channels.h> + +#include <irssi/src/fe-common/core/printtext.h> /* FIXME: evil. need to do fe-proxy */ + +#include <sys/un.h> + +GSList *proxy_listens; +GSList *proxy_clients; + +static GString *next_line; +static int ignore_next; + +static int enabled = FALSE; + +static int is_all_digits(const char *s) +{ + return strspn(s, "0123456789") == strlen(s); +} + +static GIOChannel *net_listen_unix(const char *path) +{ + struct sockaddr_un sa; + int saved_errno, handle; + + g_return_val_if_fail(path != NULL, NULL); + + handle = socket(AF_UNIX, SOCK_STREAM, 0); + if (handle == -1) { + return NULL; + } + + fcntl(handle, F_SETFL, O_NONBLOCK); + + memset(&sa, '\0', sizeof sa); + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, path, sizeof sa.sun_path - 1); + if (bind(handle, (struct sockaddr *)&sa, sizeof sa) == -1) { + saved_errno = errno; + goto error_close; + } + + if (listen(handle, 1) == -1) { + saved_errno = errno; + goto error_unlink; + } + + return i_io_channel_new(handle); + +error_unlink: + unlink(sa.sun_path); +error_close: + close(handle); + errno = saved_errno; + return NULL; +} + +static GIOChannel *net_accept_unix(GIOChannel *handle) +{ + struct sockaddr_un sa; + int ret; + socklen_t addrlen; + + g_return_val_if_fail(handle != NULL, NULL); + + addrlen = sizeof sa; + ret = accept(g_io_channel_unix_get_fd(handle), (struct sockaddr *)&sa, &addrlen); + + if (ret < 0) + return NULL; + + fcntl(ret, F_SETFL, O_NONBLOCK); + return i_io_channel_new(ret); +} + +static void remove_client(CLIENT_REC *rec) +{ + g_return_if_fail(rec != NULL); + + proxy_clients = g_slist_remove(proxy_clients, rec); + rec->listen->clients = g_slist_remove(rec->listen->clients, rec); + + signal_emit("proxy client disconnected", 1, rec); + printtext(rec->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Client %s disconnected", rec->addr); + + g_free(rec->proxy_address); + net_sendbuffer_destroy(rec->handle, TRUE); + g_source_remove(rec->recv_tag); + g_free_not_null(rec->nick); + g_free_not_null(rec->addr); + g_free(rec); +} + +static void proxy_redirect_event(CLIENT_REC *client, const char *command, + int count, const char *arg, int remote) +{ + char *str; + + g_return_if_fail(client != NULL); + + str = g_strdup_printf("proxy %p", client); + server_redirect_event(client->server, command, count, + arg, remote, NULL, "", str, NULL); + g_free(str); +} + +static void grab_who(CLIENT_REC *client, const char *channel) +{ + GString *arg; + char **list, **tmp; + int count; + + /* /WHO a,b,c may respond with either one "a,b,c End of WHO" message + or three different "a End of WHO", "b End of WHO", .. messages */ + list = g_strsplit(channel, ",", -1); + + arg = g_string_new(channel); + + for (tmp = list, count = 0; *tmp != NULL; tmp++, count++) { + if (g_strcmp0(*tmp, "0") == 0) { + /* /who 0 displays everyone */ + **tmp = '*'; + } + + g_string_append_c(arg, ' '); + g_string_append(arg, *tmp); + } + + proxy_redirect_event(client, "who", + client->server->one_endofwho ? 1 : count, + arg->str, -1); + + g_strfreev(list); + g_string_free(arg, TRUE); +} + +static void handle_client_connect_cmd(CLIENT_REC *client, + const char *cmd, const char *args) +{ + const char *password; + + password = settings_get_str("irssiproxy_password"); + + if (g_strcmp0(cmd, "PASS") == 0) { + const char *args_pass; + + if (!client->multiplex) { + args_pass = args; + } else { + IRC_CHATNET_REC *chatnet; + char *tag; + const char *tag_end; + + if ((tag_end = strchr(args, ':')) != NULL) { + args_pass = tag_end + 1; + } else { + tag_end = args + strlen(args); + args_pass = ""; + } + + tag = g_strndup(args, tag_end - args); + chatnet = IRC_CHATNET(chatnet_find(tag)); + + if (!chatnet) { + /* an invalid network was specified */ + remove_client(client); + g_free(tag); + return; + } + + client->server = IRC_SERVER(server_find_chatnet(tag)); + g_free(client->proxy_address); + client->proxy_address = g_strdup_printf("%s.proxy", tag); + g_free(tag); + } + + if (g_strcmp0(password, args_pass) != 0) { + /* wrong password! */ + remove_client(client); + return; + } + client->pass_sent = TRUE; + } else if (g_strcmp0(cmd, "NICK") == 0) { + g_free_not_null(client->nick); + client->nick = g_strdup(args); + } else if (g_strcmp0(cmd, "USER") == 0) { + client->user_sent = TRUE; + } + + if (client->nick != NULL && client->user_sent) { + if ((*password != '\0' || client->multiplex) && !client->pass_sent) { + /* client didn't send us PASS, kill it */ + remove_client(client); + } else { + signal_emit("proxy client connected", 1, client); + printtext(client->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Client %s connected", + client->addr); + client->connected = TRUE; + proxy_dump_data(client); + } + } +} + +static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args, + const char *data) +{ + GSList *tmp; + if (!client->connected) { + handle_client_connect_cmd(client, cmd, args); + return; + } + + if (g_strcmp0(cmd, "QUIT") == 0) { + remove_client(client); + return; + } + + if (g_strcmp0(cmd, "PING") == 0) { + /* Reply to PING, if the target parameter is either + proxy_adress, our own nick or empty. */ + char *params, *origin, *target; + + params = event_get_params(args, 2, &origin, &target); + if (*target == '\0' || + g_ascii_strcasecmp(target, client->proxy_address) == 0 || + g_ascii_strcasecmp(target, client->nick) == 0) { + proxy_outdata(client, ":%s PONG %s :%s\r\n", + client->proxy_address, + client->proxy_address, origin); + g_free(params); + return; + } + g_free(params); + } + + if (g_strcmp0(cmd, "PROXY") == 0) { + if (g_ascii_strcasecmp(args, "CTCP ON") == 0) { + /* client wants all ctcps */ + client->want_ctcp = 1; + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + if (g_ascii_strcasecmp(client->listen->ircnet, rec->listen->ircnet) == 0 && + /* kludgy way to check if the clients aren't the same */ + client->recv_tag != rec->recv_tag) { + if (rec->want_ctcp == 1) + proxy_outdata(rec, ":%s NOTICE %s :Another client is now receiving CTCPs sent to %s\r\n", + rec->proxy_address, rec->nick, rec->listen->ircnet); + rec->want_ctcp = 0; + } + + } + proxy_outdata(client, ":%s NOTICE %s :You're now receiving CTCPs sent to %s\r\n", + client->proxy_address, client->nick, client->listen->ircnet); + } else if (g_ascii_strcasecmp(args, "CTCP OFF") == 0) { + /* client wants proxy to handle all ctcps */ + client->want_ctcp = 0; + proxy_outdata(client, ":%s NOTICE %s :Proxy is now handling itself CTCPs sent to %s\r\n", + client->proxy_address, client->nick, client->listen->ircnet); + } else { + signal_emit("proxy client command", 3, client, args, data); + } + return; + } + + if (client->server == NULL || !client->server->connected) { + proxy_outdata(client, ":%s NOTICE %s :Not connected to server\r\n", + client->proxy_address, client->nick); + return; + } + + /* check if the command could be redirected */ + if (g_strcmp0(cmd, "WHO") == 0) + grab_who(client, args); + else if (g_strcmp0(cmd, "WHOWAS") == 0) + proxy_redirect_event(client, "whowas", 1, args, -1); + else if (g_strcmp0(cmd, "WHOIS") == 0) { + char *p; + + /* convert dots to spaces */ + for (p = args; *p != '\0'; p++) + if (*p == ',') *p = ' '; + + proxy_redirect_event(client, "whois", 1, args, TRUE); + } else if (g_strcmp0(cmd, "ISON") == 0) + proxy_redirect_event(client, "ison", 1, args, -1); + else if (g_strcmp0(cmd, "USERHOST") == 0) + proxy_redirect_event(client, "userhost", 1, args, -1); + else if (g_strcmp0(cmd, "MODE") == 0) { + /* convert dots to spaces */ + char *slist, *str, mode, *p; + int argc; + + p = strchr(args, ' '); + if (p != NULL) *p++ = '\0'; + mode = p == NULL ? '\0' : *p; + + slist = g_strdup(args); + argc = 1; + for (p = slist; *p != '\0'; p++) { + if (*p == ',') { + *p = ' '; + argc++; + } + } + + /* get channel mode / bans / exception / invite list */ + str = g_strdup_printf("%s %s", args, slist); + switch (mode) { + case '\0': + proxy_redirect_event(client, "mode channel", argc, str, -1); + break; + case 'b': + proxy_redirect_event(client, "mode b", argc, str, -1); + break; + case 'e': + proxy_redirect_event(client, "mode e", argc, str, -1); + break; + case 'I': + proxy_redirect_event(client, "mode I", argc, str, -1); + break; + } + g_free(str); + g_free(slist); + } else if (g_strcmp0(cmd, "PRIVMSG") == 0) { + /* send the message to other clients as well */ + char *params, *target, *msg; + + params = event_get_params(args, 2 | PARAM_FLAG_GETREST, + &target, &msg); + proxy_outserver_all_except(client, "PRIVMSG %s", args); + + ignore_next = TRUE; + if (*msg != '\001' || msg[strlen(msg)-1] != '\001') { + signal_emit(server_ischannel(SERVER(client->server), target) ? + "message own_public" : "message own_private", 4, + client->server, msg, target, target); + } else if (strncmp(msg+1, "ACTION ", 7) == 0) { + /* action */ + msg[strlen(msg)-1] = '\0'; + signal_emit("message irc own_action", 3, + client->server, msg+8, target); + } else { + /* CTCP */ + char *p; + + msg[strlen(msg)-1] = '\0'; + p = strchr(msg, ' '); + if (p != NULL) *p++ = '\0'; else p = ""; + + signal_emit("message irc own_ctcp", 4, + client->server, msg+1, p, target); + } + ignore_next = FALSE; + g_free(params); + } else if (g_strcmp0(cmd, "PING") == 0) { + proxy_redirect_event(client, "ping", 1, NULL, TRUE); + } else if (g_strcmp0(cmd, "AWAY") == 0) { + /* set the away reason */ + if (args != NULL) { + g_free(client->server->away_reason); + client->server->away_reason = g_strdup(args); + } + } + + irc_send_cmd(client->server, data); +} + +static void sig_listen_client(CLIENT_REC *client) +{ + char *str, *cmd, *args; + int ret; + + g_return_if_fail(client != NULL); + + while (g_slist_find(proxy_clients, client) != NULL) { + ret = net_sendbuffer_receive_line(client->handle, &str, 1); + if (ret == -1) { + /* connection lost */ + remove_client(client); + break; + } + + if (ret == 0) + break; + + cmd = g_strdup(str); + args = strchr(cmd, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + if (*args == ':') args++; + ascii_strup(cmd); + + handle_client_cmd(client, cmd, args, str); + + g_free(cmd); + } +} + +static void sig_listen(LISTEN_REC *listen) +{ + CLIENT_REC *rec; + IPADDR ip; + NET_SENDBUF_REC *sendbuf; + GIOChannel *handle; + char host[MAX_IP_LEN]; + int port; + char *addr; + + g_return_if_fail(listen != NULL); + + /* accept connection */ + if (listen->port) { + handle = net_accept(listen->handle, &ip, &port); + if (handle == NULL) + return; + net_ip2host(&ip, host); + addr = g_strdup_printf("%s:%d", host, port); + } else { + /* no port => this is a unix socket */ + handle = net_accept_unix(listen->handle); + if (handle == NULL) + return; + addr = g_strdup("(local)"); + } + + sendbuf = net_sendbuffer_create(handle, 0); + rec = g_new0(CLIENT_REC, 1); + rec->listen = listen; + rec->handle = sendbuf; + rec->addr = addr; + if (g_strcmp0(listen->ircnet, "?") == 0) { + rec->multiplex = TRUE; + rec->proxy_address = g_strdup("multiplex.proxy"); + rec->server = NULL; + } else if (g_strcmp0(listen->ircnet, "*") == 0) { + rec->proxy_address = g_strdup("irc.proxy"); + rec->server = servers == NULL ? NULL : IRC_SERVER(servers->data); + } else { + rec->proxy_address = g_strdup_printf("%s.proxy", listen->ircnet); + rec->server = servers == NULL ? NULL : + IRC_SERVER(server_find_chatnet(listen->ircnet)); + } + rec->recv_tag = i_input_add(handle, I_INPUT_READ, (GInputFunction) sig_listen_client, rec); + + proxy_clients = g_slist_prepend(proxy_clients, rec); + listen->clients = g_slist_prepend(listen->clients, rec); + + signal_emit("proxy client connecting", 1, rec); + printtext(rec->server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: New client %s on port %s (%s)", + rec->addr, listen->port_or_path, listen->ircnet); +} + +static void sig_incoming(IRC_SERVER_REC *server, const char *line) +{ + g_return_if_fail(line != NULL); + + /* send server event to all clients */ + g_string_printf(next_line, "%s\r\n", line); +} + +static void sig_server_event(IRC_SERVER_REC *server, const char *line, + const char *nick, const char *address) +{ + GSList *tmp; + void *client; + const char *signal; + char *event, *args; + int redirected; + + g_return_if_fail(line != NULL); + if (!IS_IRC_SERVER(server)) + return; + + /* get command.. */ + event = g_strconcat("event ", line, NULL); + args = strchr(event+6, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + while (*args == ' ') args++; + ascii_strdown(event); + + signal = server_redirect_peek_signal(server, nick, event, args, &redirected); + if ((signal != NULL && strncmp(signal, "proxy ", 6) != 0) || + (signal == NULL && redirected)) { + /* we want to send this to one client (or proxy itself) only */ + /* proxy only */ + g_free(event); + return; + } + + if (signal != NULL) { + server_redirect_get_signal(server, nick, event, args); + if (sscanf(signal+6, "%p", &client) == 1) { + /* send it to specific client only */ + if (g_slist_find(proxy_clients, client) != NULL) + net_sendbuffer_send(((CLIENT_REC *) client)->handle, next_line->str, next_line->len); + g_free(event); + signal_stop(); + return; + } + } + + if (g_strcmp0(event, "event privmsg") == 0 && + strstr(args, " :\001") != NULL && + strstr(args, " :\001ACTION") == NULL) { + /* CTCP - either answer ourself or forward it to one client */ + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->want_ctcp == 1) { + /* only CTCP for the chatnet where client is connected to will be forwarded */ + if (strstr(rec->proxy_address, server->connrec->chatnet) != NULL) { + net_sendbuffer_send(rec->handle, + next_line->str, next_line->len); + signal_stop(); + } + } + } + g_free(event); + return; + } + + if (g_strcmp0(event, "event ping") == 0 || + g_strcmp0(event, "event pong") == 0) { + /* We want to answer ourself to PINGs and CTCPs. + Also hide PONGs from clients. */ + g_free(event); + return; + } + + /* send the data to clients.. */ + proxy_outdata_all(server, "%s", next_line->str); + + g_free(event); +} + +static void event_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + const char *chatnet; + + if (!IS_IRC_SERVER(server)) + return; + + chatnet = server->connrec->chatnet; + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec->server == NULL && + (g_strcmp0(rec->listen->ircnet, "*") == 0 || + (chatnet != NULL && + strstr(rec->proxy_address, chatnet) == rec->proxy_address && + rec->proxy_address[strlen(chatnet)] == '.'))) { + proxy_outdata(rec, ":%s NOTICE %s :Connected to server\r\n", + rec->proxy_address, rec->nick); + rec->server = server; + proxy_client_reset_nick(rec); + } + } +} + +static void proxy_server_disconnected(CLIENT_REC *client, + IRC_SERVER_REC *server) +{ + GSList *tmp; + + proxy_outdata(client, ":%s NOTICE %s :Connection lost to server %s\r\n", + client->proxy_address, client->nick, + server->connrec->address); + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + IRC_CHANNEL_REC *rec = tmp->data; + + proxy_outserver(client, "PART %s :Connection lost to server", + rec->name); + } +} + +static void sig_server_disconnected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!IS_IRC_SERVER(server)) + return; + + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec->server == server) { + proxy_server_disconnected(rec, server); + rec->server = NULL; + } + } +} + +static void event_nick(IRC_SERVER_REC *server, const char *data, + const char *orignick) +{ + GSList *tmp; + + if (!IS_IRC_SERVER(server)) + return; + + if (g_ascii_strcasecmp(orignick, server->nick) != 0) + return; + + if (*data == ':') data++; + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + if (rec->connected && rec->server == server) { + g_free(rec->nick); + rec->nick = g_strdup(data); + } + } +} + +static void sig_message_own_public(IRC_SERVER_REC *server, const char *msg, + const char *target) +{ + if (!IS_IRC_SERVER(server)) + return; + + if (!ignore_next) + proxy_outserver_all(server, "PRIVMSG %s :%s", target, msg); +} + +static void sig_message_own_private(IRC_SERVER_REC *server, const char *msg, + const char *target, const char *origtarget) +{ + if (!IS_IRC_SERVER(server)) + return; + + if (!ignore_next) + proxy_outserver_all(server, "PRIVMSG %s :%s", target, msg); +} + +static void sig_message_own_action(IRC_SERVER_REC *server, const char *msg, + const char *target) +{ + if (!IS_IRC_SERVER(server)) + return; + + if (!ignore_next) + proxy_outserver_all(server, "PRIVMSG %s :\001ACTION %s\001", target, msg); +} + +static LISTEN_REC *find_listen(const char *ircnet, int port, const char *port_or_path) +{ + GSList *tmp; + + for (tmp = proxy_listens; tmp != NULL; tmp = tmp->next) { + LISTEN_REC *rec = tmp->data; + + if ((port + ? /* a tcp port */ + rec->port == port + : /* a unix socket path */ + g_strcmp0(rec->port_or_path, port_or_path) == 0 + ) && + g_ascii_strcasecmp(rec->ircnet, ircnet) == 0) + return rec; + } + + return NULL; +} + +static void add_listen(const char *ircnet, int port, const char *port_or_path) +{ + LISTEN_REC *rec; + IPADDR ip4, ip6, *my_ip; + GIOChannel *handle; + + if (*port_or_path == '\0' || port < 0 || *ircnet == '\0') + return; + + if (port == 0) { + /* listening on a unix socket */ + handle = net_listen_unix(port_or_path); + } else { + /* bind to specific host/ip? */ + my_ip = NULL; + if (*settings_get_str("irssiproxy_bind") != '\0') { + if (net_gethostbyname(settings_get_str("irssiproxy_bind"), + &ip4, &ip6) != 0) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Proxy: can not resolve '%s' - aborting", + settings_get_str("irssiproxy_bind")); + return; + } + + my_ip = ip6.family == 0 ? &ip4 : ip4.family == 0 || + settings_get_bool("resolve_prefer_ipv6") ? &ip6 : &ip4; + } + handle = net_listen(my_ip, &port); + } + + if (handle == NULL) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Proxy: Listen in port %s failed: %s", + port_or_path, g_strerror(errno)); + return; + } + + rec = g_new0(LISTEN_REC, 1); + rec->handle = handle; + rec->ircnet = g_strdup(ircnet); + rec->port = port; + rec->port_or_path = g_strdup(port_or_path); + + rec->tag = i_input_add(rec->handle, I_INPUT_READ, (GInputFunction) sig_listen, rec); + + proxy_listens = g_slist_append(proxy_listens, rec); +} + +static void remove_listen(LISTEN_REC *rec) +{ + proxy_listens = g_slist_remove(proxy_listens, rec); + + while (rec->clients != NULL) + remove_client(rec->clients->data); + + /* remove unix socket because bind wants to (re)create it */ + if (rec->port == 0) + unlink(rec->port_or_path); + + net_disconnect(rec->handle); + g_source_remove(rec->tag); + g_free(rec->port_or_path); + g_free(rec->ircnet); + g_free(rec); +} + +static void read_settings(void) +{ + LISTEN_REC *rec; + GSList *remove_listens = NULL; + GSList *add_listens = NULL; + char **ports, **tmp, *ircnet, *port_or_path; + int portnum; + + remove_listens = g_slist_copy(proxy_listens); + + ports = g_strsplit(settings_get_str("irssiproxy_ports"), " ", -1); + for (tmp = ports; *tmp != NULL; tmp++) { + ircnet = *tmp; + port_or_path = strchr(ircnet, '='); + if (port_or_path == NULL) + continue; + + *port_or_path++ = '\0'; + if (is_all_digits(port_or_path)) { + portnum = atoi(port_or_path); + if (portnum <= 0) + continue; + } else { + portnum = 0; + } + + rec = find_listen(ircnet, portnum, port_or_path); + if (rec == NULL) { + rec = g_new0(LISTEN_REC, 1); + rec->ircnet = ircnet; /* borrow */ + rec->port = portnum; + rec->port_or_path = port_or_path; /* borrow */ + add_listens = g_slist_prepend(add_listens, rec); + } else { + /* remove from the list of listens to remove == keep it */ + remove_listens = g_slist_remove(remove_listens, rec); + } + } + + while (remove_listens != NULL) { + remove_listen(remove_listens->data); + remove_listens = g_slist_remove(remove_listens, remove_listens->data); + } + + while (add_listens != NULL) { + rec = add_listens->data; + add_listen(rec->ircnet, rec->port, rec->port_or_path); + add_listens = g_slist_remove(add_listens, rec); + g_free(rec); + } + + g_strfreev(ports); +} + +static void sig_dump(CLIENT_REC *client, const char *data) +{ + g_return_if_fail(client != NULL); + g_return_if_fail(data != NULL); + + proxy_outdata(client, data); +} + +void proxy_listen_init(void) +{ + if (enabled) { + return; + } + enabled = TRUE; + + next_line = g_string_new(NULL); + + proxy_clients = NULL; + proxy_listens = NULL; + read_settings(); + + signal_add("server incoming", (SIGNAL_FUNC) sig_incoming); + signal_add("server event", (SIGNAL_FUNC) sig_server_event); + signal_add("event connected", (SIGNAL_FUNC) event_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add_first("event nick", (SIGNAL_FUNC) event_nick); + signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_add("message irc own_action", (SIGNAL_FUNC) sig_message_own_action); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + signal_add("proxy client dump", (SIGNAL_FUNC) sig_dump); +} + +void proxy_listen_deinit(void) +{ + if (!enabled) { + return; + } + enabled = FALSE; + + while (proxy_listens != NULL) + remove_listen(proxy_listens->data); + g_string_free(next_line, TRUE); + + signal_remove("server incoming", (SIGNAL_FUNC) sig_incoming); + signal_remove("server event", (SIGNAL_FUNC) sig_server_event); + signal_remove("event connected", (SIGNAL_FUNC) event_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); + signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_remove("message irc own_action", (SIGNAL_FUNC) sig_message_own_action); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + signal_remove("proxy client dump", (SIGNAL_FUNC) sig_dump); +} diff --git a/src/irc/proxy/meson.build b/src/irc/proxy/meson.build new file mode 100644 index 0000000..30ac90a --- /dev/null +++ b/src/irc/proxy/meson.build @@ -0,0 +1,21 @@ +# this file is part of irssi + +shared_module('irc_proxy', + files( + 'dump.c', + 'listen.c', + 'proxy.c', + ) + + [ irssi_version_h ], + include_directories : rootinc, + implicit_include_directories : false, + name_suffix : module_suffix, + install : true, + install_dir : moduledir, + dependencies : dep, +) + +# noinst_headers = files( +# 'module.h', +# 'proxy.h', +# ) diff --git a/src/irc/proxy/module.h b/src/irc/proxy/module.h new file mode 100644 index 0000000..b6ad3a4 --- /dev/null +++ b/src/irc/proxy/module.h @@ -0,0 +1,26 @@ +#include <irssi/src/common.h> + +#define MODULE_NAME "proxy" + +#include <irssi/src/core/network.h> +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/proxy/proxy.h> + +extern GSList *proxy_listens; +extern GSList *proxy_clients; + +void proxy_listen_init(void); +void proxy_listen_deinit(void); + +void proxy_settings_init(void); + +void proxy_dump_data(CLIENT_REC *client); +void proxy_client_reset_nick(CLIENT_REC *client); + +void proxy_outdata(CLIENT_REC *client, const char *data, ...); +void proxy_outdata_all(IRC_SERVER_REC *server, const char *data, ...); +void proxy_outserver(CLIENT_REC *client, const char *data, ...); +void proxy_outserver_all(IRC_SERVER_REC *server, const char *data, ...); +void proxy_outserver_all_except(CLIENT_REC *client, const char *data, ...); diff --git a/src/irc/proxy/proxy.c b/src/irc/proxy/proxy.c new file mode 100644 index 0000000..d32b600 --- /dev/null +++ b/src/irc/proxy/proxy.c @@ -0,0 +1,116 @@ +/* + proxy.c : irc proxy + + Copyright (C) 1999-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/settings.h> +#include <irssi/src/core/levels.h> + +#include <irssi/src/fe-common/core/printtext.h> + +/* SYNTAX: IRSSIPROXY STATUS */ +static void cmd_irssiproxy_status(const char *data, IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!settings_get_bool("irssiproxy")) { + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy is currently disabled"); + return; + } + + + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + "Proxy: Currently connected clients: %d", + g_slist_length(proxy_clients)); + + for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { + CLIENT_REC *rec = tmp->data; + + printtext(server, NULL, MSGLEVEL_CLIENTNOTICE, + " %s connect%s to %s (%s)", + rec->addr, + rec->connected ? "ed" : "ing", + rec->listen->port_or_path, rec->listen->ircnet); + } +} + +/* SYNTAX: IRSSIPROXY */ +static void cmd_irssiproxy(const char *data, IRC_SERVER_REC *server, void *item) +{ + if (*data == '\0') { + cmd_irssiproxy_status(data, server); + return; + } + + command_runsub("irssiproxy", data, server, item); +} + +static void irc_proxy_setup_changed(void) +{ + if (settings_get_bool("irssiproxy")) { + proxy_listen_init(); + } else { + proxy_listen_deinit(); + } +} + +void irc_proxy_init(void) +{ + settings_add_str("irssiproxy", "irssiproxy_ports", ""); + settings_add_str("irssiproxy", "irssiproxy_password", ""); + settings_add_str("irssiproxy", "irssiproxy_bind", ""); + settings_add_bool("irssiproxy", "irssiproxy", TRUE); + + if (*settings_get_str("irssiproxy_password") == '\0') { + /* no password - bad idea! */ + signal_emit("gui dialog", 2, "warning", + "Warning!! Password not specified, everyone can " + "use this proxy! Use /set irssiproxy_password " + "<password> to set it"); + } + if (*settings_get_str("irssiproxy_ports") == '\0') { + signal_emit("gui dialog", 2, "warning", + "No proxy ports specified. Use /SET " + "irssiproxy_ports <ircnet>=<port> <ircnet2>=<port2> " + "... to set them."); + } + + command_bind("irssiproxy", NULL, (SIGNAL_FUNC) cmd_irssiproxy); + command_bind("irssiproxy status", NULL, (SIGNAL_FUNC) cmd_irssiproxy_status); + + signal_add_first("setup changed", (SIGNAL_FUNC) irc_proxy_setup_changed); + + if (settings_get_bool("irssiproxy")) { + proxy_listen_init(); + } + settings_check(); + module_register("proxy", "irc"); +} + +void irc_proxy_deinit(void) +{ + proxy_listen_deinit(); +} + +void irc_proxy_abicheck(int *version) +{ + *version = IRSSI_ABI_VERSION; +} diff --git a/src/irc/proxy/proxy.h b/src/irc/proxy/proxy.h new file mode 100644 index 0000000..5b3d25f --- /dev/null +++ b/src/irc/proxy/proxy.h @@ -0,0 +1,36 @@ +#ifndef IRSSI_IRC_PROXY_PROXY_H +#define IRSSI_IRC_PROXY_PROXY_H + +#include <irssi/src/common.h> + +#include <irssi/src/core/network.h> +#include <irssi/src/irc/core/irc.h> +#include <irssi/src/irc/core/irc-servers.h> + +typedef struct { + int port; + char *port_or_path; + char *ircnet; + + int tag; + GIOChannel *handle; + + GSList *clients; + +} LISTEN_REC; + +typedef struct { + char *nick, *addr; + NET_SENDBUF_REC *handle; + int recv_tag; + char *proxy_address; + LISTEN_REC *listen; + IRC_SERVER_REC *server; + unsigned int pass_sent:1; + unsigned int user_sent:1; + unsigned int connected:1; + unsigned int want_ctcp:1; + unsigned int multiplex:1; +} CLIENT_REC; + +#endif |