summaryrefslogtreecommitdiffstats
path: root/src/irc
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc')
-rw-r--r--src/irc/Makefile.am11
-rw-r--r--src/irc/Makefile.in767
-rw-r--r--src/irc/core/Makefile.am62
-rw-r--r--src/irc/core/Makefile.in870
-rw-r--r--src/irc/core/bans.c359
-rw-r--r--src/irc/core/bans.h14
-rw-r--r--src/irc/core/channel-events.c402
-rw-r--r--src/irc/core/channel-events.h9
-rw-r--r--src/irc/core/channel-rejoin.c286
-rw-r--r--src/irc/core/channel-rejoin.h13
-rw-r--r--src/irc/core/channels-query.c674
-rw-r--r--src/irc/core/ctcp.c368
-rw-r--r--src/irc/core/ctcp.h14
-rw-r--r--src/irc/core/irc-cap.c334
-rw-r--r--src/irc/core/irc-cap.h9
-rw-r--r--src/irc/core/irc-channels-setup.c35
-rw-r--r--src/irc/core/irc-channels.c289
-rw-r--r--src/irc/core/irc-channels.h59
-rw-r--r--src/irc/core/irc-chatnets.c132
-rw-r--r--src/irc/core/irc-chatnets.h44
-rw-r--r--src/irc/core/irc-commands.c1128
-rw-r--r--src/irc/core/irc-commands.h26
-rw-r--r--src/irc/core/irc-core.c153
-rw-r--r--src/irc/core/irc-expandos.c205
-rw-r--r--src/irc/core/irc-masks.c92
-rw-r--r--src/irc/core/irc-masks.h13
-rw-r--r--src/irc/core/irc-nicklist.c652
-rw-r--r--src/irc/core/irc-nicklist.h20
-rw-r--r--src/irc/core/irc-queries.c116
-rw-r--r--src/irc/core/irc-queries.h22
-rw-r--r--src/irc/core/irc-servers-reconnect.c128
-rw-r--r--src/irc/core/irc-servers-setup.c255
-rw-r--r--src/irc/core/irc-servers-setup.h34
-rw-r--r--src/irc/core/irc-servers.c1288
-rw-r--r--src/irc/core/irc-servers.h187
-rw-r--r--src/irc/core/irc-session.c251
-rw-r--r--src/irc/core/irc.c608
-rw-r--r--src/irc/core/irc.h70
-rw-r--r--src/irc/core/lag.c140
-rw-r--r--src/irc/core/massjoin.c379
-rw-r--r--src/irc/core/meson.build66
-rw-r--r--src/irc/core/mode-lists.c142
-rw-r--r--src/irc/core/mode-lists.h24
-rw-r--r--src/irc/core/modes.c932
-rw-r--r--src/irc/core/modes.h67
-rw-r--r--src/irc/core/module.h4
-rw-r--r--src/irc/core/netsplit.c441
-rw-r--r--src/irc/core/netsplit.h43
-rw-r--r--src/irc/core/sasl.c365
-rw-r--r--src/irc/core/sasl.h34
-rw-r--r--src/irc/core/servers-idle.c264
-rw-r--r--src/irc/core/servers-idle.h37
-rw-r--r--src/irc/core/servers-redirect.c809
-rw-r--r--src/irc/core/servers-redirect.h86
-rw-r--r--src/irc/dcc/Makefile.am30
-rw-r--r--src/irc/dcc/Makefile.in758
-rw-r--r--src/irc/dcc/dcc-autoget.c97
-rw-r--r--src/irc/dcc/dcc-chat.c873
-rw-r--r--src/irc/dcc/dcc-chat.h40
-rw-r--r--src/irc/dcc/dcc-file-rec.h9
-rw-r--r--src/irc/dcc/dcc-file.h10
-rw-r--r--src/irc/dcc/dcc-get.c627
-rw-r--r--src/irc/dcc/dcc-get.h45
-rw-r--r--src/irc/dcc/dcc-queue.c227
-rw-r--r--src/irc/dcc/dcc-queue.h45
-rw-r--r--src/irc/dcc/dcc-rec.h27
-rw-r--r--src/irc/dcc/dcc-resume.c248
-rw-r--r--src/irc/dcc/dcc-send.c487
-rw-r--r--src/irc/dcc/dcc-send.h27
-rw-r--r--src/irc/dcc/dcc-server.c413
-rw-r--r--src/irc/dcc/dcc-server.h29
-rw-r--r--src/irc/dcc/dcc.c600
-rw-r--r--src/irc/dcc/dcc.h67
-rw-r--r--src/irc/dcc/meson.build31
-rw-r--r--src/irc/dcc/module.h4
-rw-r--r--src/irc/flood/Makefile.am15
-rw-r--r--src/irc/flood/Makefile.in719
-rw-r--r--src/irc/flood/autoignore.c89
-rw-r--r--src/irc/flood/flood.c349
-rw-r--r--src/irc/flood/meson.build14
-rw-r--r--src/irc/flood/module.h13
-rw-r--r--src/irc/irc.c5
-rw-r--r--src/irc/meson.build24
-rw-r--r--src/irc/notifylist/Makefile.am20
-rw-r--r--src/irc/notifylist/Makefile.in737
-rw-r--r--src/irc/notifylist/meson.build21
-rw-r--r--src/irc/notifylist/module.h42
-rw-r--r--src/irc/notifylist/notify-commands.c81
-rw-r--r--src/irc/notifylist/notify-ison.c348
-rw-r--r--src/irc/notifylist/notify-setup.c79
-rw-r--r--src/irc/notifylist/notify-setup.h9
-rw-r--r--src/irc/notifylist/notify-whois.c153
-rw-r--r--src/irc/notifylist/notifylist.c365
-rw-r--r--src/irc/notifylist/notifylist.h29
-rw-r--r--src/irc/proxy/Makefile.am28
-rw-r--r--src/irc/proxy/Makefile.in740
-rw-r--r--src/irc/proxy/dump.c303
-rw-r--r--src/irc/proxy/listen.c874
-rw-r--r--src/irc/proxy/meson.build21
-rw-r--r--src/irc/proxy/module.h26
-rw-r--r--src/irc/proxy/proxy.c116
-rw-r--r--src/irc/proxy/proxy.h36
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(" ", &params[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