summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/Makefile.am122
-rw-r--r--src/core/Makefile.in964
-rw-r--r--src/core/args.c52
-rw-r--r--src/core/args.h7
-rw-r--r--src/core/capsicum.c508
-rw-r--r--src/core/capsicum.h15
-rw-r--r--src/core/channel-rec.h31
-rw-r--r--src/core/channel-setup-rec.h12
-rw-r--r--src/core/channels-setup.c241
-rw-r--r--src/core/channels-setup.h34
-rw-r--r--src/core/channels.c308
-rw-r--r--src/core/channels.h39
-rw-r--r--src/core/chat-commands.c504
-rw-r--r--src/core/chat-protocols.c239
-rw-r--r--src/core/chat-protocols.h61
-rw-r--r--src/core/chatnet-rec.h12
-rw-r--r--src/core/chatnets.c194
-rw-r--r--src/core/chatnets.h32
-rw-r--r--src/core/commands.c1021
-rw-r--r--src/core/commands.h174
-rw-r--r--src/core/core.c328
-rw-r--r--src/core/core.h25
-rw-r--r--src/core/expandos.c757
-rw-r--r--src/core/expandos.h45
-rw-r--r--src/core/ignore.c543
-rw-r--r--src/core/ignore.h53
-rw-r--r--src/core/iregex-gregex.c165
-rw-r--r--src/core/iregex-regexh.c99
-rw-r--r--src/core/iregex.h47
-rw-r--r--src/core/levels.c199
-rw-r--r--src/core/levels.h54
-rw-r--r--src/core/line-split.c136
-rw-r--r--src/core/line-split.h11
-rw-r--r--src/core/log-away.c127
-rw-r--r--src/core/log.c617
-rw-r--r--src/core/log.h64
-rw-r--r--src/core/masks.c134
-rw-r--r--src/core/masks.h11
-rw-r--r--src/core/meson.build131
-rw-r--r--src/core/misc.c1094
-rw-r--r--src/core/misc.h130
-rw-r--r--src/core/module.h3
-rw-r--r--src/core/modules-load.c443
-rw-r--r--src/core/modules-load.h16
-rw-r--r--src/core/modules.c311
-rw-r--r--src/core/modules.h98
-rw-r--r--src/core/net-disconnect.c156
-rw-r--r--src/core/net-disconnect.h11
-rw-r--r--src/core/net-nonblock.c107
-rw-r--r--src/core/net-nonblock.h22
-rw-r--r--src/core/net-sendbuffer.c173
-rw-r--r--src/core/net-sendbuffer.h38
-rw-r--r--src/core/network-openssl.c944
-rw-r--r--src/core/network-openssl.h6
-rw-r--r--src/core/network.c584
-rw-r--r--src/core/network.h97
-rw-r--r--src/core/nick-rec.h29
-rw-r--r--src/core/nicklist.c607
-rw-r--r--src/core/nicklist.h64
-rw-r--r--src/core/nickmatch-cache.c122
-rw-r--r--src/core/nickmatch-cache.h27
-rw-r--r--src/core/pidwait.c78
-rw-r--r--src/core/pidwait.h16
-rw-r--r--src/core/queries.c174
-rw-r--r--src/core/queries.h34
-rw-r--r--src/core/query-rec.h11
-rw-r--r--src/core/rawlog.c260
-rw-r--r--src/core/rawlog.h27
-rw-r--r--src/core/recode.c307
-rw-r--r--src/core/recode.h15
-rw-r--r--src/core/refstrings.c129
-rw-r--r--src/core/refstrings.h10
-rw-r--r--src/core/server-connect-rec.h49
-rw-r--r--src/core/server-rec.h78
-rw-r--r--src/core/server-setup-rec.h36
-rw-r--r--src/core/servers-reconnect.c527
-rw-r--r--src/core/servers-reconnect.h23
-rw-r--r--src/core/servers-setup.c780
-rw-r--r--src/core/servers-setup.h53
-rw-r--r--src/core/servers.c785
-rw-r--r--src/core/servers.h86
-rw-r--r--src/core/session.c390
-rw-r--r--src/core/session.h13
-rw-r--r--src/core/settings.c934
-rw-r--r--src/core/settings.h139
-rw-r--r--src/core/signals.c439
-rw-r--r--src/core/signals.h76
-rw-r--r--src/core/special-vars.c843
-rw-r--r--src/core/special-vars.h53
-rw-r--r--src/core/tls.c214
-rw-r--r--src/core/tls.h88
-rw-r--r--src/core/utf8.c135
-rw-r--r--src/core/utf8.h62
-rw-r--r--src/core/wcwidth-wrapper.c141
-rw-r--r--src/core/wcwidth.c220
-rw-r--r--src/core/window-item-def.h9
-rw-r--r--src/core/window-item-rec.h21
-rw-r--r--src/core/write-buffer.c191
-rw-r--r--src/core/write-buffer.h10
99 files changed, 20654 insertions, 0 deletions
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
new file mode 100644
index 0000000..493c42b
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,122 @@
+noinst_LIBRARIES = libcore.a
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DMODULEDIR=\""$(libdir)/irssi/modules"\"
+
+if USE_GREGEX
+regex_impl=iregex-gregex.c
+else
+regex_impl=iregex-regexh.c
+endif
+
+libcore_a_SOURCES = \
+ args.c \
+ channels.c \
+ channels-setup.c \
+ commands.c \
+ chat-commands.c \
+ chat-protocols.c \
+ chatnets.c \
+ core.c \
+ expandos.c \
+ ignore.c \
+ levels.c \
+ line-split.c \
+ log.c \
+ log-away.c \
+ masks.c \
+ misc.c \
+ modules.c \
+ modules-load.c \
+ net-disconnect.c \
+ net-nonblock.c \
+ net-sendbuffer.c \
+ network.c \
+ network-openssl.c \
+ nicklist.c \
+ nickmatch-cache.c \
+ pidwait.c \
+ queries.c \
+ rawlog.c \
+ recode.c \
+ refstrings.c \
+ servers.c \
+ servers-reconnect.c \
+ servers-setup.c \
+ session.c \
+ settings.c \
+ signals.c \
+ special-vars.c \
+ utf8.c \
+ $(regex_impl) \
+ wcwidth.c \
+ wcwidth-wrapper.c \
+ tls.c \
+ write-buffer.c
+
+if HAVE_CAPSICUM
+libcore_a_SOURCES += \
+ capsicum.c
+endif
+
+structure_headers = \
+ channel-rec.h \
+ channel-setup-rec.h \
+ chatnet-rec.h \
+ query-rec.h \
+ server-rec.h \
+ server-setup-rec.h \
+ server-connect-rec.h \
+ window-item-rec.h
+
+pkginc_coredir=$(pkgincludedir)/src/core
+pkginc_core_HEADERS = \
+ args.h \
+ capsicum.h \
+ channels.h \
+ channels-setup.h \
+ commands.h \
+ chat-protocols.h \
+ chatnets.h \
+ core.h \
+ expandos.h \
+ ignore.h \
+ levels.h \
+ line-split.h \
+ log.h \
+ masks.h \
+ misc.h \
+ module.h \
+ modules.h \
+ modules-load.h \
+ net-disconnect.h \
+ net-nonblock.h \
+ net-sendbuffer.h \
+ network.h \
+ network-openssl.h \
+ nick-rec.h \
+ nicklist.h \
+ nickmatch-cache.h \
+ pidwait.h \
+ queries.h \
+ rawlog.h \
+ recode.h \
+ refstrings.h \
+ servers.h \
+ servers-reconnect.h \
+ servers-setup.h \
+ session.h \
+ settings.h \
+ signals.h \
+ special-vars.h \
+ utf8.h \
+ iregex.h \
+ window-item-def.h \
+ tls.h \
+ write-buffer.h \
+ $(structure_headers)
+
+EXTRA_DIST = meson.build
diff --git a/src/core/Makefile.in b/src/core/Makefile.in
new file mode 100644
index 0000000..9262bf3
--- /dev/null
+++ b/src/core/Makefile.in
@@ -0,0 +1,964 @@
+# 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@
+@HAVE_CAPSICUM_TRUE@am__append_1 = \
+@HAVE_CAPSICUM_TRUE@ capsicum.c
+
+subdir = src/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_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 =
+libcore_a_AR = $(AR) $(ARFLAGS)
+libcore_a_LIBADD =
+am__libcore_a_SOURCES_DIST = args.c channels.c channels-setup.c \
+ commands.c chat-commands.c chat-protocols.c chatnets.c core.c \
+ expandos.c ignore.c levels.c line-split.c log.c log-away.c \
+ masks.c misc.c modules.c modules-load.c net-disconnect.c \
+ net-nonblock.c net-sendbuffer.c network.c network-openssl.c \
+ nicklist.c nickmatch-cache.c pidwait.c queries.c rawlog.c \
+ recode.c refstrings.c servers.c servers-reconnect.c \
+ servers-setup.c session.c settings.c signals.c special-vars.c \
+ utf8.c iregex-regexh.c iregex-gregex.c wcwidth.c \
+ wcwidth-wrapper.c tls.c write-buffer.c capsicum.c
+@USE_GREGEX_FALSE@am__objects_1 = iregex-regexh.$(OBJEXT)
+@USE_GREGEX_TRUE@am__objects_1 = iregex-gregex.$(OBJEXT)
+@HAVE_CAPSICUM_TRUE@am__objects_2 = capsicum.$(OBJEXT)
+am_libcore_a_OBJECTS = args.$(OBJEXT) channels.$(OBJEXT) \
+ channels-setup.$(OBJEXT) commands.$(OBJEXT) \
+ chat-commands.$(OBJEXT) chat-protocols.$(OBJEXT) \
+ chatnets.$(OBJEXT) core.$(OBJEXT) expandos.$(OBJEXT) \
+ ignore.$(OBJEXT) levels.$(OBJEXT) line-split.$(OBJEXT) \
+ log.$(OBJEXT) log-away.$(OBJEXT) masks.$(OBJEXT) \
+ misc.$(OBJEXT) modules.$(OBJEXT) modules-load.$(OBJEXT) \
+ net-disconnect.$(OBJEXT) net-nonblock.$(OBJEXT) \
+ net-sendbuffer.$(OBJEXT) network.$(OBJEXT) \
+ network-openssl.$(OBJEXT) nicklist.$(OBJEXT) \
+ nickmatch-cache.$(OBJEXT) pidwait.$(OBJEXT) queries.$(OBJEXT) \
+ rawlog.$(OBJEXT) recode.$(OBJEXT) refstrings.$(OBJEXT) \
+ servers.$(OBJEXT) servers-reconnect.$(OBJEXT) \
+ servers-setup.$(OBJEXT) session.$(OBJEXT) settings.$(OBJEXT) \
+ signals.$(OBJEXT) special-vars.$(OBJEXT) utf8.$(OBJEXT) \
+ $(am__objects_1) wcwidth.$(OBJEXT) wcwidth-wrapper.$(OBJEXT) \
+ tls.$(OBJEXT) write-buffer.$(OBJEXT) $(am__objects_2)
+libcore_a_OBJECTS = $(am_libcore_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)/args.Po ./$(DEPDIR)/capsicum.Po \
+ ./$(DEPDIR)/channels-setup.Po ./$(DEPDIR)/channels.Po \
+ ./$(DEPDIR)/chat-commands.Po ./$(DEPDIR)/chat-protocols.Po \
+ ./$(DEPDIR)/chatnets.Po ./$(DEPDIR)/commands.Po \
+ ./$(DEPDIR)/core.Po ./$(DEPDIR)/expandos.Po \
+ ./$(DEPDIR)/ignore.Po ./$(DEPDIR)/iregex-gregex.Po \
+ ./$(DEPDIR)/iregex-regexh.Po ./$(DEPDIR)/levels.Po \
+ ./$(DEPDIR)/line-split.Po ./$(DEPDIR)/log-away.Po \
+ ./$(DEPDIR)/log.Po ./$(DEPDIR)/masks.Po ./$(DEPDIR)/misc.Po \
+ ./$(DEPDIR)/modules-load.Po ./$(DEPDIR)/modules.Po \
+ ./$(DEPDIR)/net-disconnect.Po ./$(DEPDIR)/net-nonblock.Po \
+ ./$(DEPDIR)/net-sendbuffer.Po ./$(DEPDIR)/network-openssl.Po \
+ ./$(DEPDIR)/network.Po ./$(DEPDIR)/nicklist.Po \
+ ./$(DEPDIR)/nickmatch-cache.Po ./$(DEPDIR)/pidwait.Po \
+ ./$(DEPDIR)/queries.Po ./$(DEPDIR)/rawlog.Po \
+ ./$(DEPDIR)/recode.Po ./$(DEPDIR)/refstrings.Po \
+ ./$(DEPDIR)/servers-reconnect.Po ./$(DEPDIR)/servers-setup.Po \
+ ./$(DEPDIR)/servers.Po ./$(DEPDIR)/session.Po \
+ ./$(DEPDIR)/settings.Po ./$(DEPDIR)/signals.Po \
+ ./$(DEPDIR)/special-vars.Po ./$(DEPDIR)/tls.Po \
+ ./$(DEPDIR)/utf8.Po ./$(DEPDIR)/wcwidth-wrapper.Po \
+ ./$(DEPDIR)/wcwidth.Po ./$(DEPDIR)/write-buffer.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 = $(libcore_a_SOURCES)
+DIST_SOURCES = $(am__libcore_a_SOURCES_DIST)
+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_coredir)"
+HEADERS = $(pkginc_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 = libcore.a
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DMODULEDIR=\""$(libdir)/irssi/modules"\"
+
+@USE_GREGEX_FALSE@regex_impl = iregex-regexh.c
+@USE_GREGEX_TRUE@regex_impl = iregex-gregex.c
+libcore_a_SOURCES = args.c channels.c channels-setup.c commands.c \
+ chat-commands.c chat-protocols.c chatnets.c core.c expandos.c \
+ ignore.c levels.c line-split.c log.c log-away.c masks.c misc.c \
+ modules.c modules-load.c net-disconnect.c net-nonblock.c \
+ net-sendbuffer.c network.c network-openssl.c nicklist.c \
+ nickmatch-cache.c pidwait.c queries.c rawlog.c recode.c \
+ refstrings.c servers.c servers-reconnect.c servers-setup.c \
+ session.c settings.c signals.c special-vars.c utf8.c \
+ $(regex_impl) wcwidth.c wcwidth-wrapper.c tls.c write-buffer.c \
+ $(am__append_1)
+structure_headers = \
+ channel-rec.h \
+ channel-setup-rec.h \
+ chatnet-rec.h \
+ query-rec.h \
+ server-rec.h \
+ server-setup-rec.h \
+ server-connect-rec.h \
+ window-item-rec.h
+
+pkginc_coredir = $(pkgincludedir)/src/core
+pkginc_core_HEADERS = \
+ args.h \
+ capsicum.h \
+ channels.h \
+ channels-setup.h \
+ commands.h \
+ chat-protocols.h \
+ chatnets.h \
+ core.h \
+ expandos.h \
+ ignore.h \
+ levels.h \
+ line-split.h \
+ log.h \
+ masks.h \
+ misc.h \
+ module.h \
+ modules.h \
+ modules-load.h \
+ net-disconnect.h \
+ net-nonblock.h \
+ net-sendbuffer.h \
+ network.h \
+ network-openssl.h \
+ nick-rec.h \
+ nicklist.h \
+ nickmatch-cache.h \
+ pidwait.h \
+ queries.h \
+ rawlog.h \
+ recode.h \
+ refstrings.h \
+ servers.h \
+ servers-reconnect.h \
+ servers-setup.h \
+ session.h \
+ settings.h \
+ signals.h \
+ special-vars.h \
+ utf8.h \
+ iregex.h \
+ window-item-def.h \
+ tls.h \
+ write-buffer.h \
+ $(structure_headers)
+
+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/core/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/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)
+
+libcore.a: $(libcore_a_OBJECTS) $(libcore_a_DEPENDENCIES) $(EXTRA_libcore_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libcore.a
+ $(AM_V_AR)$(libcore_a_AR) libcore.a $(libcore_a_OBJECTS) $(libcore_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libcore.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/args.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/capsicum.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channels-setup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/channels.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chat-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chat-protocols.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chatnets.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/core.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expandos.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ignore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iregex-gregex.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iregex-regexh.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/levels.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/line-split.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log-away.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masks.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/modules-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/modules.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net-disconnect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net-nonblock.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net-sendbuffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network-openssl.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/network.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nicklist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nickmatch-cache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pidwait.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queries.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rawlog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recode.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refstrings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers-reconnect.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers-setup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/servers.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signals.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/special-vars.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tls.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utf8.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wcwidth-wrapper.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wcwidth.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/write-buffer.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_coreHEADERS: $(pkginc_core_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_core_HEADERS)'; test -n "$(pkginc_coredir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_coredir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_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_coredir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_coredir)" || exit $$?; \
+ done
+
+uninstall-pkginc_coreHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_core_HEADERS)'; test -n "$(pkginc_coredir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_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_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)/args.Po
+ -rm -f ./$(DEPDIR)/capsicum.Po
+ -rm -f ./$(DEPDIR)/channels-setup.Po
+ -rm -f ./$(DEPDIR)/channels.Po
+ -rm -f ./$(DEPDIR)/chat-commands.Po
+ -rm -f ./$(DEPDIR)/chat-protocols.Po
+ -rm -f ./$(DEPDIR)/chatnets.Po
+ -rm -f ./$(DEPDIR)/commands.Po
+ -rm -f ./$(DEPDIR)/core.Po
+ -rm -f ./$(DEPDIR)/expandos.Po
+ -rm -f ./$(DEPDIR)/ignore.Po
+ -rm -f ./$(DEPDIR)/iregex-gregex.Po
+ -rm -f ./$(DEPDIR)/iregex-regexh.Po
+ -rm -f ./$(DEPDIR)/levels.Po
+ -rm -f ./$(DEPDIR)/line-split.Po
+ -rm -f ./$(DEPDIR)/log-away.Po
+ -rm -f ./$(DEPDIR)/log.Po
+ -rm -f ./$(DEPDIR)/masks.Po
+ -rm -f ./$(DEPDIR)/misc.Po
+ -rm -f ./$(DEPDIR)/modules-load.Po
+ -rm -f ./$(DEPDIR)/modules.Po
+ -rm -f ./$(DEPDIR)/net-disconnect.Po
+ -rm -f ./$(DEPDIR)/net-nonblock.Po
+ -rm -f ./$(DEPDIR)/net-sendbuffer.Po
+ -rm -f ./$(DEPDIR)/network-openssl.Po
+ -rm -f ./$(DEPDIR)/network.Po
+ -rm -f ./$(DEPDIR)/nicklist.Po
+ -rm -f ./$(DEPDIR)/nickmatch-cache.Po
+ -rm -f ./$(DEPDIR)/pidwait.Po
+ -rm -f ./$(DEPDIR)/queries.Po
+ -rm -f ./$(DEPDIR)/rawlog.Po
+ -rm -f ./$(DEPDIR)/recode.Po
+ -rm -f ./$(DEPDIR)/refstrings.Po
+ -rm -f ./$(DEPDIR)/servers-reconnect.Po
+ -rm -f ./$(DEPDIR)/servers-setup.Po
+ -rm -f ./$(DEPDIR)/servers.Po
+ -rm -f ./$(DEPDIR)/session.Po
+ -rm -f ./$(DEPDIR)/settings.Po
+ -rm -f ./$(DEPDIR)/signals.Po
+ -rm -f ./$(DEPDIR)/special-vars.Po
+ -rm -f ./$(DEPDIR)/tls.Po
+ -rm -f ./$(DEPDIR)/utf8.Po
+ -rm -f ./$(DEPDIR)/wcwidth-wrapper.Po
+ -rm -f ./$(DEPDIR)/wcwidth.Po
+ -rm -f ./$(DEPDIR)/write-buffer.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_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)/args.Po
+ -rm -f ./$(DEPDIR)/capsicum.Po
+ -rm -f ./$(DEPDIR)/channels-setup.Po
+ -rm -f ./$(DEPDIR)/channels.Po
+ -rm -f ./$(DEPDIR)/chat-commands.Po
+ -rm -f ./$(DEPDIR)/chat-protocols.Po
+ -rm -f ./$(DEPDIR)/chatnets.Po
+ -rm -f ./$(DEPDIR)/commands.Po
+ -rm -f ./$(DEPDIR)/core.Po
+ -rm -f ./$(DEPDIR)/expandos.Po
+ -rm -f ./$(DEPDIR)/ignore.Po
+ -rm -f ./$(DEPDIR)/iregex-gregex.Po
+ -rm -f ./$(DEPDIR)/iregex-regexh.Po
+ -rm -f ./$(DEPDIR)/levels.Po
+ -rm -f ./$(DEPDIR)/line-split.Po
+ -rm -f ./$(DEPDIR)/log-away.Po
+ -rm -f ./$(DEPDIR)/log.Po
+ -rm -f ./$(DEPDIR)/masks.Po
+ -rm -f ./$(DEPDIR)/misc.Po
+ -rm -f ./$(DEPDIR)/modules-load.Po
+ -rm -f ./$(DEPDIR)/modules.Po
+ -rm -f ./$(DEPDIR)/net-disconnect.Po
+ -rm -f ./$(DEPDIR)/net-nonblock.Po
+ -rm -f ./$(DEPDIR)/net-sendbuffer.Po
+ -rm -f ./$(DEPDIR)/network-openssl.Po
+ -rm -f ./$(DEPDIR)/network.Po
+ -rm -f ./$(DEPDIR)/nicklist.Po
+ -rm -f ./$(DEPDIR)/nickmatch-cache.Po
+ -rm -f ./$(DEPDIR)/pidwait.Po
+ -rm -f ./$(DEPDIR)/queries.Po
+ -rm -f ./$(DEPDIR)/rawlog.Po
+ -rm -f ./$(DEPDIR)/recode.Po
+ -rm -f ./$(DEPDIR)/refstrings.Po
+ -rm -f ./$(DEPDIR)/servers-reconnect.Po
+ -rm -f ./$(DEPDIR)/servers-setup.Po
+ -rm -f ./$(DEPDIR)/servers.Po
+ -rm -f ./$(DEPDIR)/session.Po
+ -rm -f ./$(DEPDIR)/settings.Po
+ -rm -f ./$(DEPDIR)/signals.Po
+ -rm -f ./$(DEPDIR)/special-vars.Po
+ -rm -f ./$(DEPDIR)/tls.Po
+ -rm -f ./$(DEPDIR)/utf8.Po
+ -rm -f ./$(DEPDIR)/wcwidth-wrapper.Po
+ -rm -f ./$(DEPDIR)/wcwidth.Po
+ -rm -f ./$(DEPDIR)/write-buffer.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_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_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_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/core/args.c b/src/core/args.c
new file mode 100644
index 0000000..5e2b141
--- /dev/null
+++ b/src/core/args.c
@@ -0,0 +1,52 @@
+/*
+ args.c : small frontend to GOption command line argument parser
+
+ 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/args.h>
+
+static GOptionContext *context = NULL;
+
+void args_register(GOptionEntry *options)
+{
+ if (context == NULL)
+ context = g_option_context_new("");
+
+ g_option_context_add_main_entries(context, options, PACKAGE_TARNAME);
+}
+
+void args_execute(int argc, char *argv[])
+{
+ GError* error = NULL;
+
+ if (context == NULL)
+ return;
+
+ g_option_context_parse(context, &argc, &argv, &error);
+ g_option_context_free(context);
+ context = NULL;
+
+ if (error != NULL) {
+ printf("%s\n"
+ "Run '%s --help' to see a full list of "
+ "available command line options.\n",
+ error->message, argv[0]);
+ exit(1);
+ }
+}
diff --git a/src/core/args.h b/src/core/args.h
new file mode 100644
index 0000000..aeaf571
--- /dev/null
+++ b/src/core/args.h
@@ -0,0 +1,7 @@
+#ifndef IRSSI_CORE_ARGS_H
+#define IRSSI_CORE_ARGS_H
+
+void args_register(GOptionEntry *options);
+void args_execute(int argc, char *argv[]);
+
+#endif
diff --git a/src/core/capsicum.c b/src/core/capsicum.c
new file mode 100644
index 0000000..d73d15f
--- /dev/null
+++ b/src/core/capsicum.c
@@ -0,0 +1,508 @@
+/*
+ capsicum.c : Capsicum sandboxing support
+
+ Copyright (C) 2017 Edward Tomasz Napierala <trasz@FreeBSD.org>
+
+ This software was developed by SRI International and the University of
+ Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ ("CTSRD"), as part of the DARPA CRASH research programme.
+
+ 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/capsicum.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/network-openssl.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+
+#include <sys/param.h>
+#include <sys/capsicum.h>
+#include <sys/filio.h>
+#include <sys/nv.h>
+#include <sys/procdesc.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <termios.h>
+
+#define OPCODE_CONNECT 1
+#define OPCODE_GETHOSTBYNAME 2
+
+static char *irclogs_path;
+static size_t irclogs_path_len;
+static int irclogs_fd;
+static int symbiontfds[2];
+static int port_min;
+static int port_max;
+
+gboolean capsicum_enabled(void)
+{
+ u_int mode;
+ int error;
+
+ error = cap_getmode(&mode);
+ if (error != 0)
+ return FALSE;
+
+ if (mode == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+int capsicum_net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+ nvlist_t *nvl;
+ int error, saved_errno, sock;
+
+ /* Send request to the symbiont. */
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "opcode", OPCODE_CONNECT);
+ nvlist_add_binary(nvl, "ip", ip, sizeof(*ip));
+ nvlist_add_number(nvl, "port", port);
+ if (my_ip != NULL) {
+ /* nvlist_add_binary(3) can't handle NULL values. */
+ nvlist_add_binary(nvl, "my_ip", my_ip, sizeof(*my_ip));
+ }
+ error = nvlist_send(symbiontfds[1], nvl);
+ nvlist_destroy(nvl);
+ if (error != 0) {
+ g_warning("nvlist_send: %s", strerror(errno));
+ return -1;
+ }
+
+ /* Receive response. */
+ nvl = nvlist_recv(symbiontfds[1], 0);
+ if (nvl == NULL) {
+ g_warning("nvlist_recv: %s", strerror(errno));
+ return -1;
+ }
+ if (nvlist_exists_descriptor(nvl, "sock")) {
+ sock = nvlist_take_descriptor(nvl, "sock");
+ } else {
+ sock = -1;
+ }
+ saved_errno = nvlist_get_number(nvl, "errno");
+ nvlist_destroy(nvl);
+
+ if (sock == -1 && (port < port_min || port > port_max)) {
+ g_warning("Access restricted to ports between %d and %d "
+ "due to capability mode",
+ port_min, port_max);
+ }
+
+ errno = saved_errno;
+
+ return sock;
+}
+
+int capsicum_net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6)
+{
+ nvlist_t *nvl;
+ const IPADDR *received_ip4, *received_ip6;
+ int error, ret, saved_errno;
+
+ /* Send request to the symbiont. */
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "opcode", OPCODE_GETHOSTBYNAME);
+ nvlist_add_string(nvl, "addr", addr);
+ error = nvlist_send(symbiontfds[1], nvl);
+ nvlist_destroy(nvl);
+ if (error != 0) {
+ g_warning("nvlist_send: %s", strerror(errno));
+ return -1;
+ }
+
+ /* Receive response. */
+ nvl = nvlist_recv(symbiontfds[1], 0);
+ if (nvl == NULL) {
+ g_warning("nvlist_recv: %s", strerror(errno));
+ return -1;
+ }
+
+ received_ip4 = nvlist_get_binary(nvl, "ip4", NULL);
+ received_ip6 = nvlist_get_binary(nvl, "ip6", NULL);
+ memcpy(ip4, received_ip4, sizeof(*ip4));
+ memcpy(ip6, received_ip6, sizeof(*ip6));
+
+ ret = nvlist_get_number(nvl, "ret");
+ saved_errno = nvlist_get_number(nvl, "errno");
+ nvlist_destroy(nvl);
+ errno = saved_errno;
+
+ return ret;
+}
+
+int capsicum_open(const char *path, int flags, int mode)
+{
+ int fd;
+
+ /* +1 is for the slash separating irclogs_path and the rest. */
+ if (strlen(path) > irclogs_path_len + 1 &&
+ path[irclogs_path_len] == '/' &&
+ strncmp(path, irclogs_path, irclogs_path_len) == 0) {
+ fd = openat(irclogs_fd, path + irclogs_path_len + 1,
+ flags, mode);
+ } else {
+ fd = open(path, flags, mode);
+ }
+
+ if (fd < 0 && (errno == ENOTCAPABLE || errno == ECAPMODE))
+ g_warning("File system access restricted to %s "
+ "due to capability mode", irclogs_path);
+
+ return (fd);
+}
+
+int capsicum_open_wrapper(const char *path, int flags, int mode)
+{
+ if (capsicum_enabled()) {
+ return capsicum_open(path, flags, mode);
+ }
+ return open(path, flags, mode);
+}
+
+void capsicum_mkdir_with_parents(const char *path, int mode)
+{
+ char *component, *copy, *tofree;
+ int error, fd, newfd;
+
+ /* The directory already exists, nothing to do. */
+ if (strcmp(path, irclogs_path) == 0)
+ return;
+
+ /* +1 is for the slash separating irclogs_path and the rest. */
+ if (strlen(path) <= irclogs_path_len + 1 ||
+ path[irclogs_path_len] != '/' ||
+ strncmp(path, irclogs_path, irclogs_path_len) != 0) {
+ g_warning("Cannot create %s: file system access restricted "
+ "to %s due to capability mode", path, irclogs_path);
+ return;
+ }
+
+ copy = tofree = g_strdup(path + irclogs_path_len + 1);
+ fd = irclogs_fd;
+ for (;;) {
+ component = strsep(&copy, "/");
+ if (component == NULL)
+ break;
+ error = mkdirat(fd, component, mode);
+ if (error != 0 && errno != EEXIST) {
+ g_warning("cannot create %s: %s",
+ component, strerror(errno));
+ break;
+ }
+ newfd = openat(fd, component, O_DIRECTORY);
+ if (newfd < 0) {
+ g_warning("cannot open %s: %s",
+ component, strerror(errno));
+ break;
+ }
+ if (fd != irclogs_fd)
+ close(fd);
+ fd = newfd;
+ }
+ g_free(tofree);
+ if (fd != irclogs_fd)
+ close(fd);
+}
+
+void capsicum_mkdir_with_parents_wrapper(const char *path, int mode)
+{
+ if (capsicum_enabled()) {
+ capsicum_mkdir_with_parents(path, mode);
+ return;
+ }
+ g_mkdir_with_parents(path, mode);
+}
+
+nvlist_t *symbiont_connect(const nvlist_t *request)
+{
+ nvlist_t *response;
+ const IPADDR *ip, *my_ip;
+ int port, saved_errno, sock;
+
+ ip = nvlist_get_binary(request, "ip", NULL);
+ port = (int)nvlist_get_number(request, "port");
+ if (nvlist_exists(request, "my_ip"))
+ my_ip = nvlist_get_binary(request, "my_ip", NULL);
+ else
+ my_ip = NULL;
+
+ /*
+ * Check if the port is in allowed range. This is to minimize
+ * the chance of the attacker rooting another system in case of
+ * compromise.
+ */
+ if (port < port_min || port > port_max) {
+ sock = -1;
+ saved_errno = EPERM;
+ } else {
+ /* Connect. */
+ sock = net_connect_ip_handle(ip, port, my_ip);
+ saved_errno = errno;
+ }
+
+ /* Send back the socket fd. */
+ response = nvlist_create(0);
+
+ if (sock != -1)
+ nvlist_move_descriptor(response, "sock", sock);
+ nvlist_add_number(response, "errno", saved_errno);
+
+ return (response);
+}
+
+nvlist_t *symbiont_gethostbyname(const nvlist_t *request)
+{
+ nvlist_t *response;
+ IPADDR ip4, ip6;
+ const char *addr;
+ int ret, saved_errno;
+
+ addr = nvlist_get_string(request, "addr");
+
+ /* Connect. */
+ ret = net_gethostbyname(addr, &ip4, &ip6);
+ saved_errno = errno;
+
+ /* Send back the IPs. */
+ response = nvlist_create(0);
+
+ nvlist_add_number(response, "ret", ret);
+ nvlist_add_number(response, "errno", saved_errno);
+ nvlist_add_binary(response, "ip4", &ip4, sizeof(ip4));
+ nvlist_add_binary(response, "ip6", &ip6, sizeof(ip6));
+
+ return (response);
+}
+
+/*
+ * Child process, running outside the Capsicum sandbox.
+ */
+_Noreturn static void symbiont(void)
+{
+ nvlist_t *request, *response;
+ int error, opcode;
+
+ setproctitle("capsicum symbiont");
+ close(symbiontfds[1]);
+ close(0);
+ close(1);
+ close(2);
+
+ for (;;) {
+ /* Receive parameters from the main irssi process. */
+ request = nvlist_recv(symbiontfds[0], 0);
+ if (request == NULL)
+ exit(1);
+
+ opcode = nvlist_get_number(request, "opcode");
+ switch (opcode) {
+ case OPCODE_CONNECT:
+ response = symbiont_connect(request);
+ break;
+ case OPCODE_GETHOSTBYNAME:
+ response = symbiont_gethostbyname(request);
+ break;
+ default:
+ exit(1);
+ }
+
+ /* Send back the response. */
+ error = nvlist_send(symbiontfds[0], response);
+ if (error != 0)
+ exit(1);
+ nvlist_destroy(request);
+ nvlist_destroy(response);
+ }
+}
+
+static int start_symbiont(void)
+{
+ int childfd, error;
+ pid_t pid;
+
+ error = socketpair(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, symbiontfds);
+ if (error != 0) {
+ g_warning("socketpair: %s", strerror(errno));
+ return 1;
+ }
+
+ pid = pdfork(&childfd, PD_CLOEXEC);
+ if (pid < 0) {
+ g_warning("pdfork: %s", strerror(errno));
+ return 1;
+ }
+
+ if (pid > 0) {
+ close(symbiontfds[0]);
+ return 0;
+ }
+
+ symbiont();
+ /* NOTREACHED */
+}
+
+static void cmd_capsicum(const char *data, SERVER_REC *server, void *item)
+{
+ command_runsub("capsicum", data, server, item);
+}
+
+/*
+ * The main difference between this and caph_limit_stdio(3) is that this
+ * one permits TIOCSETAW, which is requred for restoring the terminal state
+ * on exit.
+ */
+static int
+limit_stdio_fd(int fd)
+{
+ cap_rights_t rights;
+ unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, TIOCSETAW, FIODTYPE };
+
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_FCNTL,
+ CAP_FSTAT, CAP_IOCTL, CAP_SEEK);
+
+ if (cap_rights_limit(fd, &rights) < 0) {
+ g_warning("cap_rights_limit(3) failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if (cap_ioctls_limit(fd, cmds, nitems(cmds)) < 0) {
+ g_warning("cap_ioctls_limit(3) failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ if (cap_fcntls_limit(fd, CAP_FCNTL_GETFL) < 0) {
+ g_warning("cap_fcntls_limit(3) failed: %s", strerror(errno));
+ return (-1);
+ }
+
+ return (0);
+}
+
+static void cmd_capsicum_enter(void)
+{
+ u_int mode;
+ gboolean inited;
+ int error;
+
+ error = cap_getmode(&mode);
+ if (error == 0 && mode != 0) {
+ g_warning("Already in capability mode");
+ return;
+ }
+
+ inited = irssi_ssl_init();
+ if (!inited) {
+ signal_emit("capability mode failed", 1, strerror(errno));
+ return;
+ }
+
+ port_min = settings_get_int("capsicum_port_min");
+ port_max = settings_get_int("capsicum_port_max");
+
+ irclogs_path = convert_home(settings_get_str("capsicum_irclogs_path"));
+ irclogs_path_len = strlen(irclogs_path);
+
+ /* Strip trailing slashes, if any. */
+ while (irclogs_path_len > 0 && irclogs_path[irclogs_path_len - 1] == '/') {
+ irclogs_path[irclogs_path_len - 1] = '\0';
+ irclogs_path_len--;
+ }
+
+ g_mkdir_with_parents(irclogs_path, log_dir_create_mode);
+ irclogs_fd = open(irclogs_path, O_DIRECTORY | O_CLOEXEC);
+ if (irclogs_fd < 0) {
+ g_warning("Unable to open %s: %s", irclogs_path, strerror(errno));
+ signal_emit("capability mode failed", 1, strerror(errno));
+ return;
+ }
+
+ error = start_symbiont();
+ if (error != 0) {
+ signal_emit("capability mode failed", 1, strerror(errno));
+ return;
+ }
+
+ /*
+ * XXX: We should use pdwait(2) to wait for children. Unfortunately
+ * it's not implemented yet. Thus the workaround, to get rid
+ * of the zombies at least.
+ */
+ signal(SIGCHLD, SIG_IGN);
+
+ if (limit_stdio_fd(STDIN_FILENO) != 0 ||
+ limit_stdio_fd(STDOUT_FILENO) != 0 ||
+ limit_stdio_fd(STDERR_FILENO) != 0) {
+ signal_emit("capability mode failed", 1, strerror(errno));
+ return;
+ }
+
+ error = cap_enter();
+ if (error != 0) {
+ signal_emit("capability mode failed", 1, strerror(errno));
+ } else {
+ signal_emit("capability mode enabled", 0);
+ }
+}
+
+static void cmd_capsicum_status(void)
+{
+ u_int mode;
+ int error;
+
+ error = cap_getmode(&mode);
+ if (error != 0) {
+ signal_emit("capability mode failed", 1, strerror(errno));
+ } else if (mode == 0) {
+ signal_emit("capability mode disabled", 0);
+ } else {
+ signal_emit("capability mode enabled", 0);
+ }
+}
+
+void sig_init_finished(void)
+{
+ if (settings_get_bool("capsicum"))
+ cmd_capsicum_enter();
+}
+
+void capsicum_init(void)
+{
+ settings_add_bool("misc", "capsicum", FALSE);
+ settings_add_str("misc", "capsicum_irclogs_path", "~/irclogs");
+ settings_add_int("misc", "capsicum_port_min", 6667);
+ settings_add_int("misc", "capsicum_port_max", 9999);
+
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+
+ command_bind("capsicum", NULL, (SIGNAL_FUNC) cmd_capsicum);
+ command_bind("capsicum enter", NULL, (SIGNAL_FUNC) cmd_capsicum_enter);
+ command_bind("capsicum status", NULL, (SIGNAL_FUNC) cmd_capsicum_status);
+}
+
+void capsicum_deinit(void)
+{
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+
+ command_unbind("capsicum", (SIGNAL_FUNC) cmd_capsicum);
+ command_unbind("capsicum enter", (SIGNAL_FUNC) cmd_capsicum_enter);
+ command_unbind("capsicum status", (SIGNAL_FUNC) cmd_capsicum_status);
+}
diff --git a/src/core/capsicum.h b/src/core/capsicum.h
new file mode 100644
index 0000000..ab980a4
--- /dev/null
+++ b/src/core/capsicum.h
@@ -0,0 +1,15 @@
+#ifndef IRSSI_CORE_CAPSICUM_H
+#define IRSSI_CORE_CAPSICUM_H
+
+gboolean capsicum_enabled(void);
+int capsicum_net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+int capsicum_net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6);
+int capsicum_open(const char *path, int flags, int mode);
+int capsicum_open_wrapper(const char *path, int flags, int mode);
+void capsicum_mkdir_with_parents(const char *path, int mode);
+void capsicum_mkdir_with_parents_wrapper(const char *path, int mode);
+
+void capsicum_init(void);
+void capsicum_deinit(void);
+
+#endif /* !IRSSI_CORE_CAPSICUM_H */
diff --git a/src/core/channel-rec.h b/src/core/channel-rec.h
new file mode 100644
index 0000000..f1e30fa
--- /dev/null
+++ b/src/core/channel-rec.h
@@ -0,0 +1,31 @@
+/* CHANNEL_REC definition, used for inheritance */
+
+#include <irssi/src/core/window-item-rec.h>
+
+char *topic;
+char *topic_by;
+time_t topic_time;
+
+GHashTable *nicks; /* list of nicks */
+NICK_REC *ownnick; /* our own nick */
+
+unsigned int no_modes:1; /* channel doesn't support modes */
+char *mode;
+int limit; /* user limit */
+char *key; /* password key */
+
+unsigned int chanop:1; /* You're a channel operator */
+unsigned int names_got:1; /* Received /NAMES list */
+unsigned int wholist:1; /* WHO list got */
+unsigned int synced:1; /* Channel synced - all queries done */
+
+unsigned int joined:1; /* Have we even received JOIN event for this channel? */
+unsigned int left:1; /* You just left the channel */
+unsigned int kicked:1; /* You just got kicked */
+unsigned int session_rejoin:1; /* This channel was joined with /UPGRADE */
+unsigned int destroying:1;
+
+/* Return the information needed to call SERVER_REC->channels_join() for
+ this channel. Usually just the channel name, but may contain also the
+ channel key. */
+char *(*get_join_data)(CHANNEL_REC *channel);
diff --git a/src/core/channel-setup-rec.h b/src/core/channel-setup-rec.h
new file mode 100644
index 0000000..a0b2897
--- /dev/null
+++ b/src/core/channel-setup-rec.h
@@ -0,0 +1,12 @@
+int type; /* module_get_uniq_id("CHANNEL SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+char *chatnet;
+char *password;
+
+char *botmasks;
+char *autosendcmd;
+
+unsigned int autojoin:1;
+GHashTable *module_data;
diff --git a/src/core/channels-setup.c b/src/core/channels-setup.c
new file mode 100644
index 0000000..c72f057
--- /dev/null
+++ b/src/core/channels-setup.c
@@ -0,0 +1,241 @@
+/*
+ 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/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels-setup.h>
+
+GSList *setupchannels;
+
+static int compare_channel_setup (CONFIG_NODE *node, CHANNEL_SETUP_REC *channel)
+{
+ char *name, *chatnet;
+
+ /* skip comment nodes */
+ if (node->type == NODE_TYPE_COMMENT)
+ return -1;
+
+ name = config_node_get_str(node, "name", NULL);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+
+ if (name == NULL || chatnet == NULL) {
+ return 0;
+ }
+
+ if (g_ascii_strcasecmp(name, channel->name) != 0 ||
+ g_ascii_strcasecmp(chatnet, channel->chatnet) != 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void channel_setup_save(CHANNEL_SETUP_REC *channel)
+{
+ CONFIG_NODE *parent_node, *node;
+ GSList *config_node;
+
+ parent_node = iconfig_node_traverse("(channels", TRUE);
+
+ /* Try to find this channel in the configuration */
+ config_node = g_slist_find_custom(parent_node->value, channel,
+ (GCompareFunc)compare_channel_setup);
+ if (config_node != NULL)
+ /* Let's update this channel record */
+ node = config_node->data;
+ else
+ /* Create a brand-new channel record */
+ node = iconfig_node_section(parent_node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_clear(node);
+ iconfig_node_set_str(node, "name", channel->name);
+ iconfig_node_set_str(node, "chatnet", channel->chatnet);
+ if (channel->autojoin)
+ iconfig_node_set_bool(node, "autojoin", TRUE);
+ iconfig_node_set_str(node, "password", channel->password);
+ iconfig_node_set_str(node, "botmasks", channel->botmasks);
+ iconfig_node_set_str(node, "autosendcmd", channel->autosendcmd);
+}
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel)
+{
+ channel->type = module_get_uniq_id("CHANNEL SETUP", 0);
+
+ if (g_slist_find(setupchannels, channel) == NULL)
+ setupchannels = g_slist_append(setupchannels, channel);
+ channel_setup_save(channel);
+
+ signal_emit("channel setup created", 1, channel);
+}
+
+static void channel_config_remove(CHANNEL_SETUP_REC *channel)
+{
+ CONFIG_NODE *parent_node;
+ GSList *config_node;
+
+ parent_node = iconfig_node_traverse("channels", FALSE);
+
+ if (parent_node == NULL)
+ return;
+
+ /* Try to find this channel in the configuration */
+ config_node = g_slist_find_custom(parent_node->value, channel,
+ (GCompareFunc)compare_channel_setup);
+
+ if (config_node != NULL)
+ /* Delete the channel from the configuration */
+ iconfig_node_remove(parent_node, config_node->data);
+}
+
+static void channel_setup_destroy(CHANNEL_SETUP_REC *channel)
+{
+ g_return_if_fail(channel != NULL);
+
+ setupchannels = g_slist_remove(setupchannels, channel);
+ signal_emit("channel setup destroyed", 1, channel);
+
+ g_free_not_null(channel->chatnet);
+ g_free_not_null(channel->password);
+ g_free_not_null(channel->botmasks);
+ g_free_not_null(channel->autosendcmd);
+ g_free(channel->name);
+ g_free(channel);
+}
+
+void channel_setup_remove_chatnet(const char *chatnet)
+{
+ GSList *tmp, *next;
+
+ g_return_if_fail(chatnet != NULL);
+
+ for (tmp = setupchannels; tmp != NULL; tmp = next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (g_ascii_strcasecmp(rec->chatnet, chatnet) == 0)
+ channel_setup_remove(rec);
+ }
+}
+
+void channel_setup_remove(CHANNEL_SETUP_REC *channel)
+{
+ channel_config_remove(channel);
+ channel_setup_destroy(channel);
+}
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+ const char *chatnet)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(channel != NULL, NULL);
+
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, channel) == 0 &&
+ channel_chatnet_match(rec->chatnet, chatnet))
+ return rec;
+ }
+
+ return NULL;
+}
+
+static CHANNEL_SETUP_REC *channel_setup_read(CONFIG_NODE *node)
+{
+ CHANNEL_SETUP_REC *rec;
+ CHATNET_REC *chatnetrec;
+ char *channel, *chatnet;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ channel = config_node_get_str(node, "name", NULL);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+
+ chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+ if (channel == NULL || chatnetrec == NULL) {
+ /* missing information.. */
+ return NULL;
+ }
+
+ rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup();
+ rec->type = module_get_uniq_id("CHANNEL SETUP", 0);
+ rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+ rec->autojoin = config_node_get_bool(node, "autojoin", FALSE);
+ rec->name = g_strdup(channel);
+ rec->chatnet = g_strdup(chatnetrec != NULL ? chatnetrec->name : chatnet);
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+ rec->botmasks = g_strdup(config_node_get_str(node, "botmasks", NULL));
+ rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+ setupchannels = g_slist_append(setupchannels, rec);
+ signal_emit("channel setup created", 2, rec, node);
+ return rec;
+}
+
+static void channels_read_config(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupchannels != NULL)
+ channel_setup_destroy(setupchannels->data);
+
+ /* Read channels */
+ node = iconfig_node_traverse("channels", FALSE);
+ if (node != NULL) {
+ int i = 0;
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp), i++) {
+ node = tmp->data;
+ if (node->type != NODE_TYPE_BLOCK) {
+ g_critical("Expected block node at `channels[%d]' was of %s type. "
+ "Corrupt config?",
+ i, node->type == NODE_TYPE_LIST ? "list" : "scalar");
+ } else {
+ channel_setup_read(node);
+ }
+ }
+ }
+}
+
+void channels_setup_init(void)
+{
+ setupchannels = NULL;
+ source_host_ok = FALSE;
+
+ signal_add("setup reread", (SIGNAL_FUNC) channels_read_config);
+ signal_add("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
+
+void channels_setup_deinit(void)
+{
+ while (setupchannels != NULL)
+ channel_setup_destroy(setupchannels->data);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) channels_read_config);
+}
diff --git a/src/core/channels-setup.h b/src/core/channels-setup.h
new file mode 100644
index 0000000..c0297ae
--- /dev/null
+++ b/src/core/channels-setup.h
@@ -0,0 +1,34 @@
+#ifndef IRSSI_CORE_CHANNELS_SETUP_H
+#define IRSSI_CORE_CHANNELS_SETUP_H
+
+#include <irssi/src/core/modules.h>
+
+#define CHANNEL_SETUP(server) \
+ MODULE_CHECK_CAST(server, CHANNEL_SETUP_REC, type, "CHANNEL SETUP")
+
+#define IS_CHANNEL_SETUP(server) \
+ (CHANNEL_SETUP(server) ? TRUE : FALSE)
+
+struct _CHANNEL_SETUP_REC {
+#include <irssi/src/core/channel-setup-rec.h>
+};
+
+extern GSList *setupchannels;
+
+void channels_setup_init(void);
+void channels_setup_deinit(void);
+
+void channel_setup_create(CHANNEL_SETUP_REC *channel);
+void channel_setup_remove(CHANNEL_SETUP_REC *channel);
+
+/* Remove channels attached to chatnet */
+void channel_setup_remove_chatnet(const char *chatnet);
+
+CHANNEL_SETUP_REC *channel_setup_find(const char *channel,
+ const char *chatnet);
+
+#define channel_chatnet_match(rec, chatnet) \
+ ((rec) == NULL || (rec)[0] == '\0' || \
+ ((chatnet) != NULL && g_ascii_strcasecmp(rec, chatnet) == 0))
+
+#endif
diff --git a/src/core/channels.c b/src/core/channels.c
new file mode 100644
index 0000000..68209a7
--- /dev/null
+++ b/src/core/channels.c
@@ -0,0 +1,308 @@
+/*
+ channel.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/special-vars.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/channels-setup.h>
+#include <irssi/src/core/nicklist.h>
+
+GSList *channels; /* List of all channels */
+
+static char *get_join_data(CHANNEL_REC *channel)
+{
+ return g_strdup(channel->name);
+}
+
+static const char *channel_get_target(WI_ITEM_REC *item)
+{
+ return ((CHANNEL_REC *) item)->name;
+}
+
+void channel_init(CHANNEL_REC *channel, SERVER_REC *server, const char *name,
+ const char *visible_name, int automatic)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(name != NULL);
+ g_return_if_fail(server != NULL);
+
+ if (visible_name == NULL)
+ visible_name = name;
+
+ MODULE_DATA_INIT(channel);
+ channel->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL");
+ channel->destroy = (void (*) (WI_ITEM_REC *)) channel_destroy;
+ channel->get_target = channel_get_target;
+ channel->get_join_data = get_join_data;
+
+ channel->chat_type = server->chat_type;
+ channel->server = server;
+ channel->name = g_strdup(name);
+ channel->visible_name = g_strdup(visible_name);
+ channel->mode = g_strdup("");
+ channel->createtime = time(NULL);
+
+ channels = g_slist_append(channels, channel);
+ server->channels = g_slist_append(server->channels, channel);
+
+ signal_emit("channel created", 2, channel, GINT_TO_POINTER(automatic));
+}
+
+void channel_destroy(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ if (channel->destroying) return;
+ channel->destroying = TRUE;
+
+ channels = g_slist_remove(channels, channel);
+ channel->server->channels =
+ g_slist_remove(channel->server->channels, channel);
+
+ signal_emit("channel destroyed", 1, channel);
+
+ MODULE_DATA_DEINIT(channel);
+ g_free_not_null(channel->hilight_color);
+ g_free_not_null(channel->topic);
+ g_free_not_null(channel->topic_by);
+ g_free_not_null(channel->key);
+ g_free(channel->mode);
+ g_free(channel->name);
+ g_free(channel->visible_name);
+
+ channel->type = 0;
+ g_free(channel);
+}
+
+static CHANNEL_REC *channel_find_server(SERVER_REC *server,
+ const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ if (server->channel_find_func != NULL) {
+ /* use the server specific channel find function */
+ return server->channel_find_func(server, name);
+ }
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(name, rec->name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHANNEL_REC *channel_find(SERVER_REC *server, const char *name)
+{
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (server != NULL)
+ return channel_find_server(server, name);
+
+ /* find from any server */
+ return i_slist_foreach_find(servers, (FOREACH_FIND_FUNC) channel_find_server,
+ (void *) name);
+}
+
+void channel_change_name(CHANNEL_REC *channel, const char *name)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ g_free(channel->name);
+ channel->name = g_strdup(name);
+
+ signal_emit("channel name changed", 1, channel);
+}
+
+void channel_change_visible_name(CHANNEL_REC *channel, const char *name)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ g_free(channel->visible_name);
+ channel->visible_name = g_strdup(name);
+
+ signal_emit("window item name changed", 1, channel);
+}
+
+static CHANNEL_REC *channel_find_servers(GSList *servers, const char *name)
+{
+ return i_slist_foreach_find(servers, (FOREACH_FIND_FUNC) channel_find_server,
+ (void *) name);
+}
+
+static GSList *servers_find_chatnet_except(SERVER_REC *server)
+{
+ GSList *tmp, *list;
+
+ list = NULL;
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *rec = tmp->data;
+
+ if (server != rec && rec->connrec->chatnet != NULL &&
+ g_strcmp0(server->connrec->chatnet,
+ rec->connrec->chatnet) == 0) {
+ /* chatnets match */
+ list = g_slist_append(list, rec);
+ }
+ }
+
+ return list;
+}
+
+/* connected to server, autojoin to channels. */
+static void event_connected(SERVER_REC *server)
+{
+ GString *chans;
+ GSList *tmp, *chatnet_servers;
+
+ g_return_if_fail(SERVER(server));
+
+ if (server->connrec->reconnection ||
+ server->connrec->no_autojoin_channels)
+ return;
+
+ /* get list of servers in same chat network */
+ chatnet_servers = server->connrec->chatnet == NULL ? NULL:
+ servers_find_chatnet_except(server);
+
+ /* join to the channels marked with autojoin in setup */
+ chans = g_string_new(NULL);
+ for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) {
+ CHANNEL_SETUP_REC *rec = tmp->data;
+
+ if (!rec->autojoin ||
+ !channel_chatnet_match(rec->chatnet,
+ server->connrec->chatnet))
+ continue;
+
+ /* check that we haven't already joined this channel in
+ same chat network connection.. */
+ if (channel_find_servers(chatnet_servers, rec->name) == NULL)
+ g_string_append_printf(chans, "%s,", rec->name);
+ }
+ g_slist_free(chatnet_servers);
+
+ if (chans->len > 0) {
+ g_string_truncate(chans, chans->len-1);
+ server->channels_join(server, chans->str, TRUE);
+ }
+
+ g_string_free(chans, TRUE);
+}
+
+static int match_nick_flags(SERVER_REC *server, NICK_REC *nick, char flag)
+{
+ const char *flags = server->get_nick_flags(server);
+
+ return strchr(flags, flag) == NULL ||
+ (flag == flags[0] && nick->op) ||
+ (flag == flags[1] && (nick->voice || nick->halfop ||
+ nick->op)) ||
+ (flag == flags[2] && (nick->halfop || nick->op));
+}
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel)
+{
+ CHANNEL_SETUP_REC *rec;
+
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ if (channel->session_rejoin)
+ return;
+
+ rec = channel_setup_find(channel->name, channel->server->connrec->chatnet);
+ if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd)
+ return;
+
+ /* if the autosendcmd alone (with no -bots parameter) has been
+ * specified then send it right after joining the channel, when
+ * the WHO list hasn't been yet retrieved.
+ * Depending on the value of the 'channel_max_who_sync' option
+ * the WHO list might not be retrieved after the join event. */
+
+ if (rec->botmasks == NULL || !*rec->botmasks) {
+ /* just send the command. */
+ eval_special_string(rec->autosendcmd, "", channel->server, channel);
+ }
+}
+
+void channel_send_botcommands(CHANNEL_REC *channel)
+{
+ CHANNEL_SETUP_REC *rec;
+ NICK_REC *nick;
+ char **bots, **bot;
+
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ if (channel->session_rejoin)
+ return;
+
+ rec = channel_setup_find(channel->name, channel->server->connrec->chatnet);
+ if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd)
+ return;
+
+ /* this case has already been handled by channel_send_autocommands */
+ if (rec->botmasks == NULL || !*rec->botmasks)
+ return;
+
+ /* find first available bot.. */
+ bots = g_strsplit(rec->botmasks, " ", -1);
+ for (bot = bots; *bot != NULL; bot++) {
+ const char *botnick = *bot;
+
+ if (*botnick == '\0')
+ continue;
+
+ nick = nicklist_find_mask(channel,
+ channel->server->isnickflag(channel->server, *botnick) ?
+ botnick+1 : botnick);
+ if (nick != NULL &&
+ match_nick_flags(channel->server, nick, *botnick)) {
+ eval_special_string(rec->autosendcmd, nick->nick,
+ channel->server, channel);
+ break;
+ }
+ }
+ g_strfreev(bots);
+}
+
+void channels_init(void)
+{
+ channels_setup_init();
+
+ signal_add("event connected", (SIGNAL_FUNC) event_connected);
+}
+
+void channels_deinit(void)
+{
+ channels_setup_deinit();
+
+ signal_remove("event connected", (SIGNAL_FUNC) event_connected);
+}
diff --git a/src/core/channels.h b/src/core/channels.h
new file mode 100644
index 0000000..197d225
--- /dev/null
+++ b/src/core/channels.h
@@ -0,0 +1,39 @@
+#ifndef IRSSI_CORE_CHANNELS_H
+#define IRSSI_CORE_CHANNELS_H
+
+#include <irssi/src/core/modules.h>
+
+/* Returns CHANNEL_REC if it's channel, NULL if it isn't. */
+#define CHANNEL(channel) \
+ MODULE_CHECK_CAST_MODULE(channel, CHANNEL_REC, type, \
+ "WINDOW ITEM TYPE", "CHANNEL")
+
+#define IS_CHANNEL(channel) \
+ (CHANNEL(channel) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _CHANNEL_REC {
+#include <irssi/src/core/channel-rec.h>
+};
+
+extern GSList *channels;
+
+/* Create new channel record */
+void channel_init(CHANNEL_REC *channel, SERVER_REC *server, const char *name,
+ const char *visible_name, int automatic);
+void channel_destroy(CHANNEL_REC *channel);
+
+/* find channel by name, if `server' is NULL, search from all servers */
+CHANNEL_REC *channel_find(SERVER_REC *server, const char *name);
+
+void channel_change_name(CHANNEL_REC *channel, const char *name);
+void channel_change_visible_name(CHANNEL_REC *channel, const char *name);
+
+/* Send the auto send command to channel */
+void channel_send_autocommands(CHANNEL_REC *channel);
+void channel_send_botcommands(CHANNEL_REC *channel);
+
+void channels_init(void);
+void channels_deinit(void);
+
+#endif
diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c
new file mode 100644
index 0000000..faeec45
--- /dev/null
+++ b/src/core/chat-commands.c
@@ -0,0 +1,504 @@
+/*
+ chat-commands.c : irssi
+
+ 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/network.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/servers-reconnect.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/window-item-def.h>
+#include <irssi/src/core/rawlog.h>
+
+static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr,
+ char **rawlog_file)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_CONNECT_REC *conn;
+ GHashTable *optlist;
+ char *addr, *portstr, *password, *nick, *chatnet, *host;
+ void *free_arg;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
+ "connect", &optlist, &addr, &portstr,
+ &password, &nick))
+ return NULL;
+ if (plus_addr != NULL) *plus_addr = *addr == '+';
+ if (*addr == '+') addr++;
+ if (*addr == '\0') {
+ signal_emit("error command", 1,
+ GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ if (g_strcmp0(password, "-") == 0)
+ *password = '\0';
+
+ /* check if -<chatnet> option is used to specify chat protocol */
+ proto = chat_protocol_find_net(optlist);
+
+ /* connect to server */
+ chatnet = proto == NULL ? NULL :
+ g_hash_table_lookup(optlist, proto->chatnet);
+
+ if (chatnet == NULL)
+ chatnet = g_hash_table_lookup(optlist, "network");
+
+ conn = server_create_conn_opt(proto != NULL ? proto->id : -1, addr, atoi(portstr), chatnet,
+ password, nick, optlist);
+ if (conn == NULL) {
+ signal_emit("error command", 1,
+ GINT_TO_POINTER(CMDERR_NO_SERVER_DEFINED));
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ if (proto == NULL)
+ proto = chat_protocol_find_id(conn->chat_type);
+
+ if (proto->not_initialized) {
+ /* trying to use protocol that isn't yet initialized */
+ signal_emit("chat protocol unknown", 1, proto->name);
+ server_connect_unref(conn);
+ cmd_params_free(free_arg);
+ return NULL;
+ }
+
+ if (strchr(addr, '/') != NULL)
+ conn->unix_socket = TRUE;
+
+ /* TLS options are handled in server_create_conn_opt ... -> server_setup_fill_optlist */
+
+ *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog"));
+
+ host = g_hash_table_lookup(optlist, "host");
+ if (host != NULL && *host != '\0') {
+ IPADDR ip4, ip6;
+
+ if (net_gethostbyname(host, &ip4, &ip6) == 0)
+ server_connect_own_ip_save(conn, &ip4, &ip6);
+ }
+
+ cmd_params_free(free_arg);
+ return conn;
+}
+
+/* SYNTAX: CONNECT [-4 | -6] [-tls_cert <cert>] [-tls_pkey <pkey>] [-tls_pass <password>]
+ [-tls_verify] [-tls_cafile <cafile>] [-tls_capath <capath>]
+ [-tls_ciphers <list>] [-tls_pinned_cert <fingerprint>]
+ [-tls_pinned_pubkey <fingerprint>] [-!] [-noautosendcmd] [-tls | -notls]
+ [-nocap] [-starttls | -disallow_starttls] [-noproxy]
+ [-network <network>] [-host <hostname>] [-rawlog <file>]
+ <address>|<chatnet> [<port> [<password> [<nick>]]] */
+/* NOTE: -network replaces the old -ircnet flag. */
+static void cmd_connect(const char *data)
+{
+ SERVER_CONNECT_REC *conn;
+ SERVER_REC *server;
+ char *rawlog_file;
+
+ conn = get_server_connect(data, NULL, &rawlog_file);
+ if (conn != NULL) {
+ server = server_connect(conn);
+ server_connect_unref(conn);
+
+ if (server != NULL && rawlog_file != NULL)
+ rawlog_open(server->rawlog, rawlog_file);
+
+ g_free(rawlog_file);
+ }
+}
+
+static RECONNECT_REC *find_reconnect_server(int chat_type,
+ const char *addr, int port)
+{
+ RECONNECT_REC *match, *last_proto_match;
+ GSList *tmp;
+ int count;
+
+ g_return_val_if_fail(addr != NULL, NULL);
+
+ /* check if there's a reconnection to the same host and maybe even
+ the same port */
+ match = last_proto_match = NULL; count = 0;
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (rec->conn->chat_type == chat_type) {
+ count++; last_proto_match = rec;
+ if (g_ascii_strcasecmp(rec->conn->address, addr) == 0) {
+ if (rec->conn->port == port)
+ return rec;
+ match = rec;
+ }
+ }
+ }
+
+ if (count == 1) {
+ /* only one reconnection with wanted protocol,
+ we probably want to use it */
+ return last_proto_match;
+ }
+
+ return match;
+}
+
+static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *oldconn;
+ RECONNECT_REC *recon;
+
+ if (server != NULL) {
+ oldconn = server->connrec;
+ server_connect_ref(oldconn);
+ reconnect_save_status(conn, server);
+ } else {
+ /* maybe we can reconnect some server from
+ reconnection queue */
+ recon = find_reconnect_server(conn->chat_type,
+ conn->address, conn->port);
+ if (recon == NULL) return;
+
+ oldconn = recon->conn;
+ server_connect_ref(oldconn);
+ server_reconnect_destroy(recon);
+
+ conn->away_reason = g_strdup(oldconn->away_reason);
+ conn->channels = g_strdup(oldconn->channels);
+ }
+
+ conn->reconnection = TRUE;
+
+ if (conn->chatnet == NULL && oldconn->chatnet != NULL)
+ conn->chatnet = g_strdup(oldconn->chatnet);
+
+ server_connect_unref(oldconn);
+ if (server != NULL) {
+ signal_emit("command disconnect", 2,
+ "* Changing server", server);
+ }
+}
+
+static void cmd_server(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ command_runsub("server", data, server, item);
+}
+
+/* SYNTAX: SERVER CONNECT [-4 | -6] [-tls | -notls] [-tls_cert <cert>] [-tls_pkey <pkey>]
+ [-tls_pass <password>] [-tls_verify | -notls_verify] [-tls_cafile <cafile>]
+ [-tls_capath <capath>] [-tls_ciphers <list>]
+ [-tls_pinned_cert <fingerprint>] [-tls_pinned_pubkey <fingerprint>]
+ [-!] [-noautosendcmd] [-nocap]
+ [-noproxy] [-network <network>] [-host <hostname>]
+ [-rawlog <file>]
+ [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
+/* NOTE: -network replaces the old -ircnet flag. */
+static void cmd_server_connect(const char *data, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ char *rawlog_file;
+ int plus_addr;
+
+ g_return_if_fail(data != NULL);
+
+ /* create connection record */
+ conn = get_server_connect(data, &plus_addr, &rawlog_file);
+ if (conn != NULL) {
+ if (!plus_addr)
+ update_reconnection(conn, server);
+ server = server_connect(conn);
+ server_connect_unref(conn);
+
+ if (server != NULL && rawlog_file != NULL)
+ rawlog_open(server->rawlog, rawlog_file);
+
+ g_free(rawlog_file);
+ }
+}
+
+/* SYNTAX: DISCONNECT *|<tag> [<message>] */
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+ char *tag, *msg;
+ void *free_arg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
+ return;
+
+ if (*tag != '\0' && g_strcmp0(tag, "*") != 0) {
+ server = server_find_tag(tag);
+ if (server == NULL)
+ server = server_find_lookup_tag(tag);
+ }
+ if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
+ signal_emit("server quit", 2, server, msg);
+
+ cmd_params_free(free_arg);
+ server_disconnect(server);
+}
+
+/* SYNTAX: QUIT [<message>] */
+static void cmd_quit(const char *data)
+{
+ GSList *tmp, *next;
+ const char *quitmsg;
+ char *str;
+
+ g_return_if_fail(data != NULL);
+
+ quitmsg = *data != '\0' ? data :
+ settings_get_str("quit_message");
+
+ /* disconnect from every server */
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ next = tmp->next;
+
+ str = g_strdup_printf("* %s", quitmsg);
+ cmd_disconnect(str, tmp->data);
+ g_free(str);
+ }
+
+ signal_emit("gui exit", 0);
+}
+
+/* SYNTAX: MSG [-<server tag>] [-channel | -nick] *|<targets> <message> */
+static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
+{
+ GHashTable *optlist;
+ char *target, *origtarget, *msg;
+ void *free_arg;
+ int free_ret, target_type = SEND_TARGET_NICK;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
+ "msg", &optlist, &target, &msg))
+ return;
+ if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+
+ server = cmd_options_get_server("msg", optlist, server);
+ if (server == NULL || !server->connected)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+
+ origtarget = target;
+ free_ret = FALSE;
+ if (g_strcmp0(target, ",") == 0 || g_strcmp0(target, ".") == 0) {
+ target = parse_special(&target, server, item,
+ NULL, &free_ret, NULL, 0);
+ if (target != NULL && *target == '\0') {
+ if (free_ret)
+ g_free(target);
+ target = NULL;
+ free_ret = FALSE;
+ }
+ }
+
+ if (target != NULL) {
+ if (g_strcmp0(target, "*") == 0) {
+ /* send to active channel/query */
+ if (item == NULL)
+ cmd_param_error(CMDERR_NOT_JOINED);
+
+ target_type = IS_CHANNEL(item) ?
+ SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
+ target = (char *) window_item_get_target(item);
+ } else if (g_hash_table_lookup(optlist, "channel") != NULL)
+ target_type = SEND_TARGET_CHANNEL;
+ else if (g_hash_table_lookup(optlist, "nick") != NULL)
+ target_type = SEND_TARGET_NICK;
+ else {
+ /* Need to rely on server_ischannel(). If the protocol
+ doesn't really know if it's channel or nick based on
+ the name, it should just assume it's nick, because
+ when typing text to channels it's always sent with
+ /MSG -channel. */
+ target_type = server_ischannel(server, target) ?
+ SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
+ }
+ }
+ if (target != NULL) {
+ char **splitmsgs;
+ char **tmp = NULL;
+ char *singlemsg[] = { msg, NULL };
+ char *m;
+ int n = 0;
+
+ /*
+ * If split_message is NULL, the server doesn't need to split
+ * long messages.
+ */
+ if (server->split_message != NULL)
+ splitmsgs = tmp = server->split_message(server, target,
+ msg);
+ else
+ splitmsgs = singlemsg;
+
+ while ((m = splitmsgs[n++])) {
+ signal_emit("server sendmsg", 4, server, target, m,
+ GINT_TO_POINTER(target_type));
+ signal_emit(target_type == SEND_TARGET_CHANNEL ?
+ "message own_public" :
+ "message own_private", 4, server, m,
+ target, origtarget);
+ }
+ g_strfreev(tmp);
+ } else {
+ signal_emit("message own_private", 4, server, msg, target,
+ origtarget);
+ }
+
+ if (free_ret && target != NULL) g_free(target);
+ cmd_params_free(free_arg);
+}
+
+static void sig_server_sendmsg(SERVER_REC *server, const char *target,
+ const char *msg, void *target_type_p)
+{
+ server->send_message(server, target, msg,
+ GPOINTER_TO_INT(target_type_p));
+}
+
+static void cmd_foreach(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ command_runsub("foreach", data, server, item);
+}
+
+/* SYNTAX: FOREACH SERVER <command> */
+static void cmd_foreach_server(const char *data, SERVER_REC *server)
+{
+ GSList *list;
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+ list = g_slist_copy(servers);
+ while (list != NULL) {
+ signal_emit("send command", 3, str, list->data, NULL);
+ list = g_slist_remove(list, list->data);
+ }
+
+ g_free(str);
+}
+
+/* SYNTAX: FOREACH CHANNEL <command> */
+static void cmd_foreach_channel(const char *data)
+{
+ GSList *list;
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+ list = g_slist_copy(channels);
+ while (list != NULL) {
+ CHANNEL_REC *rec = list->data;
+
+ signal_emit("send command", 3, str, rec->server, rec);
+ list = g_slist_remove(list, list->data);
+ }
+
+ g_free(str);
+}
+
+/* SYNTAX: FOREACH QUERY <command> */
+static void cmd_foreach_query(const char *data)
+{
+ GSList *list;
+ const char *cmdchars;
+ char *str;
+
+ cmdchars = settings_get_str("cmdchars");
+ str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
+ g_strdup_printf("%c%s", *cmdchars, data);
+
+
+ list = g_slist_copy(queries);
+ while (list != NULL) {
+ QUERY_REC *rec = list->data;
+
+ signal_emit("send command", 3, str, rec->server, rec);
+ list = g_slist_remove(list, list->data);
+ }
+
+ g_free(str);
+}
+
+void chat_commands_init(void)
+{
+ settings_add_str("misc", "quit_message", "leaving");
+
+ command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
+ command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
+ command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
+ command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+ command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
+ command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
+ command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
+ command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
+ command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
+ command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
+
+ signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
+
+ command_set_options(
+ "connect",
+ "4 6 !! -network ~ssl ~+ssl_cert ~+ssl_pkey ~+ssl_pass ~ssl_verify ~+ssl_cafile "
+ "~+ssl_capath ~+ssl_ciphers ~+ssl_pinned_cert ~+ssl_pinned_pubkey tls notls +tls_cert "
+ "+tls_pkey +tls_pass tls_verify notls_verify +tls_cafile +tls_capath +tls_ciphers "
+ "+tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd");
+ command_set_options("msg", "channel nick");
+}
+
+void chat_commands_deinit(void)
+{
+ command_unbind("server", (SIGNAL_FUNC) cmd_server);
+ command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
+ command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+ command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
+ command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
+ command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
+ command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
+ command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
+ command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
+
+ signal_remove("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
+}
diff --git a/src/core/chat-protocols.c b/src/core/chat-protocols.c
new file mode 100644
index 0000000..c53b01b
--- /dev/null
+++ b/src/core/chat-protocols.c
@@ -0,0 +1,239 @@
+/*
+ chat-protocol.c : irssi
+
+ 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/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/chat-protocols.h>
+
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels-setup.h>
+
+GSList *chat_protocols;
+
+static CHAT_PROTOCOL_REC *default_proto;
+
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id)
+{
+ return object == NULL ||
+ chat_protocol_lookup(id) !=
+ G_STRUCT_MEMBER(int, object, type_pos) ? NULL : object;
+}
+
+/* Return the ID for the specified chat protocol. */
+int chat_protocol_lookup(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec;
+
+ g_return_val_if_fail(name != NULL, -1);
+
+ rec = chat_protocol_find(name);
+ return rec == NULL ? -1 : rec->id;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(id > 0, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (rec->id == id)
+ return rec;
+ }
+
+ return NULL;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(optlist != NULL, NULL);
+
+ for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) {
+ CHAT_PROTOCOL_REC *rec = tmp->data;
+
+ if (rec->chatnet != NULL &&
+ g_hash_table_lookup(optlist, rec->chatnet) != NULL)
+ return rec;
+ }
+
+ return NULL;
+}
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec)
+{
+ CHAT_PROTOCOL_REC *newrec;
+ int created;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ newrec = chat_protocol_find(rec->name);
+ created = newrec == NULL;
+ if (newrec == NULL) {
+ newrec = g_new0(CHAT_PROTOCOL_REC, 1);
+ chat_protocols = g_slist_append(chat_protocols, newrec);
+ } else {
+ /* updating existing protocol */
+ g_free(newrec->name);
+ }
+
+ memcpy(newrec, rec, sizeof(CHAT_PROTOCOL_REC));
+ newrec->id = module_get_uniq_id_str("PROTOCOL", rec->name);
+ newrec->name = g_strdup(rec->name);
+
+ if (default_proto == NULL)
+ chat_protocol_set_default(newrec);
+
+ if (created)
+ signal_emit("chat protocol created", 1, newrec);
+ else
+ signal_emit("chat protocol updated", 1, newrec);
+ return newrec;
+}
+
+static void chat_protocol_destroy(CHAT_PROTOCOL_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ chat_protocols = g_slist_remove(chat_protocols, rec);
+
+ if (default_proto == rec) {
+ chat_protocol_set_default(chat_protocols == NULL ? NULL :
+ chat_protocols->data);
+ }
+
+ signal_emit("chat protocol destroyed", 1, rec);
+
+ g_free(rec->name);
+ g_free(rec);
+}
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec;
+
+ g_return_if_fail(name != NULL);
+
+ rec = chat_protocol_find(name);
+ if (rec != NULL) {
+ chat_protocol_destroy(rec);
+
+ /* there might still be references to this chat protocol -
+ recreate it as a dummy protocol */
+ chat_protocol_get_unknown(name);
+ }
+}
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec)
+{
+ default_proto = rec;
+}
+
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void)
+{
+ return default_proto;
+}
+
+static CHATNET_REC *create_chatnet(void)
+{
+ return g_new0(CHATNET_REC, 1);
+}
+
+static SERVER_SETUP_REC *create_server_setup(void)
+{
+ return g_new0(SERVER_SETUP_REC, 1);
+}
+
+static CHANNEL_SETUP_REC *create_channel_setup(void)
+{
+ return g_new0(CHANNEL_SETUP_REC, 1);
+}
+
+static SERVER_CONNECT_REC *create_server_connect(void)
+{
+ return g_new0(SERVER_CONNECT_REC, 1);
+}
+
+static void destroy_server_connect(SERVER_CONNECT_REC *conn)
+{
+}
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+ specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name)
+{
+ CHAT_PROTOCOL_REC *rec, *newrec;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = chat_protocol_find(name);
+ if (rec != NULL)
+ return rec;
+
+ rec = g_new0(CHAT_PROTOCOL_REC, 1);
+ rec->not_initialized = TRUE;
+ rec->name = (char *) name;
+ 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;
+
+ newrec = chat_protocol_register(rec);
+ g_free(rec);
+ return newrec;
+}
+
+void chat_protocols_init(void)
+{
+ default_proto = NULL;
+ chat_protocols = NULL;
+}
+
+void chat_protocols_deinit(void)
+{
+ while (chat_protocols != NULL)
+ chat_protocol_destroy(chat_protocols->data);
+}
diff --git a/src/core/chat-protocols.h b/src/core/chat-protocols.h
new file mode 100644
index 0000000..ab7327b
--- /dev/null
+++ b/src/core/chat-protocols.h
@@ -0,0 +1,61 @@
+#ifndef IRSSI_CORE_CHAT_PROTOCOLS_H
+#define IRSSI_CORE_CHAT_PROTOCOLS_H
+
+struct _CHAT_PROTOCOL_REC {
+ int id;
+
+ unsigned int not_initialized:1;
+ unsigned int case_insensitive:1;
+
+ char *name;
+ char *fullname;
+ char *chatnet;
+
+ CHATNET_REC *(*create_chatnet) (void);
+ SERVER_SETUP_REC *(*create_server_setup) (void);
+ CHANNEL_SETUP_REC *(*create_channel_setup) (void);
+ SERVER_CONNECT_REC *(*create_server_connect) (void);
+ void (*destroy_server_connect) (SERVER_CONNECT_REC *);
+
+ SERVER_REC *(*server_init_connect) (SERVER_CONNECT_REC *);
+ void (*server_connect) (SERVER_REC *);
+ CHANNEL_REC *(*channel_create) (SERVER_REC *, const char *,
+ const char *, int);
+ QUERY_REC *(*query_create) (const char *, const char *, int);
+};
+
+extern GSList *chat_protocols;
+
+#define PROTO_CHECK_CAST(object, cast, type_field, id) \
+ ((cast *) chat_protocol_check_cast(object, \
+ offsetof(cast, type_field), id))
+void *chat_protocol_check_cast(void *object, int type_pos, const char *id);
+
+#define CHAT_PROTOCOL(object) \
+ ((object) == NULL ? chat_protocol_get_default() : \
+ chat_protocol_find_id((object)->chat_type))
+
+/* Register new chat protocol. */
+CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec);
+
+/* Unregister chat protocol. */
+void chat_protocol_unregister(const char *name);
+
+/* Find functions */
+int chat_protocol_lookup(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find(const char *name);
+CHAT_PROTOCOL_REC *chat_protocol_find_id(int id);
+CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist);
+
+/* Default chat protocol to use */
+void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec);
+CHAT_PROTOCOL_REC *chat_protocol_get_default(void);
+
+/* Return "unknown chat protocol" record. Used when protocol name is
+ specified but it isn't registered yet. */
+CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name);
+
+void chat_protocols_init(void);
+void chat_protocols_deinit(void);
+
+#endif
diff --git a/src/core/chatnet-rec.h b/src/core/chatnet-rec.h
new file mode 100644
index 0000000..e3ed8aa
--- /dev/null
+++ b/src/core/chatnet-rec.h
@@ -0,0 +1,12 @@
+int type; /* module_get_uniq_id("CHATNET", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *name;
+
+char *nick;
+char *username;
+char *realname;
+
+char *own_host; /* address to use when connecting this server */
+char *autosendcmd; /* command to send after connecting to this ircnet */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
diff --git a/src/core/chatnets.c b/src/core/chatnets.c
new file mode 100644
index 0000000..2d49a6d
--- /dev/null
+++ b/src/core/chatnets.c
@@ -0,0 +1,194 @@
+/*
+ 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/network.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+
+GSList *chatnets; /* list of available chat networks */
+
+static void chatnet_config_save(CHATNET_REC *chatnet)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("chatnets", TRUE);
+ node = iconfig_node_section(node, chatnet->name, NODE_TYPE_BLOCK);
+ iconfig_node_clear(node);
+
+ iconfig_node_set_str(node, "type", chat_protocol_find_id(chatnet->chat_type)->name);
+ iconfig_node_set_str(node, "nick", chatnet->nick);
+ iconfig_node_set_str(node, "username", chatnet->username);
+ iconfig_node_set_str(node, "realname", chatnet->realname);
+ iconfig_node_set_str(node, "host", chatnet->own_host);
+ iconfig_node_set_str(node, "autosendcmd", chatnet->autosendcmd);
+
+ signal_emit("chatnet saved", 2, chatnet, node);
+}
+
+static void chatnet_config_remove(CHATNET_REC *chatnet)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("chatnets", FALSE);
+ if (node != NULL) iconfig_node_set_str(node, chatnet->name, NULL);
+}
+
+void chatnet_create(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(chatnet != NULL);
+
+ chatnet->type = module_get_uniq_id("CHATNET", 0);
+ if (g_slist_find(chatnets, chatnet) == NULL)
+ chatnets = g_slist_append(chatnets, chatnet);
+
+ chatnet_config_save(chatnet);
+ signal_emit("chatnet created", 1, chatnet);
+}
+
+void chatnet_remove(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ signal_emit("chatnet removed", 1, chatnet);
+
+ chatnet_config_remove(chatnet);
+ chatnet_destroy(chatnet);
+}
+
+void chatnet_destroy(CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ chatnets = g_slist_remove(chatnets, chatnet);
+ signal_emit("chatnet destroyed", 1, chatnet);
+
+ g_free_not_null(chatnet->nick);
+ g_free_not_null(chatnet->username);
+ g_free_not_null(chatnet->realname);
+ g_free_not_null(chatnet->own_host);
+ g_free_not_null(chatnet->autosendcmd);
+ g_free(chatnet->name);
+ g_free(chatnet);
+}
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ for (tmp = chatnets; tmp != NULL; tmp = tmp->next) {
+ CHATNET_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+ CHATNET_REC *rec;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ if (server->connrec->chatnet == NULL || server->session_reconnect)
+ return;
+
+ rec = chatnet_find(server->connrec->chatnet);
+ if (!server->connrec->no_autosendcmd && rec != NULL && rec->autosendcmd)
+ eval_special_string(rec->autosendcmd, "", server, NULL);
+}
+
+static void chatnet_read(CONFIG_NODE *node)
+{
+ CHAT_PROTOCOL_REC *proto;
+ CHATNET_REC *rec;
+ char *type;
+
+ if (node == NULL || node->key == NULL || !is_node_list(node))
+ return;
+
+ type = config_node_get_str(node, "type", NULL);
+ proto = type == NULL ? NULL : chat_protocol_find(type);
+ if (proto == NULL) {
+ proto = type == NULL ? chat_protocol_get_default() :
+ chat_protocol_get_unknown(type);
+ }
+
+ if (type == NULL)
+ iconfig_node_set_str(node, "type", proto->name);
+
+ rec = proto->create_chatnet();
+ rec->type = module_get_uniq_id("CHATNET", 0);
+ rec->chat_type = proto->id;
+ rec->name = g_strdup(node->key);
+ rec->nick = g_strdup(config_node_get_str(node, "nick", NULL));
+ rec->username = g_strdup(config_node_get_str(node, "username", NULL));
+ rec->realname = g_strdup(config_node_get_str(node, "realname", NULL));
+ rec->own_host = g_strdup(config_node_get_str(node, "host", NULL));
+ rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL));
+
+ chatnets = g_slist_append(chatnets, rec);
+ signal_emit("chatnet read", 2, rec, node);
+}
+
+static void read_chatnets(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (chatnets != NULL)
+ chatnet_destroy(chatnets->data);
+
+ node = iconfig_node_traverse("chatnets", FALSE);
+ if (node != NULL) {
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp))
+ chatnet_read(tmp->data);
+ }
+}
+
+void chatnets_init(void)
+{
+ chatnets = NULL;
+
+ signal_add_first("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("setup reread", (SIGNAL_FUNC) read_chatnets);
+ signal_add_first("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
+
+void chatnets_deinit(void)
+{
+ module_uniq_destroy("CHATNET");
+
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_chatnets);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_chatnets);
+}
diff --git a/src/core/chatnets.h b/src/core/chatnets.h
new file mode 100644
index 0000000..6f36fe3
--- /dev/null
+++ b/src/core/chatnets.h
@@ -0,0 +1,32 @@
+#ifndef IRSSI_CORE_CHATNETS_H
+#define IRSSI_CORE_CHATNETS_H
+
+#include <irssi/src/core/modules.h>
+
+/* Returns CHATNET_REC if it's chatnet, NULL if it isn't. */
+#define CHATNET(chatnet) \
+ MODULE_CHECK_CAST(chatnet, CHATNET_REC, type, "CHATNET")
+
+#define IS_CHATNET(chatnet) \
+ (CHATNET(chatnet) ? TRUE : FALSE)
+
+struct _CHATNET_REC {
+#include <irssi/src/core/chatnet-rec.h>
+};
+
+extern GSList *chatnets; /* list of available chat networks */
+
+/* add the chatnet to chat networks list */
+void chatnet_create(CHATNET_REC *chatnet);
+/* remove the chatnet from chat networks list */
+void chatnet_remove(CHATNET_REC *chatnet);
+/* destroy the chatnet structure. doesn't remove from config file */
+void chatnet_destroy(CHATNET_REC *chatnet);
+
+/* Find the chat network by name */
+CHATNET_REC *chatnet_find(const char *name);
+
+void chatnets_init(void);
+void chatnets_deinit(void);
+
+#endif
diff --git a/src/core/commands.c b/src/core/commands.c
new file mode 100644
index 0000000..deb5fe9
--- /dev/null
+++ b/src/core/commands.c
@@ -0,0 +1,1021 @@
+/*
+ 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/special-vars.h>
+#include <irssi/src/core/window-item-def.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+GSList *commands;
+char *current_command;
+
+static int signal_default_command;
+
+static GSList *alias_runstack;
+
+COMMAND_REC *command_find(const char *cmd)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->cmd, cmd) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec,
+ const char *module)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(module != NULL, NULL);
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, module) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static COMMAND_MODULE_REC *
+command_module_find_and_remove(COMMAND_REC *rec, SIGNAL_FUNC func)
+{
+ GSList *tmp, *tmp2;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(func != NULL, NULL);
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ for (tmp2 = rec->callbacks; tmp2 != NULL; tmp2 = tmp2->next) {
+ COMMAND_CALLBACK_REC *cb = tmp2->data;
+
+ if (cb->func == func) {
+ rec->callbacks =
+ g_slist_remove(rec->callbacks, cb);
+ g_free(cb);
+ return rec;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+int command_have_sub(const char *command)
+{
+ GSList *tmp;
+ int len;
+
+ g_return_val_if_fail(command != NULL, FALSE);
+
+ /* find "command "s */
+ len = strlen(command);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->cmd, command, len) == 0 &&
+ rec->cmd[len] == ' ')
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static COMMAND_MODULE_REC *
+command_module_get(COMMAND_REC *rec, const char *module, int protocol)
+{
+ COMMAND_MODULE_REC *modrec;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ modrec = command_module_find(rec, module);
+ if (modrec == NULL) {
+ modrec = g_new0(COMMAND_MODULE_REC, 1);
+ modrec->name = g_strdup(module);
+ modrec->protocol = -1;
+ rec->modules = g_slist_append(rec->modules, modrec);
+ }
+
+ if (protocol != -1)
+ modrec->protocol = protocol;
+
+ return modrec;
+}
+
+void command_bind_full(const char *module, int priority, const char *cmd,
+ int protocol, const char *category, SIGNAL_FUNC func,
+ void *user_data)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ COMMAND_CALLBACK_REC *cb;
+ char *str;
+
+ g_return_if_fail(module != NULL);
+ g_return_if_fail(cmd != NULL);
+
+ rec = command_find(cmd);
+ if (rec == NULL) {
+ rec = g_new0(COMMAND_REC, 1);
+ rec->cmd = g_strdup(cmd);
+ rec->category = category == NULL ? NULL : g_strdup(category);
+ commands = g_slist_append(commands, rec);
+ }
+ modrec = command_module_get(rec, module, protocol);
+
+ cb = g_new0(COMMAND_CALLBACK_REC, 1);
+ cb->func = func;
+ cb->user_data = user_data;
+ modrec->callbacks = g_slist_append(modrec->callbacks, cb);
+
+ if (func != NULL) {
+ str = g_strconcat("command ", cmd, NULL);
+ signal_add_full(module, priority, str, func, user_data);
+ g_free(str);
+ }
+
+ signal_emit("commandlist new", 1, rec);
+}
+
+static void command_free(COMMAND_REC *rec)
+{
+ commands = g_slist_remove(commands, rec);
+ signal_emit("commandlist remove", 1, rec);
+
+ g_free_not_null(rec->category);
+ g_strfreev(rec->options);
+ g_free(rec->cmd);
+ g_free(rec);
+}
+
+static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec)
+{
+ rec->modules = g_slist_remove(rec->modules, modrec);
+
+ g_slist_foreach(modrec->callbacks, (GFunc) g_free, NULL);
+ g_slist_free(modrec->callbacks);
+ g_free(modrec->name);
+ g_free_not_null(modrec->options);
+ g_free(modrec);
+}
+
+static void command_module_destroy(COMMAND_REC *rec,
+ COMMAND_MODULE_REC *modrec)
+{
+ GSList *tmp, *freelist;
+
+ command_module_free(modrec, rec);
+
+ /* command_set_options() might have added module declaration of it's
+ own without any signals .. check if they're the only ones left
+ and if so, destroy them. */
+ freelist = NULL;
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (rec->callbacks == NULL)
+ freelist = g_slist_append(freelist, rec);
+ else {
+ g_slist_free(freelist);
+ freelist = NULL;
+ break;
+ }
+ }
+
+ g_slist_foreach(freelist, (GFunc) command_module_free, rec);
+ g_slist_free(freelist);
+
+ if (rec->modules == NULL)
+ command_free(rec);
+}
+
+void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ char *str;
+
+ g_return_if_fail(cmd != NULL);
+ g_return_if_fail(func != NULL);
+
+ rec = command_find(cmd);
+ if (rec != NULL) {
+ modrec = command_module_find_and_remove(rec, func);
+ g_return_if_fail(modrec != NULL);
+
+ if (modrec->callbacks == NULL)
+ command_module_destroy(rec, modrec);
+ }
+
+ str = g_strconcat("command ", cmd, NULL);
+ signal_remove_data(str, func, user_data);
+ g_free(str);
+}
+
+/* Expand `cmd' - returns `cmd' if not found, NULL if more than one
+ match is found */
+static const char *command_expand(char *cmd)
+{
+ GSList *tmp;
+ const char *match;
+ int len, multiple;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+
+ multiple = FALSE;
+ match = NULL;
+ len = strlen(cmd);
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_ascii_strncasecmp(rec->cmd, cmd, len) == 0 &&
+ strchr(rec->cmd+len, ' ') == NULL) {
+ if (rec->cmd[len] == '\0') {
+ /* full match */
+ return rec->cmd;
+ }
+
+ if (match != NULL) {
+ /* multiple matches, we still need to check
+ if there's some command left that is a
+ full match.. */
+ multiple = TRUE;
+ }
+
+ /* check that this is the only match */
+ match = rec->cmd;
+ }
+ }
+
+ if (multiple) {
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd);
+ return NULL;
+ }
+
+ return match != NULL ? match : cmd;
+}
+
+void command_runsub(const char *cmd, const char *data,
+ void *server, void *item)
+{
+ const char *newcmd;
+ char *orig, *subcmd, *defcmd, *args;
+
+ g_return_if_fail(data != NULL);
+
+ while (*data == ' ') data++;
+
+ if (*data == '\0') {
+ /* no subcommand given - list the subcommands */
+ signal_emit("list subcommands", 1, cmd);
+ return;
+ }
+
+ /* get command.. */
+ orig = subcmd = g_strdup_printf("command %s %s", cmd, data);
+ args = strchr(subcmd+8 + strlen(cmd)+1, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+ while (*args == ' ') args++;
+
+ /* check if this command can be expanded */
+ newcmd = command_expand(subcmd+8);
+ if (newcmd == NULL) {
+ /* ambiguous command */
+ g_free(orig);
+ return;
+ }
+
+ subcmd = g_strconcat("command ", newcmd, NULL);
+
+ ascii_strdown(subcmd);
+ if (!signal_emit(subcmd, 3, args, server, item)) {
+ defcmd = g_strdup_printf("default command %s", cmd);
+ if (!signal_emit(defcmd, 3, data, server, item)) {
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8);
+ }
+ g_free(defcmd);
+ }
+
+ g_free(subcmd);
+ g_free(orig);
+}
+
+static char *optname(char *option)
+{
+ char *opt = option;
+ if (*opt == '~')
+ opt++;
+ if (iscmdtype(*opt))
+ opt++;
+ return opt;
+}
+
+static gboolean optflag(char *option, char *flag)
+{
+ if (*option == '~')
+ return optflag(option + 1, flag);
+
+ return (strchr(flag, *option) != NULL) || (!iscmdtype(*option) && strchr(flag, ' '));
+}
+
+static GSList *optlist_find(GSList *optlist, const char *option)
+{
+ while (optlist != NULL) {
+ char *name = optname(optlist->data);
+
+ if (g_ascii_strcasecmp(name, option) == 0)
+ return optlist;
+
+ optlist = optlist->next;
+ }
+
+ return NULL;
+}
+
+int command_have_option(const char *cmd, const char *option)
+{
+ COMMAND_REC *rec;
+ char **tmp;
+
+ g_return_val_if_fail(cmd != NULL, FALSE);
+ g_return_val_if_fail(option != NULL, FALSE);
+
+ rec = command_find(cmd);
+ g_return_val_if_fail(rec != NULL, FALSE);
+
+ if (rec->options == NULL)
+ return FALSE;
+
+ for (tmp = rec->options; *tmp != NULL; tmp++) {
+ char *name = optname(*tmp);
+
+ if (g_ascii_strcasecmp(name, option) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void command_calc_options(COMMAND_REC *rec, const char *options)
+{
+ char **optlist, **tmp, *name, *str;
+ GSList *list, *oldopt;
+
+ optlist = g_strsplit(options, " ", -1);
+
+ if (rec->options == NULL) {
+ /* first call - use specified args directly */
+ rec->options = optlist;
+ return;
+ }
+
+ /* save old options to linked list */
+ list = NULL;
+ for (tmp = rec->options; *tmp != NULL; tmp++)
+ list = g_slist_append(list, g_strdup(*tmp));
+ g_strfreev(rec->options);
+
+ /* merge the options */
+ for (tmp = optlist; *tmp != NULL; tmp++) {
+ name = optname(*tmp);
+
+ oldopt = optlist_find(list, name);
+ if (oldopt != NULL) {
+ /* already specified - overwrite old definition */
+ g_free(oldopt->data);
+ oldopt->data = g_strdup(*tmp);
+ } else {
+ /* new option, append to list */
+ list = g_slist_append(list, g_strdup(*tmp));
+ }
+ }
+ g_strfreev(optlist);
+
+ /* linked list -> string[] */
+ str = i_slist_to_string(list, " ");
+ rec->options = g_strsplit(str, " ", -1);
+ g_free(str);
+
+ g_slist_foreach(list, (GFunc) g_free, NULL);
+ g_slist_free(list);
+}
+
+/* recalculate options to command from options in all modules */
+static void command_update_options(COMMAND_REC *rec)
+{
+ GSList *tmp;
+
+ g_strfreev(rec->options);
+ rec->options = NULL;
+
+ for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *modrec = tmp->data;
+
+ if (modrec->options != NULL)
+ command_calc_options(rec, modrec->options);
+ }
+}
+
+void command_set_options_module(const char *module,
+ const char *cmd, const char *options)
+{
+ COMMAND_REC *rec;
+ COMMAND_MODULE_REC *modrec;
+ int reload;
+
+ g_return_if_fail(module != NULL);
+ g_return_if_fail(cmd != NULL);
+ g_return_if_fail(options != NULL);
+
+ rec = command_find(cmd);
+ g_return_if_fail(rec != NULL);
+ modrec = command_module_get(rec, module, -1);
+
+ reload = modrec->options != NULL;
+ if (reload) {
+ /* options already set for the module ..
+ we need to recalculate everything */
+ g_free(modrec->options);
+ }
+
+ modrec->options = g_strdup(options);
+
+ if (reload)
+ command_update_options(rec);
+ else
+ command_calc_options(rec, options);
+}
+
+char *cmd_get_param(char **data)
+{
+ char *pos;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ pos = *data;
+
+ while (**data != '\0' && **data != ' ') (*data)++;
+ if (**data == ' ') *(*data)++ = '\0';
+
+ return pos;
+}
+
+char *cmd_get_quoted_param(char **data)
+{
+ char *pos, quote;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ if (**data != '\'' && **data != '"')
+ return cmd_get_param(data);
+
+ quote = **data; (*data)++;
+
+ pos = *data;
+ while (**data != '\0' && (**data != quote ||
+ ((*data)[1] != ' ' && (*data)[1] != '\0'))) {
+ if (**data == '\\' && (*data)[1] != '\0')
+ memmove(*data, (*data)+1, strlen(*data));
+ (*data)++;
+ }
+
+ if (**data == quote) {
+ *(*data)++ = '\0';
+ if (**data == ' ')
+ (*data)++;
+ }
+
+ return pos;
+}
+
+/* Find specified option from list of options - the `option' might be
+ shortened version of the full command. Returns index where the
+ option was found, -1 if not found or -2 if there was multiple matches. */
+static int option_find(char **array, const char *option)
+{
+ char **tmp;
+ int index, found, len, multiple;
+
+ g_return_val_if_fail(array != NULL, -1);
+ g_return_val_if_fail(option != NULL, -1);
+
+ len = strlen(option);
+
+ found = -1; index = 0; multiple = FALSE;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ const char *text = optname(*tmp);
+
+ if (g_ascii_strncasecmp(text, option, len) == 0) {
+ if (text[len] == '\0') {
+ /* full match */
+ return index;
+ }
+
+ if (found != -1) {
+ /* multiple matches - we still need to check
+ if there's a full match left.. */
+ multiple = TRUE;
+ }
+
+ /* partial match, check that it's the only one */
+ found = index;
+ }
+ }
+
+ if (multiple)
+ return -2;
+
+ return found;
+}
+
+static int get_cmd_options(char **data, int ignore_unknown,
+ const char *cmd, GHashTable *options)
+{
+ COMMAND_REC *rec;
+ char *option, *arg, **optlist;
+ int pos;
+
+ /* get option definitions */
+ rec = cmd == NULL ? NULL : command_find(cmd);
+ optlist = rec == NULL ? NULL : rec->options;
+
+ option = NULL; pos = -1;
+ for (;;) {
+ if (**data == '\0' || **data == '-') {
+ if (option != NULL && optflag(optlist[pos], "+")) {
+ /* required argument missing! */
+ *data = optname(optlist[pos]);
+ return CMDERR_OPTION_ARG_MISSING;
+ }
+ }
+ if (**data == '-') {
+ (*data)++;
+ if (**data == '-' && (*data)[1] == ' ') {
+ /* -- option means end of options even
+ if next word starts with - */
+ (*data)++;
+ while (**data == ' ') (*data)++;
+ break;
+ }
+
+ if (**data == '\0')
+ option = "-";
+ else if (**data != ' ')
+ option = cmd_get_param(data);
+ else {
+ option = "-";
+ (*data)++;
+ }
+
+ /* check if this option can have argument */
+ pos = optlist == NULL ? -1 :
+ option_find(optlist, option);
+
+ if (pos == -1 && optlist != NULL &&
+ is_numeric(option, '\0')) {
+ /* check if we want -<number> option */
+ pos = option_find(optlist, "#");
+ if (pos != -1) {
+ g_hash_table_insert(options, "#",
+ option);
+ pos = -3;
+ }
+ }
+
+ if (pos == -1 && !ignore_unknown) {
+ /* unknown option! */
+ *data = option;
+ return CMDERR_OPTION_UNKNOWN;
+ }
+ if (pos == -2 && !ignore_unknown) {
+ /* multiple matches */
+ *data = option;
+ return CMDERR_OPTION_AMBIGUOUS;
+ }
+ if (pos >= 0) {
+ /* if we used a shortcut of parameter, put
+ the whole parameter name in options table */
+ option = optname(optlist[pos]);
+ }
+ if (options != NULL && pos != -3)
+ g_hash_table_insert(options, option, "");
+
+ if (pos < 0 || optflag(optlist[pos], " !"))
+ option = NULL;
+
+ while (**data == ' ') (*data)++;
+ continue;
+ }
+
+ if (option == NULL)
+ break;
+
+ if (optflag(optlist[pos], "@") && !is_numeric(*data, ' '))
+ break; /* expected a numeric argument */
+
+ /* save the argument */
+ arg = cmd_get_quoted_param(data);
+ if (options != NULL) {
+ g_hash_table_remove(options, option);
+ g_hash_table_insert(options, option, arg);
+ }
+ option = NULL;
+
+ while (**data == ' ') (*data)++;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ char *data;
+ GHashTable *options;
+} CMD_TEMP_REC;
+
+static const char *
+get_optional_channel(WI_ITEM_REC *active_item, char **data, int require_name)
+{
+ CHANNEL_REC *chanrec;
+ const char *ret;
+ char *tmp, *origtmp, *channel;
+
+ if (active_item == NULL || active_item->server == NULL) {
+ /* no active channel in window, channel required */
+ return cmd_get_param(data);
+ }
+
+ origtmp = tmp = g_strdup(*data);
+ channel = cmd_get_param(&tmp);
+
+ if (g_strcmp0(channel, "*") == 0 && IS_CHANNEL(active_item) &&
+ !require_name) {
+ /* "*" means active channel */
+ cmd_get_param(data);
+ ret = window_item_get_target(active_item);
+ } else if (IS_CHANNEL(active_item) &&
+ !server_ischannel(active_item->server, channel)) {
+ /* we don't have channel parameter - use active channel */
+ ret = window_item_get_target(active_item);
+ } else {
+ /* Find the channel first and use it's name if found.
+ This allows automatic !channel -> !XXXXXchannel replaces. */
+ channel = cmd_get_param(data);
+
+ chanrec = channel_find(active_item->server, channel);
+ ret = chanrec == NULL ? channel : chanrec->name;
+ }
+
+ g_free(origtmp);
+ return ret;
+}
+
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...)
+{
+ WI_ITEM_REC *item;
+ CMD_TEMP_REC *rec;
+ GHashTable **opthash;
+ char **str, *arg, *datad;
+ va_list args;
+ int cnt, error, ignore_unknown, require_name;
+
+ g_return_val_if_fail(data != NULL, FALSE);
+
+ va_start(args, count);
+
+ rec = g_new0(CMD_TEMP_REC, 1);
+ rec->data = g_strdup(data);
+ *free_me = rec;
+
+ datad = rec->data;
+ error = FALSE;
+
+ item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL:
+ (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *);
+
+ if (count & PARAM_FLAG_OPTIONS) {
+ arg = (char *) va_arg(args, char *);
+ opthash = (GHashTable **) va_arg(args, GHashTable **);
+
+ rec->options = *opthash =
+ g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+
+ ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS;
+ error = get_cmd_options(&datad, ignore_unknown,
+ arg, *opthash);
+ }
+
+ if (!error) {
+ /* and now handle the string */
+ cnt = PARAM_WITHOUT_FLAGS(count);
+ if (count & PARAM_FLAG_OPTCHAN) {
+ /* optional channel as first parameter */
+ require_name = (count & PARAM_FLAG_OPTCHAN_NAME) ==
+ PARAM_FLAG_OPTCHAN_NAME;
+ arg = (char *) get_optional_channel(item, &datad, require_name);
+
+ str = (char **) va_arg(args, char **);
+ if (str != NULL) *str = arg;
+ cnt--;
+ }
+
+ while (cnt-- > 0) {
+ if (cnt == 0 && count & PARAM_FLAG_GETREST) {
+ /* get rest */
+ arg = datad;
+
+ /* strip the trailing whitespace */
+ if (count & PARAM_FLAG_STRIP_TRAILING_WS) {
+ arg = g_strchomp(arg);
+ }
+ } else {
+ arg = (count & PARAM_FLAG_NOQUOTES) ?
+ cmd_get_param(&datad) :
+ cmd_get_quoted_param(&datad);
+ }
+
+ str = (char **) va_arg(args, char **);
+ if (str != NULL) *str = arg;
+ }
+ }
+ va_end(args);
+
+ if (error) {
+ signal_emit("error command", 2, GINT_TO_POINTER(error), datad);
+ signal_stop();
+
+ cmd_params_free(rec);
+ *free_me = NULL;
+ }
+
+ return !error;
+}
+
+void cmd_params_free(void *free_me)
+{
+ CMD_TEMP_REC *rec = free_me;
+
+ if (rec->options != NULL) g_hash_table_destroy(rec->options);
+ g_free(rec->data);
+ g_free(rec);
+}
+
+static void command_module_unbind_all(COMMAND_REC *rec,
+ COMMAND_MODULE_REC *modrec)
+{
+ GSList *tmp, *next;
+
+ for (tmp = modrec->callbacks; tmp != NULL; tmp = next) {
+ COMMAND_CALLBACK_REC *cb = tmp->data;
+ next = tmp->next;
+
+ command_unbind_full(rec->cmd, cb->func, cb->user_data);
+ }
+
+ if (g_slist_find(commands, rec) != NULL) {
+ /* this module might have removed some options
+ from command, update them. */
+ command_update_options(rec);
+ }
+}
+
+void commands_remove_module(const char *module)
+{
+ GSList *tmp, *next, *modlist;
+
+ g_return_if_fail(module != NULL);
+
+ for (tmp = commands; tmp != NULL; tmp = next) {
+ COMMAND_REC *rec = tmp->data;
+
+ next = tmp->next;
+ modlist = i_slist_find_string(rec->modules, module);
+ if (modlist != NULL)
+ command_module_unbind_all(rec, modlist->data);
+ }
+}
+
+static int cmd_protocol_match(COMMAND_REC *cmd, SERVER_REC *server)
+{
+ GSList *tmp;
+
+ for (tmp = cmd->modules; tmp != NULL; tmp = tmp->next) {
+ COMMAND_MODULE_REC *rec = tmp->data;
+
+ if (rec->protocol == -1) {
+ /* at least one module accepts the command
+ without specific protocol */
+ return 1;
+ }
+
+ if (server != NULL && rec->protocol == server->chat_type) {
+ /* matching protocol found */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#define alias_runstack_push(alias) \
+ alias_runstack = g_slist_append(alias_runstack, alias)
+
+#define alias_runstack_pop(alias) \
+ alias_runstack = g_slist_remove(alias_runstack, alias)
+
+#define alias_runstack_find(alias) (i_slist_find_icase_string(alias_runstack, alias) != NULL)
+
+static void parse_command(const char *command, int expand_aliases,
+ SERVER_REC *server, void *item)
+{
+ COMMAND_REC *rec;
+ const char *alias, *newcmd;
+ char *cmd, *orig, *args, *oldcmd;
+
+ g_return_if_fail(command != NULL);
+
+ cmd = orig = g_strconcat("command ", command, NULL);
+ args = strchr(cmd+8, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ /* check if there's an alias for command. Don't allow
+ recursive aliases */
+ alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL :
+ alias_find(cmd+8);
+ if (alias != NULL) {
+ alias_runstack_push(cmd+8);
+ eval_special_string(alias, args, server, item);
+ alias_runstack_pop(cmd+8);
+ g_free(orig);
+ return;
+ }
+
+ /* check if this command can be expanded */
+ newcmd = command_expand(cmd+8);
+ if (newcmd == NULL) {
+ /* ambiguous command */
+ g_free(orig);
+ return;
+ }
+
+ rec = command_find(newcmd);
+ if (rec != NULL && !cmd_protocol_match(rec, server)) {
+ g_free(orig);
+
+ signal_emit("error command", 1,
+ GINT_TO_POINTER(server == NULL ?
+ CMDERR_NOT_CONNECTED :
+ CMDERR_ILLEGAL_PROTO));
+ return;
+ }
+
+ cmd = g_strconcat("command ", newcmd, NULL);
+ ascii_strdown(cmd);
+
+ oldcmd = current_command;
+ current_command = cmd+8;
+ if (server != NULL) server_ref(server);
+ if (!signal_emit(cmd, 3, args, server, item)) {
+ signal_emit_id(signal_default_command, 3,
+ command, server, item);
+ }
+ if (server != NULL) {
+ if (server->connection_lost)
+ server_disconnect(server);
+ server_unref(server);
+ }
+ current_command = oldcmd;
+
+ g_free(cmd);
+ g_free(orig);
+}
+
+static void event_command(const char *line, SERVER_REC *server, void *item)
+{
+ char *cmdchar;
+ int expand_aliases = TRUE;
+
+ g_return_if_fail(line != NULL);
+
+ cmdchar = *line == '\0' ? NULL :
+ strchr(settings_get_str("cmdchars"), *line);
+ if (cmdchar != NULL && line[1] == ' ') {
+ /* "/ text" = same as sending "text" to active channel. */
+ line += 2;
+ cmdchar = NULL;
+ }
+ if (cmdchar == NULL) {
+ /* non-command - let someone else handle this */
+ signal_emit("send text", 3, line, server, item);
+ return;
+ }
+
+ /* same cmdchar twice ignores aliases */
+ line++;
+ if (*line == *cmdchar) {
+ line++;
+ expand_aliases = FALSE;
+ }
+
+ /* ^command hides the output - we'll do this at fe-common but
+ we have to skip the ^ char here.. */
+ if (*line == '^') line++;
+
+ parse_command(line, expand_aliases, server, item);
+}
+
+static int eval_recursion_depth=0;
+/* SYNTAX: EVAL <command(s)> */
+static void cmd_eval(const char *data, SERVER_REC *server, void *item)
+{
+ g_return_if_fail(data != NULL);
+ if (eval_recursion_depth > 100)
+ cmd_return_error(CMDERR_EVAL_MAX_RECURSE);
+
+
+ eval_recursion_depth++;
+ eval_special_string(data, "", server, item);
+ eval_recursion_depth--;
+}
+
+/* SYNTAX: CD <directory> */
+static void cmd_cd(const char *data)
+{
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') return;
+
+ str = convert_home(data);
+ if (chdir(str) != 0) {
+ g_warning("Failed to chdir(): %s", strerror(errno));
+ }
+ g_free(str);
+}
+
+void commands_init(void)
+{
+ commands = NULL;
+ current_command = NULL;
+ alias_runstack = NULL;
+
+ signal_default_command = signal_get_uniq_id("default command");
+
+ settings_add_str("misc", "cmdchars", "/");
+ signal_add("send command", (SIGNAL_FUNC) event_command);
+
+ command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
+ command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
+}
+
+void commands_deinit(void)
+{
+ g_free_not_null(current_command);
+
+ signal_remove("send command", (SIGNAL_FUNC) event_command);
+
+ command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
+ command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
+}
diff --git a/src/core/commands.h b/src/core/commands.h
new file mode 100644
index 0000000..7c6bd87
--- /dev/null
+++ b/src/core/commands.h
@@ -0,0 +1,174 @@
+#ifndef IRSSI_CORE_COMMANDS_H
+#define IRSSI_CORE_COMMANDS_H
+
+#include <irssi/src/core/signals.h>
+
+typedef struct {
+ SIGNAL_FUNC func;
+ void *user_data;
+} COMMAND_CALLBACK_REC;
+
+typedef struct {
+ char *name;
+ char *options;
+ int protocol; /* chat protocol required for this command */
+ GSList *callbacks;
+} COMMAND_MODULE_REC;
+
+typedef struct {
+ GSList *modules;
+ char *category;
+ char *cmd;
+ char **options; /* combined from modules[..]->options */
+} COMMAND_REC;
+
+enum {
+ CMDERR_OPTION_UNKNOWN = -3, /* unknown -option */
+ CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */
+ CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */
+
+ CMDERR_UNKNOWN, /* unknown command */
+ CMDERR_AMBIGUOUS, /* ambiguous command */
+
+ CMDERR_ERRNO, /* get the error from errno */
+ CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */
+ CMDERR_NOT_CONNECTED, /* not connected to server */
+ CMDERR_NOT_JOINED, /* not joined to any channels in this window */
+ CMDERR_CHAN_NOT_FOUND, /* channel not found */
+ CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */
+ CMDERR_ILLEGAL_PROTO, /* requires different chat protocol than the active server */
+ CMDERR_NOT_GOOD_IDEA, /* not good idea to do, -yes overrides this */
+ CMDERR_INVALID_TIME, /* invalid time specification */
+ CMDERR_INVALID_CHARSET, /* invalid charset specification */
+ CMDERR_EVAL_MAX_RECURSE, /* eval hit recursion limit */
+ CMDERR_PROGRAM_NOT_FOUND, /* program not found */
+ CMDERR_NO_SERVER_DEFINED, /* no server has been defined for a given chatnet */
+};
+
+/* Return the full command for `alias' */
+#define alias_find(alias) \
+ iconfig_get_str("aliases", alias, NULL)
+
+/* Returning from command function with error */
+#define cmd_return_error(a) \
+ G_STMT_START { \
+ signal_emit("error command", 1, GINT_TO_POINTER(a)); \
+ signal_stop(); \
+ return; \
+ } G_STMT_END
+
+#define cmd_param_error(a) \
+ G_STMT_START { \
+ cmd_params_free(free_arg); \
+ cmd_return_error(a); \
+ } G_STMT_END
+
+extern GSList *commands;
+extern char *current_command; /* the command we're right now. */
+
+/* Bind command to specified function. */
+void command_bind_full(const char *module, int priority, const char *cmd,
+ int protocol, const char *category, SIGNAL_FUNC func,
+ void *user_data);
+#define command_bind(a, b, c) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, a, -1, b, c, NULL)
+#define command_bind_first(a, b, c) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_HIGH, a, -1, b, c, NULL)
+#define command_bind_last(a, b, c) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_LOW, a, -1, b, c, NULL)
+
+#define command_bind_data(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, a, -1, b, c, d)
+#define command_bind_data_first(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_HIGH, a, -1, b, c, d)
+#define command_bind_data_last(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_LOW, a, -1, b, c, d)
+
+#define command_bind_proto(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, a, b, c, d, NULL)
+#define command_bind_proto_first(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_HIGH, a, b, c, d, NULL)
+#define command_bind_proto_last(a, b, c, d) command_bind_full(MODULE_NAME, SIGNAL_PRIORITY_LOW, a, b, c, d, NULL)
+
+void command_unbind_full(const char *cmd, SIGNAL_FUNC func, void *user_data);
+#define command_unbind(cmd, func) command_unbind_full(cmd, func, NULL)
+
+/* Run subcommand, `cmd' contains the base command, first word in `data'
+ contains the subcommand */
+void command_runsub(const char *cmd, const char *data,
+ void *server, void *item);
+
+COMMAND_REC *command_find(const char *cmd);
+int command_have_sub(const char *command);
+
+/* Specify options that command can accept. `options' contains list of
+ options separated with space, each option can contain a special
+ char in front of it:
+
+ '!': no argument (default)
+ '-': optional argument
+ '+': argument required
+ '@': optional numeric argument
+
+ for example if options = "save -file +nick", you can use
+ /command -save -file [<filename>] -nick <nickname>
+
+ You can call this command multiple times for same command, options
+ will be merged. If there's any conflicts with option types, the last
+ call will override the previous */
+#define iscmdtype(c) \
+ ((c) == '!' || (c) == '-' || (c) == '+' || (c) == '@')
+void command_set_options_module(const char *module,
+ const char *cmd, const char *options);
+#define command_set_options(cmd, options) \
+ command_set_options_module(MODULE_NAME, cmd, options)
+
+/* Returns TRUE if command has specified option. */
+int command_have_option(const char *cmd, const char *option);
+
+/* count can have these flags: */
+#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00000fff)
+/* don't check for quotes - "arg1 arg2" is NOT treated as one argument */
+#define PARAM_FLAG_NOQUOTES 0x00001000
+/* final argument gets all the rest of the arguments */
+#define PARAM_FLAG_GETREST 0x00002000
+/* command contains options - first you need to specify them with
+ command_set_options() function. Example:
+
+ -cmd requiredarg -noargcmd -cmd2 "another arg" -optnumarg rest of text
+
+ You would call this with:
+
+ // only once in init
+ command_set_options("mycmd", "+cmd noargcmd -cmd2 @optnumarg");
+
+ GHashTable *optlist;
+
+ cmd_get_params(data, &free_me, 1 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_GETREST, "mycmd", &optlist, &rest);
+
+ The optlist hash table is filled:
+
+ "cmd" = "requiredarg"
+ "noargcmd" = ""
+ "cmd2" = "another arg"
+ "optnumarg" = "" - this is because "rest" isn't a numeric value
+*/
+#define PARAM_FLAG_OPTIONS 0x00004000
+/* don't complain about unknown options */
+#define PARAM_FLAG_UNKNOWN_OPTIONS 0x00008000
+/* optional channel in first argument */
+#define PARAM_FLAG_OPTCHAN 0x00010000
+/* optional channel in first argument, but don't treat "*" as current channel */
+#define PARAM_FLAG_OPTCHAN_NAME (0x00020000|PARAM_FLAG_OPTCHAN)
+/* strip the trailing whitespace */
+#define PARAM_FLAG_STRIP_TRAILING_WS 0x00040000
+
+char *cmd_get_param(char **data);
+char *cmd_get_quoted_param(char **data);
+/* get parameters from command - you should point free_me somewhere and
+ cmd_params_free() it after you don't use any of the parameters anymore.
+
+ Returns TRUE if all ok, FALSE if error occurred. */
+int cmd_get_params(const char *data, gpointer *free_me, int count, ...);
+
+void cmd_params_free(void *free_me);
+
+void commands_remove_module(const char *module);
+
+void commands_init(void);
+void commands_deinit(void);
+
+#endif
diff --git a/src/core/core.c b/src/core/core.c
new file mode 100644
index 0000000..cbe3eb7
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,328 @@
+/*
+ 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 <signal.h>
+
+#include <irssi/src/core/args.h>
+#include <irssi/src/core/pidwait.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/core/net-disconnect.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/session.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/expandos.h>
+#include <irssi/src/core/ignore.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/rawlog.h>
+#include <irssi/src/core/recode.h>
+#include <irssi/src/core/refstrings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/write-buffer.h>
+
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/core/nickmatch-cache.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+ static struct rlimit orig_core_rlimit;
+#endif
+
+void chat_commands_init(void);
+void chat_commands_deinit(void);
+
+void log_away_init(void);
+void log_away_deinit(void);
+
+void wcwidth_wrapper_init(void);
+void wcwidth_wrapper_deinit(void);
+
+int irssi_gui;
+int irssi_init_finished;
+int sighup_received;
+time_t client_start_time;
+
+static char *irssi_dir, *irssi_config_file;
+static GSList *dialog_type_queue, *dialog_text_queue;
+
+const char *get_irssi_dir(void)
+{
+ return irssi_dir;
+}
+
+/* return full path for ~/.irssi/config */
+const char *get_irssi_config(void)
+{
+ return irssi_config_file;
+}
+
+static void sig_hup(int signo)
+{
+ sighup_received = TRUE;
+}
+
+static void read_settings(void)
+{
+ static int signals[] = {
+ SIGINT, SIGQUIT, SIGTERM,
+ SIGALRM, SIGUSR1, SIGUSR2
+ };
+ static char *signames[] = {
+ "int", "quit", "term",
+ "alrm", "usr1", "usr2"
+ };
+
+ const char *ignores;
+ struct sigaction act;
+ int n;
+
+ ignores = settings_get_str("ignore_signals");
+
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+
+ act.sa_handler = sig_hup;
+ sigaction(SIGHUP, &act, NULL);
+
+ for (n = 0; n < sizeof(signals)/sizeof(signals[0]); n++) {
+ act.sa_handler = find_substr(ignores, signames[n]) ?
+ SIG_IGN : SIG_DFL;
+ sigaction(signals[n], &act, NULL);
+ }
+
+#ifdef HAVE_SYS_RESOURCE_H
+ if (!settings_get_bool("override_coredump_limit"))
+ setrlimit(RLIMIT_CORE, &orig_core_rlimit);
+ else {
+ struct rlimit rlimit;
+
+ rlimit.rlim_cur = RLIM_INFINITY;
+ rlimit.rlim_max = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_CORE, &rlimit) == -1)
+ settings_set_bool("override_coredump_limit", FALSE);
+ }
+#endif
+}
+
+static void sig_gui_dialog(const char *type, const char *text)
+{
+ dialog_type_queue = g_slist_append(dialog_type_queue, g_strdup(type));
+ dialog_text_queue = g_slist_append(dialog_text_queue, g_strdup(text));
+}
+
+static void sig_init_finished(void)
+{
+ GSList *type, *text;
+
+ signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+
+ /* send the dialog texts that were in queue before irssi
+ was initialized */
+ type = dialog_type_queue;
+ text = dialog_text_queue;
+ for (; text != NULL; text = text->next, type = type->next) {
+ signal_emit("gui dialog", 2, type->data, text->data);
+ g_free(type->data);
+ g_free(text->data);
+ }
+ g_slist_free(dialog_type_queue);
+ g_slist_free(dialog_text_queue);
+}
+
+static char *fix_path(const char *str)
+{
+ char *new_str = convert_home(str);
+
+ if (!g_path_is_absolute(new_str)) {
+ char *tmp_str = new_str;
+ char *current_dir = g_get_current_dir();
+
+ new_str = g_build_path(G_DIR_SEPARATOR_S, current_dir, tmp_str, NULL);
+
+ g_free(current_dir);
+ g_free(tmp_str);
+ }
+
+ return new_str;
+}
+
+void core_register_options(void)
+{
+ static GOptionEntry options[] = {
+ { "config", 0, 0, G_OPTION_ARG_STRING, &irssi_config_file, "Configuration file location (~/.irssi/config)", "PATH" },
+ { "home", 0, 0, G_OPTION_ARG_STRING, &irssi_dir, "Irssi home dir location (~/.irssi)", "PATH" },
+ { NULL }
+ };
+
+ args_register(options);
+ session_register_options();
+}
+
+void core_preinit(const char *path)
+{
+ const char *home;
+ char *str;
+ int len;
+
+ if (irssi_dir == NULL) {
+ home = g_get_home_dir();
+ if (home == NULL)
+ home = ".";
+
+ irssi_dir = g_strdup_printf(IRSSI_DIR_FULL, home);
+ } else {
+ str = irssi_dir;
+ irssi_dir = fix_path(str);
+ g_free(str);
+ len = strlen(irssi_dir);
+ if (irssi_dir[len-1] == G_DIR_SEPARATOR)
+ irssi_dir[len-1] = '\0';
+ }
+ if (irssi_config_file == NULL)
+ irssi_config_file = g_strdup_printf("%s/"IRSSI_HOME_CONFIG, irssi_dir);
+ else {
+ str = irssi_config_file;
+ irssi_config_file = fix_path(str);
+ g_free(str);
+ }
+
+ session_set_binary(path);
+}
+
+static void sig_irssi_init_finished(void)
+{
+ irssi_init_finished = TRUE;
+}
+
+void core_init(void)
+{
+ dialog_type_queue = NULL;
+ dialog_text_queue = NULL;
+ client_start_time = time(NULL);
+
+ modules_init();
+ pidwait_init();
+
+ net_disconnect_init();
+ signals_init();
+
+ signal_add_first("gui dialog", (SIGNAL_FUNC) sig_gui_dialog);
+ signal_add_first("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+
+ settings_init();
+ commands_init();
+ nickmatch_cache_init();
+ session_init();
+#ifdef HAVE_CAPSICUM
+ capsicum_init();
+#endif
+
+ chat_protocols_init();
+ chatnets_init();
+ expandos_init();
+ ignore_init();
+ servers_init();
+ write_buffer_init();
+ log_init();
+ log_away_init();
+ rawlog_init();
+ recode_init();
+
+ channels_init();
+ queries_init();
+ nicklist_init();
+
+ chat_commands_init();
+ i_refstr_init();
+ special_vars_init();
+ wcwidth_wrapper_init();
+
+ settings_add_str("misc", "ignore_signals", "");
+ settings_add_bool("misc", "override_coredump_limit", FALSE);
+ settings_add_bool("misc", "quit_on_hup", FALSE);
+ settings_add_str("misc", "autoload_modules", "perl otr");
+
+#ifdef HAVE_SYS_RESOURCE_H
+ getrlimit(RLIMIT_CORE, &orig_core_rlimit);
+#endif
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_irssi_init_finished);
+
+ settings_check();
+
+ module_register("core", "core");
+}
+
+void core_deinit(void)
+{
+ module_uniq_destroy("WINDOW ITEM TYPE");
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_irssi_init_finished);
+
+ wcwidth_wrapper_deinit();
+ special_vars_deinit();
+ i_refstr_deinit();
+ chat_commands_deinit();
+
+ nicklist_deinit();
+ queries_deinit();
+ channels_deinit();
+
+ recode_deinit();
+ rawlog_deinit();
+ log_away_deinit();
+ log_deinit();
+ write_buffer_deinit();
+ servers_deinit();
+ ignore_deinit();
+ expandos_deinit();
+ chatnets_deinit();
+ chat_protocols_deinit();
+
+#ifdef HAVE_CAPSICUM
+ capsicum_deinit();
+#endif
+ session_deinit();
+ nickmatch_cache_deinit();
+ commands_deinit();
+ settings_deinit();
+ signals_deinit();
+ net_disconnect_deinit();
+
+ pidwait_deinit();
+ modules_deinit();
+
+ g_free(irssi_dir);
+ g_free(irssi_config_file);
+}
diff --git a/src/core/core.h b/src/core/core.h
new file mode 100644
index 0000000..3c54475
--- /dev/null
+++ b/src/core/core.h
@@ -0,0 +1,25 @@
+#ifndef IRSSI_CORE_CORE_H
+#define IRSSI_CORE_CORE_H
+
+#include <irssi/src/common.h>
+
+/* for determining what GUI is currently in use: */
+#define IRSSI_GUI_NONE 0
+#define IRSSI_GUI_TEXT 1
+#define IRSSI_GUI_GTK 2
+#define IRSSI_GUI_GNOME 3
+#define IRSSI_GUI_QT 4
+#define IRSSI_GUI_KDE 5
+
+extern int irssi_gui;
+extern int irssi_init_finished; /* TRUE after "irssi init finished" signal is sent */
+extern int sighup_received; /* TRUE after received SIGHUP. */
+extern time_t client_start_time;
+
+void core_preinit(const char *path);
+
+void core_register_options(void);
+void core_init(void);
+void core_deinit(void);
+
+#endif
diff --git a/src/core/expandos.c b/src/core/expandos.c
new file mode 100644
index 0000000..0f317e5
--- /dev/null
+++ b/src/core/expandos.c
@@ -0,0 +1,757 @@
+/*
+ expandos.c : irssi
+
+ 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 <irssi/src/core/core.h>
+#include "module.h"
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/expandos.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/irssi-version.h>
+
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+#include <irssi/src/core/window-item-def.h>
+
+#ifdef HAVE_SYS_UTSNAME_H
+# include <sys/utsname.h>
+#endif
+
+#define MAX_EXPANDO_SIGNALS 10
+
+typedef struct {
+ EXPANDO_FUNC func;
+
+ int signals;
+ int signal_ids[MAX_EXPANDO_SIGNALS];
+ int signal_args[MAX_EXPANDO_SIGNALS];
+} EXPANDO_REC;
+
+const char *current_expando = NULL;
+time_t reference_time = (time_t) -1;
+time_t current_time = (time_t)-1;
+
+static int timer_tag;
+
+static EXPANDO_REC *char_expandos[256];
+static GHashTable *expandos;
+static char *last_sent_msg, *last_sent_msg_body;
+static char *last_privmsg_from, *last_public_from;
+static char *sysname, *sysrelease, *sysarch;
+
+static char *timestamp_format;
+static char *timestamp_format_alt;
+static int timestamp_seconds;
+static time_t last_timestamp;
+
+#define CHAR_EXPANDO(chr) \
+ (char_expandos[(int) (unsigned char) chr])
+
+/* Create expando - overrides any existing ones. */
+void expando_create(const char *key, EXPANDO_FUNC func, ...)
+{
+ EXPANDO_REC *rec;
+ const char *signal;
+ va_list va;
+
+ g_return_if_fail(key != NULL && *key != '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] != '\0')
+ rec = g_hash_table_lookup(expandos, key);
+ else {
+ /* single character expando */
+ rec = CHAR_EXPANDO(*key);
+ }
+
+ if (rec != NULL)
+ rec->signals = 0;
+ else {
+ rec = g_new0(EXPANDO_REC, 1);
+ if (key[1] != '\0')
+ g_hash_table_insert(expandos, g_strdup(key), rec);
+ else
+ char_expandos[(int) (unsigned char) *key] = rec;
+ }
+
+ rec->func = func;
+
+ va_start(va, func);
+ while ((signal = (const char *) va_arg(va, const char *)) != NULL)
+ expando_add_signal(key, signal, (int) va_arg(va, int));
+ va_end(va);
+}
+
+static EXPANDO_REC *expando_find(const char *key)
+{
+ if (key[1] != '\0')
+ return g_hash_table_lookup(expandos, key);
+ else
+ return CHAR_EXPANDO(*key);
+}
+
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg)
+{
+ EXPANDO_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(signal != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (arg == EXPANDO_NEVER) {
+ /* expando changes never */
+ rec->signals = -1;
+ } else if (rec->signals < MAX_EXPANDO_SIGNALS) {
+ g_return_if_fail(rec->signals != -1);
+
+ rec->signal_ids[rec->signals] = signal_get_uniq_id(signal);
+ rec->signal_args[rec->signals] = arg;
+ rec->signals++;
+ }
+}
+
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func)
+{
+ gpointer origkey, value;
+ EXPANDO_REC *rec;
+
+ g_return_if_fail(key != NULL && *key != '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] == '\0') {
+ /* single character expando */
+ rec = CHAR_EXPANDO(*key);
+ if (rec != NULL && rec->func == func) {
+ char_expandos[(int) (unsigned char) *key] = NULL;
+ g_free(rec);
+ }
+ } else if (g_hash_table_lookup_extended(expandos, key,
+ &origkey, &value)) {
+ rec = value;
+ if (rec->func == func) {
+ g_hash_table_remove(expandos, key);
+ g_free(origkey);
+ g_free(rec);
+ }
+ }
+}
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+ SIGNAL_FUNC func;
+ EXPANDO_REC *rec;
+ int n, arg;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(funccount >= 1);
+ g_return_if_fail(funcs != NULL);
+ g_return_if_fail(funcs[0] != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (rec->signals == 0) {
+ /* it's unknown when this expando changes..
+ check it once in a second */
+ signal_add("expando timer", funcs[EXPANDO_ARG_NONE]);
+ }
+
+ for (n = 0; n < rec->signals; n++) {
+ arg = rec->signal_args[n];
+ func = arg < funccount ? funcs[arg] : NULL;
+ if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+ signal_add_full_id(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT,
+ rec->signal_ids[n], func, NULL);
+ }
+}
+
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs)
+{
+ SIGNAL_FUNC func;
+ EXPANDO_REC *rec;
+ int n, arg;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(funccount >= 1);
+ g_return_if_fail(funcs != NULL);
+ g_return_if_fail(funcs[0] != NULL);
+
+ rec = expando_find(key);
+ g_return_if_fail(rec != NULL);
+
+ if (rec->signals == 0) {
+ /* it's unknown when this expando changes..
+ check it once in a second */
+ signal_remove("expando timer", funcs[EXPANDO_ARG_NONE]);
+ }
+
+ for (n = 0; n < rec->signals; n++) {
+ arg = rec->signal_args[n];
+ func = arg < funccount ? funcs[arg] : NULL;
+ if (func == NULL) func = funcs[EXPANDO_ARG_NONE];
+
+ signal_remove_id(rec->signal_ids[n], func, NULL);
+ }
+}
+
+/* Returns [<signal id>, EXPANDO_ARG_xxx, <signal id>, ..., -1] */
+int *expando_get_signals(const char *key)
+{
+ EXPANDO_REC *rec;
+ int *signals;
+ int n;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = expando_find(key);
+ if (rec == NULL || rec->signals < 0)
+ return NULL;
+
+ if (rec->signals == 0) {
+ /* it's unknown when this expando changes..
+ check it once in a second */
+ signals = g_new(int, 3);
+ signals[0] = signal_get_uniq_id("expando timer");
+ signals[1] = EXPANDO_ARG_NONE;
+ signals[2] = -1;
+ return signals;
+ }
+
+ signals = g_new(int, rec->signals*2+1);
+ for (n = 0; n < rec->signals; n++) {
+ signals[n*2] = rec->signal_ids[n];
+ signals[n*2+1] = rec->signal_args[n];
+ }
+ signals[rec->signals*2] = -1;
+ return signals;
+}
+
+EXPANDO_FUNC expando_find_char(char chr)
+{
+ return CHAR_EXPANDO(chr) == NULL ? NULL :
+ CHAR_EXPANDO(chr)->func;
+}
+
+EXPANDO_FUNC expando_find_long(const char *key)
+{
+ EXPANDO_REC *rec = g_hash_table_lookup(expandos, key);
+ return rec == NULL ? NULL : rec->func;
+}
+
+static gboolean free_expando(gpointer key, gpointer value, gpointer user_data)
+{
+ g_free(key);
+ g_free(value);
+ return TRUE;
+}
+
+/* last person who sent you a MSG */
+static char *expando_lastmsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_privmsg_from;
+}
+
+/* last person to whom you sent a MSG */
+static char *expando_lastmymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_sent_msg;
+}
+
+/* last person to send a public message to a channel you are on */
+static char *expando_lastpublic(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_public_from;
+}
+
+/* text of your AWAY message, if any */
+static char *expando_awaymsg(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->away_reason;
+}
+
+/* body of last MSG you sent */
+static char *expando_lastmymsg_body(SERVER_REC *server, void *item, int *free_ret)
+{
+ return last_sent_msg_body;
+}
+
+/* current channel */
+static char *expando_channel(SERVER_REC *server, void *item, int *free_ret)
+{
+ return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->name;
+}
+
+/* time client was started, $time() format */
+static char *expando_clientstarted(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%ld", (long) client_start_time);
+}
+
+/* channel you were last INVITEd to */
+static char *expando_last_invite(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->last_invite;
+}
+
+/* client version text string */
+static char *expando_version(SERVER_REC *server, void *item, int *free_ret)
+{
+ return PACKAGE_VERSION;
+}
+
+/* current value of CMDCHARS */
+static char *expando_cmdchars(SERVER_REC *server, void *item, int *free_ret)
+{
+ return (char *) settings_get_str("cmdchars");
+}
+
+/* first CMDCHAR */
+static char *expando_cmdchar(SERVER_REC *server, void *item, int *free_ret)
+{
+ char str[2] = { 0, 0 };
+
+ str[0] = *settings_get_str("cmdchars");
+
+ *free_ret = TRUE;
+ return g_strdup(str);
+}
+
+/* modes of current channel, if any */
+static char *expando_chanmode(SERVER_REC *server, void *item, int *free_ret)
+{
+ char *cmode;
+ char *args;
+
+ *free_ret = FALSE;
+
+ if (!IS_CHANNEL(item))
+ return NULL;
+
+ if (!settings_get_bool("chanmode_expando_strip"))
+ return CHANNEL(item)->mode;
+
+ *free_ret = TRUE;
+ cmode = g_strdup(CHANNEL(item)->mode);
+ args = strchr(cmode, ' ');
+ if (args != NULL)
+ *args = 0;
+
+ return cmode;
+}
+
+/* current nickname */
+static char *expando_nick(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->nick;
+}
+
+/* value of STATUS_OPER if you are an irc operator */
+static char *expando_statusoper(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL || !server->server_operator ? "" :
+ (char *) settings_get_str("STATUS_OPER");
+}
+
+/* if you are a channel operator in $C, expands to a '@' */
+static char *expando_chanop(SERVER_REC *server, void *item, int *free_ret)
+{
+ return IS_CHANNEL(item) && CHANNEL(item)->chanop ? "@" : "";
+}
+
+/* nickname of whomever you are QUERYing */
+static char *expando_query(SERVER_REC *server, void *item, int *free_ret)
+{
+ return !IS_QUERY(item) ? "" : QUERY(item)->name;
+}
+
+/* version of current server */
+static char *expando_serverversion(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->version;
+}
+
+/* target of current input (channel or QUERY nickname) */
+static char *expando_target(SERVER_REC *server, void *item, int *free_ret)
+{
+ return item == NULL ? "" :
+ (char *) window_item_get_target((WI_ITEM_REC *) item);
+}
+
+/* client release date (in YYYYMMDD format) */
+static char *expando_releasedate(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", IRSSI_VERSION_DATE);
+}
+
+/* client release time (in HHMM format) */
+static char *expando_releasetime(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%04d", IRSSI_VERSION_TIME);
+}
+
+/* client abi */
+static char *expando_abiversion(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", IRSSI_ABI_VERSION);
+}
+
+/* current working directory */
+static char *expando_workdir(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_get_current_dir();
+}
+
+/* value of REALNAME */
+static char *expando_realname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->connrec->realname;
+}
+
+/* time of day (hh:mm) */
+static char *expando_time(SERVER_REC *server, void *item, int *free_ret)
+{
+ time_t now;
+ struct tm *tm;
+ char str[256];
+ char *format;
+
+ now = current_time != (time_t) -1 ? current_time : time(NULL);
+ tm = localtime(&now);
+ format = timestamp_format;
+
+ if (reference_time != (time_t) -1) {
+ time_t ref = reference_time;
+ struct tm tm_ref;
+ if (localtime_r(&ref, &tm_ref)) {
+ if (tm_ref.tm_yday != tm->tm_yday || tm_ref.tm_year != tm->tm_year) {
+ format = timestamp_format_alt;
+ }
+ }
+ }
+
+ if (strftime(str, sizeof(str), format, tm) == 0)
+ return "";
+
+ *free_ret = TRUE;
+ return g_strdup(str);
+}
+
+/* a literal '$' */
+static char *expando_dollar(SERVER_REC *server, void *item, int *free_ret)
+{
+ return "$";
+}
+
+/* system name */
+static char *expando_sysname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysname;
+}
+
+/* system release */
+static char *expando_sysrelease(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysrelease;
+}
+
+/* system architecture */
+static char *expando_sysarch(SERVER_REC *server, void *item, int *free_ret)
+{
+ return sysarch;
+}
+
+/* Topic of active channel (or address of queried nick) */
+static char *expando_topic(SERVER_REC *server, void *item, int *free_ret)
+{
+ if (IS_CHANNEL(item))
+ return CHANNEL(item)->topic;
+ if (IS_QUERY(item)) {
+ QUERY_REC *query = QUERY(item);
+
+ if (query->server_tag == NULL)
+ return "";
+
+ *free_ret = TRUE;
+ return query->address == NULL ?
+ g_strdup_printf("(%s)", query->server_tag) :
+ g_strdup_printf("%s (%s)", query->address,
+ query->server_tag);
+ }
+ return "";
+}
+
+/* Server tag */
+static char *expando_servertag(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->tag;
+}
+
+/* Server chatnet */
+static char *expando_chatnet(SERVER_REC *server, void *item, int *free_ret)
+{
+ return server == NULL ? "" : server->connrec->chatnet;
+}
+
+/* visible_name of current window item */
+static char *expando_itemname(SERVER_REC *server, void *item, int *free_ret)
+{
+ return item == NULL ? "" : ((WI_ITEM_REC *) item)->visible_name;
+}
+
+static void sig_message_public(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address,
+ const char *target)
+{
+ g_free_not_null(last_public_from);
+ last_public_from = g_strdup(nick);
+}
+
+static void sig_message_private(SERVER_REC *server, const char *msg,
+ const char *nick, const char *address)
+{
+ g_free_not_null(last_privmsg_from);
+ last_privmsg_from = g_strdup(nick);
+}
+
+static void sig_message_own_private(SERVER_REC *server, const char *msg,
+ const char *target, const char *origtarget)
+{
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(msg != NULL);
+
+ if (target != NULL) {
+ if (target != last_sent_msg) {
+ g_free_not_null(last_sent_msg);
+ last_sent_msg = g_strdup(target);
+ }
+ g_free_not_null(last_sent_msg_body);
+ last_sent_msg_body = g_strdup(msg);
+ }
+}
+
+static int sig_timer(void)
+{
+ time_t now;
+ struct tm *tm;
+ int last_min;
+
+ signal_emit("expando timer", 0);
+
+ /* check if $Z has changed */
+ now = time(NULL);
+ if (last_timestamp != now) {
+ if (!timestamp_seconds && last_timestamp != 0) {
+ /* assume it changes every minute */
+ tm = localtime(&last_timestamp);
+ last_min = tm->tm_min;
+
+ tm = localtime(&now);
+ if (tm->tm_min == last_min)
+ return 1;
+ }
+
+ signal_emit("time changed", 0);
+ last_timestamp = now;
+ }
+
+ return 1;
+}
+
+static void read_settings(void)
+{
+ g_free_not_null(timestamp_format);
+ g_free_not_null(timestamp_format_alt);
+ timestamp_format = g_strdup(settings_get_str("timestamp_format"));
+ timestamp_format_alt = g_strdup(settings_get_str("timestamp_format_alt"));
+
+ timestamp_seconds =
+ strstr(timestamp_format, "%r") != NULL ||
+ strstr(timestamp_format, "%s") != NULL ||
+ strstr(timestamp_format, "%S") != NULL ||
+ strstr(timestamp_format, "%X") != NULL ||
+ strstr(timestamp_format, "%T") != NULL;
+
+}
+
+void expandos_init(void)
+{
+#ifdef HAVE_SYS_UTSNAME_H
+ struct utsname un;
+#endif
+ settings_add_str("misc", "STATUS_OPER", "*");
+ settings_add_str("lookandfeel", "timestamp_format", "%H:%M");
+ settings_add_str("lookandfeel", "timestamp_format_alt", "%a %e %b %H:%M");
+ settings_add_bool("lookandfeel", "chanmode_expando_strip", FALSE);
+
+ last_sent_msg = NULL; last_sent_msg_body = NULL;
+ last_privmsg_from = NULL; last_public_from = NULL;
+ last_timestamp = 0;
+
+ sysname = sysrelease = sysarch = NULL;
+#ifdef HAVE_SYS_UTSNAME_H
+ if (uname(&un) >= 0) {
+ sysname = g_strdup(un.sysname);
+ sysrelease = g_strdup(un.release);
+ sysarch = g_strdup(un.machine);
+ }
+#endif
+
+ memset(char_expandos, 0, sizeof(char_expandos));
+ expandos = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ expando_create(",", expando_lastmsg,
+ "message private", EXPANDO_ARG_SERVER, NULL);
+ expando_create(".", expando_lastmymsg,
+ "command msg", EXPANDO_ARG_NONE, NULL);
+ expando_create(";", expando_lastpublic,
+ "message public", EXPANDO_ARG_SERVER, NULL);
+ expando_create("A", expando_awaymsg,
+ "away mode changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("B", expando_lastmymsg_body,
+ "command msg", EXPANDO_ARG_NONE, NULL);
+ expando_create("C", expando_channel,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("F", expando_clientstarted,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("I", expando_last_invite, NULL);
+ expando_create("J", expando_version,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("K", expando_cmdchars,
+ "setup changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("k", expando_cmdchar,
+ "setup changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("M", expando_chanmode,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "channel mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("N", expando_nick,
+ "window changed", EXPANDO_ARG_NONE,
+ "window connect changed", EXPANDO_ARG_WINDOW,
+ "window server changed", EXPANDO_ARG_WINDOW,
+ "server nick changed", EXPANDO_ARG_SERVER, NULL);
+ expando_create("O", expando_statusoper,
+ "setup changed", EXPANDO_ARG_NONE,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW,
+ "user mode changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("P", expando_chanop,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("Q", expando_query,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("R", expando_serverversion,
+ "window changed", EXPANDO_ARG_NONE,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("T", expando_target,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("V", expando_releasedate,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("versiontime", expando_releasetime,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("abiversion", expando_abiversion,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("W", expando_workdir, NULL);
+ expando_create("Y", expando_realname,
+ "window changed", EXPANDO_ARG_NONE,
+ "window connect changed", EXPANDO_ARG_WINDOW,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("Z", expando_time,
+ "time changed", EXPANDO_ARG_NONE, NULL);
+ expando_create("$", expando_dollar,
+ "", EXPANDO_NEVER, NULL);
+
+ expando_create("sysname", expando_sysname,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("sysrelease", expando_sysrelease,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("sysarch", expando_sysarch,
+ "", EXPANDO_NEVER, NULL);
+ expando_create("topic", expando_topic,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "channel topic changed", EXPANDO_ARG_WINDOW_ITEM,
+ "query address changed", EXPANDO_ARG_WINDOW_ITEM, NULL);
+ expando_create("tag", expando_servertag,
+ "window changed", EXPANDO_ARG_NONE,
+ "window connect changed", EXPANDO_ARG_WINDOW,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("chatnet", expando_chatnet,
+ "window changed", EXPANDO_ARG_NONE,
+ "window connect changed", EXPANDO_ARG_WINDOW,
+ "window server changed", EXPANDO_ARG_WINDOW, NULL);
+ expando_create("itemname", expando_itemname,
+ "window changed", EXPANDO_ARG_NONE,
+ "window item changed", EXPANDO_ARG_WINDOW,
+ "window item name changed", EXPANDO_ARG_WINDOW_ITEM,
+ NULL);
+
+ read_settings();
+
+ timer_tag = g_timeout_add(1000, (GSourceFunc) sig_timer, NULL);
+ signal_add("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_add("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void expandos_deinit(void)
+{
+ int n;
+
+ for (n = 0; n < sizeof(char_expandos)/sizeof(char_expandos[0]); n++)
+ g_free_not_null(char_expandos[n]);
+
+ g_hash_table_foreach_remove(expandos, free_expando, NULL);
+ g_hash_table_destroy(expandos);
+
+ g_free_not_null(last_sent_msg);
+ g_free_not_null(last_sent_msg_body);
+ g_free_not_null(last_privmsg_from);
+ g_free_not_null(last_public_from);
+ g_free_not_null(sysname);
+ g_free_not_null(sysrelease);
+ g_free_not_null(sysarch);
+ g_free_not_null(timestamp_format);
+ g_free_not_null(timestamp_format_alt);
+
+ g_source_remove(timer_tag);
+ signal_remove("message public", (SIGNAL_FUNC) sig_message_public);
+ signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
+ signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/core/expandos.h b/src/core/expandos.h
new file mode 100644
index 0000000..17cee60
--- /dev/null
+++ b/src/core/expandos.h
@@ -0,0 +1,45 @@
+#ifndef IRSSI_CORE_EXPANDOS_H
+#define IRSSI_CORE_EXPANDOS_H
+
+#include <irssi/src/core/signals.h>
+
+/* first argument of signal must match to active .. */
+typedef enum {
+ EXPANDO_ARG_NONE = 1,
+ EXPANDO_ARG_SERVER,
+ EXPANDO_ARG_WINDOW,
+ EXPANDO_ARG_WINDOW_ITEM,
+
+ EXPANDO_NEVER /* special: expando never changes */
+} ExpandoArg;
+
+typedef char* (*EXPANDO_FUNC)
+ (SERVER_REC *server, void *item, int *free_ret);
+
+extern const char *current_expando;
+extern time_t current_time;
+extern time_t reference_time;
+
+/* Create expando - overrides any existing ones.
+ ... = signal, type, ..., NULL - list of signals that might change the
+ value of this expando */
+void expando_create(const char *key, EXPANDO_FUNC func, ...);
+/* Add new signal to expando */
+void expando_add_signal(const char *key, const char *signal, ExpandoArg arg);
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func);
+
+void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs);
+
+/* Returns [<signal id>, EXPANDO_ARG_xxx, <signal id>, ..., -1] */
+int *expando_get_signals(const char *key);
+
+/* internal: */
+EXPANDO_FUNC expando_find_char(char chr);
+EXPANDO_FUNC expando_find_long(const char *key);
+
+void expandos_init(void);
+void expandos_deinit(void);
+
+#endif
diff --git a/src/core/ignore.c b/src/core/ignore.c
new file mode 100644
index 0000000..da892c1
--- /dev/null
+++ b/src/core/ignore.c
@@ -0,0 +1,543 @@
+/*
+ ignore.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/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/iregex.h>
+
+#include <irssi/src/core/masks.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/core/nickmatch-cache.h>
+
+#include <irssi/src/core/ignore.h>
+
+GSList *ignores;
+
+static NICKMATCH_REC *nickmatch;
+static int time_tag;
+
+/* check if `text' contains ignored nick at the start of the line. */
+static int ignore_check_replies_rec(IGNORE_REC *rec, CHANNEL_REC *channel,
+ const char *text)
+{
+ GSList *nicks, *tmp;
+
+ nicks = nicklist_find_multiple(channel, rec->mask);
+ if (nicks == NULL) return FALSE;
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *nick = tmp->data;
+
+ if (nick_match_msg(channel, text, nick->nick))
+ return TRUE;
+ }
+ g_slist_free(nicks);
+
+ return FALSE;
+}
+
+static int ignore_match_pattern(IGNORE_REC *rec, const char *text)
+{
+ if (rec->pattern == NULL)
+ return TRUE;
+
+ if (text == NULL)
+ return FALSE;
+
+ if (rec->regexp) {
+ return rec->preg != NULL &&
+ i_regex_match(rec->preg, text, 0, NULL);
+ }
+
+ return rec->fullword ?
+ stristr_full(text, rec->pattern) != NULL :
+ stristr(text, rec->pattern) != NULL;
+}
+
+/* MSGLEVEL_NO_ACT is special in ignores, when provided to ignore_check() it's
+ * used as a flag to indicate it should only look at ignore items with NO_ACT.
+ * However we also want to allow NO_ACT combined with levels, so mask it out and
+ * match levels if set. */
+#define FLAG_MSGLEVELS (MSGLEVEL_NO_ACT | MSGLEVEL_HIDDEN | MSGLEVEL_NOHILIGHT)
+static int ignore_match_level(IGNORE_REC *rec, int level, int flags)
+{
+ level &= ~FLAG_MSGLEVELS;
+ flags &= FLAG_MSGLEVELS;
+
+ if (!flags) {
+ /* case: we are not checking special flags. then, the
+ record must not have any flags either, but it must
+ have some of the levels */
+ return !(FLAG_MSGLEVELS & rec->level) && (level & rec->level);
+ } else {
+ /* case: we want to test if some special flags
+ apply. then, the record must have some of the
+ flags and some of the levels */
+ return (flags & rec->level) && (level & rec->level);
+ }
+}
+
+#define ignore_match_nickmask(rec, nick, nickmask) \
+ ((rec)->mask == NULL || \
+ (strchr((rec)->mask, '!') != NULL ? \
+ match_wildcards((rec)->mask, nickmask) : \
+ match_wildcards((rec)->mask, nick)))
+
+#define ignore_match_server(rec, server) \
+ ((rec)->servertag == NULL || ((server) != NULL && \
+ g_ascii_strcasecmp((server)->tag, (rec)->servertag) == 0))
+
+#define ignore_match_channel(rec, channel) \
+ ((rec)->channels == NULL || ((channel) != NULL && \
+ strarray_find((rec)->channels, (channel)) != -1))
+
+static int ignore_check_replies(CHANNEL_REC *chanrec, const char *text, int level, int flags)
+{
+ GSList *tmp;
+
+ if (text == NULL || chanrec == NULL)
+ return FALSE;
+
+ /* check reply ignores */
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec->mask != NULL && rec->replies &&
+ ignore_match_level(rec, level, flags) &&
+ ignore_match_channel(rec, chanrec->name) &&
+ ignore_check_replies_rec(rec, chanrec, text))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+int ignore_check_flags(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level, int flags)
+{
+ CHANNEL_REC *chanrec;
+ NICK_REC *nickrec;
+ IGNORE_REC *rec;
+ GSList *tmp;
+ char *nickmask;
+ int len, best_mask, best_match, best_patt;
+
+ if (nick == NULL) nick = "";
+
+ chanrec = server == NULL || channel == NULL ? NULL :
+ channel_find(server, channel);
+ if (chanrec != NULL && nick != NULL &&
+ (nickrec = nicklist_find(chanrec, nick)) != NULL) {
+ /* nick found - check only ignores in nickmatch cache */
+ if (nickrec->host == NULL)
+ nicklist_set_host(chanrec, nickrec, host);
+
+ tmp = nickmatch_find(nickmatch, nickrec);
+ nickmask = NULL;
+ } else {
+ tmp = ignores;
+ nickmask = g_strconcat(nick, "!", host, NULL);
+ }
+
+ best_mask = best_patt = -1; best_match = FALSE;
+ for (; tmp != NULL; tmp = tmp->next) {
+ int match = 1;
+ rec = tmp->data;
+
+ if (nickmask != NULL)
+ match = ignore_match_server(rec, server) &&
+ ignore_match_channel(rec, channel) &&
+ ignore_match_nickmask(rec, nick, nickmask);
+ if (match &&
+ ignore_match_level(rec, level, flags) &&
+ ignore_match_pattern(rec, text)) {
+ len = rec->mask == NULL ? 0 : strlen(rec->mask);
+ if (len > best_mask) {
+ best_mask = len;
+ best_match = !rec->exception;
+ } else if (len == best_mask) {
+ len = rec->pattern == NULL ? 0 : strlen(rec->pattern);
+ if (len > best_patt) {
+ best_patt = len;
+ best_match = !rec->exception;
+ } else if (len == best_patt && rec->exception)
+ best_match = 0;
+ }
+ }
+ }
+ g_free(nickmask);
+
+ if (best_match || (level & MSGLEVEL_PUBLIC) == 0)
+ return best_match;
+
+ return ignore_check_replies(chanrec, text, level, flags);
+}
+
+int ignore_check(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level) {
+ return ignore_check_flags(server, nick, host, channel, text, level, 0);
+}
+
+int ignore_check_plus(SERVER_REC *server, const char *nick, const char *address,
+ const char *target, const char *msg, int *level, int test_ignore) {
+ int olevel = *level;
+
+ if (test_ignore && ignore_check(server, nick, address, target, msg, olevel))
+ return TRUE;
+
+ if (ignore_check_flags(server, nick, address, target, msg, olevel, MSGLEVEL_NO_ACT))
+ *level |= MSGLEVEL_NO_ACT;
+
+ if (ignore_check_flags(server, nick, address, target, msg, olevel, MSGLEVEL_HIDDEN))
+ *level |= MSGLEVEL_HIDDEN;
+
+ if (ignore_check_flags(server, nick, address, target, msg, olevel, MSGLEVEL_NOHILIGHT))
+ *level |= MSGLEVEL_NOHILIGHT;
+
+ return FALSE;
+}
+
+IGNORE_REC *ignore_find_full(const char *servertag, const char *mask, const char *pattern,
+ char **channels, const int flags)
+{
+ GSList *tmp;
+ char **chan;
+ int ignore_servertag;
+
+ if (mask != NULL && (*mask == '\0' || g_strcmp0(mask, "*") == 0))
+ mask = NULL;
+
+ ignore_servertag = servertag != NULL && g_strcmp0(servertag, "*") == 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (!ignore_servertag) {
+ if ((servertag == NULL && rec->servertag != NULL) ||
+ (servertag != NULL && rec->servertag == NULL))
+ continue;
+
+ if (servertag != NULL && g_ascii_strcasecmp(servertag, rec->servertag) != 0)
+ continue;
+ }
+
+ if ((flags & IGNORE_FIND_NOACT) && (rec->level & MSGLEVEL_NO_ACT) == 0)
+ continue;
+
+ if (!(flags & IGNORE_FIND_NOACT) && (rec->level & MSGLEVEL_NO_ACT) != 0)
+ continue;
+
+ if ((flags & IGNORE_FIND_HIDDEN) && (rec->level & MSGLEVEL_HIDDEN) == 0)
+ continue;
+
+ if (!(flags & IGNORE_FIND_HIDDEN) && (rec->level & MSGLEVEL_HIDDEN) != 0)
+ continue;
+
+ if ((flags & IGNORE_FIND_NOHILIGHT) && (rec->level & MSGLEVEL_NOHILIGHT) == 0)
+ continue;
+
+ if (!(flags & IGNORE_FIND_NOHILIGHT) && (rec->level & MSGLEVEL_NOHILIGHT) != 0)
+ continue;
+
+ if ((rec->mask == NULL && mask != NULL) ||
+ (rec->mask != NULL && mask == NULL))
+ continue;
+
+ if (rec->mask != NULL && g_ascii_strcasecmp(rec->mask, mask) != 0)
+ continue;
+
+ /* match the pattern too if requested */
+ if (flags & IGNORE_FIND_PATTERN) {
+ if ((rec->pattern == NULL && pattern != NULL) ||
+ (rec->pattern != NULL && pattern == NULL))
+ continue;
+
+ if (rec->pattern != NULL && g_ascii_strcasecmp(rec->pattern, pattern) != 0)
+ continue;
+ }
+
+ if ((channels == NULL && rec->channels == NULL))
+ return rec; /* no channels - ok */
+
+ if (channels != NULL && g_strcmp0(*channels, "*") == 0)
+ return rec; /* ignore channels */
+
+ if (channels == NULL || rec->channels == NULL)
+ continue; /* other doesn't have channels */
+
+ if (g_strv_length(channels) != g_strv_length(rec->channels))
+ continue; /* different amount of channels */
+
+ /* check that channels match */
+ for (chan = channels; *chan != NULL; chan++) {
+ if (strarray_find(rec->channels, *chan) == -1)
+ break;
+ }
+
+ if (*chan == NULL)
+ return rec; /* channels ok */
+ }
+
+ return NULL;
+}
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels)
+{
+ return ignore_find_full(servertag, mask, NULL, channels, 0);
+}
+
+static void ignore_set_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ if (rec->level == 0)
+ return;
+
+ node = iconfig_node_traverse("(ignores", TRUE);
+ node = iconfig_node_section(node, NULL, NODE_TYPE_BLOCK);
+
+ if (rec->mask != NULL) iconfig_node_set_str(node, "mask", rec->mask);
+ if (rec->level) {
+ levelstr = bits2level(rec->level);
+ iconfig_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+ }
+ iconfig_node_set_str(node, "pattern", rec->pattern);
+ if (rec->exception) iconfig_node_set_bool(node, "exception", TRUE);
+ if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE);
+ if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE);
+ if (rec->replies) iconfig_node_set_bool(node, "replies", TRUE);
+ if (rec->unignore_time != 0)
+ iconfig_node_set_int(node, "unignore_time", rec->unignore_time);
+ iconfig_node_set_str(node, "servertag", rec->servertag);
+
+ if (rec->channels != NULL && *rec->channels != NULL) {
+ node = iconfig_node_section(node, "channels", NODE_TYPE_LIST);
+ iconfig_node_add_list(node, rec->channels);
+ }
+}
+
+static int ignore_index(IGNORE_REC *find)
+{
+ GSList *tmp;
+ int index;
+
+ index = 0;
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (rec == find)
+ return index;
+ index++;
+ }
+
+ return -1;
+}
+
+static void ignore_remove_config(IGNORE_REC *rec)
+{
+ CONFIG_NODE *node;
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node != NULL) iconfig_node_list_remove(node, ignore_index(rec));
+}
+
+static void ignore_init_rec(IGNORE_REC *rec)
+{
+ if (rec->preg != NULL)
+ i_regex_unref(rec->preg);
+
+ if (rec->regexp && rec->pattern != NULL) {
+ GError *re_error = NULL;
+
+ rec->preg = i_regex_new(rec->pattern, G_REGEX_OPTIMIZE | G_REGEX_CASELESS, 0, &re_error);
+
+ if (rec->preg == NULL) {
+ g_warning("Failed to compile regexp '%s': %s", rec->pattern, re_error->message);
+ g_error_free(re_error);
+ }
+ }
+}
+
+void ignore_add_rec(IGNORE_REC *rec)
+{
+ ignore_init_rec(rec);
+
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+
+ signal_emit("ignore created", 1, rec);
+ nickmatch_rebuild(nickmatch);
+}
+
+static void ignore_destroy(IGNORE_REC *rec, int send_signal)
+{
+ ignores = g_slist_remove(ignores, rec);
+ if (send_signal)
+ signal_emit("ignore destroyed", 1, rec);
+
+ if (rec->preg != NULL) i_regex_unref(rec->preg);
+ if (rec->channels != NULL) g_strfreev(rec->channels);
+ g_free_not_null(rec->mask);
+ g_free_not_null(rec->servertag);
+ g_free_not_null(rec->pattern);
+ g_free(rec);
+}
+
+void ignore_update_rec(IGNORE_REC *rec)
+{
+ if (rec->level == 0) {
+ /* unignored everything */
+ ignore_remove_config(rec);
+ ignore_destroy(rec, TRUE);
+ } else {
+ /* unignore just some levels.. */
+ ignore_remove_config(rec);
+ ignores = g_slist_remove(ignores, rec);
+
+ ignores = g_slist_append(ignores, rec);
+ ignore_set_config(rec);
+
+ ignore_init_rec(rec);
+ signal_emit("ignore changed", 1, rec);
+ }
+ nickmatch_rebuild(nickmatch);
+}
+
+static int unignore_timeout(void)
+{
+ GSList *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+ for (tmp = ignores; tmp != NULL; tmp = next) {
+ IGNORE_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->unignore_time > 0 && now >= rec->unignore_time) {
+ rec->level = 0;
+ ignore_update_rec(rec);
+ }
+ }
+
+ return TRUE;
+}
+
+static void read_ignores(void)
+{
+ IGNORE_REC *rec;
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (ignores != NULL)
+ ignore_destroy(ignores->data, FALSE);
+
+ node = iconfig_node_traverse("ignores", FALSE);
+ if (node == NULL) {
+ nickmatch_rebuild(nickmatch);
+ 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(IGNORE_REC, 1);
+ ignores = g_slist_append(ignores, rec);
+
+ rec->mask = g_strdup(config_node_get_str(node, "mask", NULL));
+ rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL));
+ rec->level = level2bits(config_node_get_str(node, "level", ""), NULL);
+ rec->exception = config_node_get_bool(node, "exception", FALSE);
+ rec->regexp = config_node_get_bool(node, "regexp", FALSE);
+ rec->fullword = config_node_get_bool(node, "fullword", FALSE);
+ rec->replies = config_node_get_bool(node, "replies", FALSE);
+ rec->unignore_time = config_node_get_int(node, "unignore_time", 0);
+ rec->servertag = g_strdup(config_node_get_str(node, "servertag", 0));
+
+ node = iconfig_node_section(node, "channels", -1);
+ if (node != NULL) rec->channels = config_node_get_list(node);
+
+ ignore_init_rec(rec);
+ }
+
+ nickmatch_rebuild(nickmatch);
+}
+
+static void free_cache_matches(GSList *matches)
+{
+ g_slist_free(matches);
+}
+
+static void ignore_nick_cache(GHashTable *list, CHANNEL_REC *channel,
+ NICK_REC *nick)
+{
+ GSList *tmp, *matches;
+ char *nickmask;
+
+ if (nick->host == NULL)
+ return; /* don't check until host is known */
+
+ matches = NULL;
+ nickmask = g_strconcat(nick->nick, "!", nick->host, NULL);
+ for (tmp = ignores; tmp != NULL; tmp = tmp->next) {
+ IGNORE_REC *rec = tmp->data;
+
+ if (ignore_match_nickmask(rec, nick->nick, nickmask) &&
+ ignore_match_server(rec, channel->server) &&
+ ignore_match_channel(rec, channel->name))
+ matches = g_slist_append(matches, rec);
+ }
+ g_free_not_null(nickmask);
+
+ if (matches == NULL)
+ g_hash_table_remove(list, nick);
+ else
+ g_hash_table_insert(list, nick, matches);
+}
+
+void ignore_init(void)
+{
+ ignores = NULL;
+ nickmatch = nickmatch_init(ignore_nick_cache, (GDestroyNotify) free_cache_matches);
+ time_tag = g_timeout_add(1000, (GSourceFunc) unignore_timeout, NULL);
+
+ read_ignores();
+ signal_add("setup reread", (SIGNAL_FUNC) read_ignores);
+}
+
+void ignore_deinit(void)
+{
+ g_source_remove(time_tag);
+ while (ignores != NULL)
+ ignore_destroy(ignores->data, TRUE);
+ nickmatch_deinit(nickmatch);
+
+ signal_remove("setup reread", (SIGNAL_FUNC) read_ignores);
+}
diff --git a/src/core/ignore.h b/src/core/ignore.h
new file mode 100644
index 0000000..739514a
--- /dev/null
+++ b/src/core/ignore.h
@@ -0,0 +1,53 @@
+#ifndef IRSSI_CORE_IGNORE_H
+#define IRSSI_CORE_IGNORE_H
+
+#include <irssi/src/core/iregex.h>
+
+typedef struct _IGNORE_REC IGNORE_REC;
+
+struct _IGNORE_REC {
+ int level; /* ignore these levels */
+ char *mask; /* nick mask */
+ char *servertag; /* this is for autoignoring */
+ char **channels; /* ignore only in these channels */
+ char *pattern; /* text body must match this pattern */
+
+ time_t unignore_time; /* time in sec for temp ignores */
+
+ unsigned int exception:1; /* *don't* ignore */
+ unsigned int regexp:1;
+ unsigned int fullword:1;
+ unsigned int replies:1; /* ignore replies to nick in channel */
+ Regex *preg;
+};
+
+extern GSList *ignores;
+
+int ignore_check(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level);
+int ignore_check_flags(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int level, int flags);
+int ignore_check_plus(SERVER_REC *server, const char *nick, const char *host,
+ const char *channel, const char *text, int *level, int test_ignore);
+
+enum {
+ IGNORE_FIND_PATTERN = 0x01, /* Match the pattern */
+ IGNORE_FIND_NOACT = 0x02, /* Exclude the targets with NOACT level */
+ IGNORE_FIND_HIDDEN = 0x04, /* Exclude the targets with HIDDEN level */
+ IGNORE_FIND_NOHILIGHT = 0x08, /* Exclude the targets with NOHILIGHT level */
+};
+
+IGNORE_REC *ignore_find_full (const char *servertag, const char *mask, const char *pattern,
+ char **channels, const int flags);
+
+/* Convenience wrappers around ignore_find_full, for compatibility purpose */
+
+IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels);
+
+void ignore_add_rec(IGNORE_REC *rec);
+void ignore_update_rec(IGNORE_REC *rec);
+
+void ignore_init(void);
+void ignore_deinit(void);
+
+#endif
diff --git a/src/core/iregex-gregex.c b/src/core/iregex-gregex.c
new file mode 100644
index 0000000..60b4ec5
--- /dev/null
+++ b/src/core/iregex-gregex.c
@@ -0,0 +1,165 @@
+#include <string.h>
+
+#include <irssi/src/core/iregex.h>
+
+struct _MatchInfo {
+ const char *valid_string;
+ GMatchInfo *g_match_info;
+};
+
+static const gchar *
+make_valid_utf8(const gchar *text, gboolean *free_ret)
+{
+ GString *str;
+ const gchar *ptr;
+ if (g_utf8_validate(text, -1, NULL)) {
+ if (free_ret)
+ *free_ret = FALSE;
+ return text;
+ }
+
+ str = g_string_sized_new(strlen(text) + 12);
+
+ ptr = text;
+ while (*ptr) {
+ gunichar c = g_utf8_get_char_validated(ptr, -1);
+ /* the unicode is invalid */
+ if (c == (gunichar)-1 || c == (gunichar)-2) {
+ /* encode the byte into PUA-A */
+ g_string_append_unichar(str, (gunichar) (0xfff00 | (*ptr & 0xff)));
+ ptr++;
+ } else {
+ g_string_append_unichar(str, c);
+ ptr = g_utf8_next_char(ptr);
+ }
+ }
+
+ if (free_ret)
+ *free_ret = TRUE;
+ return g_string_free(str, FALSE);
+}
+
+Regex *
+i_regex_new (const gchar *pattern,
+ GRegexCompileFlags compile_options,
+ GRegexMatchFlags match_options,
+ GError **error)
+{
+ const gchar *valid_pattern;
+ gboolean free_valid_pattern;
+ Regex *ret = NULL;
+
+ valid_pattern = make_valid_utf8(pattern, &free_valid_pattern);
+ ret = g_regex_new(valid_pattern, compile_options, match_options, error);
+
+ if (free_valid_pattern)
+ g_free_not_null((gchar *)valid_pattern);
+
+ return ret;
+}
+
+void
+i_regex_unref (Regex *regex)
+{
+ g_regex_unref(regex);
+}
+
+gboolean
+i_regex_match (const Regex *regex,
+ const gchar *string,
+ GRegexMatchFlags match_options,
+ MatchInfo **match_info)
+{
+ gboolean ret;
+ gboolean free_valid_string;
+ const gchar *valid_string = make_valid_utf8(string, &free_valid_string);
+
+ if (match_info != NULL)
+ *match_info = g_new0(MatchInfo, 1);
+
+ ret = g_regex_match(regex, valid_string, match_options,
+ match_info != NULL ? &(*match_info)->g_match_info : NULL);
+
+ if (free_valid_string) {
+ if (match_info != NULL)
+ (*match_info)->valid_string = valid_string;
+ else
+ g_free_not_null((gchar *)valid_string);
+ }
+
+ return ret;
+}
+
+static gsize
+strlen_pua_oddly(const char *str)
+{
+ const gchar *ptr;
+ gsize ret = 0;
+ ptr = str;
+
+ while (*ptr) {
+ const gchar *old;
+ gunichar c = g_utf8_get_char(ptr);
+ old = ptr;
+ ptr = g_utf8_next_char(ptr);
+
+ /* it is our PUA encoded byte */
+ if ((c & 0xfff00) == 0xfff00)
+ ret++;
+ else
+ ret += ptr - old;
+ }
+
+ return ret;
+}
+
+/* new_string should be passed in here from the i_regex_match call.
+ The start_pos and end_pos will then be calculated as if they were on
+ the original string */
+gboolean
+i_match_info_fetch_pos (const MatchInfo *match_info,
+ gint match_num,
+ gint *start_pos,
+ gint *end_pos)
+{
+ gint tmp_start, tmp_end, new_start_pos;
+ gboolean ret;
+
+ if (!match_info->valid_string || (!start_pos && !end_pos))
+ return g_match_info_fetch_pos(match_info->g_match_info,
+ match_num, start_pos, end_pos);
+
+ ret = g_match_info_fetch_pos(match_info->g_match_info,
+ match_num, &tmp_start, &tmp_end);
+ if (start_pos || end_pos) {
+ const gchar *str = match_info->valid_string;
+ gchar *to_start = g_strndup(str, tmp_start);
+ new_start_pos = strlen_pua_oddly(to_start);
+ g_free_not_null(to_start);
+
+ if (start_pos)
+ *start_pos = new_start_pos;
+
+ if (end_pos) {
+ gchar *to_end = g_strndup(str + tmp_start, tmp_end - tmp_start);
+ *end_pos = new_start_pos + strlen_pua_oddly(to_end);
+ g_free_not_null(to_end);
+ }
+ }
+ return ret;
+}
+
+gboolean
+i_match_info_matches (const MatchInfo *match_info)
+{
+ g_return_val_if_fail(match_info != NULL, FALSE);
+
+ return g_match_info_matches(match_info->g_match_info);
+}
+
+void
+i_match_info_free (MatchInfo *match_info)
+{
+ g_match_info_free(match_info->g_match_info);
+ g_free(match_info);
+}
diff --git a/src/core/iregex-regexh.c b/src/core/iregex-regexh.c
new file mode 100644
index 0000000..aae8ecd
--- /dev/null
+++ b/src/core/iregex-regexh.c
@@ -0,0 +1,99 @@
+#include <irssi/src/core/iregex.h>
+
+Regex *
+i_regex_new (const gchar *pattern,
+ GRegexCompileFlags compile_options,
+ GRegexMatchFlags match_options,
+ GError **error)
+{
+ Regex *regex;
+ char *errbuf;
+ int cflags;
+ int errcode, errbuf_len;
+
+ regex = g_new0(Regex, 1);
+ cflags = REG_EXTENDED;
+ if (compile_options & G_REGEX_CASELESS)
+ cflags |= REG_ICASE;
+ if (compile_options & G_REGEX_MULTILINE)
+ cflags |= REG_NEWLINE;
+ if (match_options & G_REGEX_MATCH_NOTBOL)
+ cflags |= REG_NOTBOL;
+ if (match_options & G_REGEX_MATCH_NOTEOL)
+ cflags |= REG_NOTEOL;
+
+ errcode = regcomp(regex, pattern, cflags);
+ if (errcode != 0) {
+ errbuf_len = regerror(errcode, regex, 0, 0);
+ errbuf = g_malloc(errbuf_len);
+ regerror(errcode, regex, errbuf, errbuf_len);
+ g_set_error(error, G_REGEX_ERROR, errcode, "%s", errbuf);
+ g_free(errbuf);
+ g_free(regex);
+ return NULL;
+ } else {
+ return regex;
+ }
+}
+
+void
+i_regex_unref (Regex *regex)
+{
+ regfree(regex);
+ g_free(regex);
+}
+
+gboolean
+i_regex_match (const Regex *regex,
+ const gchar *string,
+ GRegexMatchFlags match_options,
+ MatchInfo **match_info)
+{
+ int groups;
+ int eflags;
+
+ g_return_val_if_fail(regex != NULL, FALSE);
+
+ if (match_info != NULL) {
+ groups = 1 + regex->re_nsub;
+ *match_info = g_new0(MatchInfo, groups);
+ } else {
+ groups = 0;
+ }
+
+ eflags = 0;
+ if (match_options & G_REGEX_MATCH_NOTBOL)
+ eflags |= REG_NOTBOL;
+ if (match_options & G_REGEX_MATCH_NOTEOL)
+ eflags |= REG_NOTEOL;
+
+ return regexec(regex, string, groups, groups ? *match_info : NULL, eflags) == 0;
+}
+
+gboolean
+i_match_info_fetch_pos (const MatchInfo *match_info,
+ gint match_num,
+ gint *start_pos,
+ gint *end_pos)
+{
+ if (start_pos != NULL)
+ *start_pos = match_info[match_num].rm_so;
+ if (end_pos != NULL)
+ *end_pos = match_info[match_num].rm_eo;
+
+ return TRUE;
+}
+
+gboolean
+i_match_info_matches (const MatchInfo *match_info)
+{
+ g_return_val_if_fail(match_info != NULL, FALSE);
+
+ return match_info[0].rm_so != -1;
+}
+
+void
+i_match_info_free (MatchInfo *match_info)
+{
+ g_free(match_info);
+}
diff --git a/src/core/iregex.h b/src/core/iregex.h
new file mode 100644
index 0000000..5326394
--- /dev/null
+++ b/src/core/iregex.h
@@ -0,0 +1,47 @@
+#ifndef IRSSI_CORE_IREGEX_H
+#define IRSSI_CORE_IREGEX_H
+
+#include <irssi/src/common.h>
+
+#ifdef USE_GREGEX
+
+#include <glib.h>
+typedef GRegex Regex;
+typedef struct _MatchInfo MatchInfo;
+
+#else
+
+#include <regex.h>
+typedef regex_t Regex;
+typedef regmatch_t MatchInfo;
+
+#endif
+
+gboolean
+i_match_info_matches (const MatchInfo *match_info);
+
+void
+i_match_info_free (MatchInfo *match_info);
+
+Regex *
+i_regex_new (const gchar *pattern,
+ GRegexCompileFlags compile_options,
+ GRegexMatchFlags match_options,
+ GError **error);
+
+void
+i_regex_unref (Regex *regex);
+
+gboolean
+i_regex_match (const Regex *regex,
+ const gchar *string,
+ GRegexMatchFlags match_options,
+ MatchInfo **match_info);
+
+gboolean
+i_match_info_fetch_pos (const MatchInfo *match_info,
+ gint match_num,
+ gint *start_pos,
+ gint *end_pos);
+
+#endif
diff --git a/src/core/levels.c b/src/core/levels.c
new file mode 100644
index 0000000..62d7a00
--- /dev/null
+++ b/src/core/levels.c
@@ -0,0 +1,199 @@
+/*
+ levels.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/levels.h>
+
+/* the order of these levels must match the bits in levels.h */
+static const char *levels[] = {
+ "CRAP",
+ "MSGS",
+ "PUBLICS",
+ "NOTICES",
+ "SNOTES",
+ "CTCPS",
+ "ACTIONS",
+ "JOINS",
+ "PARTS",
+ "QUITS",
+ "KICKS",
+ "MODES",
+ "TOPICS",
+ "WALLOPS",
+ "INVITES",
+ "NICKS",
+ "DCC",
+ "DCCMSGS",
+ "CLIENTNOTICES",
+ "CLIENTCRAP",
+ "CLIENTERRORS",
+ "HILIGHTS",
+ NULL
+};
+
+int level_get(const char *level)
+{
+ int n, len, match;
+
+ if (g_ascii_strcasecmp(level, "ALL") == 0 || g_strcmp0(level, "*") == 0)
+ return MSGLEVEL_ALL;
+
+ if (g_ascii_strcasecmp(level, "NEVER") == 0)
+ return MSGLEVEL_NEVER;
+
+ if (g_ascii_strcasecmp(level, "NO_ACT") == 0)
+ return MSGLEVEL_NO_ACT;
+
+ if (g_ascii_strcasecmp(level, "NOHILIGHT") == 0)
+ return MSGLEVEL_NOHILIGHT;
+
+ if (g_ascii_strcasecmp(level, "HIDDEN") == 0)
+ return MSGLEVEL_HIDDEN;
+
+ len = strlen(level);
+ if (len == 0) return 0;
+
+ /* partial match allowed, as long as it's the only one that matches */
+ match = 0;
+ for (n = 0; levels[n] != NULL; n++) {
+ if (g_ascii_strncasecmp(levels[n], level, len) == 0) {
+ if ((int)strlen(levels[n]) == len) {
+ /* full match */
+ return 1L << n;
+ }
+ if (match > 0) {
+ /* ambiguous - abort */
+ return 0;
+ }
+ match = 1L << n;
+ }
+ }
+
+ return match;
+}
+
+int level2bits(const char *level, int *errorp)
+{
+ char *orig, *str, *ptr;
+ int ret, singlelevel, negative;
+
+ if (errorp != NULL)
+ *errorp = FALSE;
+
+ g_return_val_if_fail(level != NULL, 0);
+
+ if (*level == '\0')
+ return 0;
+
+ orig = str = g_strdup(level);
+
+ ret = 0;
+ for (ptr = str; ; str++) {
+ if (*str == ' ')
+ *str++ = '\0';
+ else if (*str != '\0')
+ continue;
+
+ negative = *ptr == '-';
+ if (*ptr == '-' || *ptr == '+') ptr++;
+
+ singlelevel = level_get(ptr);
+ if (singlelevel != 0) {
+ ret = !negative ? (ret | singlelevel) :
+ (ret & ~singlelevel);
+ } else if (errorp != NULL)
+ *errorp = TRUE;
+
+ while (*str == ' ') str++;
+ if (*str == '\0') break;
+
+ ptr = str;
+ }
+ g_free(orig);
+
+ return ret;
+}
+
+char *bits2level(int bits)
+{
+ GString *str;
+ char *ret;
+ int n;
+
+ if (bits == 0)
+ return g_strdup("");
+
+
+ str = g_string_new(NULL);
+ if (bits & MSGLEVEL_NEVER)
+ g_string_append(str, "NEVER ");
+
+ if (bits & MSGLEVEL_NO_ACT)
+ g_string_append(str, "NO_ACT ");
+
+ if ((bits & MSGLEVEL_ALL) == MSGLEVEL_ALL) {
+ g_string_append(str, "ALL ");
+ } else {
+ for (n = 0; levels[n] != NULL; n++) {
+ if (bits & (1L << n))
+ g_string_append_printf(str, "%s ", levels[n]);
+ }
+ }
+
+ if (bits & MSGLEVEL_NOHILIGHT)
+ g_string_append(str, "NOHILIGHT ");
+
+ if (bits & MSGLEVEL_HIDDEN)
+ g_string_append(str, "HIDDEN ");
+
+ if (str->len > 0)
+ g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+
+ return ret;
+}
+
+int combine_level(int dest, const char *src)
+{
+ char **list, **item, *itemname;
+ int itemlevel;
+
+ g_return_val_if_fail(src != NULL, dest);
+
+ list = g_strsplit(src, " ", -1);
+ for (item = list; *item != NULL; item++) {
+ itemname = *item + (**item == '+' || **item == '-' || **item == '^' ? 1 : 0);
+ itemlevel = level_get(itemname);
+
+ if (g_ascii_strcasecmp(itemname, "NONE") == 0)
+ dest = 0;
+ else if (**item == '-')
+ dest &= ~(itemlevel);
+ else if (**item == '^')
+ dest ^= itemlevel;
+ else
+ dest |= itemlevel;
+ }
+ g_strfreev(list);
+
+ return dest;
+}
diff --git a/src/core/levels.h b/src/core/levels.h
new file mode 100644
index 0000000..7f1aa93
--- /dev/null
+++ b/src/core/levels.h
@@ -0,0 +1,54 @@
+#ifndef IRSSI_CORE_LEVELS_H
+#define IRSSI_CORE_LEVELS_H
+
+/* This is pretty much IRC specific, but I think it would be easier for
+ other chats to try to use these same levels instead of implementing too
+ difficult message leveling system (which might be done if really
+ needed..). */
+
+/* clang-format off */
+/* Message levels */
+enum {
+ MSGLEVEL_CRAP = 0x0000001,
+ MSGLEVEL_MSGS = 0x0000002,
+ MSGLEVEL_PUBLIC = 0x0000004,
+ MSGLEVEL_NOTICES = 0x0000008,
+ MSGLEVEL_SNOTES = 0x0000010,
+ MSGLEVEL_CTCPS = 0x0000020,
+ MSGLEVEL_ACTIONS = 0x0000040,
+ MSGLEVEL_JOINS = 0x0000080,
+ MSGLEVEL_PARTS = 0x0000100,
+ MSGLEVEL_QUITS = 0x0000200,
+ MSGLEVEL_KICKS = 0x0000400,
+ MSGLEVEL_MODES = 0x0000800,
+ MSGLEVEL_TOPICS = 0x0001000,
+ MSGLEVEL_WALLOPS = 0x0002000,
+ MSGLEVEL_INVITES = 0x0004000,
+ MSGLEVEL_NICKS = 0x0008000,
+ MSGLEVEL_DCC = 0x0010000,
+ MSGLEVEL_DCCMSGS = 0x0020000,
+ MSGLEVEL_CLIENTNOTICE = 0x0040000,
+ MSGLEVEL_CLIENTCRAP = 0x0080000,
+ MSGLEVEL_CLIENTERROR = 0x0100000,
+ MSGLEVEL_HILIGHT = 0x0200000,
+
+ MSGLEVEL_ALL = 0x03fffff,
+
+ MSGLEVEL_NOHILIGHT = 0x1000000, /* Don't highlight this message */
+ MSGLEVEL_NO_ACT = 0x2000000, /* Don't trigger channel activity */
+ MSGLEVEL_NEVER = 0x4000000, /* never ignore / never log */
+ MSGLEVEL_LASTLOG = 0x8000000, /* used for /lastlog */
+
+ MSGLEVEL_HIDDEN = 0x10000000, /* Hidden from view */
+ MSGLEVEL_RESERVED1 = 0x20000000,
+ MSGLEVEL_RESERVED2 = 0x40000000,
+ MSGLEVEL_FORMAT = (int)0x80000000 /* Format data */
+};
+/* clang-format on */
+
+int level_get(const char *level);
+int level2bits(const char *level, int *errorp);
+char *bits2level(int bits);
+int combine_level(int dest, const char *src);
+
+#endif
diff --git a/src/core/line-split.c b/src/core/line-split.c
new file mode 100644
index 0000000..b1e6791
--- /dev/null
+++ b/src/core/line-split.c
@@ -0,0 +1,136 @@
+/*
+ line-split.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>
+
+/* Maximum line length - split to two lines if it's longer than this.
+
+ This is mostly to prevent excessive memory usage. Like if someone DCC
+ chats you, you both have very fast connections and the other side sends
+ you 100 megs of text without any line feeds -> irssi will (try to)
+ allocate 128M of memory for the line and will eventually crash when it
+ can't allocate any more memory. If the line is split at every 64k the
+ text buffer will free the old lines and the memory usage never gets
+ too high. */
+#define MAX_CHARS_IN_LINE 65536
+
+struct _LINEBUF_REC {
+ int len;
+ int alloc;
+ int remove;
+ char *str;
+};
+
+static void linebuf_append(LINEBUF_REC *rec, const char *data, int len)
+{
+ if (rec->len+len > rec->alloc) {
+ rec->alloc = nearest_power(rec->len+len);;
+ rec->str = g_realloc(rec->str, rec->alloc);
+ }
+
+ memcpy(rec->str + rec->len, data, len);
+ rec->len += len;
+}
+
+static char *linebuf_find(LINEBUF_REC *rec, char chr)
+{
+ return memchr(rec->str, chr, rec->len);
+}
+
+static int remove_newline(LINEBUF_REC *rec)
+{
+ char *ptr;
+
+ ptr = linebuf_find(rec, '\n');
+ if (ptr == NULL) {
+ /* LF wasn't found, wait for more data.. */
+ if (rec->len < MAX_CHARS_IN_LINE)
+ return 0;
+
+ /* line buffer is too big - force a newline. */
+ linebuf_append(rec, "\n", 1);
+ ptr = rec->str+rec->len-1;
+ }
+
+ rec->remove = (int) (ptr-rec->str)+1;
+ if (ptr != rec->str && ptr[-1] == '\r') {
+ /* remove CR too. */
+ ptr--;
+ }
+
+ *ptr = '\0';
+ return 1;
+}
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer)
+{
+ LINEBUF_REC *rec;
+ int ret;
+
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(output != NULL, -1);
+ g_return_val_if_fail(buffer != NULL, -1);
+
+ if (*buffer == NULL)
+ *buffer = g_new0(LINEBUF_REC, 1);
+ rec = *buffer;
+
+ if (rec->remove > 0) {
+ rec->len -= rec->remove;
+ memmove(rec->str, rec->str+rec->remove, rec->len);
+ rec->remove = 0;
+ }
+
+ if (len > 0)
+ linebuf_append(rec, data, len);
+ else if (len < 0) {
+ /* connection closed.. */
+ if (rec->len == 0)
+ return -1;
+
+ /* no new data got but still something in buffer.. */
+ if (linebuf_find(rec, '\n') == NULL) {
+ /* connection closed and last line is missing \n ..
+ just add it so we can see if it had
+ anything useful.. */
+ linebuf_append(rec, "\n", 1);
+ }
+ }
+
+ ret = remove_newline(rec);
+ *output = rec->str;
+ return ret;
+}
+
+void line_split_free(LINEBUF_REC *buffer)
+{
+ if (buffer != NULL) {
+ if (buffer->str != NULL) g_free(buffer->str);
+ g_free(buffer);
+ }
+}
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer)
+{
+ return buffer->len == 0;
+}
diff --git a/src/core/line-split.h b/src/core/line-split.h
new file mode 100644
index 0000000..cf95742
--- /dev/null
+++ b/src/core/line-split.h
@@ -0,0 +1,11 @@
+#ifndef IRSSI_CORE_LINE_SPLIT_H
+#define IRSSI_CORE_LINE_SPLIT_H
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer);
+void line_split_free(LINEBUF_REC *buffer);
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer);
+
+#endif
diff --git a/src/core/log-away.c b/src/core/log-away.c
new file mode 100644
index 0000000..d6dac4d
--- /dev/null
+++ b/src/core/log-away.c
@@ -0,0 +1,127 @@
+/*
+ log-away.c : Awaylog handling
+
+ 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/levels.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/write-buffer.h>
+
+static LOG_REC *awaylog;
+static int away_filepos;
+static int away_msgs;
+
+static void sig_log_written(LOG_REC *log)
+{
+ if (log != awaylog) return;
+
+ away_msgs++;
+}
+
+static void awaylog_open(void)
+{
+ const char *fname;
+ LOG_REC *log;
+ int level;
+
+ fname = settings_get_str("awaylog_file");
+ level = settings_get_level("awaylog_level");
+ if (*fname == '\0' || level == 0) return;
+
+ log = log_find(fname);
+ if (log != NULL && log->handle != -1)
+ return; /* already open */
+
+ if (log == NULL) {
+ log = log_create_rec(fname, level);
+ log->temp = TRUE;
+ log_update(log);
+ }
+
+ if (!log_start_logging(log)) {
+ /* creating log file failed? close it. */
+ log_close(log);
+ return;
+ }
+
+ /* Flush the dirty buffers to disk before acquiring the file position */
+ write_buffer_flush();
+
+ awaylog = log;
+ away_filepos = lseek(log->handle, 0, SEEK_CUR);
+ away_msgs = 0;
+}
+
+static void awaylog_close(void)
+{
+ const char *fname;
+ LOG_REC *log;
+
+ fname = settings_get_str("awaylog_file");
+ if (*fname == '\0') return;
+
+ log = log_find(fname);
+ if (log == NULL || log->handle == -1) {
+ /* awaylog not open */
+ return;
+ }
+
+ if (awaylog == log) awaylog = NULL;
+
+ /* Flush the dirty buffers to disk before showing the away log */
+ write_buffer_flush();
+
+ signal_emit("awaylog show", 3, log, GINT_TO_POINTER(away_msgs),
+ GINT_TO_POINTER(away_filepos));
+ log_close(log);
+}
+
+static void sig_away_changed(SERVER_REC *server)
+{
+ if (server->usermode_away)
+ awaylog_open();
+ else
+ awaylog_close();
+}
+
+void log_away_init(void)
+{
+ char *awaylog_file;
+
+ awaylog = NULL;
+ away_filepos = 0;
+ away_msgs = 0;
+
+ awaylog_file = g_strconcat(get_irssi_dir(), "/away.log", NULL);
+ settings_add_str("log", "awaylog_file", awaylog_file);
+ g_free(awaylog_file);
+ settings_add_level("log", "awaylog_level", "msgs hilight");
+
+ signal_add("log written", (SIGNAL_FUNC) sig_log_written);
+ signal_add("away mode changed", (SIGNAL_FUNC) sig_away_changed);
+}
+
+void log_away_deinit(void)
+{
+ signal_remove("log written", (SIGNAL_FUNC) sig_log_written);
+ signal_remove("away mode changed", (SIGNAL_FUNC) sig_away_changed);
+}
diff --git a/src/core/log.c b/src/core/log.c
new file mode 100644
index 0000000..5bc953a
--- /dev/null
+++ b/src/core/log.c
@@ -0,0 +1,617 @@
+/*
+ log.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/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/write-buffer.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#define DEFAULT_LOG_FILE_CREATE_MODE 600
+
+GSList *logs;
+int log_file_create_mode;
+int log_dir_create_mode;
+
+static const char *log_item_types[] = {
+ "target",
+ "window",
+
+ NULL
+};
+
+static char *log_timestamp;
+static int rotate_tag;
+
+static int log_item_str2type(const char *type)
+{
+ int n;
+
+ for (n = 0; log_item_types[n] != NULL; n++) {
+ if (g_ascii_strcasecmp(log_item_types[n], type) == 0)
+ return n;
+ }
+
+ return -1;
+}
+
+static void log_write_timestamp(int handle, const char *format,
+ const char *text, time_t stamp)
+{
+ struct tm *tm;
+ char str[256];
+
+ g_return_if_fail(format != NULL);
+ if (*format == '\0') return;
+
+ tm = localtime(&stamp);
+ if (strftime(str, sizeof(str), format, tm) > 0)
+ write_buffer(handle, str, strlen(str));
+ if (text != NULL) write_buffer(handle, text, strlen(text));
+}
+
+static char *log_filename(LOG_REC *log)
+{
+ char *str, fname[1024];
+ struct tm *tm;
+ size_t ret;
+ time_t now;
+
+ now = time(NULL);
+ tm = localtime(&now);
+
+ str = convert_home(log->fname);
+ ret = strftime(fname, sizeof(fname), str, tm);
+ g_free(str);
+
+ if (ret <= 0) {
+ g_warning("log_filename() : strftime() failed");
+ return NULL;
+ }
+
+ return g_strdup(fname);
+}
+
+int log_start_logging(LOG_REC *log)
+{
+ char *dir;
+ struct flock lock;
+
+ g_return_val_if_fail(log != NULL, FALSE);
+
+ if (log->handle != -1)
+ return TRUE;
+
+ /* Append/create log file */
+ g_free_not_null(log->real_fname);
+ log->real_fname = log_filename(log);
+
+ if (log->real_fname != NULL &&
+ g_strcmp0(log->real_fname, log->fname) != 0) {
+ /* path may contain variables (%time, $vars),
+ make sure the directory is created */
+ dir = g_path_get_dirname(log->real_fname);
+#ifdef HAVE_CAPSICUM
+ capsicum_mkdir_with_parents_wrapper(dir, log_dir_create_mode);
+#else
+ g_mkdir_with_parents(dir, log_dir_create_mode);
+#endif
+ g_free(dir);
+ }
+
+#ifdef HAVE_CAPSICUM
+ log->handle = log->real_fname == NULL ? -1 :
+ capsicum_open_wrapper(log->real_fname, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+#else
+ log->handle = log->real_fname == NULL ? -1 :
+ open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+#endif
+ if (log->handle == -1) {
+ signal_emit("log create failed", 1, log);
+ log->failed = TRUE;
+ return FALSE;
+ }
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) {
+ close(log->handle);
+ log->handle = -1;
+ signal_emit("log locked", 1, log);
+ log->failed = TRUE;
+ return FALSE;
+ }
+ lseek(log->handle, 0, SEEK_END);
+
+ log->opened = log->last = time(NULL);
+ log_write_timestamp(log->handle,
+ settings_get_str("log_open_string"),
+ "\n", log->last);
+
+ signal_emit("log started", 1, log);
+ log->failed = FALSE;
+ return TRUE;
+}
+
+void log_stop_logging(LOG_REC *log)
+{
+ struct flock lock;
+
+ g_return_if_fail(log != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ signal_emit("log stopped", 1, log);
+
+ log_write_timestamp(log->handle,
+ settings_get_str("log_close_string"),
+ "\n", time(NULL));
+
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ fcntl(log->handle, F_SETLK, &lock);
+
+ write_buffer_flush();
+ close(log->handle);
+ log->handle = -1;
+}
+
+static void log_rotate_check(LOG_REC *log)
+{
+ char *new_fname;
+
+ g_return_if_fail(log != NULL);
+
+ if (log->handle == -1 || log->real_fname == NULL)
+ return;
+
+ new_fname = log_filename(log);
+ if (g_strcmp0(new_fname, log->real_fname) != 0) {
+ /* rotate log */
+ log_stop_logging(log);
+ signal_emit("log rotated", 1, log);
+
+ log_start_logging(log);
+ }
+ g_free(new_fname);
+}
+
+void log_write_rec(LOG_REC *log, const char *str, int level, time_t now)
+{
+ char *colorstr;
+ struct tm *tm;
+ int hour, day;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(str != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ if (now == (time_t) -1)
+ now = time(NULL);
+ tm = localtime(&now);
+ hour = tm->tm_hour;
+ day = tm->tm_mday;
+
+ tm = localtime(&log->last);
+ day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */
+ if (tm->tm_hour != hour) {
+ /* hour changed, check if we need to rotate log file */
+ log_rotate_check(log);
+ }
+
+ if (day != 0) {
+ /* day changed */
+ log_write_timestamp(log->handle,
+ settings_get_str("log_day_changed"),
+ "\n", now);
+ }
+
+ log->last = now;
+
+ if (log->colorizer == NULL)
+ colorstr = NULL;
+ else
+ str = colorstr = log->colorizer(str);
+
+ if ((level & MSGLEVEL_LASTLOG) == 0)
+ log_write_timestamp(log->handle, log_timestamp, str, now);
+ else
+ write_buffer(log->handle, str, strlen(str));
+ write_buffer(log->handle, "\n", 1);
+
+ signal_emit("log written", 2, log, str);
+
+ g_free_not_null(colorstr);
+}
+
+static int itemcmp(const char *patt, const char *item)
+{
+ /* returns 0 on match, nonzero otherwise */
+
+ if (!g_strcmp0(patt, "*"))
+ return 0;
+ return item ? g_ascii_strcasecmp(patt, item) : 1;
+}
+
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+ const char *servertag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(log != NULL, NULL);
+
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ LOG_ITEM_REC *rec = tmp->data;
+
+ if (rec->type == type && itemcmp(rec->name, item) == 0 &&
+ (rec->servertag == NULL || (servertag != NULL &&
+ g_ascii_strcasecmp(rec->servertag, servertag) == 0)))
+ return rec;
+ }
+
+ return NULL;
+}
+
+void log_file_write(const char *server_tag, const char *item, int level, time_t t, const char *str,
+ int no_fallbacks)
+{
+ GSList *tmp, *fallbacks;
+ char *tmpstr;
+ int found;
+
+ g_return_if_fail(str != NULL);
+
+ if (logs == NULL)
+ return;
+
+ fallbacks = NULL; found = FALSE;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (rec->handle == -1)
+ continue; /* log not opened yet */
+
+ if ((level & rec->level) == 0)
+ continue;
+
+ if (rec->items == NULL)
+ fallbacks = g_slist_append(fallbacks, rec);
+ else if (log_item_find(rec, LOG_ITEM_TARGET, item,
+ server_tag) != NULL)
+ log_write_rec(rec, str, level, t);
+ }
+
+ if (!found && !no_fallbacks && fallbacks != NULL) {
+ /* not found from any items, so write it to all main logs */
+ tmpstr = (level & MSGLEVEL_PUBLIC) && item != NULL ?
+ g_strconcat(item, ": ", str, NULL) :
+ g_strdup(str);
+
+ for (tmp = fallbacks; tmp != NULL; tmp = tmp->next)
+ log_write_rec(tmp->data, tmpstr, level, t);
+
+ g_free(tmpstr);
+ }
+ g_slist_free(fallbacks);
+}
+
+LOG_REC *log_find(const char *fname)
+{
+ GSList *tmp;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (g_strcmp0(rec->fname, fname) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent)
+{
+ GSList *tmp;
+ CONFIG_NODE *node;
+
+ parent = iconfig_node_section(parent, "items", NODE_TYPE_LIST);
+ for (tmp = log->items; tmp != NULL; tmp = tmp->next) {
+ LOG_ITEM_REC *rec = tmp->data;
+
+ node = iconfig_node_section(parent, NULL, NODE_TYPE_BLOCK);
+ iconfig_node_set_str(node, "type", log_item_types[rec->type]);
+ iconfig_node_set_str(node, "name", rec->name);
+ iconfig_node_set_str(node, "server", rec->servertag);
+ }
+}
+
+static void log_update_config(LOG_REC *log)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ if (log->temp)
+ return;
+
+ node = iconfig_node_traverse("logs", TRUE);
+ node = iconfig_node_section(node, log->fname, NODE_TYPE_BLOCK);
+
+ if (log->autoopen)
+ iconfig_node_set_bool(node, "auto_open", TRUE);
+ else
+ iconfig_node_set_str(node, "auto_open", NULL);
+
+ levelstr = bits2level(log->level);
+ iconfig_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+
+ iconfig_node_set_str(node, "items", NULL);
+
+ if (log->items != NULL)
+ log_items_update_config(log, node);
+
+ signal_emit("log config save", 2, log, node);
+}
+
+static void log_remove_config(LOG_REC *log)
+{
+ iconfig_set_str("logs", log->fname, NULL);
+}
+
+LOG_REC *log_create_rec(const char *fname, int level)
+{
+ LOG_REC *rec;
+
+ g_return_val_if_fail(fname != NULL, NULL);
+
+ rec = log_find(fname);
+ if (rec == NULL) {
+ rec = g_new0(LOG_REC, 1);
+ rec->fname = g_strdup(fname);
+ rec->real_fname = log_filename(rec);
+ rec->handle = -1;
+ }
+
+ rec->level = level;
+ return rec;
+}
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+ const char *servertag)
+{
+ LOG_ITEM_REC *rec;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(name != NULL);
+
+ if (log_item_find(log, type, name, servertag))
+ return;
+
+ rec = g_new0(LOG_ITEM_REC, 1);
+ rec->type = type;
+ rec->name = g_strdup(name);
+ rec->servertag = g_strdup(servertag);
+
+ log->items = g_slist_append(log->items, rec);
+}
+
+void log_update(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log_find(log->fname) == NULL) {
+ logs = g_slist_append(logs, log);
+ log->handle = -1;
+ }
+
+ log_update_config(log);
+ signal_emit("log new", 1, log);
+}
+
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item)
+{
+ log->items = g_slist_remove(log->items, item);
+
+ g_free(item->name);
+ g_free_not_null(item->servertag);
+ g_free(item);
+}
+
+static void log_destroy(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log->handle != -1)
+ log_stop_logging(log);
+
+ logs = g_slist_remove(logs, log);
+ signal_emit("log remove", 1, log);
+
+ while (log->items != NULL)
+ log_item_destroy(log, log->items->data);
+ g_free(log->fname);
+ g_free_not_null(log->real_fname);
+ g_free(log);
+}
+
+void log_close(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ log_remove_config(log);
+ log_destroy(log);
+}
+
+static int sig_rotate_check(void)
+{
+ static int last_hour = -1;
+ struct tm tm;
+ time_t now;
+
+ /* don't do anything until hour is changed */
+ now = time(NULL);
+ memcpy(&tm, localtime(&now), sizeof(tm));
+ if (tm.tm_hour != last_hour) {
+ last_hour = tm.tm_hour;
+ g_slist_foreach(logs, (GFunc) log_rotate_check, NULL);
+ }
+ return 1;
+}
+
+static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log)
+{
+ LOG_ITEM_REC *rec;
+ GSList *tmp;
+ char *item;
+ int type;
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ item = config_node_get_str(node, "name", NULL);
+ type = log_item_str2type(config_node_get_str(node, "type", NULL));
+ if (item == NULL || type == -1)
+ continue;
+
+ rec = g_new0(LOG_ITEM_REC, 1);
+ rec->type = type;
+ rec->name = g_strdup(item);
+ rec->servertag = g_strdup(config_node_get_str(node, "server", NULL));
+
+ log->items = g_slist_append(log->items, rec);
+ }
+}
+
+static void log_read_config(void)
+{
+ CONFIG_NODE *node;
+ LOG_REC *log;
+ GSList *tmp, *next, *fnames;
+
+ /* close old logs, save list of open logs */
+ fnames = NULL;
+ for (tmp = logs; tmp != NULL; tmp = next) {
+ log = tmp->data;
+
+ next = tmp->next;
+ if (log->temp)
+ continue;
+
+ if (log->handle != -1)
+ fnames = g_slist_append(fnames, g_strdup(log->fname));
+ log_destroy(log);
+ }
+
+ node = iconfig_node_traverse("logs", 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;
+
+ log = g_new0(LOG_REC, 1);
+ logs = g_slist_append(logs, log);
+
+ log->handle = -1;
+ log->fname = g_strdup(node->key);
+ log->autoopen = config_node_get_bool(node, "auto_open", FALSE);
+ log->level = level2bits(config_node_get_str(node, "level", 0), NULL);
+
+ signal_emit("log config read", 2, log, node);
+
+ node = iconfig_node_section(node, "items", -1);
+ if (node != NULL)
+ log_items_read_config(node, log);
+
+ if (log->autoopen || i_slist_find_string(fnames, log->fname))
+ log_start_logging(log);
+ }
+
+ g_slist_foreach(fnames, (GFunc) g_free, NULL);
+ g_slist_free(fnames);
+}
+
+static void read_settings(void)
+{
+ g_free_not_null(log_timestamp);
+ log_timestamp = g_strdup(settings_get_str("log_timestamp"));
+
+ log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
+ log_dir_create_mode = log_file_create_mode;
+ if (log_file_create_mode & 0400) log_dir_create_mode |= 0100;
+ if (log_file_create_mode & 0040) log_dir_create_mode |= 0010;
+ if (log_file_create_mode & 0004) log_dir_create_mode |= 0001;
+}
+
+void log_init(void)
+{
+ rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL);
+ logs = NULL;
+
+ settings_add_int("log", "log_create_mode",
+ DEFAULT_LOG_FILE_CREATE_MODE);
+ settings_add_str("log", "log_timestamp", "%H:%M ");
+ settings_add_str("log", "log_open_string",
+ "--- Log opened %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_close_string",
+ "--- Log closed %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_day_changed",
+ "--- Day changed %a %b %d %Y");
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) log_read_config);
+ signal_add("irssi init finished", (SIGNAL_FUNC) log_read_config);
+}
+
+void log_deinit(void)
+{
+ g_source_remove(rotate_tag);
+
+ while (logs != NULL)
+ log_close(logs->data);
+
+ g_free_not_null(log_timestamp);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) log_read_config);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) log_read_config);
+}
diff --git a/src/core/log.h b/src/core/log.h
new file mode 100644
index 0000000..19bca4f
--- /dev/null
+++ b/src/core/log.h
@@ -0,0 +1,64 @@
+#ifndef IRSSI_CORE_LOG_H
+#define IRSSI_CORE_LOG_H
+
+enum {
+ LOG_ITEM_TARGET, /* channel, query, .. */
+ LOG_ITEM_WINDOW_REFNUM
+};
+
+typedef char *(*COLORIZE_FUNC)(const char *str);
+
+typedef struct _LOG_REC LOG_REC;
+typedef struct _LOG_ITEM_REC LOG_ITEM_REC;
+
+struct _LOG_ITEM_REC {
+ int type;
+ char *name;
+ char *servertag;
+};
+
+struct _LOG_REC {
+ char *fname; /* file name, in strftime() format */
+ char *real_fname; /* the current expanded file name */
+ int handle; /* file handle */
+ time_t opened;
+
+ int level; /* log only these levels */
+ GSList *items; /* log only on these items */
+
+ time_t last; /* when last message was written */
+ COLORIZE_FUNC colorizer;
+
+ unsigned int autoopen:1; /* automatically start logging at startup */
+ unsigned int failed:1; /* opening log failed last time */
+ unsigned int temp:1; /* don't save this to config file */
+};
+
+extern GSList *logs;
+extern int log_file_create_mode;
+extern int log_dir_create_mode;
+
+/* Create log record - you still need to call log_update() to actually add it
+ into log list */
+LOG_REC *log_create_rec(const char *fname, int level);
+void log_update(LOG_REC *log);
+void log_close(LOG_REC *log);
+LOG_REC *log_find(const char *fname);
+
+void log_item_add(LOG_REC *log, int type, const char *name,
+ const char *servertag);
+void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item);
+LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item,
+ const char *servertag);
+
+void log_file_write(const char *server_tag, const char *item, int level, time_t t, const char *str,
+ int no_fallbacks);
+void log_write_rec(LOG_REC *log, const char *str, int level, time_t now);
+
+int log_start_logging(LOG_REC *log);
+void log_stop_logging(LOG_REC *log);
+
+void log_init(void);
+void log_deinit(void);
+
+#endif
diff --git a/src/core/masks.c b/src/core/masks.c
new file mode 100644
index 0000000..a84ac44
--- /dev/null
+++ b/src/core/masks.c
@@ -0,0 +1,134 @@
+/*
+ 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/core/misc.h>
+
+#include <irssi/src/core/servers.h>
+
+/* Returns TRUE if mask contains '!' ie. address should be checked too.
+ Also checks if mask contained any wildcards. */
+static int check_address(const char *mask, int *wildcards)
+{
+ int ret;
+
+ *wildcards = FALSE;
+ ret = FALSE;
+ while (*mask != '\0') {
+ if (*mask == '!') {
+ if (*wildcards) return TRUE;
+ ret = TRUE;
+ }
+
+ if (*mask == '?' || *mask == '*') {
+ *wildcards = TRUE;
+ if (ret) return TRUE;
+ }
+ mask++;
+ }
+
+ return ret;
+}
+
+static int check_mask(SERVER_REC *server, const char *mask,
+ const char *str, int wildcards)
+{
+ if (server != NULL && server->mask_match_func != NULL) {
+ /* use server specified mask match function */
+ return server->mask_match_func(mask, str);
+ }
+
+ return wildcards ? match_wildcards(mask, str) :
+ g_ascii_strcasecmp(mask, str) == 0;
+}
+
+int mask_match(SERVER_REC *server, const char *mask,
+ const char *nick, const char *user, const char *host)
+{
+ char *str;
+ int ret, wildcards;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(mask != NULL && nick != NULL &&
+ user != NULL && host != NULL, FALSE);
+
+ str = !check_address(mask, &wildcards) ? (char *) nick :
+ g_strdup_printf("%s!%s@%s", nick, user, host);
+ ret = check_mask(server, mask, str, wildcards);
+ if (str != nick) g_free(str);
+
+ return ret;
+}
+
+int mask_match_address(SERVER_REC *server, const char *mask,
+ const char *nick, const char *address)
+{
+ char *str;
+ int ret, wildcards;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(mask != NULL && nick != NULL, FALSE);
+ if (address == NULL) address = "";
+
+ str = !check_address(mask, &wildcards) ? (char *) nick :
+ g_strdup_printf("%s!%s", nick, address);
+ ret = check_mask(server, mask, str, wildcards);
+ if (str != nick) g_free(str);
+
+ return ret;
+}
+
+int masks_match(SERVER_REC *server, const char *masks,
+ const char *nick, const char *address)
+{
+ int (*mask_match_func)(const char *, const char *);
+ char **list, **tmp, *mask;
+ int found;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE);
+ g_return_val_if_fail(masks != NULL &&
+ nick != NULL && address != NULL, FALSE);
+
+ if (*masks == '\0')
+ return FALSE;
+
+ mask_match_func = server != NULL && server->mask_match_func != NULL ?
+ server->mask_match_func : match_wildcards;
+
+ found = FALSE;
+ mask = g_strdup_printf("%s!%s", nick, address);
+ list = g_strsplit(masks, " ", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ if (g_ascii_strcasecmp(*tmp, nick) == 0) {
+ found = TRUE;
+ break;
+ }
+
+ if (mask_match_func(*tmp, mask)) {
+ found = TRUE;
+ break;
+ }
+ }
+ g_strfreev(list);
+ g_free(mask);
+
+ return found;
+}
diff --git a/src/core/masks.h b/src/core/masks.h
new file mode 100644
index 0000000..e3a0437
--- /dev/null
+++ b/src/core/masks.h
@@ -0,0 +1,11 @@
+#ifndef IRSSI_CORE_MASKS_H
+#define IRSSI_CORE_MASKS_H
+
+int mask_match(SERVER_REC *server, const char *mask,
+ const char *nick, const char *user, const char *host);
+int mask_match_address(SERVER_REC *server, const char *mask,
+ const char *nick, const char *address);
+int masks_match(SERVER_REC *server, const char *masks,
+ const char *nick, const char *address);
+
+#endif
diff --git a/src/core/meson.build b/src/core/meson.build
new file mode 100644
index 0000000..c0e9b90
--- /dev/null
+++ b/src/core/meson.build
@@ -0,0 +1,131 @@
+# this file is part of irssi
+
+if want_gregex
+ regex_impl = files('iregex-gregex.c')
+else
+ regex_impl = files('iregex-regexh.c')
+endif
+
+if have_capsicum
+ core_capsicum_source = files('capsicum.c')
+else
+ core_capsicum_source = []
+endif
+
+libcore_a = static_library('core',
+ files(
+ 'args.c',
+ 'channels-setup.c',
+ 'channels.c',
+ 'chat-commands.c',
+ 'chat-protocols.c',
+ 'chatnets.c',
+ 'commands.c',
+ 'core.c',
+ 'expandos.c',
+ 'ignore.c',
+ 'levels.c',
+ 'line-split.c',
+ 'log-away.c',
+ 'log.c',
+ 'masks.c',
+ 'misc.c',
+ 'modules-load.c',
+ 'modules.c',
+ 'net-disconnect.c',
+ 'net-nonblock.c',
+ 'net-sendbuffer.c',
+ 'network-openssl.c',
+ 'network.c',
+ 'nicklist.c',
+ 'nickmatch-cache.c',
+ 'pidwait.c',
+ 'queries.c',
+ 'rawlog.c',
+ 'recode.c',
+ 'refstrings.c',
+ 'servers-reconnect.c',
+ 'servers-setup.c',
+ 'servers.c',
+ 'session.c',
+ 'settings.c',
+ 'signals.c',
+ 'special-vars.c',
+ 'tls.c',
+ 'utf8.c',
+ 'wcwidth-wrapper.c',
+ 'wcwidth.c',
+ 'write-buffer.c',
+ )
+ + core_capsicum_source
+ + regex_impl
+ + [
+ default_config_h,
+ irssi_version_h,
+ ],
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ c_args : [
+ def_moduledir,
+ def_sysconfdir,
+ ],
+ dependencies : dep)
+
+install_headers(
+ files(
+ #### structure_headers ####
+ 'channel-rec.h',
+ 'channel-setup-rec.h',
+ 'chatnet-rec.h',
+ 'query-rec.h',
+ 'server-connect-rec.h',
+ 'server-rec.h',
+ 'server-setup-rec.h',
+ 'window-item-rec.h',
+
+ ####
+ 'args.h',
+ 'capsicum.h',
+ 'channels-setup.h',
+ 'channels.h',
+ 'chat-protocols.h',
+ 'chatnets.h',
+ 'commands.h',
+ 'core.h',
+ 'expandos.h',
+ 'ignore.h',
+ 'iregex.h',
+ 'levels.h',
+ 'line-split.h',
+ 'log.h',
+ 'masks.h',
+ 'misc.h',
+ 'module.h',
+ 'modules-load.h',
+ 'modules.h',
+ 'net-disconnect.h',
+ 'net-nonblock.h',
+ 'net-sendbuffer.h',
+ 'network-openssl.h',
+ 'network.h',
+ 'nick-rec.h',
+ 'nicklist.h',
+ 'nickmatch-cache.h',
+ 'pidwait.h',
+ 'queries.h',
+ 'rawlog.h',
+ 'recode.h',
+ 'refstrings.h',
+ 'servers-reconnect.h',
+ 'servers-setup.h',
+ 'servers.h',
+ 'session.h',
+ 'settings.h',
+ 'signals.h',
+ 'special-vars.h',
+ 'tls.h',
+ 'utf8.h',
+ 'window-item-def.h',
+ 'write-buffer.h',
+ ),
+ subdir : incdir / 'src' / 'core')
diff --git a/src/core/misc.c b/src/core/misc.c
new file mode 100644
index 0000000..f0bbf4e
--- /dev/null
+++ b/src/core/misc.c
@@ -0,0 +1,1094 @@
+/*
+ misc.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/commands.h>
+
+typedef struct {
+ int condition;
+ GInputFunction function;
+ void *data;
+} IRSSI_INPUT_REC;
+
+static int irssi_io_invoke(GIOChannel *source, GIOCondition condition,
+ void *data)
+{
+ IRSSI_INPUT_REC *rec = data;
+ int icond = 0;
+
+ if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+ /* error, we have to call the function.. */
+ if (rec->condition & G_IO_IN)
+ icond |= I_INPUT_READ;
+ else
+ icond |= I_INPUT_WRITE;
+ }
+
+ if (condition & (G_IO_IN | G_IO_PRI))
+ icond |= I_INPUT_READ;
+ if (condition & G_IO_OUT)
+ icond |= I_INPUT_WRITE;
+
+ if (rec->condition & icond)
+ rec->function(rec->data, source, icond);
+
+ return TRUE;
+}
+
+int i_input_add_full(GIOChannel *source, int priority, int condition, GInputFunction function,
+ void *data)
+{
+ IRSSI_INPUT_REC *rec;
+ unsigned int result;
+ GIOCondition cond;
+
+ rec = g_new(IRSSI_INPUT_REC, 1);
+ rec->condition = condition;
+ rec->function = function;
+ rec->data = data;
+
+ cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL);
+ if (condition & I_INPUT_READ)
+ cond |= G_IO_IN|G_IO_PRI;
+ if (condition & I_INPUT_WRITE)
+ cond |= G_IO_OUT;
+
+ result = g_io_add_watch_full(source, priority, cond,
+ irssi_io_invoke, rec, g_free);
+
+ return result;
+}
+
+int i_input_add(GIOChannel *source, int condition, GInputFunction function, void *data)
+{
+ return i_input_add_full(source, G_PRIORITY_DEFAULT, condition, function, data);
+}
+
+/* easy way to bypass glib polling of io channel internal buffer */
+int i_input_add_poll(int fd, int priority, int condition, GInputFunction function, void *data)
+{
+ GIOChannel *source = g_io_channel_unix_new(fd);
+ int ret = i_input_add_full(source, priority, condition, function, data);
+ g_io_channel_unref(source);
+ return ret;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+ if (tv1->tv_sec < tv2->tv_sec)
+ return -1;
+ if (tv1->tv_sec > tv2->tv_sec)
+ return 1;
+
+ return tv1->tv_usec < tv2->tv_usec ? -1 :
+ tv1->tv_usec > tv2->tv_usec ? 1 : 0;
+}
+
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+ long secs, usecs;
+
+ secs = tv1->tv_sec - tv2->tv_sec;
+ usecs = tv1->tv_usec - tv2->tv_usec;
+ if (usecs < 0) {
+ usecs += 1000000;
+ secs--;
+ }
+ usecs = usecs/1000 + secs * 1000;
+
+ return usecs;
+}
+#pragma GCC diagnostic pop
+
+#if GLIB_CHECK_VERSION(2, 56, 0)
+/* nothing */
+#else
+/* compatibility code for old GLib */
+GDateTime *g_date_time_new_from_iso8601(const gchar *iso_date, GTimeZone *default_tz)
+{
+ GTimeVal time;
+ if (g_time_val_from_iso8601(iso_date, &time)) {
+ return g_date_time_new_from_timeval_utc(&time);
+ } else {
+ return NULL;
+ }
+}
+#endif
+
+int find_substr(const char *list, const char *item)
+{
+ const char *ptr;
+
+ g_return_val_if_fail(list != NULL, FALSE);
+ g_return_val_if_fail(item != NULL, FALSE);
+
+ if (*item == '\0')
+ return FALSE;
+
+ for (;;) {
+ while (i_isspace(*list)) list++;
+ if (*list == '\0') break;
+
+ ptr = strchr(list, ' ');
+ if (ptr == NULL) ptr = list+strlen(list);
+
+ if (g_ascii_strncasecmp(list, item, ptr-list) == 0 &&
+ item[ptr-list] == '\0')
+ return TRUE;
+
+ list = ptr;
+ }
+
+ return FALSE;
+}
+
+int strarray_find(char **array, const char *item)
+{
+ char **tmp;
+ int index;
+
+ g_return_val_if_fail(array != NULL, -1);
+ g_return_val_if_fail(item != NULL, -1);
+
+ index = 0;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ if (g_ascii_strcasecmp(*tmp, item) == 0)
+ return index;
+ }
+
+ return -1;
+}
+
+GSList *i_slist_find_string(GSList *list, const char *key)
+{
+ for (; list != NULL; list = list->next)
+ if (g_strcmp0(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GSList *i_slist_find_icase_string(GSList *list, const char *key)
+{
+ for (; list != NULL; list = list->next)
+ if (g_ascii_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+void *i_slist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data)
+{
+ void *ret;
+
+ while (list != NULL) {
+ ret = func(list->data, (void *) data);
+ if (ret != NULL) return ret;
+
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+void i_slist_free_full(GSList *list, GDestroyNotify free_func)
+{
+ GSList *tmp;
+
+ if (list == NULL)
+ return;
+
+ for (tmp = list; tmp != NULL; tmp = tmp->next)
+ free_func(tmp->data);
+
+ g_slist_free(list);
+}
+
+GSList *i_slist_remove_string(GSList *list, const char *str)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
+ if (l != NULL)
+ return g_slist_remove_link(list, l);
+
+ return list;
+}
+
+GSList *i_slist_delete_string(GSList *list, const char *str, GDestroyNotify free_func)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
+ if (l != NULL) {
+ free_func(l->data);
+ return g_slist_delete_link(list, l);
+ }
+
+ return list;
+}
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
+{
+ GString *str;
+ char **data, *ret;
+
+ str = g_string_new(NULL);
+ while (list != NULL) {
+ data = G_STRUCT_MEMBER_P(list->data, offset);
+
+ if (str->len != 0) g_string_append(str, delimiter);
+ g_string_append(str, *data);
+ list = list->next;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* `list' contains char* */
+char *i_slist_to_string(GSList *list, const char *delimiter)
+{
+ GString *str;
+ char *ret;
+
+ str = g_string_new(NULL);
+ while (list != NULL) {
+ if (str->len != 0) g_string_append(str, delimiter);
+ g_string_append(str, list->data);
+
+ list = list->next;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* remove all the options from the optlist hash table that are valid for the
+ * command cmd */
+GList *optlist_remove_known(const char *cmd, GHashTable *optlist)
+{
+ GList *list, *tmp, *next;
+
+ list = g_hash_table_get_keys(optlist);
+ if (cmd != NULL && list != NULL) {
+ for (tmp = list; tmp != NULL; tmp = next) {
+ char *option = tmp->data;
+ next = tmp->next;
+
+ if (command_have_option(cmd, option))
+ list = g_list_remove(list, option);
+ }
+ }
+
+ return list;
+}
+
+GList *i_list_find_string(GList *list, const char *key)
+{
+ for (; list != NULL; list = list->next)
+ if (g_strcmp0(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GList *i_list_find_icase_string(GList *list, const char *key)
+{
+ for (; list != NULL; list = list->next)
+ if (g_ascii_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+char *stristr(const char *data, const char *key)
+{
+ const char *max;
+ int keylen, datalen, pos;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+ if (keylen == 0)
+ return (char *) data;
+
+ max = data+datalen-keylen;
+ pos = 0;
+ while (data <= max) {
+ if (key[pos] == '\0')
+ return (char *) data;
+
+ if (i_toupper(data[pos]) == i_toupper(key[pos]))
+ pos++;
+ else {
+ data++;
+ pos = 0;
+ }
+ }
+
+ return NULL;
+}
+
+#define isbound(c) \
+ ((unsigned char) (c) < 128 && \
+ (i_isspace(c) || i_ispunct(c)))
+
+static char *strstr_full_case(const char *data, const char *key, int icase)
+{
+ const char *start, *max;
+ int keylen, datalen, pos, match;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+ if (keylen == 0)
+ return (char *) data;
+
+ max = data+datalen-keylen;
+ start = data; pos = 0;
+ while (data <= max) {
+ if (key[pos] == '\0') {
+ if (data[pos] != '\0' && !isbound(data[pos])) {
+ data++;
+ pos = 0;
+ continue;
+ }
+ return (char *) data;
+ }
+
+ match = icase ? (i_toupper(data[pos]) == i_toupper(key[pos])) :
+ data[pos] == key[pos];
+
+ if (match && (pos != 0 || data == start || isbound(data[-1])))
+ pos++;
+ else {
+ data++;
+ pos = 0;
+ }
+ }
+
+ return NULL;
+}
+
+char *strstr_full(const char *data, const char *key)
+{
+ return strstr_full_case(data, key, FALSE);
+}
+
+char *stristr_full(const char *data, const char *key)
+{
+ return strstr_full_case(data, key, TRUE);
+}
+
+/* convert ~/ to $HOME */
+char *convert_home(const char *path)
+{
+ const char *home;
+
+ if (*path == '~' && (*(path+1) == '/' || *(path+1) == '\0')) {
+ home = g_get_home_dir();
+ if (home == NULL)
+ home = ".";
+
+ return g_strconcat(home, path+1, NULL);
+ } else {
+ return g_strdup(path);
+ }
+}
+
+int i_istr_equal(gconstpointer v, gconstpointer v2)
+{
+ return g_ascii_strcasecmp((const char *) v, (const char *) v2) == 0;
+}
+
+int i_istr_cmp(gconstpointer v, gconstpointer v2)
+{
+ return g_ascii_strcasecmp((const char *) v, (const char *) v2);
+}
+
+guint i_istr_hash(gconstpointer v)
+{
+ const signed char *p;
+ guint32 h = 5381;
+
+ for (p = v; *p != '\0'; p++)
+ h = (h << 5) + h + g_ascii_toupper(*p);
+
+ return h;
+}
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *cmask, const char *data)
+{
+ char *mask, *newmask, *p1, *p2;
+ int ret;
+
+ newmask = mask = g_strdup(cmask);
+ for (; *mask != '\0' && *data != '\0'; mask++) {
+ if (*mask != '*') {
+ if (*mask != '?' && i_toupper(*mask) != i_toupper(*data))
+ break;
+
+ data++;
+ continue;
+ }
+
+ while (*mask == '?' || *mask == '*') mask++;
+ if (*mask == '\0') {
+ data += strlen(data);
+ break;
+ }
+
+ p1 = strchr(mask, '*');
+ p2 = strchr(mask, '?');
+ if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;
+
+ if (p1 != NULL) *p1 = '\0';
+
+ data = stristr(data, mask);
+ if (data == NULL) break;
+
+ data += strlen(mask);
+ mask += strlen(mask)-1;
+
+ if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
+ }
+
+ while (*mask == '*') mask++;
+
+ ret = data != NULL && *data == '\0' && *mask == '\0';
+ g_free(newmask);
+
+ return ret;
+}
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char)
+{
+ g_return_val_if_fail(str != NULL, FALSE);
+
+ if (*str == '\0' || *str == end_char)
+ return FALSE;
+
+ while (*str != '\0' && *str != end_char) {
+ if (!i_isdigit(*str)) return FALSE;
+ str++;
+ }
+
+ return TRUE;
+}
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to)
+{
+ char *p;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == from) *p = to;
+ }
+ return str;
+}
+
+int octal2dec(int octal)
+{
+ int dec, n;
+
+ dec = 0; n = 1;
+ while (octal != 0) {
+ dec += n*(octal%10);
+ octal /= 10; n *= 8;
+ }
+
+ return dec;
+}
+
+int dec2octal(int decimal)
+{
+ int octal, pos;
+
+ octal = 0; pos = 0;
+ while (decimal > 0) {
+ octal += (decimal & 7)*(pos == 0 ? 1 : pos);
+ decimal /= 8;
+ pos += 10;
+ }
+
+ return octal;
+}
+
+/* string -> uoff_t */
+uoff_t str_to_uofft(const char *str)
+{
+#ifdef UOFF_T_LONG_LONG
+ return (uoff_t)strtoull(str, NULL, 10);
+#else
+ return (uoff_t)strtoul(str, NULL, 10);
+#endif
+}
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *str)
+{
+ char *ret, *p;
+
+ ret = p = g_malloc(strlen(str)*2+1);
+ while (*str != '\0') {
+ if ((unsigned char) *str >= 32)
+ *p++ = *str;
+ else {
+ *p++ = '^';
+ *p++ = *str + 'A'-1;
+ }
+ str++;
+ }
+ *p = '\0';
+
+ return ret;
+}
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t)
+{
+ struct tm *tm;
+ char *str;
+ int len;
+
+ tm = localtime(&t);
+ if (tm == NULL)
+ return g_strdup("???");
+
+ str = g_strdup(asctime(tm));
+
+ len = strlen(str);
+ if (len > 0) str[len-1] = '\0';
+ return str;
+}
+
+/* Returns number of columns needed to print items.
+ save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+ int max_width, int max_columns,
+ int item_extra, int item_min_size,
+ int **save_column_widths, int *rows)
+{
+ GSList *tmp;
+ int **columns, *columns_width, *columns_rows;
+ int item_pos, items_count;
+ int ret, len, max_len, n, col;
+
+ items_count = g_slist_length(items);
+ if (items_count == 0) {
+ *save_column_widths = NULL;
+ *rows = 0;
+ return 0;
+ }
+
+ len = max_width/(item_extra+item_min_size);
+ if (len <= 0) len = 1;
+ if (max_columns <= 0 || len < max_columns)
+ max_columns = len;
+
+ columns = g_new0(int *, max_columns);
+ columns_width = g_new0(int, max_columns);
+ columns_rows = g_new0(int, max_columns);
+
+ for (n = 1; n < max_columns; n++) {
+ columns[n] = g_new0(int, n+1);
+ columns_rows[n] = items_count <= n+1 ? 1 :
+ (items_count+n)/(n+1);
+ }
+
+ /* for each possible column count, save the column widths and
+ find the biggest column count that fits to screen. */
+ item_pos = 0; max_len = 0;
+ for (tmp = items; tmp != NULL; tmp = tmp->next) {
+ len = item_extra+len_func(tmp->data);
+ if (max_len < len)
+ max_len = len;
+
+ for (n = 1; n < max_columns; n++) {
+ if (columns_width[n] > max_width)
+ continue; /* too wide */
+
+ col = item_pos/columns_rows[n];
+ if (columns[n][col] < len) {
+ columns_width[n] += len-columns[n][col];
+ columns[n][col] = len;
+ }
+ }
+
+ item_pos++;
+ }
+
+ for (n = max_columns-1; n >= 1; n--) {
+ if (columns_width[n] <= max_width &&
+ columns[n][n] > 0)
+ break;
+ }
+ ret = n+1;
+
+ *save_column_widths = g_new(int, ret);
+ if (ret == 1) {
+ **save_column_widths = max_len;
+ *rows = 1;
+ } else {
+ memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret);
+ *rows = columns_rows[ret-1];
+ }
+
+ for (n = 1; n < max_columns; n++)
+ g_free(columns[n]);
+ g_free(columns_width);
+ g_free(columns_rows);
+ g_free(columns);
+
+ return ret;
+}
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows)
+{
+ GSList *tmp, *sorted;
+ int row, skip;
+
+ if (list == NULL || rows == 0)
+ return list;
+
+ sorted = NULL;
+
+ for (row = 0; row < rows; row++) {
+ tmp = g_slist_nth(list, row);
+ skip = 1;
+ for (; tmp != NULL; tmp = tmp->next) {
+ if (--skip == 0) {
+ skip = rows;
+ sorted = g_slist_append(sorted, tmp->data);
+ }
+ }
+ }
+
+ g_return_val_if_fail(g_slist_length(sorted) ==
+ g_slist_length(list), sorted);
+ return sorted;
+}
+
+/* Expand escape string, the first character in data should be the
+ one after '\'. Returns the expanded character or -1 if error. */
+int expand_escape(const char **data)
+{
+ char digit[4];
+
+ switch (**data) {
+ case 't':
+ return '\t';
+ case 'r':
+ return '\r';
+ case 'n':
+ return '\n';
+ case 'e':
+ return 27; /* ESC */
+ case '\\':
+ return '\\';
+
+ case 'x':
+ /* hex digit */
+ if (!i_isxdigit((*data)[1]) || !i_isxdigit((*data)[2]))
+ return -1;
+
+ digit[0] = (*data)[1];
+ digit[1] = (*data)[2];
+ digit[2] = '\0';
+ *data += 2;
+ return strtol(digit, NULL, 16);
+ case 'c':
+ /* check for end of string */
+ if ((*data)[1] == '\0')
+ return 0;
+ /* control character (\cA = ^A) */
+ (*data)++;
+ return i_toupper(**data) - 64;
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ /* octal */
+ digit[1] = digit[2] = digit[3] = '\0';
+ digit[0] = (*data)[0];
+ if ((*data)[1] >= '0' && (*data)[1] <= '7') {
+ ++*data;
+ digit[1] = **data;
+ if ((*data)[1] >= '0' && (*data)[1] <= '7') {
+ ++*data;
+ digit[2] = **data;
+ }
+ }
+ return strtol(digit, NULL, 8);
+ default:
+ return -1;
+ }
+}
+
+/* Escape all '"', "'" and '\' chars with '\' */
+char *escape_string(const char *str)
+{
+ char *ret, *p;
+
+ p = ret = g_malloc(strlen(str)*2+1);
+ while (*str != '\0') {
+ if (*str == '"' || *str == '\'' || *str == '\\')
+ *p++ = '\\';
+ *p++ = *str++;
+ }
+ *p = '\0';
+
+ return ret;
+}
+
+/* Escape all '\' chars with '\' */
+char *escape_string_backslashes(const char *str)
+{
+ char *ret, *p;
+
+ p = ret = g_malloc(strlen(str)*2+1);
+ while (*str != '\0') {
+ if (*str == '\\')
+ *p++ = '\\';
+ *p++ = *str++;
+ }
+ *p = '\0';
+
+ return ret;
+}
+
+int nearest_power(int num)
+{
+ int n = 1;
+
+ while (n < num) n <<= 1;
+ return n;
+}
+
+/* Parses unsigned integers from strings with decent error checking.
+ * Returns true on success, false otherwise (overflow, no valid number, etc)
+ * There's a 31 bit limit so the output can be assigned to signed positive ints */
+int parse_uint(const char *nptr, char **endptr, int base, guint *number)
+{
+ char *endptr_;
+ gulong parsed;
+
+ /* strtoul accepts whitespace and plus/minus signs, for some reason */
+ if (!i_isdigit(*nptr)) {
+ return FALSE;
+ }
+
+ errno = 0;
+ parsed = strtoul(nptr, &endptr_, base);
+
+ if (errno || endptr_ == nptr || parsed >= (1U << 31)) {
+ return FALSE;
+ }
+
+ if (endptr) {
+ *endptr = endptr_;
+ }
+
+ if (number) {
+ *number = (guint) parsed;
+ }
+
+ return TRUE;
+}
+
+static int parse_number_sign(const char *input, char **endptr, int *sign)
+{
+ int sign_ = 1;
+
+ while (i_isspace(*input))
+ input++;
+
+ if (*input == '-') {
+ sign_ = -sign_;
+ input++;
+ }
+
+ *sign = sign_;
+ *endptr = (char *) input;
+ return TRUE;
+}
+
+static int parse_time_interval_uint(const char *time, guint *msecs)
+{
+ const char *desc;
+ guint number;
+ int len, ret, digits;
+
+ *msecs = 0;
+
+ /* max. return value is around 24 days */
+ number = 0; ret = TRUE; digits = FALSE;
+ while (i_isspace(*time))
+ time++;
+ for (;;) {
+ if (i_isdigit(*time)) {
+ char *endptr;
+ if (!parse_uint(time, &endptr, 10, &number)) {
+ return FALSE;
+ }
+ time = endptr;
+ digits = TRUE;
+ continue;
+ }
+
+ if (!digits)
+ return FALSE;
+
+ /* skip punctuation */
+ while (*time != '\0' && i_ispunct(*time) && *time != '-')
+ time++;
+
+ /* get description */
+ for (len = 0, desc = time; i_isalpha(*time); time++)
+ len++;
+
+ while (i_isspace(*time))
+ time++;
+
+ if (len == 0) {
+ if (*time != '\0')
+ return FALSE;
+ *msecs += number * 1000; /* assume seconds */
+ return TRUE;
+ }
+
+ if (g_ascii_strncasecmp(desc, "days", len) == 0) {
+ if (number > 24) {
+ /* would overflow */
+ return FALSE;
+ }
+ *msecs += number * 1000*3600*24;
+ } else if (g_ascii_strncasecmp(desc, "hours", len) == 0)
+ *msecs += number * 1000*3600;
+ else if (g_ascii_strncasecmp(desc, "minutes", len) == 0 ||
+ g_ascii_strncasecmp(desc, "mins", len) == 0)
+ *msecs += number * 1000*60;
+ else if (g_ascii_strncasecmp(desc, "seconds", len) == 0 ||
+ g_ascii_strncasecmp(desc, "secs", len) == 0)
+ *msecs += number * 1000;
+ else if (g_ascii_strncasecmp(desc, "milliseconds", len) == 0 ||
+ g_ascii_strncasecmp(desc, "millisecs", len) == 0 ||
+ g_ascii_strncasecmp(desc, "mseconds", len) == 0 ||
+ g_ascii_strncasecmp(desc, "msecs", len) == 0)
+ *msecs += number;
+ else {
+ ret = FALSE;
+ }
+
+ /* skip punctuation */
+ while (*time != '\0' && i_ispunct(*time) && *time != '-')
+ time++;
+
+ if (*time == '\0')
+ break;
+
+ number = 0;
+ digits = FALSE;
+ }
+
+ return ret;
+}
+
+static int parse_size_uint(const char *size, guint *bytes)
+{
+ const char *desc;
+ guint number, multiplier, limit;
+ int len;
+
+ *bytes = 0;
+
+ /* max. return value is about 1.6 years */
+ number = 0;
+ while (*size != '\0') {
+ if (i_isdigit(*size)) {
+ char *endptr;
+ if (!parse_uint(size, &endptr, 10, &number)) {
+ return FALSE;
+ }
+ size = endptr;
+ continue;
+ }
+
+ /* skip punctuation */
+ while (*size != '\0' && i_ispunct(*size))
+ size++;
+
+ /* get description */
+ for (len = 0, desc = size; i_isalpha(*size); size++)
+ len++;
+
+ if (len == 0) {
+ if (number == 0) {
+ /* "0" - allow it */
+ return TRUE;
+ }
+
+ *bytes += number*1024; /* assume kilobytes */
+ return FALSE;
+ }
+
+ multiplier = 0;
+ limit = 0;
+
+ if (g_ascii_strncasecmp(desc, "gbytes", len) == 0) {
+ multiplier = 1U << 30;
+ limit = 2U << 0;
+ }
+ if (g_ascii_strncasecmp(desc, "mbytes", len) == 0) {
+ multiplier = 1U << 20;
+ limit = 2U << 10;
+ }
+ if (g_ascii_strncasecmp(desc, "kbytes", len) == 0) {
+ multiplier = 1U << 10;
+ limit = 2U << 20;
+ }
+ if (g_ascii_strncasecmp(desc, "bytes", len) == 0) {
+ multiplier = 1;
+ limit = 2U << 30;
+ }
+
+ if (limit && number > limit) {
+ return FALSE;
+ }
+
+ *bytes += number * multiplier;
+
+ /* skip punctuation */
+ while (*size != '\0' && i_ispunct(*size))
+ size++;
+ }
+
+ return TRUE;
+}
+
+int parse_size(const char *size, int *bytes)
+{
+ guint bytes_;
+ int ret;
+
+ ret = parse_size_uint(size, &bytes_);
+
+ if (bytes_ > (1U << 31)) {
+ return FALSE;
+ }
+
+ *bytes = bytes_;
+ return ret;
+}
+
+int parse_time_interval(const char *time, int *msecs)
+{
+ guint msecs_;
+ char *number;
+ int ret, sign;
+
+ parse_number_sign(time, &number, &sign);
+
+ ret = parse_time_interval_uint(number, &msecs_);
+
+ if (msecs_ > (1U << 31)) {
+ return FALSE;
+ }
+
+ *msecs = msecs_ * sign;
+ return ret;
+}
+
+
+char *ascii_strup(char *str)
+{
+ char *s;
+
+ for (s = str; *s; s++)
+ *s = g_ascii_toupper (*s);
+ return str;
+}
+
+char *ascii_strdown(char *str)
+{
+ char *s;
+
+ for (s = str; *s; s++)
+ *s = g_ascii_tolower (*s);
+ return str;
+}
+
+char **strsplit_len(const char *str, int len, gboolean onspace)
+{
+ char **ret = g_new(char *, 1);
+ int n;
+ int offset;
+
+ for (n = 0; *str != '\0'; n++, str += offset) {
+ offset = MIN(len, strlen(str));
+ if (onspace && strlen(str) > len) {
+ /*
+ * Try to find a space to split on and leave
+ * the space on the previous line.
+ */
+ int i;
+ for (i = len - 1; i > 0; i--) {
+ if (str[i] == ' ') {
+ offset = i;
+ break;
+ }
+ }
+ }
+ ret[n] = g_strndup(str, offset);
+ ret = g_renew(char *, ret, n + 2);
+ }
+ ret[n] = NULL;
+
+ return ret;
+}
+
+char *binary_to_hex(unsigned char *buffer, size_t size)
+{
+ static const char hex[] = "0123456789ABCDEF";
+ char *result = NULL;
+ int i;
+
+ if (buffer == NULL || size == 0)
+ return NULL;
+
+ result = g_malloc(3 * size);
+
+ for (i = 0; i < size; i++) {
+ result[i * 3 + 0] = hex[(buffer[i] >> 4) & 0xf];
+ result[i * 3 + 1] = hex[(buffer[i] >> 0) & 0xf];
+ result[i * 3 + 2] = i == size - 1 ? '\0' : ':';
+ }
+
+ return result;
+}
diff --git a/src/core/misc.h b/src/core/misc.h
new file mode 100644
index 0000000..622470f
--- /dev/null
+++ b/src/core/misc.h
@@ -0,0 +1,130 @@
+#ifndef IRSSI_CORE_MISC_H
+#define IRSSI_CORE_MISC_H
+
+int i_input_add_poll(int fd, int priority, int condition, GInputFunction function, void *data);
+
+/* `str' should be type char[MAX_INT_STRLEN] */
+#define ltoa(str, num) \
+ g_snprintf(str, sizeof(str), "%d", num)
+
+typedef void* (*FOREACH_FIND_FUNC) (void *item, void *data);
+typedef int (*COLUMN_LEN_FUNC)(void *data);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+/* Returns 1 if tv1 > tv2, -1 if tv2 > tv1 or 0 if they're equal. */
+int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2) G_GNUC_DEPRECATED;
+/* Returns "tv1 - tv2", returns the result in milliseconds. Note that
+ if the difference is too large, the result might be invalid. */
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2) G_GNUC_DEPRECATED;
+#pragma GCC diagnostic pop
+
+#if GLIB_CHECK_VERSION(2, 56, 0)
+/* nothing */
+#else
+/* compatibility code for old GLib */
+GDateTime *g_date_time_new_from_iso8601(const gchar *iso_date, GTimeZone *default_tz);
+#endif
+
+GSList *i_slist_find_string(GSList *list, const char *key);
+GSList *i_slist_find_icase_string(GSList *list, const char *key);
+GList *i_list_find_string(GList *list, const char *key);
+GList *i_list_find_icase_string(GList *list, const char *key);
+GSList *i_slist_remove_string(GSList *list, const char *str) G_GNUC_DEPRECATED;
+GSList *i_slist_delete_string(GSList *list, const char *str, GDestroyNotify free_func);
+
+void i_slist_free_full(GSList *list, GDestroyNotify free_func);
+
+void *i_slist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data);
+
+/* `list' contains pointer to structure with a char* to string. */
+char *gslistptr_to_string(GSList *list, int offset, const char *delimiter);
+/* `list' contains char* */
+char *i_slist_to_string(GSList *list, const char *delimiter);
+
+GList *optlist_remove_known(const char *cmd, GHashTable *optlist);
+
+/* convert ~/ to $HOME */
+char *convert_home(const char *path);
+
+/* Case-insensitive string hash functions */
+int i_istr_equal(gconstpointer v, gconstpointer v2);
+unsigned int i_istr_hash(gconstpointer v);
+
+/* Case-insensitive GCompareFunc func */
+int i_istr_cmp(gconstpointer v, gconstpointer v2);
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *mask, const char *data);
+
+/* octal <-> decimal conversions */
+int octal2dec(int octal);
+int dec2octal(int decimal) G_GNUC_DEPRECATED;
+
+/* Get time in human readable form with localtime() + asctime() */
+char *my_asctime(time_t t);
+
+/* Returns number of columns needed to print items.
+ save_column_widths is filled with length of each column. */
+int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func,
+ int max_width, int max_columns,
+ int item_extra, int item_min_size,
+ int **save_column_widths, int *rows);
+
+/* Return a column sorted copy of a list. */
+GSList *columns_sort_list(GSList *list, int rows);
+
+/* Expand escape string, the first character in data should be the
+ one after '\'. Returns the expanded character or -1 if error. */
+int expand_escape(const char **data);
+
+int nearest_power(int num);
+
+/* Returns TRUE / FALSE */
+int parse_uint(const char *nptr, char **endptr, int base, guint *number);
+int parse_time_interval(const char *time, int *msecs);
+int parse_size(const char *size, int *bytes);
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char);
+
+/* strstr() with case-ignoring */
+char *stristr(const char *data, const char *key);
+
+/* like strstr(), but matches only for full words. */
+char *strstr_full(const char *data, const char *key);
+char *stristr_full(const char *data, const char *key);
+
+char *ascii_strup(char *str);
+char *ascii_strdown(char *str);
+
+/* Escape all '"', "'" and '\' chars with '\' */
+char *escape_string(const char *str);
+
+/* Escape all '\' chars with '\' */
+char *escape_string_backslashes(const char *str);
+
+/* convert all low-ascii (<32) to ^<A..> combinations */
+char *show_lowascii(const char *str);
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to);
+
+/* return index of `item' in `array' or -1 if not found */
+int strarray_find(char **array, const char *item);
+
+/* string -> uoff_t */
+uoff_t str_to_uofft(const char *str);
+
+/* find `item' from a space separated `list' */
+int find_substr(const char *list, const char *item);
+
+/* split `str' into `len' sized substrings */
+char **strsplit_len(const char *str, int len, gboolean onspace);
+
+/* Convert a given buffer to a printable, colon-delimited, hex string and
+ * return a pointer to the newly allocated buffer */
+char *binary_to_hex(unsigned char *buffer, size_t size);
+
+#endif
diff --git a/src/core/module.h b/src/core/module.h
new file mode 100644
index 0000000..639e7e2
--- /dev/null
+++ b/src/core/module.h
@@ -0,0 +1,3 @@
+#include <irssi/src/common.h>
+
+#define MODULE_NAME "core"
diff --git a/src/core/modules-load.c b/src/core/modules-load.c
new file mode 100644
index 0000000..a3598c6
--- /dev/null
+++ b/src/core/modules-load.c
@@ -0,0 +1,443 @@
+/*
+ modules-load.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/modules-load.h>
+#include <irssi/src/core/signals.h>
+
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+
+#ifdef HAVE_GMODULE
+
+/* Returns the module name without path, "lib" prefix or ".so" suffix */
+static char *module_get_name(const char *path, int *start, int *end)
+{
+ const char *name;
+ char *module_name, *ptr;
+
+ name = NULL;
+ if (*path == '~' || g_path_is_absolute(path)) {
+ name = strrchr(path, G_DIR_SEPARATOR);
+ if (name != NULL) name++;
+ }
+
+ if (name == NULL)
+ name = path;
+
+ if (strncmp(name, "lib", 3) == 0)
+ name += 3;
+
+ module_name = g_strdup(name);
+ ptr = strchr(module_name, '.');
+ if (ptr != NULL) *ptr = '\0';
+
+ *start = (int) (name-path);
+ *end = *start + (ptr == NULL ? strlen(name) :
+ (int) (ptr-module_name));
+
+ return module_name;
+}
+
+/* Returns the root module name for given submodule (eg. perl_core -> perl) */
+static char *module_get_root(const char *name, char **prefixes)
+{
+ int len;
+
+ /* skip any of the prefixes.. */
+ if (prefixes != NULL) {
+ while (*prefixes != NULL) {
+ len = strlen(*prefixes);
+ if (strncmp(name, *prefixes, len) == 0 &&
+ name[len] == '_') {
+ name += len+1;
+ break;
+ }
+ prefixes++;
+ }
+ }
+
+ /* skip the _core part */
+ len = strlen(name);
+ if (len > 5 && g_strcmp0(name+len-5, "_core") == 0)
+ return g_strndup(name, len-5);
+
+ return g_strdup(name);
+}
+
+/* Returns the sub module name for given submodule (eg. perl_core -> core) */
+static char *module_get_sub(const char *name, const char *root)
+{
+ int rootlen, namelen;
+
+ namelen = strlen(name);
+ rootlen = strlen(root);
+ g_return_val_if_fail(namelen >= rootlen, g_strdup(name));
+
+ if (strncmp(name, root, rootlen) == 0 &&
+ g_strcmp0(name+rootlen, "_core") == 0)
+ return g_strdup("core");
+
+ if (namelen > rootlen && name[namelen-rootlen-1] == '_' &&
+ g_strcmp0(name+namelen-rootlen, root) == 0)
+ return g_strndup(name, namelen-rootlen-1);
+
+ return g_strdup(name);
+}
+
+static GModule *module_open(const char *name, int *found)
+{
+ struct stat statbuf;
+ GModule *module;
+ char *path, *str;
+
+ if (g_path_is_absolute(name) || *name == '~' ||
+ (*name == '.' && name[1] == G_DIR_SEPARATOR))
+ path = g_strdup(name);
+ else {
+ /* first try from home dir */
+ str = g_strdup_printf("%s/modules", get_irssi_dir());
+ path = g_module_build_path(str, name);
+ g_free(str);
+
+ if (stat(path, &statbuf) == 0) {
+ module = g_module_open(path, (GModuleFlags) 0);
+ g_free(path);
+ *found = TRUE;
+ return module;
+ }
+
+ /* module not found from home dir, try global module dir */
+ g_free(path);
+ path = g_module_build_path(MODULEDIR, name);
+ }
+
+ *found = stat(path, &statbuf) == 0;
+ module = g_module_open(path, (GModuleFlags) 0);
+ g_free(path);
+ return module;
+}
+
+static char *module_get_func(const char *rootmodule, const char *submodule,
+ const char *function)
+{
+ if (g_strcmp0(submodule, "core") == 0)
+ return g_strconcat(rootmodule, "_core_", function, NULL);
+
+ if (g_strcmp0(rootmodule, submodule) == 0)
+ return g_strconcat(rootmodule, "_", function, NULL);
+
+ return g_strconcat(submodule, "_", rootmodule, "_", function, NULL);
+}
+
+#define module_error(error, text, rootmodule, submodule) \
+ signal_emit("module error", 4, GINT_TO_POINTER(error), text, \
+ rootmodule, submodule)
+
+/* Returns 1 if ok, 0 if error in module and
+ -1 if module wasn't found */
+static int module_load_name(const char *path, const char *rootmodule,
+ const char *submodule, int silent)
+{
+ void (*module_init) (void);
+ void (*module_deinit) (void);
+ void (*module_version) (int *);
+ GModule *gmodule;
+ MODULE_REC *module;
+ MODULE_FILE_REC *rec;
+ gpointer value_version = NULL;
+ gpointer value1, value2 = NULL;
+ char *versionfunc, *initfunc, *deinitfunc;
+ int module_abi_version = 0;
+ int found;
+
+ gmodule = module_open(path, &found);
+ if (gmodule == NULL) {
+ if (!silent || found) {
+ module_error(MODULE_ERROR_LOAD, g_module_error(),
+ rootmodule, submodule);
+ }
+ return found ? 0 : -1;
+ }
+
+ /* get the module's irssi abi version and bail out on mismatch */
+ versionfunc = module_get_func(rootmodule, submodule, "abicheck");
+ if (!g_module_symbol(gmodule, versionfunc, &value_version)) {
+ g_free(versionfunc);
+ module_error(MODULE_ERROR_VERSION_MISMATCH, "0",
+ rootmodule, submodule);
+ g_module_close(gmodule);
+ return 0;
+ }
+ g_free(versionfunc);
+ module_version = value_version;
+ module_version(&module_abi_version);
+ if (module_abi_version != IRSSI_ABI_VERSION) {
+ char *module_abi_versionstr = g_strdup_printf("%d", module_abi_version);
+ module_error(MODULE_ERROR_VERSION_MISMATCH, module_abi_versionstr,
+ rootmodule, submodule);
+ g_free(module_abi_versionstr);
+ g_module_close(gmodule);
+ return 0;
+ }
+
+ /* get the module's init() and deinit() functions */
+ initfunc = module_get_func(rootmodule, submodule, "init");
+ deinitfunc = module_get_func(rootmodule, submodule, "deinit");
+ found = g_module_symbol(gmodule, initfunc, &value1) &&
+ g_module_symbol(gmodule, deinitfunc, &value2);
+ g_free(initfunc);
+ g_free(deinitfunc);
+
+ if (!found) {
+ module_error(MODULE_ERROR_INVALID, NULL,
+ rootmodule, submodule);
+ g_module_close(gmodule);
+ return 0;
+ }
+
+ module_init = value1;
+ module_deinit = value2;
+
+ /* Call the module's init() function - it should register itself
+ with module_register() function, abort if it doesn't. */
+ module_init();
+
+ module = module_find(rootmodule);
+ rec = module == NULL ? NULL :
+ g_strcmp0(rootmodule, submodule) == 0 ?
+ module_file_find(module, "core") :
+ module_file_find(module, submodule);
+ if (rec == NULL) {
+ rec = module_register_full(rootmodule, submodule, NULL);
+ rec->gmodule = gmodule;
+ module_file_unload(rec);
+
+ module_error(MODULE_ERROR_INVALID, NULL,
+ rootmodule, submodule);
+ return 0;
+ }
+
+ rec->module_deinit = module_deinit;
+ rec->gmodule = gmodule;
+ rec->initialized = TRUE;
+
+ settings_check_module(rec->defined_module_name);
+
+ signal_emit("module loaded", 2, rec->root, rec);
+ return 1;
+}
+
+static int module_load_prefixes(const char *path, const char *module,
+ int start, int end, char **prefixes)
+{
+ GString *realpath;
+ int status, ok;
+
+ /* load module_core */
+ realpath = g_string_new(path);
+ g_string_insert(realpath, end, "_core");
+
+ /* Don't print the error message the first time, since the module
+ may not have the core part at all. */
+ status = module_load_name(realpath->str, module, "core", TRUE);
+ ok = status > 0;
+
+ if (prefixes != NULL) {
+ /* load all the "prefix modules", like the fe-common, irc,
+ etc. part of the module */
+ while (*prefixes != NULL) {
+ g_string_assign(realpath, path);
+ g_string_insert_c(realpath, start, '_');
+ g_string_insert(realpath, start, *prefixes);
+
+ status = module_load_name(realpath->str, module,
+ *prefixes, TRUE);
+ if (status > 0)
+ ok = TRUE;
+
+ prefixes++;
+ }
+ }
+
+ if (!ok) {
+ /* error loading module, print the error message */
+ g_string_assign(realpath, path);
+ g_string_insert(realpath, end, "_core");
+ module_load_name(realpath->str, module, "core", FALSE);
+ }
+
+ g_string_free(realpath, TRUE);
+ return ok;
+}
+
+static int module_load_full(const char *path, const char *rootmodule,
+ const char *submodule, int start, int end,
+ char **prefixes)
+{
+ MODULE_REC *module;
+ int status, try_prefixes;
+
+ if (!g_module_supported())
+ return FALSE;
+
+ module = module_find(rootmodule);
+ if (module != NULL && (g_strcmp0(submodule, rootmodule) == 0 ||
+ module_file_find(module, submodule) != NULL)) {
+ /* module is already loaded */
+ module_error(MODULE_ERROR_ALREADY_LOADED, NULL,
+ rootmodule, submodule);
+ return FALSE;
+ }
+
+ /* check if the given module exists.. */
+ try_prefixes = g_strcmp0(rootmodule, submodule) == 0;
+ status = module_load_name(path, rootmodule, submodule, try_prefixes);
+ if (status == -1 && try_prefixes) {
+ /* nope, try loading the module_core,
+ fe_module, etc. */
+ status = module_load_prefixes(path, rootmodule,
+ start, end, prefixes);
+ }
+
+ return status > 0;
+}
+
+/* Load module - automatically tries to load also the related non-core
+ modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes)
+{
+ char *exppath, *name, *submodule, *rootmodule;
+ int start, end, ret;
+
+ g_return_val_if_fail(path != NULL, FALSE);
+
+ exppath = convert_home(path);
+
+ name = module_get_name(exppath, &start, &end);
+ rootmodule = module_get_root(name, prefixes);
+ submodule = module_get_sub(name, rootmodule);
+ g_free(name);
+
+ ret = module_load_full(exppath, rootmodule, submodule,
+ start, end, prefixes);
+
+ g_free(rootmodule);
+ g_free(submodule);
+ g_free(exppath);
+ return ret;
+}
+
+/* Load a sub module. */
+int module_load_sub(const char *path, const char *submodule, char **prefixes)
+{
+ GString *full_path;
+ char *exppath, *name, *rootmodule;
+ int start, end, ret;
+
+ g_return_val_if_fail(path != NULL, FALSE);
+ g_return_val_if_fail(submodule != NULL, FALSE);
+
+ exppath = convert_home(path);
+
+ name = module_get_name(exppath, &start, &end);
+ rootmodule = module_get_root(name, prefixes);
+ g_free(name);
+
+ full_path = g_string_new(exppath);
+ if (g_strcmp0(submodule, "core") == 0)
+ g_string_insert(full_path, end, "_core");
+ else {
+ g_string_insert_c(full_path, start, '_');
+ g_string_insert(full_path, start, submodule);
+ }
+
+ ret = module_load_full(full_path->str, rootmodule, submodule,
+ start, end, NULL);
+
+ g_string_free(full_path, TRUE);
+ g_free(rootmodule);
+ g_free(exppath);
+ return ret;
+}
+
+static void module_file_deinit_gmodule(MODULE_FILE_REC *file)
+{
+ /* call the module's deinit() function */
+ if (file->module_deinit != NULL)
+ file->module_deinit();
+
+ if (file->defined_module_name != NULL) {
+ settings_remove_module(file->defined_module_name);
+ commands_remove_module(file->defined_module_name);
+ signals_remove_module(file->defined_module_name);
+ }
+
+ g_module_close(file->gmodule);
+}
+
+#else /* !HAVE_GMODULE - modules are not supported */
+
+int module_load(const char *path, char **prefixes)
+{
+ return FALSE;
+}
+
+#endif
+
+void module_file_unload(MODULE_FILE_REC *file)
+{
+ MODULE_REC *root;
+
+ root = file->root;
+ root->files = g_slist_remove(root->files, file);
+
+ if (file->initialized)
+ signal_emit("module unloaded", 2, file->root, file);
+
+#ifdef HAVE_GMODULE
+ if (file->gmodule != NULL)
+ module_file_deinit_gmodule(file);
+#endif
+
+ g_free(file->name);
+ g_free(file->defined_module_name);
+ g_free(file);
+
+ if (root->files == NULL && g_slist_find(modules, root) != NULL)
+ module_unload(root);
+}
+
+void module_unload(MODULE_REC *module)
+{
+ g_return_if_fail(module != NULL);
+
+ modules = g_slist_remove(modules, module);
+
+ signal_emit("module unloaded", 1, module);
+
+ while (module->files != NULL)
+ module_file_unload(module->files->data);
+
+ g_free(module->name);
+ g_free(module);
+}
diff --git a/src/core/modules-load.h b/src/core/modules-load.h
new file mode 100644
index 0000000..a99bcdf
--- /dev/null
+++ b/src/core/modules-load.h
@@ -0,0 +1,16 @@
+#ifndef IRSSI_CORE_MODULES_LOAD_H
+#define IRSSI_CORE_MODULES_LOAD_H
+
+#include <irssi/src/core/modules.h>
+
+/* Load module - automatically tries to load also the related non-core
+ modules given in `prefixes' (like irc, fe, fe_text, ..) */
+int module_load(const char *path, char **prefixes);
+
+/* Load a sub module. */
+int module_load_sub(const char *path, const char *submodule, char **prefixes);
+
+void module_unload(MODULE_REC *module);
+void module_file_unload(MODULE_FILE_REC *file);
+
+#endif
diff --git a/src/core/modules.c b/src/core/modules.c
new file mode 100644
index 0000000..38c33de
--- /dev/null
+++ b/src/core/modules.c
@@ -0,0 +1,311 @@
+/*
+ modules.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>
+
+GSList *modules;
+
+static GHashTable *uniqids, *uniqstrids;
+static GHashTable *idlookup, *stridlookup;
+static int next_uniq_id;
+
+void *module_check_cast(void *object, int type_pos, const char *id)
+{
+ return object == NULL || module_find_id(id,
+ G_STRUCT_MEMBER(int, object, type_pos)) == -1 ? NULL : object;
+}
+
+void *module_check_cast_module(void *object, int type_pos,
+ const char *module, const char *id)
+{
+ const char *str;
+
+ if (object == NULL)
+ return NULL;
+
+ str = module_find_id_str(module,
+ G_STRUCT_MEMBER(int, object, type_pos));
+ return str == NULL || g_strcmp0(str, id) != 0 ? NULL : object;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid, idp;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(idlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ g_hash_table_insert(idlookup, g_strdup(module), ids);
+ }
+
+ idp = GINT_TO_POINTER(id);
+ if (!g_hash_table_lookup_extended(ids, idp, &origkey, &uniqid)) {
+ /* not found */
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, idp, GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), idp);
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id_str(const char *module, const char *id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(stridlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ g_hash_table_insert(stridlookup, g_strdup(module), ids);
+ }
+
+ if (!g_hash_table_lookup_extended(ids, id, &origkey, &uniqid)) {
+ /* not found */
+ char *saveid;
+
+ saveid = g_strdup(id);
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, saveid, GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqstrids, GINT_TO_POINTER(ret), saveid);
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid)
+{
+ GHashTable *idlist;
+ gpointer origkey, id;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ if (!g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid),
+ &origkey, &id))
+ return -1;
+
+ /* check that module matches */
+ idlist = g_hash_table_lookup(idlookup, module);
+ if (idlist == NULL)
+ return -1;
+
+ ret = GPOINTER_TO_INT(id);
+ if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+ GPOINTER_TO_INT(id) != uniqid)
+ ret = -1;
+
+ return ret;
+}
+
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid)
+{
+ GHashTable *idlist;
+ gpointer origkey, id;
+ const char *ret;
+
+ g_return_val_if_fail(module != NULL, NULL);
+
+ if (!g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid),
+ &origkey, &id))
+ return NULL;
+
+ /* check that module matches */
+ idlist = g_hash_table_lookup(stridlookup, module);
+ if (idlist == NULL)
+ return NULL;
+
+ ret = id;
+ if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) ||
+ GPOINTER_TO_INT(id) != uniqid)
+ ret = NULL;
+
+ return ret;
+}
+
+static void uniq_destroy(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqids, value);
+}
+
+static void uniq_destroy_str(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqstrids, value);
+ g_free(key);
+}
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module)
+{
+ GHashTable *idlist;
+ gpointer key, value;
+
+ if (g_hash_table_lookup_extended(idlookup, module, &key, &value)) {
+ idlist = value;
+
+ g_hash_table_remove(idlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(idlist, (GHFunc) uniq_destroy, NULL);
+ g_hash_table_destroy(idlist);
+ }
+
+ if (g_hash_table_lookup_extended(stridlookup, module, &key, &value)) {
+ idlist = value;
+
+ g_hash_table_remove(stridlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(idlist, (GHFunc) uniq_destroy_str, NULL);
+ g_hash_table_destroy(idlist);
+ }
+}
+
+/* Register a new module. The `name' is the root module name, `submodule'
+ specifies the current module to be registered (eg. "perl", "fe").
+ The module is registered as statically loaded by default. */
+MODULE_FILE_REC *module_register_full(const char *name, const char *submodule,
+ const char *defined_module_name)
+{
+ MODULE_REC *module;
+ MODULE_FILE_REC *file;
+
+ module = module_find(name);
+ if (module == NULL) {
+ module = g_new0(MODULE_REC, 1);
+ module->name = g_strdup(name);
+
+ modules = g_slist_prepend(modules, module);
+ }
+
+ file = module_file_find(module, submodule);
+ if (file != NULL)
+ return file;
+
+ file = g_new0(MODULE_FILE_REC, 1);
+ file->root = module;
+ file->name = g_strdup(submodule);
+ file->defined_module_name = g_strdup(defined_module_name);
+
+ module->files = g_slist_prepend(module->files, file);
+ return file;
+}
+
+MODULE_REC *module_find(const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = modules; tmp != NULL; tmp = tmp->next) {
+ MODULE_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+MODULE_FILE_REC *module_file_find(MODULE_REC *module, const char *name)
+{
+ GSList *tmp;
+ char *tmpname, *p;
+ tmpname = g_strdup(name);
+ for (p = tmpname; *p != '\0'; p++) {
+ if (*p == '_')
+ *p = '-';
+ }
+
+ for (tmp = module->files; tmp != NULL; tmp = tmp->next) {
+ MODULE_FILE_REC *rec = tmp->data;
+
+ if (g_strcmp0(rec->name, name) == 0 ||
+ g_strcmp0(rec->name, tmpname) == 0) {
+ g_free(tmpname);
+ return rec;
+ }
+ }
+
+ g_free(tmpname);
+ return NULL;
+}
+
+static void uniq_get_modules(char *key, void *value, GSList **list)
+{
+ *list = g_slist_append(*list, g_strdup(key));
+}
+
+void modules_init(void)
+{
+ modules = NULL;
+
+ idlookup = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ uniqids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ stridlookup = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ uniqstrids = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ next_uniq_id = 0;
+}
+
+void modules_deinit(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(idlookup, (GHFunc) uniq_get_modules, &list);
+ g_hash_table_foreach(stridlookup, (GHFunc) uniq_get_modules, &list);
+
+ while (list != NULL) {
+ void *tmp = list->data;
+ module_uniq_destroy(list->data);
+ list = g_slist_remove(list, list->data);
+ g_free(tmp);
+ }
+
+ g_hash_table_destroy(idlookup);
+ g_hash_table_destroy(stridlookup);
+ g_hash_table_destroy(uniqids);
+ g_hash_table_destroy(uniqstrids);
+}
diff --git a/src/core/modules.h b/src/core/modules.h
new file mode 100644
index 0000000..1e707c0
--- /dev/null
+++ b/src/core/modules.h
@@ -0,0 +1,98 @@
+#ifndef IRSSI_CORE_MODULES_H
+#define IRSSI_CORE_MODULES_H
+
+#define MODULE_DATA_INIT(rec) \
+ (rec)->module_data = g_hash_table_new(g_str_hash, g_str_equal)
+
+#define MODULE_DATA_DEINIT(rec) \
+ g_hash_table_destroy((rec)->module_data)
+
+#define MODULE_DATA_SET(rec, data) \
+ g_hash_table_insert((rec)->module_data, MODULE_NAME, data)
+
+#define MODULE_DATA_UNSET(rec) \
+ g_hash_table_remove((rec)->module_data, MODULE_NAME)
+
+#define MODULE_DATA(rec) \
+ g_hash_table_lookup((rec)->module_data, MODULE_NAME)
+
+
+#ifdef HAVE_GMODULE
+# define MODULE_IS_STATIC(rec) \
+ ((rec)->gmodule == NULL)
+#else
+# define MODULE_IS_STATIC(rec) TRUE
+#endif
+
+#define MODULE_ABICHECK(fn_modulename) \
+void fn_modulename ## _abicheck(int *version) \
+{ \
+ *version = IRSSI_ABI_VERSION; \
+}
+
+enum {
+ MODULE_ERROR_ALREADY_LOADED,
+ MODULE_ERROR_LOAD,
+ MODULE_ERROR_VERSION_MISMATCH,
+ MODULE_ERROR_INVALID
+};
+
+typedef struct _MODULE_REC MODULE_REC;
+
+typedef struct {
+ MODULE_REC *root;
+ char *name;
+ char *defined_module_name;
+ void (*module_deinit) (void);
+
+#ifdef HAVE_GMODULE
+ GModule *gmodule; /* static, if NULL */
+#endif
+ unsigned int initialized:1;
+} MODULE_FILE_REC;
+
+struct _MODULE_REC {
+ char *name;
+ GSList *files; /* list of modules that belong to this root module */
+};
+
+extern GSList *modules;
+
+/* Register a new module. The `name' is the root module name, `submodule'
+ specifies the current module to be registered (eg. "perl", "fe").
+ The module is registered as statically loaded by default. */
+MODULE_FILE_REC *module_register_full(const char *name, const char *submodule,
+ const char *defined_module_name);
+#define module_register(name, submodule) \
+ module_register_full(name, submodule, MODULE_NAME)
+
+MODULE_REC *module_find(const char *name);
+MODULE_FILE_REC *module_file_find(MODULE_REC *module, const char *name);
+
+#define MODULE_CHECK_CAST(object, cast, type_field, id) \
+ ((cast *) module_check_cast(object, offsetof(cast, type_field), id))
+#define MODULE_CHECK_CAST_MODULE(object, cast, type_field, module, id) \
+ ((cast *) module_check_cast_module(object, \
+ offsetof(cast, type_field), module, id))
+void *module_check_cast(void *object, int type_pos, const char *id);
+void *module_check_cast_module(void *object, int type_pos,
+ const char *module, const char *id);
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id);
+/* return unique number across all modules for `id'. */
+int module_get_uniq_id_str(const char *module, const char *id);
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid);
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid);
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module);
+
+void modules_init(void);
+void modules_deinit(void);
+
+#endif
diff --git a/src/core/net-disconnect.c b/src/core/net-disconnect.c
new file mode 100644
index 0000000..a2f7e29
--- /dev/null
+++ b/src/core/net-disconnect.c
@@ -0,0 +1,156 @@
+/*
+ net-disconnect.c :
+
+ 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>
+
+/* when quitting, wait for max. 5 seconds before forcing to close the socket */
+#define MAX_QUIT_CLOSE_WAIT 5
+
+/* wait for max. 2 minutes for other side to close the socket */
+#define MAX_CLOSE_WAIT (60*2)
+
+typedef struct {
+ time_t created;
+ GIOChannel *handle;
+ int tag;
+} NET_DISCONNECT_REC;
+
+static GSList *disconnects;
+
+static int timeout_tag;
+
+static void net_disconnect_remove(NET_DISCONNECT_REC *rec)
+{
+ disconnects = g_slist_remove(disconnects, rec);
+
+ g_source_remove(rec->tag);
+ net_disconnect(rec->handle);
+ g_free(rec);
+}
+
+static void sig_disconnect(NET_DISCONNECT_REC *rec)
+{
+ char buf[512];
+ int count, ret;
+
+ /* check if there's any data waiting in socket. read max. 9kB so
+ if server just keeps sending us stuff we won't get stuck */
+ count = 0;
+ do {
+ ret = net_receive(rec->handle, buf, sizeof(buf));
+ if (ret == -1) {
+ /* socket was closed */
+ net_disconnect_remove(rec);
+ }
+ count++;
+ } while (ret == sizeof(buf) && count < 18);
+}
+
+static int sig_timeout_disconnect(void)
+{
+ NET_DISCONNECT_REC *rec;
+ GSList *tmp, *next;
+ time_t now;
+
+ /* check if we've waited enough for sockets to close themselves */
+ now = time(NULL);
+ for (tmp = disconnects; tmp != NULL; tmp = next) {
+ rec = tmp->data;
+ next = tmp->next;
+
+ if (rec->created+MAX_CLOSE_WAIT <= now)
+ net_disconnect_remove(rec);
+ }
+
+ if (disconnects == NULL) {
+ /* no more sockets in disconnect queue, stop calling this
+ function */
+ timeout_tag = -1;
+ }
+ return disconnects != NULL;
+}
+
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(GIOChannel *handle)
+{
+ NET_DISCONNECT_REC *rec;
+
+ rec = g_new(NET_DISCONNECT_REC, 1);
+ rec->created = time(NULL);
+ rec->handle = handle;
+ rec->tag = i_input_add(handle, I_INPUT_READ, (GInputFunction) sig_disconnect, rec);
+
+ if (timeout_tag == -1) {
+ timeout_tag = g_timeout_add(10000, (GSourceFunc)
+ sig_timeout_disconnect, NULL);
+ }
+
+ disconnects = g_slist_append(disconnects, rec);
+}
+
+void net_disconnect_init(void)
+{
+ disconnects = NULL;
+ timeout_tag = -1;
+}
+
+void net_disconnect_deinit(void)
+{
+ NET_DISCONNECT_REC *rec;
+ time_t now, max;
+ int first, fd;
+ struct timeval tv;
+ fd_set set;
+
+ /* give the sockets a chance to disconnect themselves.. */
+ max = time(NULL)+MAX_QUIT_CLOSE_WAIT;
+ first = 1;
+ while (disconnects != NULL) {
+ rec = disconnects->data;
+
+ now = time(NULL);
+ if (rec->created+MAX_QUIT_CLOSE_WAIT <= now || max <= now) {
+ /* this one has waited enough */
+ net_disconnect_remove(rec);
+ continue;
+ }
+
+ fd = g_io_channel_unix_get_fd(rec->handle);
+ FD_ZERO(&set);
+ FD_SET(fd, &set);
+ tv.tv_sec = first ? 0 : max-now;
+ tv.tv_usec = first ? 100000 : 0;
+ if (select(fd+1, &set, NULL, NULL, &tv) > 0 &&
+ FD_ISSET(fd, &set)) {
+ /* data coming .. check if we can close the handle */
+ sig_disconnect(rec);
+ } else if (first) {
+ /* Display the text when we have already waited
+ for a while */
+ printf("Please wait, waiting for servers to close "
+ "connections..\n");
+ fflush(stdout);
+
+ first = 0;
+ }
+ }
+}
diff --git a/src/core/net-disconnect.h b/src/core/net-disconnect.h
new file mode 100644
index 0000000..78b03c1
--- /dev/null
+++ b/src/core/net-disconnect.h
@@ -0,0 +1,11 @@
+#ifndef IRSSI_CORE_NET_DISCONNECT_H
+#define IRSSI_CORE_NET_DISCONNECT_H
+
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(GIOChannel *handle);
+
+void net_disconnect_init(void);
+void net_disconnect_deinit(void);
+
+#endif
diff --git a/src/core/net-nonblock.c b/src/core/net-nonblock.c
new file mode 100644
index 0000000..643884e
--- /dev/null
+++ b/src/core/net-nonblock.c
@@ -0,0 +1,107 @@
+/*
+ net-nonblock.c : Nonblocking net_connect()
+
+ Copyright (C) 1998-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 <signal.h>
+
+#include <irssi/src/core/pidwait.h>
+#include <irssi/src/core/net-nonblock.h>
+
+/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
+ written to pipe when found PID of the resolver child is returned */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe, int reverse_lookup)
+{
+ RESOLVED_IP_REC rec;
+ const char *errorstr;
+ int pid;
+
+ (void) reverse_lookup; /* Kept for API backward compatibility */
+
+ g_return_val_if_fail(addr != NULL, FALSE);
+
+ pid = fork();
+ if (pid > 0) {
+ /* parent */
+ pidwait_add(pid);
+ return pid;
+ }
+
+ if (pid != 0) {
+ /* failed! */
+ g_warning("net_connect_thread(): fork() failed! "
+ "Using blocking resolving");
+ }
+
+ /* child */
+ srand(time(NULL));
+
+ memset(&rec, 0, sizeof(rec));
+ rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
+ if (rec.error == 0) {
+ errorstr = NULL;
+ } else {
+ errorstr = net_gethosterror(rec.error);
+ rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
+ }
+
+ i_io_channel_write_block(pipe, &rec, sizeof(rec));
+ if (rec.errlen != 0)
+ i_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
+
+ if (pid == 0)
+ _exit(99);
+
+ /* we used blocking lookup */
+ return 0;
+}
+
+/* get the resolved IP address */
+int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
+{
+ rec->error = -1;
+ rec->errorstr = NULL;
+
+ fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
+
+ /* get ip+error */
+ if (i_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
+ rec->errorstr = g_strdup_printf("Host name lookup: %s",
+ g_strerror(errno));
+ return -1;
+ }
+
+ if (rec->error) {
+ /* read error string, if we can't read everything for some
+ reason, just ignore it. */
+ rec->errorstr = g_malloc0(rec->errlen+1);
+ i_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
+ }
+
+ return 0;
+}
+
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid)
+{
+ g_return_if_fail(pid > 0);
+
+ kill(pid, SIGKILL);
+}
diff --git a/src/core/net-nonblock.h b/src/core/net-nonblock.h
new file mode 100644
index 0000000..c93dd9e
--- /dev/null
+++ b/src/core/net-nonblock.h
@@ -0,0 +1,22 @@
+#ifndef IRSSI_CORE_NET_NONBLOCK_H
+#define IRSSI_CORE_NET_NONBLOCK_H
+
+#include <irssi/src/core/network.h>
+
+typedef struct {
+ IPADDR ip4, ip6; /* resolved ip addresses */
+ int error; /* error, 0 = no error, -1 = error: */
+ int errlen; /* error text length */
+ char *errorstr; /* error string - dynamically allocated, you'll
+ need to free() it yourself unless it's NULL */
+} RESOLVED_IP_REC;
+
+/* nonblocking gethostbyname(), PID of the resolver child is returned. */
+int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe, int reverse_lookup);
+/* get the resolved IP address. returns -1 if some error occurred with read() */
+int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec);
+
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid);
+
+#endif
diff --git a/src/core/net-sendbuffer.c b/src/core/net-sendbuffer.c
new file mode 100644
index 0000000..0cb462a
--- /dev/null
+++ b/src/core/net-sendbuffer.c
@@ -0,0 +1,173 @@
+/*
+ net-sendbuffer.c : Buffered send()
+
+ Copyright (C) 1998-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/core/net-sendbuffer.h>
+#include <irssi/src/core/line-split.h>
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+ is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize)
+{
+ NET_SENDBUF_REC *rec;
+
+ g_return_val_if_fail(handle != NULL, NULL);
+
+ rec = g_new0(NET_SENDBUF_REC, 1);
+ rec->send_tag = -1;
+ rec->handle = handle;
+ rec->bufsize = bufsize > 0 ? bufsize : DEFAULT_BUFFER_SIZE;
+ rec->def_bufsize = rec->bufsize;
+
+ return rec;
+}
+
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close)
+{
+ if (rec->send_tag != -1) g_source_remove(rec->send_tag);
+ if (close) net_disconnect(rec->handle);
+ if (rec->readbuffer != NULL) line_split_free(rec->readbuffer);
+ g_free_not_null(rec->buffer);
+ g_free(rec);
+}
+
+/* Transmit all data from buffer - return TRUE if the whole buffer was sent */
+static int buffer_send(NET_SENDBUF_REC *rec)
+{
+ int ret;
+
+ ret = net_transmit(rec->handle, rec->buffer, rec->bufpos);
+ if (ret < 0 || rec->bufpos == ret) {
+ /* error/all sent - don't try to send it anymore */
+ rec->bufsize = rec->def_bufsize;
+ rec->buffer = g_realloc(rec->buffer, rec->bufsize);
+ rec->bufpos = 0;
+ return TRUE;
+ }
+
+ if (ret > 0) {
+ rec->bufpos -= ret;
+ memmove(rec->buffer, rec->buffer+ret, rec->bufpos);
+ }
+ return FALSE;
+}
+
+static void sig_sendbuffer(NET_SENDBUF_REC *rec)
+{
+ if (rec->buffer != NULL) {
+ if (!buffer_send(rec))
+ return;
+ }
+
+ g_source_remove(rec->send_tag);
+ rec->send_tag = -1;
+}
+
+/* Add `data' to transmit buffer - return FALSE if buffer is full */
+static int buffer_add(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+ if (rec->buffer == NULL) {
+ rec->buffer = g_malloc(rec->bufsize);
+ rec->bufpos = 0;
+ }
+
+ while (rec->bufpos+size > rec->bufsize) {
+ if (rec->bufsize >= MAX_BUFFER_SIZE) {
+ if (!rec->dead)
+ g_warning("Dropping some data on an outgoing connection");
+ rec->dead = 1;
+ return FALSE;
+ }
+ rec->bufsize *= 2;
+ rec->buffer = g_realloc(rec->buffer, rec->bufsize);
+ }
+
+ memcpy(rec->buffer+rec->bufpos, data, size);
+ rec->bufpos += size;
+ return TRUE;
+}
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+ automatically after a while. Returns -1 if some unrecoverable error
+ occurred. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size)
+{
+ int ret;
+
+ g_return_val_if_fail(rec != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+ if (size <= 0) return 0;
+
+ if (rec->buffer == NULL || rec->bufpos == 0) {
+ /* nothing in buffer - transmit immediately */
+ ret = net_transmit(rec->handle, data, size);
+ if (ret < 0) return -1;
+ size -= ret;
+ data = ((const char *) data) + ret;
+ }
+
+ if (size <= 0)
+ return 0;
+
+ /* everything couldn't be sent. */
+ if (rec->send_tag == -1) {
+ rec->send_tag =
+ i_input_add(rec->handle, I_INPUT_WRITE, (GInputFunction) sig_sendbuffer, rec);
+ }
+
+ return buffer_add(rec, data, size) ? 0 : -1;
+}
+
+int net_sendbuffer_receive_line(NET_SENDBUF_REC *rec, char **str, int read_socket)
+{
+ char tmpbuf[2048];
+ int recvlen = 0;
+
+ if (read_socket)
+ recvlen = net_receive(rec->handle, tmpbuf, sizeof(tmpbuf));
+
+ return line_split(tmpbuf, recvlen, str, &rec->readbuffer);
+}
+
+/* Flush the buffer, blocks until finished. */
+void net_sendbuffer_flush(NET_SENDBUF_REC *rec)
+{
+ int handle;
+
+ if (rec->buffer == NULL)
+ return;
+
+ /* set the socket blocking while doing this */
+ handle = g_io_channel_unix_get_fd(rec->handle);
+ fcntl(handle, F_SETFL, 0);
+ while (!buffer_send(rec)) ;
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+}
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec)
+{
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return rec->handle;
+}
diff --git a/src/core/net-sendbuffer.h b/src/core/net-sendbuffer.h
new file mode 100644
index 0000000..7cf1ef6
--- /dev/null
+++ b/src/core/net-sendbuffer.h
@@ -0,0 +1,38 @@
+#ifndef IRSSI_CORE_NET_SENDBUFFER_H
+#define IRSSI_CORE_NET_SENDBUFFER_H
+
+#define DEFAULT_BUFFER_SIZE 8192
+#define MAX_BUFFER_SIZE 1048576
+
+struct _NET_SENDBUF_REC {
+ GIOChannel *handle;
+ LINEBUF_REC *readbuffer; /* receive buffer */
+
+ int send_tag;
+ int bufsize;
+ int bufpos;
+ char *buffer; /* Buffer is NULL until it's actually needed. */
+ int def_bufsize;
+ unsigned int dead:1;
+};
+
+/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE
+ is used */
+NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize);
+/* Destroy the buffer. `close' specifies if socket handle should be closed. */
+void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close);
+
+/* Send data, if all of it couldn't be sent immediately, it will be resent
+ automatically after a while. Returns -1 if some unrecoverable error
+ occurred. */
+int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size);
+
+int net_sendbuffer_receive_line(NET_SENDBUF_REC *rec, char **str, int read_socket);
+
+/* Flush the buffer, blocks until finished. */
+void net_sendbuffer_flush(NET_SENDBUF_REC *rec);
+
+/* Returns the socket handle */
+GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec);
+
+#endif
diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c
new file mode 100644
index 0000000..9956f21
--- /dev/null
+++ b/src/core/network-openssl.c
@@ -0,0 +1,944 @@
+/*
+ network-ssl.c : SSL support
+
+ Copyright (C) 2002 vjt
+
+ 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/network-openssl.h>
+#include <irssi/src/core/net-sendbuffer.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/tls.h>
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+/* OpenSSL 1.1.0 introduced some backward-incompatible changes to the api */
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \
+ (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+/* The two functions below could be already defined if OPENSSL_API_COMPAT is
+ * below the 1.1.0 version so let's do a clean start */
+#undef X509_get_notBefore
+#undef X509_get_notAfter
+#define X509_get_notBefore(x) X509_get0_notBefore(x)
+#define X509_get_notAfter(x) X509_get0_notAfter(x)
+#define ASN1_STRING_data(x) ASN1_STRING_get0_data(x)
+#endif
+
+/* OpenSSL 1.1.0 also introduced some useful additions to the api */
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
+ (defined (LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL)
+static int X509_STORE_up_ref(X509_STORE *vfy)
+{
+ int n;
+
+ n = CRYPTO_add(&vfy->references, 1, CRYPTO_LOCK_X509_STORE);
+ g_assert(n > 1);
+
+ return (n > 1) ? 1 : 0;
+}
+#endif
+#endif
+
+/* ssl i/o channel object */
+typedef struct
+{
+ GIOChannel pad;
+ gint fd;
+ GIOChannel *giochan;
+ SSL *ssl;
+ SSL_CTX *ctx;
+ unsigned int verify:1;
+ SERVER_REC *server;
+ int port;
+} GIOSSLChannel;
+
+static int ssl_inited = FALSE;
+/* https://github.com/irssi/irssi/issues/820 */
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
+static X509_STORE *store = NULL;
+#endif
+
+static void irssi_ssl_free(GIOChannel *handle)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ g_io_channel_unref(chan->giochan);
+ SSL_free(chan->ssl);
+ SSL_CTX_free(chan->ctx);
+ g_free(chan);
+}
+
+/* Checks if the given string has internal NUL characters. */
+static gboolean has_internal_nul(const char* str, int len) {
+ /* Remove trailing nul characters. They would give false alarms */
+ while (len > 0 && str[len-1] == 0)
+ len--;
+ return strlen(str) != len;
+}
+
+/* tls_dns_name - Extract valid DNS name from subjectAltName value */
+static const char *tls_dns_name(const GENERAL_NAME * gn)
+{
+ const char *dnsname;
+
+ /* We expect the OpenSSL library to construct GEN_DNS extension objects as
+ ASN1_IA5STRING values. Check we got the right union member. */
+ if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING) {
+ g_warning("Invalid ASN1 value type in subjectAltName");
+ return NULL;
+ }
+
+ /* Safe to treat as an ASCII string possibly holding a DNS name */
+ dnsname = (char *) ASN1_STRING_data(gn->d.ia5);
+
+ if (has_internal_nul(dnsname, ASN1_STRING_length(gn->d.ia5))) {
+ g_warning("Internal NUL in subjectAltName");
+ return NULL;
+ }
+
+ return dnsname;
+}
+
+/* tls_text_name - extract certificate property value by name */
+static char *tls_text_name(X509_NAME *name, int nid)
+{
+ int pos;
+ X509_NAME_ENTRY *entry;
+ ASN1_STRING *entry_str;
+ int utf8_length;
+ unsigned char *utf8_value;
+ char *result;
+
+ if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) {
+ return NULL;
+ }
+
+ entry = X509_NAME_get_entry(name, pos);
+ g_return_val_if_fail(entry != NULL, NULL);
+ entry_str = X509_NAME_ENTRY_get_data(entry);
+ g_return_val_if_fail(entry_str != NULL, NULL);
+
+ /* Convert everything into UTF-8. It's up to OpenSSL to do something
+ reasonable when converting ASCII formats that contain non-ASCII
+ content. */
+ if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) {
+ g_warning("Error decoding ASN.1 type=%d", ASN1_STRING_type(entry_str));
+ return NULL;
+ }
+
+ if (has_internal_nul((char *)utf8_value, utf8_length)) {
+ g_warning("NUL character in hostname in certificate");
+ OPENSSL_free(utf8_value);
+ return NULL;
+ }
+
+ result = g_strdup((char *) utf8_value);
+ OPENSSL_free(utf8_value);
+ return result;
+}
+
+
+/** check if a hostname in the certificate matches the hostname we used for the connection */
+static gboolean match_hostname(const char *cert_hostname, const char *hostname)
+{
+ const char *hostname_left;
+
+ if (!strcasecmp(cert_hostname, hostname)) { /* exact match */
+ return TRUE;
+ } else if (cert_hostname[0] == '*' && cert_hostname[1] == '.' && cert_hostname[2] != 0) { /* wildcard match */
+ /* The initial '*' matches exactly one hostname component */
+ hostname_left = strchr(hostname, '.');
+ if (hostname_left != NULL && ! strcasecmp(hostname_left + 1, cert_hostname + 2)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* based on verify_extract_name from tls_client.c in postfix */
+static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname)
+{
+ int gen_index, gen_count;
+ gboolean matched = FALSE, has_dns_name = FALSE;
+ const char *cert_dns_name;
+ char *cert_subject_cn;
+ const GENERAL_NAME *gn;
+ STACK_OF(GENERAL_NAME) * gens;
+
+ /* Verify the dNSName(s) in the peer certificate against the hostname. */
+ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0);
+ if (gens) {
+ gen_count = sk_GENERAL_NAME_num(gens);
+ for (gen_index = 0; gen_index < gen_count && !matched; ++gen_index) {
+ gn = sk_GENERAL_NAME_value(gens, gen_index);
+ if (gn->type != GEN_DNS)
+ continue;
+
+ /* Even if we have an invalid DNS name, we still ultimately
+ ignore the CommonName, because subjectAltName:DNS is
+ present (though malformed). */
+ has_dns_name = TRUE;
+ cert_dns_name = tls_dns_name(gn);
+ if (cert_dns_name && *cert_dns_name) {
+ matched = match_hostname(cert_dns_name, hostname);
+ }
+ }
+
+ /* Free stack *and* member GENERAL_NAME objects */
+ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
+ }
+
+ if (has_dns_name) {
+ if (! matched) {
+ /* The CommonName in the issuer DN is obsolete when SubjectAltName is available. */
+ g_warning("None of the Subject Alt Names in the certificate match hostname '%s'", hostname);
+ }
+ return matched;
+ } else { /* No subjectAltNames, look at CommonName */
+ cert_subject_cn = tls_text_name(X509_get_subject_name(cert), NID_commonName);
+ if (cert_subject_cn && *cert_subject_cn) {
+ matched = match_hostname(cert_subject_cn, hostname);
+ if (! matched) {
+ g_warning("SSL certificate common name '%s' doesn't match host name '%s'", cert_subject_cn, hostname);
+ }
+ } else {
+ g_warning("No subjectAltNames and no valid common name in certificate");
+ }
+ g_free(cert_subject_cn);
+ }
+
+ return matched;
+}
+
+static gboolean irssi_ssl_verify(SSL *ssl, SSL_CTX *ctx, const char* hostname, int port, X509 *cert, SERVER_REC *server, TLS_REC *tls_rec)
+{
+ long result;
+
+ result = SSL_get_verify_result(ssl);
+ if (result != X509_V_OK) {
+ g_warning("Could not verify TLS servers certificate: %s", X509_verify_cert_error_string(result));
+ return FALSE;
+ } else if (! irssi_ssl_verify_hostname(cert, hostname)){
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ gint ret1, err;
+ const char *errstr;
+ gchar *errmsg;
+
+ ERR_clear_error();
+ ret1 = SSL_read(chan->ssl, buf, len);
+ if(ret1 <= 0)
+ {
+ *ret = 0;
+ err = SSL_get_error(chan->ssl, ret1);
+ if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+ return G_IO_STATUS_AGAIN;
+ else if(err == SSL_ERROR_ZERO_RETURN)
+ return G_IO_STATUS_EOF;
+ else if (err == SSL_ERROR_SYSCALL)
+ {
+ errstr = ERR_reason_error_string(ERR_get_error());
+ if (errstr == NULL && ret1 == -1)
+ errstr = strerror(errno);
+ if (errstr == NULL)
+ errstr = "server closed connection unexpectedly";
+ }
+ else
+ {
+ errstr = ERR_reason_error_string(ERR_get_error());
+ if (errstr == NULL)
+ errstr = "unknown SSL error";
+ }
+ errmsg = g_strdup_printf("SSL read error: %s", errstr);
+ *gerr = g_error_new_literal(G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED,
+ errmsg);
+ g_free(errmsg);
+ return G_IO_STATUS_ERROR;
+ }
+ else
+ {
+ *ret = ret1;
+ return G_IO_STATUS_NORMAL;
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_write(GIOChannel *handle, const gchar *buf, gsize len, gsize *ret, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ gint ret1, err;
+ const char *errstr;
+ gchar *errmsg;
+
+ ERR_clear_error();
+ ret1 = SSL_write(chan->ssl, (const char *)buf, len);
+ if(ret1 <= 0)
+ {
+ *ret = 0;
+ err = SSL_get_error(chan->ssl, ret1);
+ if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+ return G_IO_STATUS_AGAIN;
+ else if(err == SSL_ERROR_ZERO_RETURN)
+ errstr = "server closed connection";
+ else if (err == SSL_ERROR_SYSCALL)
+ {
+ errstr = ERR_reason_error_string(ERR_get_error());
+ if (errstr == NULL && ret1 == -1)
+ errstr = strerror(errno);
+ if (errstr == NULL)
+ errstr = "server closed connection unexpectedly";
+ }
+ else
+ {
+ errstr = ERR_reason_error_string(ERR_get_error());
+ if (errstr == NULL)
+ errstr = "unknown SSL error";
+ }
+ errmsg = g_strdup_printf("SSL write error: %s", errstr);
+ *gerr = g_error_new_literal(G_IO_CHANNEL_ERROR, G_IO_CHANNEL_ERROR_FAILED,
+ errmsg);
+ g_free(errmsg);
+ return G_IO_STATUS_ERROR;
+ }
+ else
+ {
+ *ret = ret1;
+ return G_IO_STATUS_NORMAL;
+ }
+ /*UNREACH*/
+ return G_IO_STATUS_ERROR;
+}
+
+static GIOStatus irssi_ssl_seek(GIOChannel *handle, gint64 offset, GSeekType type, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_seek(handle, offset, type, gerr);
+}
+
+static GIOStatus irssi_ssl_close(GIOChannel *handle, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_close(handle, gerr);
+}
+
+static GSource *irssi_ssl_create_watch(GIOChannel *handle, GIOCondition cond)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_create_watch(handle, cond);
+}
+
+static GIOStatus irssi_ssl_set_flags(GIOChannel *handle, GIOFlags flags, GError **gerr)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_set_flags(handle, flags, gerr);
+}
+
+static GIOFlags irssi_ssl_get_flags(GIOChannel *handle)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+
+ return chan->giochan->funcs->io_get_flags(handle);
+}
+
+static GIOFuncs irssi_ssl_channel_funcs = {
+ irssi_ssl_read,
+ irssi_ssl_write,
+ irssi_ssl_seek,
+ irssi_ssl_close,
+ irssi_ssl_create_watch,
+ irssi_ssl_free,
+ irssi_ssl_set_flags,
+ irssi_ssl_get_flags
+};
+
+gboolean irssi_ssl_init(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
+ int success;
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER)
+ if (!OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, NULL)) {
+ g_error("Could not initialize OpenSSL");
+ return FALSE;
+ }
+#else
+ SSL_library_init();
+ SSL_load_error_strings();
+ OpenSSL_add_all_algorithms();
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
+ store = X509_STORE_new();
+ if (store == NULL) {
+ g_error("Could not initialize OpenSSL: X509_STORE_new() failed");
+ return FALSE;
+ }
+
+ success = X509_STORE_set_default_paths(store);
+ if (success == 0) {
+ g_warning("Could not load default certificates");
+ X509_STORE_free(store);
+ store = NULL;
+ /* Don't return an error; the user might have their own cafile/capath. */
+ }
+#endif
+
+ ssl_inited = TRUE;
+
+ return TRUE;
+}
+
+static int get_pem_password_callback(char *buffer, int max_length, int rwflag, void *pass)
+{
+ char *password;
+ size_t length;
+
+ if (pass == NULL)
+ return 0;
+
+ password = (char *)pass;
+ length = strlen(pass);
+
+ if (length > max_length)
+ return 0;
+
+ memcpy(buffer, password, length + 1);
+ return length;
+}
+
+static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, int port, SERVER_REC *server)
+{
+ GIOSSLChannel *chan;
+ GIOChannel *gchan;
+ int fd;
+ SSL *ssl;
+ SSL_CTX *ctx = NULL;
+
+ const char *mycert = server->connrec->tls_cert;
+ const char *mypkey = server->connrec->tls_pkey;
+ const char *mypass = server->connrec->tls_pass;
+ const char *cafile = server->connrec->tls_cafile;
+ const char *capath = server->connrec->tls_capath;
+ const char *ciphers = server->connrec->tls_ciphers;
+ gboolean verify = server->connrec->tls_verify;
+
+ g_return_val_if_fail(handle != NULL, NULL);
+
+ if(!ssl_inited && !irssi_ssl_init())
+ return NULL;
+
+ if(!(fd = g_io_channel_unix_get_fd(handle)))
+ return NULL;
+
+ ERR_clear_error();
+ ctx = SSL_CTX_new(SSLv23_client_method());
+ if (ctx == NULL) {
+ g_error("Could not allocate memory for SSL context");
+ return NULL;
+ }
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_default_passwd_cb(ctx, get_pem_password_callback);
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)mypass);
+
+ if (ciphers != NULL && ciphers[0] != '\0') {
+ if (SSL_CTX_set_cipher_list(ctx, ciphers) != 1)
+ g_warning("No valid SSL cipher suite could be selected");
+ }
+
+ if (mycert && *mycert) {
+ char *scert = NULL, *spkey = NULL;
+ FILE *fp;
+ scert = convert_home(mycert);
+ if (mypkey && *mypkey)
+ spkey = convert_home(mypkey);
+
+ if ((fp = fopen(scert, "r"))) {
+ X509 *cert;
+ /* Let's parse the certificate by hand instead of using
+ * SSL_CTX_use_certificate_file so that we can validate
+ * some parts of it. */
+ cert = PEM_read_X509(fp, NULL, get_pem_password_callback, (void *)mypass);
+ if (cert != NULL) {
+ /* Only the expiration date is checked right now */
+ if (X509_cmp_current_time(X509_get_notAfter(cert)) <= 0 ||
+ X509_cmp_current_time(X509_get_notBefore(cert)) >= 0)
+ g_warning("The client certificate is expired");
+
+ ERR_clear_error();
+ if (! SSL_CTX_use_certificate(ctx, cert))
+ g_warning("Loading of client certificate '%s' failed: %s", mycert, ERR_reason_error_string(ERR_get_error()));
+ else if (! SSL_CTX_use_PrivateKey_file(ctx, spkey ? spkey : scert, SSL_FILETYPE_PEM))
+ g_warning("Loading of private key '%s' failed: %s", mypkey ? mypkey : mycert, ERR_reason_error_string(ERR_get_error()));
+ else if (! SSL_CTX_check_private_key(ctx))
+ g_warning("Private key does not match the certificate");
+
+ X509_free(cert);
+ } else
+ g_warning("Loading of client certificate '%s' failed: %s", mycert, ERR_reason_error_string(ERR_get_error()));
+
+ fclose(fp);
+ } else
+ g_warning("Could not find client certificate '%s'", scert);
+ g_free(scert);
+ g_free(spkey);
+ }
+
+ if ((cafile && *cafile) || (capath && *capath)) {
+ char *scafile = NULL;
+ char *scapath = NULL;
+ if (cafile && *cafile)
+ scafile = convert_home(cafile);
+ if (capath && *capath)
+ scapath = convert_home(capath);
+ if (! SSL_CTX_load_verify_locations(ctx, scafile, scapath)) {
+ g_warning("Could not load CA list for verifying TLS server certificate");
+ g_free(scafile);
+ g_free(scapath);
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+ g_free(scafile);
+ g_free(scapath);
+ verify = TRUE;
+ }
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
+ else if (store != NULL) {
+ /* Make sure to increment the refcount every time the store is
+ * used, that's essential not to get it free'd by OpenSSL when
+ * the SSL_CTX is destroyed. */
+ X509_STORE_up_ref(store);
+ SSL_CTX_set_cert_store(ctx, store);
+ }
+#else
+ else {
+ if (!SSL_CTX_set_default_verify_paths(ctx))
+ g_warning("Could not load default certificates");
+ }
+#endif
+
+ if(!(ssl = SSL_new(ctx)))
+ {
+ g_warning("Failed to allocate SSL structure");
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+
+ if(!SSL_set_fd(ssl, fd))
+ {
+ g_warning("Failed to associate socket to SSL stream");
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ SSL_set_tlsext_host_name(ssl, server->connrec->address);
+#endif
+
+ SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE |
+ SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ chan = g_new0(GIOSSLChannel, 1);
+ chan->fd = fd;
+ chan->giochan = handle;
+ chan->ssl = ssl;
+ chan->ctx = ctx;
+ chan->server = server;
+ chan->port = port;
+ chan->verify = verify;
+
+ gchan = (GIOChannel *)chan;
+ gchan->funcs = &irssi_ssl_channel_funcs;
+ g_io_channel_init(gchan);
+ gchan->is_readable = gchan->is_writeable = TRUE;
+ gchan->use_buffer = FALSE;
+
+ return gchan;
+}
+
+static void set_cipher_info(TLS_REC *tls, SSL *ssl)
+{
+ g_return_if_fail(tls != NULL);
+ g_return_if_fail(ssl != NULL);
+
+ tls_rec_set_protocol_version(tls, SSL_get_version(ssl));
+
+ tls_rec_set_cipher(tls, SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)));
+ tls_rec_set_cipher_size(tls, SSL_get_cipher_bits(ssl, NULL));
+}
+
+static gboolean set_pubkey_info(TLS_REC *tls, X509 *cert, unsigned char *cert_fingerprint, size_t cert_fingerprint_size, unsigned char *public_key_fingerprint, size_t public_key_fingerprint_size)
+{
+ gboolean ret = TRUE;
+ EVP_PKEY *pubkey = NULL;
+ char *cert_fingerprint_hex = NULL;
+ char *public_key_fingerprint_hex = NULL;
+
+ BIO *bio = NULL;
+ char buffer[128];
+ ssize_t length;
+
+ g_return_val_if_fail(tls != NULL, FALSE);
+ g_return_val_if_fail(cert != NULL, FALSE);
+
+ pubkey = X509_get_pubkey(cert);
+
+ cert_fingerprint_hex = binary_to_hex(cert_fingerprint, cert_fingerprint_size);
+ tls_rec_set_certificate_fingerprint(tls, cert_fingerprint_hex);
+ tls_rec_set_certificate_fingerprint_algorithm(tls, "SHA256");
+
+ /* Show algorithm. */
+ switch (EVP_PKEY_id(pubkey)) {
+ case EVP_PKEY_RSA:
+ tls_rec_set_public_key_algorithm(tls, "RSA");
+ break;
+
+ case EVP_PKEY_DSA:
+ tls_rec_set_public_key_algorithm(tls, "DSA");
+ break;
+
+ case EVP_PKEY_EC:
+ tls_rec_set_public_key_algorithm(tls, "EC");
+ break;
+
+ default:
+ tls_rec_set_public_key_algorithm(tls, "Unknown");
+ break;
+ }
+
+ public_key_fingerprint_hex = binary_to_hex(public_key_fingerprint, public_key_fingerprint_size);
+ tls_rec_set_public_key_fingerprint(tls, public_key_fingerprint_hex);
+ tls_rec_set_public_key_size(tls, EVP_PKEY_bits(pubkey));
+ tls_rec_set_public_key_fingerprint_algorithm(tls, "SHA256");
+
+ /* Read the NotBefore timestamp. */
+ bio = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(bio, X509_get_notBefore(cert));
+ length = BIO_read(bio, buffer, sizeof(buffer));
+ if (length < 0) {
+ ret = FALSE;
+ BIO_free(bio);
+ goto done;
+ }
+ buffer[length] = '\0';
+ BIO_free(bio);
+ tls_rec_set_not_before(tls, buffer);
+
+ /* Read the NotAfter timestamp. */
+ bio = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(bio, X509_get_notAfter(cert));
+ length = BIO_read(bio, buffer, sizeof(buffer));
+ if (length < 0) {
+ ret = FALSE;
+ BIO_free(bio);
+ goto done;
+ }
+ buffer[length] = '\0';
+ BIO_free(bio);
+ tls_rec_set_not_after(tls, buffer);
+
+done:
+ g_free(cert_fingerprint_hex);
+ g_free(public_key_fingerprint_hex);
+ EVP_PKEY_free(pubkey);
+
+ return ret;
+}
+
+static void set_peer_cert_chain_info(TLS_REC *tls, SSL *ssl)
+{
+ int nid;
+ char *key = NULL;
+ char *value = NULL;
+ STACK_OF(X509) *chain = NULL;
+ int i;
+ int j;
+ TLS_CERT_REC *cert_rec = NULL;
+ X509_NAME *name = NULL;
+ X509_NAME_ENTRY *entry = NULL;
+ TLS_CERT_ENTRY_REC *tls_cert_entry_rec = NULL;
+ ASN1_STRING *data = NULL;
+
+ g_return_if_fail(tls != NULL);
+ g_return_if_fail(ssl != NULL);
+
+ chain = SSL_get_peer_cert_chain(ssl);
+
+ if (chain == NULL)
+ return;
+
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ cert_rec = tls_cert_create_rec();
+
+ /* Subject. */
+ name = X509_get_subject_name(sk_X509_value(chain, i));
+
+ for (j = 0; j < X509_NAME_entry_count(name); j++) {
+ entry = X509_NAME_get_entry(name, j);
+
+ nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry));
+ key = (char *)OBJ_nid2sn(nid);
+
+ if (key == NULL)
+ key = (char *)OBJ_nid2ln(nid);
+
+ data = X509_NAME_ENTRY_get_data(entry);
+ value = (char *)ASN1_STRING_data(data);
+
+ tls_cert_entry_rec = tls_cert_entry_create_rec(key, value);
+ tls_cert_rec_append_subject_entry(cert_rec, tls_cert_entry_rec);
+ }
+
+ /* Issuer. */
+ name = X509_get_issuer_name(sk_X509_value(chain, i));
+
+ for (j = 0; j < X509_NAME_entry_count(name); j++) {
+ entry = X509_NAME_get_entry(name, j);
+
+ nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry));
+ key = (char *)OBJ_nid2sn(nid);
+
+ if (key == NULL)
+ key = (char *)OBJ_nid2ln(nid);
+
+ data = X509_NAME_ENTRY_get_data(entry);
+ value = (char *)ASN1_STRING_data(data);
+
+ tls_cert_entry_rec = tls_cert_entry_create_rec(key, value);
+ tls_cert_rec_append_issuer_entry(cert_rec, tls_cert_entry_rec);
+ }
+
+ tls_rec_append_cert(tls, cert_rec);
+ }
+}
+
+static void set_server_temporary_key_info(TLS_REC *tls, SSL *ssl)
+{
+#ifdef SSL_get_server_tmp_key
+ /* Show ephemeral key information. */
+ EVP_PKEY *ephemeral_key = NULL;
+
+ /* OPENSSL_NO_EC is for solaris 11.3 (2016), github ticket #598 */
+#ifndef OPENSSL_NO_EC
+ EC_KEY *ec_key = NULL;
+#endif
+ char *ephemeral_key_algorithm = NULL;
+ char *cname = NULL;
+ int nid;
+
+ g_return_if_fail(tls != NULL);
+ g_return_if_fail(ssl != NULL);
+
+ if (SSL_get_server_tmp_key(ssl, &ephemeral_key)) {
+ switch (EVP_PKEY_id(ephemeral_key)) {
+ case EVP_PKEY_DH:
+ tls_rec_set_ephemeral_key_algorithm(tls, "DH");
+ tls_rec_set_ephemeral_key_size(tls, EVP_PKEY_bits(ephemeral_key));
+ break;
+
+#ifndef OPENSSL_NO_EC
+ case EVP_PKEY_EC:
+ ec_key = EVP_PKEY_get1_EC_KEY(ephemeral_key);
+ nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key));
+ EC_KEY_free(ec_key);
+ cname = (char *)OBJ_nid2sn(nid);
+ ephemeral_key_algorithm = g_strdup_printf("ECDH: %s", cname);
+
+ tls_rec_set_ephemeral_key_algorithm(tls, ephemeral_key_algorithm);
+ tls_rec_set_ephemeral_key_size(tls, EVP_PKEY_bits(ephemeral_key));
+
+ g_free_and_null(ephemeral_key_algorithm);
+ break;
+#endif
+
+ default:
+ tls_rec_set_ephemeral_key_algorithm(tls, "Unknown");
+ tls_rec_set_ephemeral_key_size(tls, EVP_PKEY_bits(ephemeral_key));
+ break;
+ }
+
+ EVP_PKEY_free(ephemeral_key);
+ }
+#endif /* SSL_get_server_tmp_key. */
+}
+
+GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server)
+{
+ GIOChannel *handle, *ssl_handle;
+
+ handle = net_connect_ip(ip, port, my_ip);
+ if (handle == NULL)
+ return NULL;
+ ssl_handle = irssi_ssl_get_iochannel(handle, port, server);
+ if (ssl_handle == NULL)
+ g_io_channel_unref(handle);
+ return ssl_handle;
+}
+
+GIOChannel *net_start_ssl(SERVER_REC *server)
+{
+ GIOChannel *handle, *ssl_handle;
+
+ g_return_val_if_fail(server != NULL, NULL);
+
+ handle = net_sendbuffer_handle(server->handle);
+ if (handle == NULL)
+ return NULL;
+
+ ssl_handle = irssi_ssl_get_iochannel(handle, server->connrec->port, server);
+ return ssl_handle;
+}
+
+
+int irssi_ssl_handshake(GIOChannel *handle)
+{
+ GIOSSLChannel *chan = (GIOSSLChannel *)handle;
+ int ret, err;
+ const char *errstr = NULL;
+ X509 *cert = NULL;
+ X509_PUBKEY *pubkey = NULL;
+ int pubkey_size = 0;
+ unsigned char *pubkey_der = NULL;
+ unsigned char *pubkey_der_tmp = NULL;
+ unsigned char pubkey_fingerprint[EVP_MAX_MD_SIZE];
+ unsigned int pubkey_fingerprint_size;
+ unsigned char cert_fingerprint[EVP_MAX_MD_SIZE];
+ unsigned int cert_fingerprint_size;
+ const char *pinned_cert_fingerprint = chan->server->connrec->tls_pinned_cert;
+ const char *pinned_pubkey_fingerprint = chan->server->connrec->tls_pinned_pubkey;
+ TLS_REC *tls = NULL;
+
+ ERR_clear_error();
+ ret = SSL_connect(chan->ssl);
+ if (ret <= 0) {
+ err = SSL_get_error(chan->ssl, ret);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ return 1;
+ case SSL_ERROR_WANT_WRITE:
+ return 3;
+ case SSL_ERROR_ZERO_RETURN:
+ g_warning("SSL handshake failed: %s", "server closed connection");
+ return -1;
+ case SSL_ERROR_SYSCALL:
+ errstr = ERR_reason_error_string(ERR_get_error());
+ if (errstr == NULL && ret == -1 && errno)
+ errstr = strerror(errno);
+ g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "server closed connection unexpectedly");
+ return -1;
+ default:
+ errstr = ERR_reason_error_string(ERR_get_error());
+ g_warning("SSL handshake failed: %s", errstr != NULL ? errstr : "unknown SSL error");
+ return -1;
+ }
+ }
+
+ cert = SSL_get_peer_certificate(chan->ssl);
+ if (cert == NULL) {
+ g_warning("TLS server supplied no certificate");
+ ret = 0;
+ goto done;
+ }
+
+ pubkey = X509_get_X509_PUBKEY(cert);
+ if (pubkey == NULL) {
+ g_warning("TLS server supplied no certificate public key");
+ ret = 0;
+ goto done;
+ }
+
+ if (! X509_digest(cert, EVP_sha256(), cert_fingerprint, &cert_fingerprint_size)) {
+ g_warning("Unable to generate certificate fingerprint");
+ ret = 0;
+ goto done;
+ }
+
+ pubkey_size = i2d_X509_PUBKEY(pubkey, NULL);
+ pubkey_der = pubkey_der_tmp = g_new(unsigned char, pubkey_size);
+ i2d_X509_PUBKEY(pubkey, &pubkey_der_tmp);
+
+ EVP_Digest(pubkey_der, pubkey_size, pubkey_fingerprint, &pubkey_fingerprint_size, EVP_sha256(), 0);
+
+ tls = tls_create_rec();
+ set_cipher_info(tls, chan->ssl);
+ if (! set_pubkey_info(tls, cert, cert_fingerprint, cert_fingerprint_size, pubkey_fingerprint, pubkey_fingerprint_size)) {
+ g_warning("Couldn't set pubkey information");
+ ret = 0;
+ goto done;
+ }
+ set_peer_cert_chain_info(tls, chan->ssl);
+ set_server_temporary_key_info(tls, chan->ssl);
+
+ /* Emit the TLS rec. */
+ signal_emit("tls handshake finished", 2, chan->server, tls);
+
+ ret = 1;
+
+ if (pinned_cert_fingerprint != NULL && pinned_cert_fingerprint[0] != '\0') {
+ ret = g_ascii_strcasecmp(pinned_cert_fingerprint, tls->certificate_fingerprint) == 0;
+
+ if (! ret) {
+ g_warning(" Pinned certificate mismatch");
+ goto done;
+ }
+ }
+
+ if (pinned_pubkey_fingerprint != NULL && pinned_pubkey_fingerprint[0] != '\0') {
+ ret = g_ascii_strcasecmp(pinned_pubkey_fingerprint, tls->public_key_fingerprint) == 0;
+
+ if (! ret) {
+ g_warning(" Pinned public key mismatch");
+ goto done;
+ }
+ }
+
+ if (chan->verify) {
+ ret = irssi_ssl_verify(chan->ssl, chan->ctx, chan->server->connrec->address, chan->port, cert, chan->server, tls);
+
+ if (! ret) {
+ /* irssi_ssl_verify emits a warning itself. */
+ goto done;
+ }
+ }
+
+done:
+ tls_rec_free(tls);
+ X509_free(cert);
+ g_free(pubkey_der);
+
+ return ret ? 0 : -1;
+}
diff --git a/src/core/network-openssl.h b/src/core/network-openssl.h
new file mode 100644
index 0000000..2c204fb
--- /dev/null
+++ b/src/core/network-openssl.h
@@ -0,0 +1,6 @@
+#ifndef IRSSI_CORE_NETWORK_OPENSSL_H
+#define IRSSI_CORE_NETWORK_OPENSSL_H
+
+gboolean irssi_ssl_init(void);
+
+#endif /* !IRSSI_CORE_NETWORK_OPENSSL_H */
diff --git a/src/core/network.c b/src/core/network.c
new file mode 100644
index 0000000..85fe989
--- /dev/null
+++ b/src/core/network.c
@@ -0,0 +1,584 @@
+/*
+ network.c : Network stuff
+
+ 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>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <sys/un.h>
+
+#ifndef INADDR_NONE
+# define INADDR_NONE INADDR_BROADCAST
+#endif
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
+
+#define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \
+ sizeof(so.sin6) : sizeof(so.sin))
+
+GIOChannel *i_io_channel_new(int handle)
+{
+ GIOChannel *chan;
+ chan = g_io_channel_unix_new(handle);
+ g_io_channel_set_encoding(chan, NULL, NULL);
+ g_io_channel_set_buffered(chan, FALSE);
+ return chan;
+}
+
+int i_io_channel_write_block(GIOChannel *channel, void *data, int len)
+{
+ gsize ret;
+ int sent;
+ GIOStatus status;
+
+ sent = 0;
+ do {
+ status = g_io_channel_write_chars(channel, (char *) data + sent, len - sent, &ret, NULL);
+ sent += ret;
+ } while (sent < len && status != G_IO_STATUS_ERROR);
+
+ return sent < len ? -1 : 0;
+}
+
+int i_io_channel_read_block(GIOChannel *channel, void *data, int len)
+{
+ time_t maxwait;
+ gsize ret;
+ int received;
+ GIOStatus status;
+
+ maxwait = time(NULL)+2;
+ received = 0;
+ do {
+ status = g_io_channel_read_chars(channel, (char *) data + received, len - received, &ret, NULL);
+ received += ret;
+ } while (received < len && time(NULL) < maxwait &&
+ status != G_IO_STATUS_ERROR && status != G_IO_STATUS_EOF);
+
+ return received < len ? -1 : 0;
+}
+
+IPADDR ip4_any = {
+ AF_INET,
+#if defined(IN6ADDR_ANY_INIT)
+ IN6ADDR_ANY_INIT
+#else
+ { INADDR_ANY }
+#endif
+};
+
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2)
+{
+ if (ip1->family != ip2->family)
+ return 0;
+
+ if (ip1->family == AF_INET6)
+ return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0;
+
+ return memcmp(&ip1->ip, &ip2->ip, 4) == 0;
+}
+
+
+static void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
+{
+ if (ip == NULL) {
+ so->sin6.sin6_family = AF_INET6;
+ so->sin6.sin6_addr = in6addr_any;
+ return;
+ }
+
+ so->sin.sin_family = ip->family;
+
+ if (ip->family == AF_INET6)
+ memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip));
+ else
+ memcpy(&so->sin.sin_addr, &ip->ip, 4);
+}
+
+void sin_get_ip(const union sockaddr_union *so, IPADDR *ip)
+{
+ ip->family = so->sin.sin_family;
+
+ if (ip->family == AF_INET6)
+ memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip));
+ else
+ memcpy(&ip->ip, &so->sin.sin_addr, 4);
+}
+
+static void sin_set_port(union sockaddr_union *so, int port)
+{
+ if (so->sin.sin_family == AF_INET6)
+ so->sin6.sin6_port = htons((unsigned short)port);
+ else
+ so->sin.sin_port = htons((unsigned short)port);
+}
+
+static int sin_get_port(union sockaddr_union *so)
+{
+ return ntohs((so->sin.sin_family == AF_INET6) ?
+ so->sin6.sin6_port :
+ so->sin.sin_port);
+}
+
+int net_connect_ip_handle(const IPADDR *ip, int port, const IPADDR *my_ip)
+{
+ union sockaddr_union so;
+ int handle, ret, opt = 1;
+
+ if (my_ip != NULL && ip->family != my_ip->family) {
+ g_warning("net_connect_ip(): ip->family != my_ip->family");
+ my_ip = NULL;
+ }
+
+ /* create the socket */
+ memset(&so, 0, sizeof(so));
+ so.sin.sin_family = ip->family;
+ handle = socket(ip->family, SOCK_STREAM, 0);
+
+ if (handle == -1)
+ return -1;
+
+ /* set socket options */
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
+
+ /* set our own address */
+ if (my_ip != NULL) {
+ sin_set_ip(&so, my_ip);
+ if (bind(handle, &so.sa, SIZEOF_SOCKADDR(so)) < 0) {
+ int old_errno = errno;
+
+ close(handle);
+ errno = old_errno;
+ return -1;
+ }
+ }
+
+ /* connect */
+ sin_set_ip(&so, ip);
+ sin_set_port(&so, port);
+ ret = connect(handle, &so.sa, SIZEOF_SOCKADDR(so));
+
+ if (ret < 0 && errno != EINPROGRESS)
+ {
+ int old_errno = errno;
+ close(handle);
+ errno = old_errno;
+ return -1;
+ }
+
+ return handle;
+}
+
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+ int handle = -1;
+
+#ifdef HAVE_CAPSICUM
+ if (capsicum_enabled())
+ handle = capsicum_net_connect_ip(ip, port, my_ip);
+ else
+ handle = net_connect_ip_handle(ip, port, my_ip);
+#else
+ handle = net_connect_ip_handle(ip, port, my_ip);
+#endif
+
+ if (handle == -1)
+ return (NULL);
+
+ return i_io_channel_new(handle);
+}
+
+/* Connect to named UNIX socket */
+GIOChannel *net_connect_unix(const char *path)
+{
+ struct sockaddr_un sa;
+ int handle, ret;
+
+ /* create the socket */
+ handle = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (handle == -1)
+ return NULL;
+
+ /* set socket options */
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+
+ /* connect */
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ strncpy(sa.sun_path, path, sizeof(sa.sun_path)-1);
+ sa.sun_path[sizeof(sa.sun_path)-1] = '\0';
+
+ ret = connect(handle, (struct sockaddr *) &sa, sizeof(sa));
+ if (ret < 0 && errno != EINPROGRESS) {
+ int old_errno = errno;
+ close(handle);
+ errno = old_errno;
+ return NULL;
+ }
+
+ return i_io_channel_new(handle);
+}
+
+/* Disconnect socket */
+void net_disconnect(GIOChannel *handle)
+{
+ g_return_if_fail(handle != NULL);
+
+ g_io_channel_shutdown(handle, TRUE, NULL);
+ g_io_channel_unref(handle);
+}
+
+/* Listen for connections on a socket. if `my_ip' is NULL, listen in any
+ address. */
+GIOChannel *net_listen(IPADDR *my_ip, int *port)
+{
+ union sockaddr_union so;
+ int ret, handle, opt = 1;
+ socklen_t len;
+
+ g_return_val_if_fail(port != NULL, NULL);
+
+ memset(&so, 0, sizeof(so));
+ sin_set_ip(&so, my_ip);
+ sin_set_port(&so, *port);
+
+ /* create the socket */
+ handle = socket(so.sin.sin_family, SOCK_STREAM, 0);
+
+ if (handle == -1 && (errno == EINVAL || errno == EAFNOSUPPORT)) {
+ /* IPv6 is not supported by OS */
+ so.sin.sin_family = AF_INET;
+ so.sin.sin_addr.s_addr = INADDR_ANY;
+
+ handle = socket(AF_INET, SOCK_STREAM, 0);
+ }
+
+ if (handle == -1)
+ return NULL;
+
+ /* set socket options */
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
+
+ /* specify the address/port we want to listen in */
+ ret = bind(handle, &so.sa, SIZEOF_SOCKADDR(so));
+ if (ret >= 0) {
+ /* get the actual port we started listen */
+ len = SIZEOF_SOCKADDR(so);
+ ret = getsockname(handle, &so.sa, &len);
+ if (ret >= 0) {
+ *port = sin_get_port(&so);
+
+ /* start listening */
+ if (listen(handle, 1) >= 0)
+ return i_io_channel_new(handle);
+ }
+
+ }
+
+ /* error */
+ close(handle);
+ return NULL;
+}
+
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+ int ret;
+ socklen_t addrlen;
+
+ g_return_val_if_fail(handle != NULL, NULL);
+
+ addrlen = sizeof(so);
+ ret = accept(g_io_channel_unix_get_fd(handle), &so.sa, &addrlen);
+
+ if (ret < 0)
+ return NULL;
+
+ if (addr != NULL) sin_get_ip(&so, addr);
+ if (port != NULL) *port = sin_get_port(&so);
+
+ fcntl(ret, F_SETFL, O_NONBLOCK);
+ return i_io_channel_new(ret);
+}
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len)
+{
+ gsize ret;
+ GIOStatus status;
+ GError *err = NULL;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(buf != NULL, -1);
+
+ status = g_io_channel_read_chars(handle, buf, len, &ret, &err);
+ if (err != NULL) {
+ g_warning("%s", err->message);
+ g_error_free(err);
+ }
+ if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF)
+ return -1; /* disconnected */
+
+ return ret;
+}
+
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len)
+{
+ gsize ret;
+ GIOStatus status;
+ GError *err = NULL;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(data != NULL, -1);
+
+ status = g_io_channel_write_chars(handle, (char *) data, len, &ret, &err);
+ if (err != NULL) {
+ g_warning("%s", err->message);
+ g_error_free(err);
+ }
+ if (status == G_IO_STATUS_ERROR)
+ return -1;
+
+ return ret;
+}
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+ socklen_t addrlen;
+
+ g_return_val_if_fail(handle != NULL, -1);
+ g_return_val_if_fail(addr != NULL, -1);
+
+ addrlen = sizeof(so);
+ if (getsockname(g_io_channel_unix_get_fd(handle),
+ (struct sockaddr *) &so, &addrlen) == -1)
+ return -1;
+
+ sin_get_ip(&so, addr);
+ if (port) *port = sin_get_port(&so);
+
+ return 0;
+}
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+ If ip->family is 0, the address wasn't found.
+ Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6)
+{
+ union sockaddr_union *so;
+ struct addrinfo hints, *ai, *ailist;
+ int ret, count_v4, count_v6, use_v4, use_v6;
+
+#ifdef HAVE_CAPSICUM
+ if (capsicum_enabled())
+ return (capsicum_net_gethostbyname(addr, ip4, ip6));
+#endif
+
+ g_return_val_if_fail(addr != NULL, -1);
+
+ memset(ip4, 0, sizeof(IPADDR));
+ memset(ip6, 0, sizeof(IPADDR));
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+ /* save error to host_error for later use */
+ ret = getaddrinfo(addr, NULL, &hints, &ailist);
+ if (ret != 0)
+ return ret;
+
+ /* count IPs */
+ count_v4 = count_v6 = 0;
+ for (ai = ailist; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_family == AF_INET)
+ count_v4++;
+ else if (ai->ai_family == AF_INET6)
+ count_v6++;
+ }
+
+ if (count_v4 == 0 && count_v6 == 0)
+ return HOST_NOT_FOUND; /* shouldn't happen? */
+
+ /* if there are multiple addresses, return random one */
+ use_v4 = count_v4 <= 1 ? 0 : rand() % count_v4;
+ use_v6 = count_v6 <= 1 ? 0 : rand() % count_v6;
+
+ count_v4 = count_v6 = 0;
+ for (ai = ailist; ai != NULL; ai = ai->ai_next) {
+ so = (union sockaddr_union *) ai->ai_addr;
+
+ if (ai->ai_family == AF_INET) {
+ if (use_v4 == count_v4)
+ sin_get_ip(so, ip4);
+ count_v4++;
+ } else if (ai->ai_family == AF_INET6) {
+ if (use_v6 == count_v6)
+ sin_get_ip(so, ip6);
+ count_v6++;
+ }
+ }
+ freeaddrinfo(ailist);
+ return 0;
+}
+
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name)
+{
+ union sockaddr_union so;
+ int host_error;
+ char hostname[NI_MAXHOST];
+
+ g_return_val_if_fail(ip != NULL, -1);
+ g_return_val_if_fail(name != NULL, -1);
+
+ *name = NULL;
+
+ memset(&so, 0, sizeof(so));
+ sin_set_ip(&so, ip);
+
+ /* save error to host_error for later use */
+ host_error = getnameinfo((struct sockaddr *)&so, sizeof(so),
+ hostname, sizeof(hostname),
+ NULL, 0,
+ NI_NAMEREQD);
+ if (host_error != 0)
+ return host_error;
+
+ *name = g_strdup(hostname);
+
+ return 0;
+}
+
+int net_ip2host(IPADDR *ip, char *host)
+{
+ host[0] = '\0';
+ return inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN) ? 0 : -1;
+}
+
+int net_host2ip(const char *host, IPADDR *ip)
+{
+ unsigned long addr;
+
+ if (strchr(host, ':') != NULL) {
+ /* IPv6 */
+ ip->family = AF_INET6;
+ if (inet_pton(AF_INET6, host, &ip->ip) == 0)
+ return -1;
+ } else {
+ /* IPv4 */
+ ip->family = AF_INET;
+#ifdef HAVE_INET_ATON
+ if (inet_aton(host, &ip->ip.s_addr) == 0)
+ return -1;
+#else
+ addr = inet_addr(host);
+ if (addr == INADDR_NONE)
+ return -1;
+
+ memcpy(&ip->ip, &addr, 4);
+#endif
+ }
+
+ return 0;
+}
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle)
+{
+ int data;
+ socklen_t len = sizeof(data);
+
+ if (getsockopt(g_io_channel_unix_get_fd(handle),
+ SOL_SOCKET, SO_ERROR, (void *) &data, &len) == -1)
+ return -1;
+
+ return data;
+}
+
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error)
+{
+ g_return_val_if_fail(error != 0, NULL);
+
+ if (error == EAI_SYSTEM) {
+ return strerror(errno);
+ } else {
+ return gai_strerror(error);
+ }
+}
+
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+ some error with name server) */
+int net_hosterror_notfound(int error)
+{
+#ifdef EAI_NODATA /* NODATA is deprecated */
+ return error != 1 && (error == EAI_NONAME || error == EAI_NODATA);
+#else
+ return error != 1 && (error == EAI_NONAME);
+#endif
+}
+
+/* Get name of TCP service */
+char *net_getservbyport(int port)
+{
+ struct servent *entry;
+
+ entry = getservbyport(htons((unsigned short) port), "tcp");
+ return entry == NULL ? NULL : entry->s_name;
+}
+
+int is_ipv4_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != '.' && !i_isdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
+
+int is_ipv6_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != ':' && !i_isxdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
diff --git a/src/core/network.h b/src/core/network.h
new file mode 100644
index 0000000..0747f01
--- /dev/null
+++ b/src/core/network.h
@@ -0,0 +1,97 @@
+#ifndef IRSSI_CORE_NETWORK_H
+#define IRSSI_CORE_NETWORK_H
+
+#ifdef HAVE_SOCKS_H
+#include <socks.h>
+#endif
+
+#include <sys/types.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# include <arpa/inet.h>
+
+#ifndef AF_INET6
+# ifdef PF_INET6
+# define AF_INET6 PF_INET6
+# else
+# define AF_INET6 10
+# endif
+#endif
+
+struct _IPADDR {
+ unsigned short family;
+ struct in6_addr ip;
+};
+
+/* maxmimum string length of IP address */
+#define MAX_IP_LEN INET6_ADDRSTRLEN
+
+#define IPADDR_IS_V6(ip) ((ip)->family != AF_INET)
+
+extern IPADDR ip4_any;
+
+GIOChannel *i_io_channel_new(int handle);
+
+/* Returns 1 if IPADDRs are the same. */
+/* Deprecated since it is unused. It will be deleted in a later release. */
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2) G_GNUC_DEPRECATED;
+int i_io_channel_write_block(GIOChannel *channel, void *data, int len);
+int i_io_channel_read_block(GIOChannel *channel, void *data, int len);
+
+int net_connect_ip_handle(const IPADDR *ip, int port, const IPADDR *my_ip);
+
+/* Connect to socket with ip address and SSL*/
+GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, SERVER_REC *server);
+/* Start TLS */
+GIOChannel *net_start_ssl(SERVER_REC *server);
+
+int irssi_ssl_handshake(GIOChannel *handle);
+/* Connect to socket with ip address */
+GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+/* Connect to named UNIX socket */
+GIOChannel *net_connect_unix(const char *path);
+/* Disconnect socket */
+void net_disconnect(GIOChannel *handle);
+
+/* Listen for connections on a socket */
+GIOChannel *net_listen(IPADDR *my_ip, int *port);
+/* Accept a connection on a socket */
+GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(GIOChannel *handle, char *buf, int len);
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(GIOChannel *handle, const char *data, int len);
+
+/* Get IP addresses for host, both IPv4 and IPv6 if possible.
+ If ip->family is 0, the address wasn't found.
+ Returns 0 = ok, others = error code for net_gethosterror() */
+int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6);
+/* Get name for host, *name should be g_free()'d unless it's NULL.
+ Return values are the same as with net_gethostbyname() */
+int net_gethostbyaddr(IPADDR *ip, char **name);
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error);
+/* return TRUE if host lookup failed because it didn't exist (ie. not
+ some error with name server) */
+int net_hosterror_notfound(int error);
+
+/* Get socket address/port */
+int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port);
+
+/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */
+int net_ip2host(IPADDR *ip, char *host);
+/* char* -> IPADDR translation. */
+int net_host2ip(const char *host, IPADDR *ip);
+
+/* Get socket error */
+int net_geterror(GIOChannel *handle);
+
+/* Get name of TCP service */
+char *net_getservbyport(int port);
+
+int is_ipv4_address(const char *host);
+int is_ipv6_address(const char *host);
+
+#endif
diff --git a/src/core/nick-rec.h b/src/core/nick-rec.h
new file mode 100644
index 0000000..53bc709
--- /dev/null
+++ b/src/core/nick-rec.h
@@ -0,0 +1,29 @@
+/* NICK_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("NICK", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+time_t last_check; /* last time gone was checked */
+
+char *nick;
+char *host;
+char *realname;
+char *account;
+int hops;
+
+/* status in server */
+unsigned int gone:1;
+unsigned int serverop:1;
+
+/* status in channel */
+unsigned int send_massjoin:1; /* Waiting to be sent in massjoin signal */
+unsigned int op:1;
+unsigned int halfop:1;
+unsigned int voice:1;
+char prefixes[MAX_USER_PREFIXES+1];
+
+/*GHashTable *module_data;*/
+
+void *unique_id; /* unique ID to use for comparing if one nick is in another channels,
+ or NULL = nicks are unique, just keep comparing them. */
+NICK_REC *next; /* support for multiple identically named nicks */
diff --git a/src/core/nicklist.c b/src/core/nicklist.c
new file mode 100644
index 0000000..34c4b24
--- /dev/null
+++ b/src/core/nicklist.c
@@ -0,0 +1,607 @@
+/*
+ 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/core/servers.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/nicklist.h>
+#include <irssi/src/core/masks.h>
+
+#define isalnumhigh(a) \
+ (i_isalnum(a) || (unsigned char) (a) >= 128)
+
+static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *list;
+
+ nick->next = NULL;
+
+ list = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (list == NULL)
+ g_hash_table_insert(channel->nicks, nick->nick, nick);
+ else {
+ /* multiple nicks with same name */
+ while (list->next != NULL)
+ list = list->next;
+ list->next = nick;
+ }
+
+ if (nick == channel->ownnick) {
+ /* move our own nick to beginning of the nick list.. */
+ nicklist_set_own(channel, nick);
+ }
+}
+
+static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *list, *newlist;
+
+ list = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (list == NULL)
+ return;
+
+ if (list == nick) {
+ newlist = nick->next;
+ } else {
+ newlist = list;
+ while (list->next != nick)
+ list = list->next;
+ list->next = nick->next;
+ }
+
+ g_hash_table_remove(channel->nicks, nick->nick);
+ if (newlist != NULL) {
+ g_hash_table_insert(channel->nicks, newlist->nick,
+ newlist);
+ }
+}
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ /*MODULE_DATA_INIT(nick);*/
+
+ nick->type = module_get_uniq_id("NICK", 0);
+ nick->chat_type = channel->chat_type;
+
+ nick_hash_add(channel, nick);
+ signal_emit("nicklist new", 2, channel, nick);
+}
+
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host)
+{
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+ g_return_if_fail(host != NULL);
+
+ g_free_not_null(nick->host);
+ nick->host = g_strdup(host);
+
+ signal_emit("nicklist host changed", 2, channel, nick);
+}
+
+void nicklist_set_account(CHANNEL_REC *channel, NICK_REC *nick, const char *account)
+{
+ g_free(nick->account);
+ nick->account = g_strdup(account);
+
+ signal_emit("nicklist account changed", 2, channel, nick);
+}
+
+static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ signal_emit("nicklist remove", 2, channel, nick);
+
+ if (channel->ownnick == nick)
+ channel->ownnick = NULL;
+
+ /*MODULE_DATA_DEINIT(nick);*/
+ g_free(nick->nick);
+ g_free_not_null(nick->realname);
+ g_free_not_null(nick->host);
+ g_free(nick->account);
+ g_free(nick);
+}
+
+/* Remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+ g_return_if_fail(nick != NULL);
+
+ nick_hash_remove(channel, nick);
+ nicklist_destroy(channel, nick);
+}
+
+static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id,
+ const char *old_nick, const char *new_nick,
+ GSList *nicks)
+{
+ CHANNEL_REC *channel;
+ NICK_REC *nickrec;
+ GSList *tmp;
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ nickrec = tmp->next->data;
+
+ /* remove old nick from hash table */
+ nick_hash_remove(channel, nickrec);
+
+ if (new_nick_id != NULL)
+ nickrec->unique_id = new_nick_id;
+
+ g_free(nickrec->nick);
+ nickrec->nick = g_strdup(new_nick);
+
+ /* add new nick to hash table */
+ nick_hash_add(channel, nickrec);
+
+ signal_emit("nicklist changed", 3, channel, nickrec, old_nick);
+ }
+ g_slist_free(nicks);
+}
+
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+ const char *new_nick)
+{
+ nicklist_rename_list(server, NULL, old_nick, new_nick,
+ nicklist_get_same(server, old_nick));
+}
+
+void nicklist_rename_unique(SERVER_REC *server,
+ void *old_nick_id, const char *old_nick,
+ void *new_nick_id, const char *new_nick)
+{
+ nicklist_rename_list(server, new_nick_id, old_nick, new_nick,
+ nicklist_get_same_unique(server, old_nick_id));
+}
+
+static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel,
+ const char *mask)
+{
+ NICK_REC *nick;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init(&iter, channel->nicks);
+ while (g_hash_table_iter_next(&iter, NULL, (void*)&nick)) {
+ for (; nick != NULL; nick = nick->next) {
+ if (mask_match_address(channel->server, mask,
+ nick->nick, nick->host))
+ return nick;
+ }
+ }
+
+ return NULL;
+}
+
+GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask)
+{
+ GSList *nicks;
+ NICK_REC *nick;
+ GHashTableIter iter;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ nicks = NULL;
+
+ g_hash_table_iter_init(&iter, channel->nicks);
+ while (g_hash_table_iter_next(&iter, NULL, (void*)&nick)) {
+ for (; nick != NULL; nick = nick->next) {
+ if (mask_match_address(channel->server, mask,
+ nick->nick, nick->host))
+ nicks = g_slist_prepend(nicks, nick);
+ }
+ }
+
+ return nicks;
+}
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick)
+{
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ return g_hash_table_lookup(channel->nicks, nick);
+}
+
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+ void *id)
+{
+ NICK_REC *rec;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ rec = g_hash_table_lookup(channel->nicks, nick);
+ while (rec != NULL && rec->unique_id != id)
+ rec = rec->next;
+
+ return rec;
+}
+
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask)
+{
+ NICK_REC *nickrec;
+ char *nick, *host;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+ g_return_val_if_fail(mask != NULL, NULL);
+
+ nick = g_strdup(mask);
+ host = strchr(nick, '!');
+ if (host != NULL) *host++ = '\0';
+
+ if (strchr(nick, '*') || strchr(nick, '?')) {
+ g_free(nick);
+ return nicklist_find_wildcards(channel, mask);
+ }
+
+ nickrec = g_hash_table_lookup(channel->nicks, nick);
+
+ if (host != NULL) {
+ while (nickrec != NULL) {
+ if (nickrec->host != NULL &&
+ match_wildcards(host, nickrec->host))
+ break; /* match */
+ nickrec = nickrec->next;
+ }
+ }
+ g_free(nick);
+ return nickrec;
+}
+
+static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list)
+{
+ while (rec != NULL) {
+ *list = g_slist_prepend(*list, rec);
+ rec = rec->next;
+ }
+}
+
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel)
+{
+ GSList *list;
+
+ g_return_val_if_fail(IS_CHANNEL(channel), NULL);
+
+ list = NULL;
+ g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list);
+ return list;
+}
+
+GSList *nicklist_get_same(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+ GSList *list = NULL;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *nick_rec;
+ CHANNEL_REC *channel = tmp->data;
+
+ for (nick_rec = g_hash_table_lookup(channel->nicks, nick);
+ nick_rec != NULL;
+ nick_rec = nick_rec->next) {
+ list = g_slist_append(list, channel);
+ list = g_slist_append(list, nick_rec);
+ }
+ }
+
+ return list;
+}
+
+typedef struct {
+ CHANNEL_REC *channel;
+ void *id;
+ GSList *list;
+} NICKLIST_GET_SAME_UNIQUE_REC;
+
+static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick,
+ NICKLIST_GET_SAME_UNIQUE_REC *rec)
+{
+ while (nick != NULL) {
+ if (nick->unique_id == rec->id) {
+ rec->list = g_slist_append(rec->list, rec->channel);
+ rec->list = g_slist_append(rec->list, nick);
+ break;
+ }
+
+ nick = nick->next;
+ }
+}
+
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id)
+{
+ NICKLIST_GET_SAME_UNIQUE_REC rec;
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+ g_return_val_if_fail(id != NULL, NULL);
+
+ rec.id = id;
+ rec.list = NULL;
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
+ rec.channel = tmp->data;
+ g_hash_table_foreach(rec.channel->nicks,
+ (GHFunc) get_nicks_same_hash_unique,
+ &rec);
+ }
+ return rec.list;
+}
+
+/* nick record comparison for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2, const char *nick_prefix)
+{
+ int i;
+
+ if (p1 == NULL) return -1;
+ if (p2 == NULL) return 1;
+
+ if (p1->prefixes[0] == p2->prefixes[0])
+ return g_ascii_strcasecmp(p1->nick, p2->nick);
+
+ if (!p1->prefixes[0])
+ return 1;
+ if (!p2->prefixes[0])
+ return -1;
+
+ /* They aren't equal. We've taken care of that already.
+ * The first one we encounter in this list is the greater.
+ */
+
+ for (i = 0; nick_prefix[i] != '\0'; i++) {
+ if (p1->prefixes[0] == nick_prefix[i])
+ return -1;
+ if (p2->prefixes[0] == nick_prefix[i])
+ return 1;
+ }
+
+ /* we should never have gotten here... */
+ return g_ascii_strcasecmp(p1->nick, p2->nick);
+}
+
+static void nicklist_update_flags_list(SERVER_REC *server, int gone,
+ int serverop, GSList *nicks)
+{
+ GSList *tmp;
+ CHANNEL_REC *channel;
+ NICK_REC *rec;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
+ channel = tmp->data;
+ rec = tmp->next->data;
+
+ rec->last_check = time(NULL);
+
+ if (gone != -1 && (int)rec->gone != gone) {
+ rec->gone = gone;
+ signal_emit("nicklist gone changed", 2, channel, rec);
+ }
+
+ if (serverop != -1 && (int)rec->serverop != serverop) {
+ rec->serverop = serverop;
+ signal_emit("nicklist serverop changed", 2, channel, rec);
+ }
+ }
+ g_slist_free(nicks);
+}
+
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+ int gone, int serverop)
+{
+ nicklist_update_flags_list(server, gone, serverop,
+ nicklist_get_same(server, nick));
+}
+
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+ int gone, int serverop)
+{
+ nicklist_update_flags_list(server, gone, serverop,
+ nicklist_get_same_unique(server, id));
+}
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ NICK_REC *first, *next;
+
+ channel->ownnick = nick;
+
+ /* move our nick in the list to first, makes some things easier
+ (like handling multiple identical nicks in fe-messages.c) */
+ first = g_hash_table_lookup(channel->nicks, nick->nick);
+ if (first->next == NULL)
+ return;
+
+ next = nick->next;
+ nick->next = first;
+
+ while (first->next != nick)
+ first = first->next;
+ first->next = next;
+
+ g_hash_table_insert(channel->nicks, nick->nick, nick);
+}
+
+static void sig_channel_created(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ channel->nicks = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+}
+
+static void nicklist_remove_hash(gpointer key, NICK_REC *nick,
+ CHANNEL_REC *channel)
+{
+ NICK_REC *next;
+
+ while (nick != NULL) {
+ next = nick->next;
+ nicklist_destroy(channel, nick);
+ nick = next;
+ }
+}
+
+static void sig_channel_destroyed(CHANNEL_REC *channel)
+{
+ g_return_if_fail(IS_CHANNEL(channel));
+
+ g_hash_table_foreach(channel->nicks,
+ (GHFunc) nicklist_remove_hash, channel);
+ g_hash_table_destroy(channel->nicks);
+}
+
+static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len)
+{
+ NICK_REC *rec;
+ char *tmpnick;
+
+ tmpnick = g_strndup(nick, len);
+ rec = g_hash_table_lookup(channel->nicks, tmpnick);
+
+ if (rec != NULL) {
+ /* if there's multiple, get the one with identical case */
+ while (rec->next != NULL) {
+ if (g_strcmp0(rec->nick, tmpnick) == 0)
+ break;
+ rec = rec->next;
+ }
+ }
+
+ g_free(tmpnick);
+ return rec;
+}
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick)
+{
+ const char *msgstart, *orignick;
+ int len, fullmatch;
+
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ if (channel != NULL && channel->server->nick_match_msg != NULL)
+ return channel->server->nick_match_msg(msg, nick);
+
+ /* first check for identical match */
+ len = strlen(nick);
+ if (g_ascii_strncasecmp(msg, nick, len) == 0 &&
+ !isalnumhigh((int) msg[len]))
+ return TRUE;
+
+ orignick = nick;
+ for (;;) {
+ nick = orignick;
+ msgstart = msg;
+ fullmatch = TRUE;
+
+ /* check if it matches for alphanumeric parts of nick */
+ while (*nick != '\0' && *msg != '\0') {
+ if (i_toupper(*nick) == i_toupper(*msg)) {
+ /* total match */
+ msg++;
+ } else if (i_isalnum(*msg) && !i_isalnum(*nick)) {
+ /* some strange char in your nick, pass it */
+ fullmatch = FALSE;
+ } else
+ break;
+
+ nick++;
+ }
+
+ if (msg != msgstart && !isalnumhigh(*msg)) {
+ /* at least some of the chars in line matched the
+ nick, and msg continue with non-alphanum character,
+ this might be for us.. */
+ if (*nick != '\0') {
+ /* remove the rest of the non-alphanum chars
+ from nick and check if it then matches. */
+ fullmatch = FALSE;
+ while (*nick != '\0' && !i_isalnum(*nick))
+ nick++;
+ }
+
+ if (*nick == '\0') {
+ /* yes, match! */
+ break;
+ }
+ }
+
+ /* no match. check if this is a message to multiple people
+ (like nick1,nick2: text) */
+ while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++;
+
+ if (*msg != ',') {
+ nick = orignick;
+ break;
+ }
+
+ msg++;
+ }
+
+ if (*nick != '\0')
+ return FALSE; /* didn't match */
+
+ if (fullmatch)
+ return TRUE; /* matched without fuzzyness */
+
+ if (channel != NULL) {
+ /* matched with some fuzzyness .. check if there's an exact match
+ for some other nick in the same channel. */
+ return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL;
+ } else {
+ return TRUE;
+ }
+}
+
+int nick_match_msg_everywhere(CHANNEL_REC *channel, const char *msg, const char *nick)
+{
+ g_return_val_if_fail(nick != NULL, FALSE);
+ g_return_val_if_fail(msg != NULL, FALSE);
+
+ return stristr_full(msg, nick) != NULL;
+}
+
+void nicklist_init(void)
+{
+ signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+}
+
+void nicklist_deinit(void)
+{
+ signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created);
+ signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed);
+
+ module_uniq_destroy("NICK");
+}
diff --git a/src/core/nicklist.h b/src/core/nicklist.h
new file mode 100644
index 0000000..7999be0
--- /dev/null
+++ b/src/core/nicklist.h
@@ -0,0 +1,64 @@
+#ifndef IRSSI_CORE_NICKLIST_H
+#define IRSSI_CORE_NICKLIST_H
+
+/* Returns NICK_REC if it's nick, NULL if it isn't. */
+#define NICK(server) \
+ MODULE_CHECK_CAST(server, NICK_REC, type, "NICK")
+
+#define IS_NICK(server) \
+ (NICK(server) ? TRUE : FALSE)
+
+#define MAX_USER_PREFIXES 7 /* Max prefixes kept for any user-in-chan. 7+1 is a memory unit */
+
+struct _NICK_REC {
+#include <irssi/src/core/nick-rec.h>
+};
+
+/* Add new nick to list */
+void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick);
+/* Set host address for nick */
+void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host);
+void nicklist_set_account(CHANNEL_REC *channel, NICK_REC *nick, const char *account);
+/* Remove nick from list */
+void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick);
+/* Change nick */
+void nicklist_rename(SERVER_REC *server, const char *old_nick,
+ const char *new_nick);
+void nicklist_rename_unique(SERVER_REC *server,
+ void *old_nick_id, const char *old_nick,
+ void *new_nick_id, const char *new_nick);
+
+/* Find nick */
+NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick);
+NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick,
+ void *id);
+/* Find nick mask, wildcards allowed */
+NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks that match the mask */
+GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask);
+/* Get list of nicks */
+GSList *nicklist_getnicks(CHANNEL_REC *channel);
+/* Get all the nick records of `nick'. Returns channel, nick, channel, ... */
+GSList *nicklist_get_same(SERVER_REC *server, const char *nick);
+GSList *nicklist_get_same_unique(SERVER_REC *server, void *id);
+
+/* Update specified nick's status in server. */
+void nicklist_update_flags(SERVER_REC *server, const char *nick,
+ int gone, int ircop);
+void nicklist_update_flags_unique(SERVER_REC *server, void *id,
+ int gone, int ircop);
+
+/* Specify which nick in channel is ours */
+void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick);
+
+/* Nick record comparison for sort functions */
+int nicklist_compare(NICK_REC *p1, NICK_REC *p2, const char *nick_prefix);
+
+/* Check is `msg' is meant for `nick'. */
+int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick);
+int nick_match_msg_everywhere(CHANNEL_REC *channel, const char *msg, const char *nick);
+
+void nicklist_init(void);
+void nicklist_deinit(void);
+
+#endif
diff --git a/src/core/nickmatch-cache.c b/src/core/nickmatch-cache.c
new file mode 100644
index 0000000..c8fc115
--- /dev/null
+++ b/src/core/nickmatch-cache.c
@@ -0,0 +1,122 @@
+/*
+ nickmatch-cache.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/channels.h>
+#include <irssi/src/core/nicklist.h>
+
+#include <irssi/src/core/nickmatch-cache.h>
+
+static GSList *lists;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func, GDestroyNotify value_destroy_func)
+{
+ NICKMATCH_REC *rec;
+
+ rec = g_new0(NICKMATCH_REC, 1);
+ rec->func = func;
+ rec->value_destroy_func = value_destroy_func;
+
+ lists = g_slist_append(lists, rec);
+ return rec;
+}
+
+void nickmatch_deinit(NICKMATCH_REC *rec)
+{
+ lists = g_slist_remove(lists, rec);
+
+ if (rec->nicks != NULL)
+ g_hash_table_destroy(rec->nicks);
+ g_free(rec);
+}
+
+static void nickmatch_check_channel(CHANNEL_REC *channel, NICKMATCH_REC *rec)
+{
+ GSList *nicks, *tmp;
+
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next) {
+ NICK_REC *nick = tmp->data;
+
+ rec->func(rec->nicks, channel, nick);
+ }
+ g_slist_free(nicks);
+}
+
+void nickmatch_rebuild(NICKMATCH_REC *rec)
+{
+ if (rec->nicks != NULL)
+ g_hash_table_destroy(rec->nicks);
+
+ rec->nicks = g_hash_table_new_full((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal,
+ NULL, (GDestroyNotify) rec->value_destroy_func);
+
+ g_slist_foreach(channels, (GFunc) nickmatch_check_channel, rec);
+}
+
+static void sig_nick_new(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ GSList *tmp;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+ NICKMATCH_REC *rec = tmp->data;
+
+ rec->func(rec->nicks, channel, nick);
+ }
+}
+
+static void sig_nick_remove(CHANNEL_REC *channel, NICK_REC *nick)
+{
+ GSList *tmp;
+
+ g_return_if_fail(channel != NULL);
+ g_return_if_fail(nick != NULL);
+
+ for (tmp = lists; tmp != NULL; tmp = tmp->next) {
+ NICKMATCH_REC *rec = tmp->data;
+
+ g_hash_table_remove(rec->nicks, nick);
+ }
+}
+
+void nickmatch_cache_init(void)
+{
+ lists = NULL;
+ signal_add("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
+
+void nickmatch_cache_deinit(void)
+{
+ g_slist_foreach(lists, (GFunc) nickmatch_deinit, NULL);
+ g_slist_free(lists);
+
+ signal_remove("nicklist new", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nick_new);
+ signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_remove);
+}
diff --git a/src/core/nickmatch-cache.h b/src/core/nickmatch-cache.h
new file mode 100644
index 0000000..bae99c1
--- /dev/null
+++ b/src/core/nickmatch-cache.h
@@ -0,0 +1,27 @@
+#ifndef IRSSI_CORE_NICKMATCH_CACHE_H
+#define IRSSI_CORE_NICKMATCH_CACHE_H
+
+typedef void (*NICKMATCH_REBUILD_FUNC) (GHashTable *list,
+ CHANNEL_REC *channel, NICK_REC *nick);
+
+typedef struct {
+ GHashTable *nicks;
+ NICKMATCH_REBUILD_FUNC func;
+ GDestroyNotify value_destroy_func;
+} NICKMATCH_REC;
+
+NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func, GDestroyNotify value_destroy_func);
+void nickmatch_deinit(NICKMATCH_REC *rec);
+
+/* Calls rebuild function for all nicks in all channels.
+ This must be called soon after nickmatch_init(), before any nicklist
+ signals get sent. */
+void nickmatch_rebuild(NICKMATCH_REC *rec);
+
+#define nickmatch_find(rec, nick) \
+ g_hash_table_lookup((rec)->nicks, nick)
+
+void nickmatch_cache_init(void);
+void nickmatch_cache_deinit(void);
+
+#endif
diff --git a/src/core/pidwait.c b/src/core/pidwait.c
new file mode 100644
index 0000000..f421c1d
--- /dev/null
+++ b/src/core/pidwait.c
@@ -0,0 +1,78 @@
+/*
+ pidwait.c :
+
+ 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/modules.h>
+
+static GHashTable *child_pids;
+static GSList *pids;
+
+static int signal_pidwait;
+
+static void sig_child(GPid pid, gint status, gpointer data)
+{
+ signal_emit_id(signal_pidwait, 2, GINT_TO_POINTER(pid),
+ GINT_TO_POINTER(status));
+ g_hash_table_remove(child_pids, GINT_TO_POINTER(pid));
+ pids = g_slist_remove(pids, GINT_TO_POINTER(pid));
+}
+
+/* add a pid to wait list */
+void pidwait_add(int pid)
+{
+ if (g_hash_table_lookup(child_pids, GINT_TO_POINTER(pid)) == NULL) {
+ int id = g_child_watch_add_full(10, pid, sig_child, NULL, NULL);
+ g_hash_table_insert(child_pids, GINT_TO_POINTER(pid), GINT_TO_POINTER(id));
+ pids = g_slist_append(pids, GINT_TO_POINTER(pid));
+ }
+}
+
+/* remove pid from wait list */
+void pidwait_remove(int pid)
+{
+ gpointer id = g_hash_table_lookup(child_pids, GINT_TO_POINTER(pid));
+ if (id != NULL) {
+ g_source_remove(GPOINTER_TO_INT(id));
+ g_hash_table_remove(child_pids, GINT_TO_POINTER(pid));
+ pids = g_slist_remove(pids, GINT_TO_POINTER(pid));
+ }
+}
+
+/* return list of pids that are being waited.
+ don't free the return value. */
+GSList *pidwait_get_pids(void)
+{
+ return pids;
+}
+
+void pidwait_init(void)
+{
+ child_pids = g_hash_table_new(g_direct_hash, g_direct_equal);
+ pids = NULL;
+
+ signal_pidwait = signal_get_uniq_id("pidwait");
+}
+
+void pidwait_deinit(void)
+{
+ g_hash_table_destroy(child_pids);
+ g_slist_free(pids);
+}
diff --git a/src/core/pidwait.h b/src/core/pidwait.h
new file mode 100644
index 0000000..148c65d
--- /dev/null
+++ b/src/core/pidwait.h
@@ -0,0 +1,16 @@
+#ifndef IRSSI_CORE_PIDWAIT_H
+#define IRSSI_CORE_PIDWAIT_H
+
+void pidwait_init(void);
+void pidwait_deinit(void);
+
+/* add a pid to wait list */
+void pidwait_add(int pid);
+/* remove pid from wait list */
+void pidwait_remove(int pid);
+
+/* return list of pids that are being waited.
+ don't free the return value. */
+GSList *pidwait_get_pids(void);
+
+#endif
diff --git a/src/core/queries.c b/src/core/queries.c
new file mode 100644
index 0000000..fe86271
--- /dev/null
+++ b/src/core/queries.c
@@ -0,0 +1,174 @@
+/*
+ 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/core/servers.h>
+#include <irssi/src/core/queries.h>
+
+GSList *queries;
+
+static const char *query_get_target(WI_ITEM_REC *item)
+{
+ return ((QUERY_REC *) item)->name;
+}
+
+void query_init(QUERY_REC *query, int automatic)
+{
+ g_return_if_fail(query != NULL);
+ g_return_if_fail(query->name != NULL);
+
+ queries = g_slist_append(queries, query);
+
+ MODULE_DATA_INIT(query);
+ query->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY");
+ query->destroy = (void (*) (WI_ITEM_REC *)) query_destroy;
+ query->get_target = query_get_target;
+ query->createtime = time(NULL);
+ query->last_unread_msg = time(NULL);
+ query->visible_name = g_strdup(query->name);
+
+ if (query->server_tag != NULL) {
+ query->server = server_find_tag(query->server_tag);
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_append(query->server->queries, query);
+ }
+ }
+
+ signal_emit("query created", 2, query, GINT_TO_POINTER(automatic));
+}
+
+void query_destroy(QUERY_REC *query)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ if (query->destroying) return;
+ query->destroying = TRUE;
+
+ queries = g_slist_remove(queries, query);
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_remove(query->server->queries, query);
+ }
+ signal_emit("query destroyed", 1, query);
+
+ MODULE_DATA_DEINIT(query);
+ g_free_not_null(query->hilight_color);
+ g_free_not_null(query->server_tag);
+ g_free_not_null(query->address);
+ g_free(query->visible_name);
+ g_free(query->name);
+
+ query->type = 0;
+ g_free(query);
+}
+
+static QUERY_REC *query_find_server(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(IS_SERVER(server), NULL);
+
+ if (server->query_find_func != NULL) {
+ /* use the server specific query find function */
+ return server->query_find_func(server, nick);
+ }
+
+ for (tmp = server->queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+QUERY_REC *query_find(SERVER_REC *server, const char *nick)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL);
+ g_return_val_if_fail(nick != NULL, NULL);
+
+ if (server != NULL)
+ return query_find_server(server, nick);
+
+ for (tmp = queries; tmp != NULL; tmp = tmp->next) {
+ QUERY_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->name, nick) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+void query_change_nick(QUERY_REC *query, const char *nick)
+{
+ char *oldnick;
+
+ g_return_if_fail(IS_QUERY(query));
+
+ oldnick = query->name;
+ query->name = g_strdup(nick);
+
+ g_free(query->visible_name);
+ query->visible_name = g_strdup(nick);
+
+ signal_emit("query nick changed", 2, query, oldnick);
+ signal_emit("window item name changed", 1, query);
+ g_free(oldnick);
+}
+
+void query_change_address(QUERY_REC *query, const char *address)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ g_free_not_null(query->address);
+ query->address = g_strdup(address);
+ signal_emit("query address changed", 1, query);
+}
+
+void query_change_server(QUERY_REC *query, SERVER_REC *server)
+{
+ g_return_if_fail(IS_QUERY(query));
+
+ if (query->server != NULL) {
+ query->server->queries =
+ g_slist_remove(query->server->queries, query);
+ }
+ if (server != NULL)
+ server->queries = g_slist_append(server->queries, query);
+
+ query->server = server;
+ signal_emit("query server changed", 1, query);
+}
+
+void queries_init(void)
+{
+}
+
+void queries_deinit(void)
+{
+}
diff --git a/src/core/queries.h b/src/core/queries.h
new file mode 100644
index 0000000..c617036
--- /dev/null
+++ b/src/core/queries.h
@@ -0,0 +1,34 @@
+#ifndef IRSSI_CORE_QUERIES_H
+#define IRSSI_CORE_QUERIES_H
+
+#include <irssi/src/core/modules.h>
+
+/* Returns QUERY_REC if it's query, NULL if it isn't. */
+#define QUERY(query) \
+ MODULE_CHECK_CAST_MODULE(query, QUERY_REC, type, \
+ "WINDOW ITEM TYPE", "QUERY")
+
+#define IS_QUERY(query) \
+ (QUERY(query) ? TRUE : FALSE)
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _QUERY_REC {
+#include <irssi/src/core/query-rec.h>
+};
+
+extern GSList *queries;
+
+void query_init(QUERY_REC *query, int automatic);
+void query_destroy(QUERY_REC *query);
+
+/* Find query by name, if `server' is NULL, search from all servers */
+QUERY_REC *query_find(SERVER_REC *server, const char *nick);
+
+void query_change_nick(QUERY_REC *query, const char *nick);
+void query_change_address(QUERY_REC *query, const char *address);
+void query_change_server(QUERY_REC *query, SERVER_REC *server);
+
+void queries_init(void);
+void queries_deinit(void);
+
+#endif
diff --git a/src/core/query-rec.h b/src/core/query-rec.h
new file mode 100644
index 0000000..cc15c9e
--- /dev/null
+++ b/src/core/query-rec.h
@@ -0,0 +1,11 @@
+/* QUERY_REC definition, used for inheritance */
+
+#include <irssi/src/core/window-item-rec.h>
+
+char *address;
+char *server_tag;
+time_t last_unread_msg;
+
+unsigned int unwanted:1; /* TRUE if the other side closed or
+ some error occurred (DCC chats!) */
+unsigned int destroying:1;
diff --git a/src/core/rawlog.c b/src/core/rawlog.c
new file mode 100644
index 0000000..ea2b5e6
--- /dev/null
+++ b/src/core/rawlog.c
@@ -0,0 +1,260 @@
+/*
+ rawlog.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/rawlog.h>
+#include <irssi/src/core/log.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/write-buffer.h>
+#include <irssi/src/core/settings.h>
+#ifdef HAVE_CAPSICUM
+#include <irssi/src/core/capsicum.h>
+#endif
+
+#include <irssi/src/core/servers.h>
+
+static int rawlog_lines;
+static int signal_rawlog;
+
+RAWLOG_REC *rawlog_create(void)
+{
+ RAWLOG_REC *rec;
+
+ rec = g_new0(RAWLOG_REC, 1);
+ rec->lines = g_queue_new();
+ return rec;
+}
+
+void rawlog_destroy(RAWLOG_REC *rawlog)
+{
+ g_return_if_fail(rawlog != NULL);
+
+ g_queue_foreach(rawlog->lines, (GFunc) g_free, NULL);
+ g_queue_free(rawlog->lines);
+
+ if (rawlog->logging) {
+ write_buffer_flush();
+ close(rawlog->handle);
+ }
+ g_free(rawlog);
+}
+
+/* NOTE! str must be dynamically allocated and must not be freed after! */
+static void rawlog_add(RAWLOG_REC *rawlog, char *str)
+{
+ while (rawlog->lines->length >= rawlog_lines && rawlog_lines > 0) {
+ void *tmp = g_queue_pop_head(rawlog->lines);
+ g_free(tmp);
+ }
+
+ if (rawlog->logging) {
+ write_buffer(rawlog->handle, str, strlen(str));
+ write_buffer(rawlog->handle, "\n", 1);
+ }
+
+ g_queue_push_tail(rawlog->lines, str);
+ signal_emit_id(signal_rawlog, 2, rawlog, str);
+}
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf(">> %s", str));
+}
+
+void rawlog_output(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("<< %s", str));
+}
+
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("--> %s", str));
+}
+
+static void rawlog_dump(RAWLOG_REC *rawlog, int f)
+{
+ GList *tmp;
+ ssize_t ret = 0;
+
+ for (tmp = rawlog->lines->head; ret != -1 && tmp != NULL; tmp = tmp->next) {
+ ret = write(f, tmp->data, strlen((char *) tmp->data));
+ if (ret != -1)
+ ret = write(f, "\n", 1);
+ }
+
+ if (ret == -1) {
+ g_warning("rawlog write() failed: %s", strerror(errno));
+ }
+}
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path;
+
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(fname != NULL);
+
+ if (rawlog->logging)
+ return;
+
+ path = convert_home(fname);
+#ifdef HAVE_CAPSICUM
+ rawlog->handle = capsicum_open_wrapper(path,
+ O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+#else
+ rawlog->handle = open(path, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+#endif
+
+ g_free(path);
+
+ if (rawlog->handle == -1) {
+ g_warning("rawlog open() failed: %s", strerror(errno));
+ return;
+ }
+
+ rawlog_dump(rawlog, rawlog->handle);
+ rawlog->logging = TRUE;
+}
+
+void rawlog_close(RAWLOG_REC *rawlog)
+{
+ if (rawlog->logging) {
+ write_buffer_flush();
+ close(rawlog->handle);
+ rawlog->logging = FALSE;
+ }
+}
+
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path, *dir;
+ int f;
+
+ dir = g_path_get_dirname(fname);
+#ifdef HAVE_CAPSICUM
+ capsicum_mkdir_with_parents_wrapper(dir, log_dir_create_mode);
+#else
+ g_mkdir_with_parents(dir, log_dir_create_mode);
+#endif
+ g_free(dir);
+
+ path = convert_home(fname);
+#ifdef HAVE_CAPSICUM
+ f = capsicum_open_wrapper(path, O_WRONLY | O_APPEND | O_CREAT,
+ log_file_create_mode);
+#else
+ f = open(path, O_WRONLY | O_APPEND | O_CREAT, log_file_create_mode);
+#endif
+ g_free(path);
+
+ if (f < 0) {
+ g_warning("rawlog open() failed: %s", strerror(errno));
+ return;
+ }
+
+ rawlog_dump(rawlog, f);
+ close(f);
+}
+
+void rawlog_set_size(int lines)
+{
+ rawlog_lines = lines;
+}
+
+static void read_settings(void)
+{
+ rawlog_set_size(settings_get_int("rawlog_lines"));
+}
+
+static void cmd_rawlog(const char *data, SERVER_REC *server, void *item)
+{
+ command_runsub("rawlog", data, server, item);
+}
+
+/* SYNTAX: RAWLOG SAVE <file> */
+static void cmd_rawlog_save(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || server->rawlog == NULL)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ rawlog_save(server->rawlog, data);
+}
+
+/* SYNTAX: RAWLOG OPEN <file> */
+static void cmd_rawlog_open(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || server->rawlog == NULL)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ rawlog_open(server->rawlog, data);
+}
+
+/* SYNTAX: RAWLOG CLOSE */
+static void cmd_rawlog_close(const char *data, SERVER_REC *server)
+{
+ g_return_if_fail(data != NULL);
+ if (server == NULL || server->rawlog == NULL)
+ cmd_return_error(CMDERR_NOT_CONNECTED);
+
+ rawlog_close(server->rawlog);
+}
+
+void rawlog_init(void)
+{
+ signal_rawlog = signal_get_uniq_id("rawlog");
+
+ settings_add_int("history", "rawlog_lines", 200);
+ read_settings();
+
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("rawlog", NULL, (SIGNAL_FUNC) cmd_rawlog);
+ command_bind("rawlog save", NULL, (SIGNAL_FUNC) cmd_rawlog_save);
+ command_bind("rawlog open", NULL, (SIGNAL_FUNC) cmd_rawlog_open);
+ command_bind("rawlog close", NULL, (SIGNAL_FUNC) cmd_rawlog_close);
+}
+
+void rawlog_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("rawlog", (SIGNAL_FUNC) cmd_rawlog);
+ command_unbind("rawlog save", (SIGNAL_FUNC) cmd_rawlog_save);
+ command_unbind("rawlog open", (SIGNAL_FUNC) cmd_rawlog_open);
+ command_unbind("rawlog close", (SIGNAL_FUNC) cmd_rawlog_close);
+}
diff --git a/src/core/rawlog.h b/src/core/rawlog.h
new file mode 100644
index 0000000..53c0d3c
--- /dev/null
+++ b/src/core/rawlog.h
@@ -0,0 +1,27 @@
+#ifndef IRSSI_CORE_RAWLOG_H
+#define IRSSI_CORE_RAWLOG_H
+
+struct _RAWLOG_REC {
+ int logging;
+ int handle;
+
+ GQueue *lines;
+};
+
+RAWLOG_REC *rawlog_create(void);
+void rawlog_destroy(RAWLOG_REC *rawlog);
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str);
+void rawlog_output(RAWLOG_REC *rawlog, const char *str);
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str);
+
+void rawlog_set_size(int lines);
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname);
+void rawlog_close(RAWLOG_REC *rawlog);
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname);
+
+void rawlog_init(void);
+void rawlog_deinit(void);
+
+#endif
diff --git a/src/core/recode.c b/src/core/recode.c
new file mode 100644
index 0000000..7185dd9
--- /dev/null
+++ b/src/core/recode.c
@@ -0,0 +1,307 @@
+/*
+ recode.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/settings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/misc.h>
+
+static char *translit_charset;
+static gboolean term_is_utf8;
+
+gboolean is_utf8(void)
+{
+ return term_is_utf8;
+}
+
+static gboolean is_translit(const char *charset)
+{
+ char *pos;
+
+ pos = stristr(charset, "//translit");
+ return (pos != NULL);
+}
+
+gboolean is_valid_charset(const char *charset)
+{
+ GIConv cd;
+ char *to = NULL;
+
+ if (!charset || *charset == '\0')
+ return FALSE;
+
+ if (settings_get_bool("recode_transliterate") && !is_translit(charset))
+ charset = to = g_strconcat(charset, "//TRANSLIT", NULL);
+
+ cd = g_iconv_open(charset, "UTF-8");
+ g_free(to);
+ if (cd != (GIConv)-1) {
+ g_iconv_close(cd);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static char *find_conversion(const SERVER_REC *server, const char *target)
+{
+ char *conv = NULL;
+
+ if (server != NULL && target != NULL) {
+ char *tagtarget = g_strdup_printf("%s/%s", server->tag, target);
+ conv = iconfig_get_str("conversions", tagtarget, NULL);
+ g_free(tagtarget);
+ }
+ if (conv == NULL && target != NULL)
+ conv = iconfig_get_str("conversions", target, NULL);
+ if (conv == NULL && server != NULL)
+ conv = iconfig_get_str("conversions", server->tag, NULL);
+ return conv;
+}
+
+static int str_is_ascii(const char *str)
+{
+ int i;
+
+ for (i = 0; str[i] != '\0'; i++)
+ if (str[i] & 0x80)
+ return 0;
+ return 1;
+}
+
+char *recode_in(const SERVER_REC *server, const char *str, const char *target)
+{
+ const char *from = NULL;
+ const char *to = translit_charset;
+ char *recoded = NULL;
+ gboolean str_is_utf8, recode, autodetect;
+ int len;
+
+ if (!str)
+ return NULL;
+
+ recode = settings_get_bool("recode");
+ if (!recode)
+ return g_strdup(str);
+
+ len = strlen(str);
+
+ /* Only validate for UTF-8 if an 8-bit encoding. */
+ str_is_utf8 = 0;
+ if (!str_is_ascii(str))
+ str_is_utf8 = g_utf8_validate(str, len, NULL);
+ else if (!strchr(str, '\e'))
+ str_is_utf8 = 1;
+ autodetect = settings_get_bool("recode_autodetect_utf8");
+
+ if (autodetect && str_is_utf8)
+ if (term_is_utf8)
+ return g_strdup(str);
+ else
+ from = "UTF-8";
+ else
+ from = find_conversion(server, target);
+
+ if (from)
+ recoded = g_convert_with_fallback(str, len, to, from, NULL, NULL, NULL, NULL);
+
+ if (!recoded) {
+ if (str_is_utf8)
+ if (term_is_utf8)
+ return g_strdup(str);
+ else
+ from = "UTF-8";
+ else
+ if (term_is_utf8)
+ from = settings_get_str("recode_fallback");
+ else
+ from = NULL;
+
+ if (from)
+ recoded = g_convert_with_fallback(str, len, to, from, NULL, NULL, NULL, NULL);
+
+ if (!recoded)
+ recoded = g_strdup(str);
+ }
+ return recoded;
+}
+
+char *recode_out(const SERVER_REC *server, const char *str, const char *target)
+{
+ char *recoded = NULL;
+ const char *from = translit_charset;
+ const char *to = NULL;
+ char *translit_to = NULL;
+ gboolean translit, recode;
+ int len;
+
+ if (!str)
+ return NULL;
+
+ recode = settings_get_bool("recode");
+ if (!recode)
+ return g_strdup(str);
+
+ len = strlen(str);
+
+ translit = settings_get_bool("recode_transliterate");
+
+ to = find_conversion(server, target);
+ if (to == NULL)
+ /* default outgoing charset if set */
+ to = settings_get_str("recode_out_default_charset");
+
+ if (to && *to != '\0') {
+ if (translit && !is_translit(to))
+ to = translit_to = g_strconcat(to ,"//TRANSLIT", NULL);
+
+ recoded = g_convert(str, len, to, from, NULL, NULL, NULL);
+ }
+ g_free(translit_to);
+ if (!recoded)
+ recoded = g_strdup(str);
+
+ return recoded;
+}
+
+char **recode_split(const SERVER_REC *server, const char *str,
+ const char *target, int len, gboolean onspace)
+{
+ GIConv cd = (GIConv)-1;
+ const char *from = translit_charset;
+ const char *to = translit_charset;
+ char *translit_to = NULL;
+ const char *inbuf = str;
+ const char *previnbuf = inbuf;
+ char *tmp = NULL;
+ char *outbuf;
+ gsize inbytesleft = strlen(inbuf);
+ gsize outbytesleft = len;
+ int n = 0;
+ char **ret;
+
+ g_warn_if_fail(str != NULL);
+ if (str == NULL) {
+ ret = g_new(char *, 1);
+ ret[0] = NULL;
+ return ret;
+ }
+
+ if (settings_get_bool("recode")) {
+ to = find_conversion(server, target);
+ if (to == NULL)
+ /* default outgoing charset if set */
+ to = settings_get_str("recode_out_default_charset");
+ if (to != NULL && *to != '\0') {
+ if (settings_get_bool("recode_transliterate") &&
+ !is_translit(to))
+ to = translit_to = g_strconcat(to,
+ "//TRANSLIT",
+ NULL);
+ } else {
+ to = from;
+ }
+ }
+
+ cd = g_iconv_open(to, from);
+ if (cd == (GIConv)-1) {
+ /* Fall back to splitting by byte. */
+ ret = strsplit_len(str, len, onspace);
+ goto out;
+ }
+
+ tmp = g_malloc(outbytesleft);
+ outbuf = tmp;
+ ret = g_new(char *, 1);
+ while (g_iconv(cd, (char **)&inbuf, &inbytesleft, &outbuf,
+ &outbytesleft) == -1) {
+ if (errno != E2BIG) {
+ /*
+ * Conversion failed. Fall back to splitting
+ * by byte.
+ */
+ ret[n] = NULL;
+ g_strfreev(ret);
+ ret = strsplit_len(str, len, onspace);
+ goto out;
+ }
+
+ /* Outbuf overflowed, split the input string. */
+ if (onspace) {
+ /*
+ * Try to find a space to split on and leave
+ * the space on the previous line.
+ */
+ int i;
+ for (i = 0; i < inbuf - previnbuf; i++) {
+ if (previnbuf[inbuf-previnbuf-1-i] == ' ') {
+ inbuf -= i;
+ inbytesleft += i;
+ break;
+ }
+ }
+ }
+ ret[n++] = g_strndup(previnbuf, inbuf - previnbuf);
+ ret = g_renew(char *, ret, n + 1);
+ previnbuf = inbuf;
+
+ /* Reset outbuf for the next substring. */
+ outbuf = tmp;
+ outbytesleft = len;
+ }
+ /* Copy the last substring into the array. */
+ ret[n++] = g_strndup(previnbuf, inbuf - previnbuf);
+ ret = g_renew(char *, ret, n + 1);
+ ret[n] = NULL;
+
+out:
+ if (cd != (GIConv)-1)
+ g_iconv_close(cd);
+ g_free(translit_to);
+ g_free(tmp);
+
+ return ret;
+}
+
+void recode_update_charset(void)
+{
+ const char *charset = settings_get_str("term_charset");
+ term_is_utf8 = !g_ascii_strcasecmp(charset, "UTF-8");
+ g_free(translit_charset);
+ if (settings_get_bool("recode_transliterate") && !is_translit(charset))
+ translit_charset = g_strconcat(charset, "//TRANSLIT", NULL);
+ else
+ translit_charset = g_strdup(charset);
+}
+
+void recode_init(void)
+{
+ settings_add_bool("misc", "recode", TRUE);
+ settings_add_str("misc", "recode_fallback", "CP1252");
+ settings_add_str("misc", "recode_out_default_charset", "");
+ settings_add_bool("misc", "recode_transliterate", TRUE);
+ settings_add_bool("misc", "recode_autodetect_utf8", TRUE);
+}
+
+void recode_deinit(void)
+{
+ g_free(translit_charset);
+}
diff --git a/src/core/recode.h b/src/core/recode.h
new file mode 100644
index 0000000..ce1b9da
--- /dev/null
+++ b/src/core/recode.h
@@ -0,0 +1,15 @@
+#ifndef IRSSI_CORE_RECODE_H
+#define IRSSI_CORE_RECODE_H
+
+char *recode_in (const SERVER_REC *server, const char *str, const char *target);
+char *recode_out (const SERVER_REC *server, const char *str, const char *target);
+char **recode_split(const SERVER_REC *server, const char *str,
+ const char *target, int len, gboolean onspace);
+gboolean is_valid_charset(const char *charset);
+gboolean is_utf8(void);
+void recode_update_charset(void);
+
+void recode_init (void);
+void recode_deinit (void);
+
+#endif /* IRSSI_CORE_RECODE_H */
diff --git a/src/core/refstrings.c b/src/core/refstrings.c
new file mode 100644
index 0000000..8f220f6
--- /dev/null
+++ b/src/core/refstrings.c
@@ -0,0 +1,129 @@
+#include <glib.h>
+#include <string.h>
+
+#include <irssi/src/core/refstrings.h>
+
+#if GLIB_CHECK_VERSION(2, 58, 0)
+
+void i_refstr_init(void)
+{
+ /* nothing */
+}
+
+char *i_refstr_intern(const char *str)
+{
+ if (str == NULL) {
+ return NULL;
+ }
+
+ return g_ref_string_new_intern(str);
+}
+
+void i_refstr_release(char *str)
+{
+ if (str == NULL) {
+ return;
+ }
+
+ g_ref_string_release(str);
+}
+
+void i_refstr_deinit(void)
+{
+ /* nothing */
+}
+
+char *i_refstr_table_size_info(void)
+{
+ /* not available */
+ return NULL;
+}
+
+#else
+
+GHashTable *i_refstr_table;
+
+void i_refstr_init(void)
+{
+ i_refstr_table = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+char *i_refstr_intern(const char *str)
+{
+ char *ret;
+ gpointer rc_p, ret_p;
+ size_t rc;
+
+ if (str == NULL)
+ return NULL;
+
+ if (g_hash_table_lookup_extended(i_refstr_table, str, &ret_p, &rc_p)) {
+ rc = GPOINTER_TO_SIZE(rc_p);
+ ret = ret_p;
+ } else {
+ rc = 0;
+ ret = g_strdup(str);
+ }
+
+ if (rc + 1 <= G_MAXSIZE) {
+ g_hash_table_insert(i_refstr_table, ret, GSIZE_TO_POINTER(rc + 1));
+ return ret;
+ } else {
+ return g_strdup(str);
+ }
+}
+
+void i_refstr_release(char *str)
+{
+ char *ret;
+ gpointer rc_p, ret_p;
+ size_t rc;
+
+ if (str == NULL)
+ return;
+
+ if (g_hash_table_lookup_extended(i_refstr_table, str, &ret_p, &rc_p)) {
+ rc = GPOINTER_TO_SIZE(rc_p);
+ ret = ret_p;
+ } else {
+ rc = 0;
+ ret = NULL;
+ }
+
+ if (ret == str) {
+ if (rc > 1) {
+ g_hash_table_insert(i_refstr_table, ret, GSIZE_TO_POINTER(rc - 1));
+ } else {
+ g_hash_table_remove(i_refstr_table, ret);
+ g_free(ret);
+ }
+ } else {
+ g_free(str);
+ }
+}
+
+void i_refstr_deinit(void)
+{
+ g_hash_table_foreach(i_refstr_table, (GHFunc) g_free, NULL);
+ g_hash_table_destroy(i_refstr_table);
+}
+
+char *i_refstr_table_size_info(void)
+{
+ GHashTableIter iter;
+ void *k_p, *v_p;
+ size_t count, mem;
+ count = 0;
+ mem = 0;
+ g_hash_table_iter_init(&iter, i_refstr_table);
+ while (g_hash_table_iter_next(&iter, &k_p, &v_p)) {
+ char *key = k_p;
+ count++;
+ mem += sizeof(char) * (strlen(key) + 1) + 2 * sizeof(void *);
+ }
+
+ return g_strdup_printf("Shared strings: %ld, %dkB of data", count,
+ (int) (mem / 1024));
+}
+
+#endif
diff --git a/src/core/refstrings.h b/src/core/refstrings.h
new file mode 100644
index 0000000..89ef84d
--- /dev/null
+++ b/src/core/refstrings.h
@@ -0,0 +1,10 @@
+#ifndef IRSSI_CORE_REFSTRINGS_H
+#define IRSSI_CORE_REFSTRINGS_H
+
+void i_refstr_init(void);
+char *i_refstr_intern(const char *str);
+void i_refstr_release(char *str);
+void i_refstr_deinit(void);
+char *i_refstr_table_size_info(void);
+
+#endif
diff --git a/src/core/server-connect-rec.h b/src/core/server-connect-rec.h
new file mode 100644
index 0000000..2f68bd0
--- /dev/null
+++ b/src/core/server-connect-rec.h
@@ -0,0 +1,49 @@
+/* SERVER_CONNECT_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER CONNECT", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+int refcount;
+
+/* if we're connecting via proxy, or just NULLs */
+char *proxy;
+int proxy_port;
+char *proxy_string, *proxy_string_after, *proxy_password;
+
+unsigned short family; /* 0 = don't care, AF_INET or AF_INET6 */
+unsigned short chosen_family; /* family actually chosen during name resolution */
+char *tag; /* try to keep this tag when connected to server */
+char *address;
+int port;
+char *chatnet;
+
+IPADDR *own_ip4, *own_ip6;
+
+char *password;
+char *nick;
+char *username;
+char *realname;
+
+char *tls_cert;
+char *tls_pkey;
+char *tls_pass;
+char *tls_cafile;
+char *tls_capath;
+char *tls_ciphers;
+char *tls_pinned_cert;
+char *tls_pinned_pubkey;
+
+GIOChannel *connect_handle; /* connect using this handle */
+
+/* when reconnecting, the old server status */
+unsigned int reconnection:1; /* we're trying to reconnect a connected server */
+unsigned int reconnecting:1; /* we're trying to reconnect any connection */
+unsigned int no_autojoin_channels:1; /* don't autojoin any channels */
+unsigned int no_autosendcmd:1; /* don't execute autosendcmd */
+unsigned int unix_socket:1; /* Connect using named unix socket */
+unsigned int use_tls:1; /* this connection uses TLS */
+unsigned int tls_verify:1;
+unsigned int no_connect:1; /* don't connect() at all, it's done by plugin */
+unsigned short last_failed_family; /* #641: if we failed to connect to ipv6, try ipv4 and vice versa */
+char *channels;
+char *away_reason;
diff --git a/src/core/server-rec.h b/src/core/server-rec.h
new file mode 100644
index 0000000..6c7c63e
--- /dev/null
+++ b/src/core/server-rec.h
@@ -0,0 +1,78 @@
+/* SERVER_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("SERVER", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+int refcount;
+
+STRUCT_SERVER_CONNECT_REC *connrec;
+time_t connect_time; /* connection time */
+time_t real_connect_time; /* time when server replied that we really are connected */
+
+char *tag; /* tag name for addressing server */
+char *nick; /* current nick */
+
+unsigned int connected:1; /* Connected to server */
+unsigned int disconnected:1; /* Disconnected, waiting for refcount to drop zero */
+unsigned int connection_lost:1; /* Connection lost unintentionally */
+unsigned int session_reconnect:1; /* Connected to this server with /UPGRADE */
+unsigned int no_reconnect:1; /* Don't reconnect to server */
+
+NET_SENDBUF_REC *handle;
+int readtag; /* input tag */
+
+/* for net_gethostbyname_return() */
+GIOChannel *connect_pipe[2];
+int connect_tag;
+int connect_pid;
+
+RAWLOG_REC *rawlog;
+GHashTable *module_data;
+
+char *version; /* server version */
+char *away_reason;
+char *last_invite; /* channel where you were last invited */
+unsigned int server_operator:1;
+unsigned int usermode_away:1;
+unsigned int banned:1; /* not allowed to connect to this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+
+gint64 lag_sent; /* 0 or time when last lag query was sent to server */
+time_t lag_last_check; /* last time we checked lag */
+int lag; /* server lag in milliseconds */
+
+GSList *channels;
+GSList *queries;
+
+/* transient meta data stash */
+GHashTable *current_incoming_meta;
+
+/* -- support for multiple server types -- */
+
+/* -- must not be NULL: -- */
+/* join to a number of channels, channels are specified in `data' separated
+ with commas. there can exist other information after first space like
+ channel keys etc. */
+void (*channels_join)(SERVER_REC *server, const char *data, int automatic);
+/* returns true if `flag' indicates a nick flag (op/voice/halfop) */
+int (*isnickflag)(SERVER_REC *server, char flag);
+/* returns true if `data' indicates a channel */
+int (*ischannel)(SERVER_REC *server, const char *data);
+/* returns all nick flag characters in order op, voice, halfop. If some
+ of them aren't supported '\0' can be used. */
+const char *(*get_nick_flags)(SERVER_REC *server);
+/* send public or private message to server */
+void (*send_message)(SERVER_REC *server, const char *target,
+ const char *msg, int target_type);
+/* split message in case it is too long for the server to receive */
+char **(*split_message)(SERVER_REC *server, const char *target,
+ const char *msg);
+
+/* -- Default implementations are used if NULL -- */
+CHANNEL_REC *(*channel_find_func)(SERVER_REC *server, const char *name);
+QUERY_REC *(*query_find_func)(SERVER_REC *server, const char *nick);
+int (*mask_match_func)(const char *mask, const char *data);
+/* returns true if `msg' was meant for `nick' */
+int (*nick_match_msg)(const char *nick, const char *msg);
+
+#undef STRUCT_SERVER_CONNECT_REC
diff --git a/src/core/server-setup-rec.h b/src/core/server-setup-rec.h
new file mode 100644
index 0000000..e6b0431
--- /dev/null
+++ b/src/core/server-setup-rec.h
@@ -0,0 +1,36 @@
+int type; /* module_get_uniq_id("SERVER SETUP", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+
+char *chatnet;
+
+unsigned short family; /* 0 = default, AF_INET or AF_INET6 */
+char *address;
+int port;
+char *password;
+
+int sasl_mechanism;
+char *sasl_password;
+
+char *tls_cert;
+char *tls_pkey;
+char *tls_pass;
+char *tls_cafile;
+char *tls_capath;
+char *tls_ciphers;
+char *tls_pinned_cert;
+char *tls_pinned_pubkey;
+
+char *own_host; /* address to use when connecting this server */
+IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */
+
+time_t last_connect; /* to avoid reconnecting too fast.. */
+
+unsigned int autoconnect:1;
+unsigned int no_proxy:1;
+unsigned int last_failed:1; /* if last connection attempt failed */
+unsigned int banned:1; /* if we're banned from this server */
+unsigned int dns_error:1; /* DNS said the host doesn't exist */
+unsigned int use_tls:1; /* this connection uses TLS */
+unsigned int tls_verify:1;
+
+GHashTable *module_data;
diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c
new file mode 100644
index 0000000..a9d9422
--- /dev/null
+++ b/src/core/servers-reconnect.c
@@ -0,0 +1,527 @@
+/*
+ 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/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/servers-reconnect.h>
+
+#include <irssi/src/core/settings.h>
+
+GSList *reconnects;
+static int last_reconnect_tag;
+static int reconnect_timeout_tag;
+static int reconnect_time;
+static int connect_timeout;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server)
+{
+ g_free_not_null(conn->tag);
+ conn->tag = g_strdup(server->tag);
+
+ g_free_not_null(conn->away_reason);
+ conn->away_reason = !server->usermode_away ? NULL :
+ g_strdup(server->away_reason);
+
+ if (!server->connected) {
+ /* default to channels/usermode from connect record
+ since server isn't fully connected yet */
+ /* XXX when is reconnect_save_status() called with
+ * server->connected==FALSE? */
+ g_free_not_null(conn->channels);
+ conn->channels = server->connrec->no_autojoin_channels ? NULL :
+ g_strdup(server->connrec->channels);
+ }
+
+ signal_emit("server reconnect save status", 2, conn, server);
+}
+
+static void server_reconnect_add(SERVER_CONNECT_REC *conn,
+ time_t next_connect)
+{
+ RECONNECT_REC *rec;
+
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+ rec = g_new(RECONNECT_REC, 1);
+ rec->tag = ++last_reconnect_tag;
+ rec->next_connect = next_connect;
+
+ rec->conn = conn;
+ conn->reconnecting = TRUE;
+ server_connect_ref(conn);
+
+ reconnects = g_slist_append(reconnects, rec);
+}
+
+void server_reconnect_destroy(RECONNECT_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ reconnects = g_slist_remove(reconnects, rec);
+
+ signal_emit("server reconnect remove", 1, rec);
+ server_connect_unref(rec->conn);
+ g_free(rec);
+
+ if (reconnects == NULL)
+ last_reconnect_tag = 0;
+}
+
+static int server_reconnect_timeout(void)
+{
+ SERVER_CONNECT_REC *conn;
+ GSList *list, *tmp, *next;
+ time_t now;
+
+ now = time(NULL);
+
+ /* timeout any connections that haven't gotten to connected-stage */
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ if (!server->connected &&
+ server->connect_time + connect_timeout < now &&
+ connect_timeout > 0) {
+ server->connection_lost = TRUE;
+ server_disconnect(server);
+ }
+ }
+
+ for (tmp = lookup_servers; tmp != NULL; tmp = next) {
+ SERVER_REC *server = tmp->data;
+
+ next = tmp->next;
+ if (server->connect_time + connect_timeout < now &&
+ connect_timeout > 0) {
+ if (server->connect_tag != -1) {
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+ }
+ server->connection_lost = TRUE;
+ server_connect_failed(server, "Timeout");
+ }
+ }
+
+ /* If server_connect() removes the next reconnection in queue,
+ we're screwed. I don't think this should happen anymore, but just
+ to be sure we don't crash, do this safely. */
+ list = g_slist_copy(reconnects);
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (g_slist_find(reconnects, rec) == NULL)
+ continue;
+
+ if (rec->next_connect <= now) {
+ conn = rec->conn;
+ server_connect_ref(conn);
+ server_reconnect_destroy(rec);
+ server_connect(conn);
+ server_connect_unref(conn);
+ }
+ }
+
+ g_slist_free(list);
+ return 1;
+}
+
+static void sserver_connect(SERVER_SETUP_REC *rec, SERVER_CONNECT_REC *conn)
+{
+ server_setup_fill_reconn(conn, rec);
+ server_reconnect_add(conn, rec->last_connect+reconnect_time);
+ server_connect_unref(conn);
+}
+
+static SERVER_CONNECT_REC *
+server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info)
+{
+ SERVER_CONNECT_REC *dest;
+
+ dest = NULL;
+ signal_emit("server connect copy", 2, &dest, src);
+ g_return_val_if_fail(dest != NULL, NULL);
+
+ server_connect_ref(dest);
+ dest->type = module_get_uniq_id("SERVER CONNECT", 0);
+ dest->reconnection = src->reconnection;
+ dest->last_failed_family = src->last_failed_family;
+ dest->proxy = g_strdup(src->proxy);
+ dest->proxy_port = src->proxy_port;
+ dest->proxy_string = g_strdup(src->proxy_string);
+ dest->proxy_string_after = g_strdup(src->proxy_string_after);
+ dest->proxy_password = g_strdup(src->proxy_password);
+
+ dest->tag = g_strdup(src->tag);
+
+ if (connect_info) {
+ dest->family = src->family;
+ dest->address = g_strdup(src->address);
+ dest->port = src->port;
+ dest->password = g_strdup(src->password);
+
+ dest->use_tls = src->use_tls;
+ dest->tls_cert = g_strdup(src->tls_cert);
+ dest->tls_pkey = g_strdup(src->tls_pkey);
+ dest->tls_verify = src->tls_verify;
+ dest->tls_cafile = g_strdup(src->tls_cafile);
+ dest->tls_capath = g_strdup(src->tls_capath);
+ dest->tls_ciphers = g_strdup(src->tls_ciphers);
+ dest->tls_pinned_cert = g_strdup(src->tls_pinned_cert);
+ dest->tls_pinned_pubkey = g_strdup(src->tls_pinned_pubkey);
+ }
+
+ dest->chatnet = g_strdup(src->chatnet);
+ dest->nick = g_strdup(src->nick);
+ dest->username = g_strdup(src->username);
+ dest->realname = g_strdup(src->realname);
+
+ if (src->own_ip4 != NULL) {
+ dest->own_ip4 = g_new(IPADDR, 1);
+ memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR));
+ }
+ if (src->own_ip6 != NULL) {
+ dest->own_ip6 = g_new(IPADDR, 1);
+ memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR));
+ }
+
+ dest->channels = g_strdup(src->channels);
+ dest->away_reason = g_strdup(src->away_reason);
+ dest->no_autojoin_channels = src->no_autojoin_channels;
+ dest->no_autosendcmd = src->no_autosendcmd;
+ dest->unix_socket = src->unix_socket;
+
+ return dest;
+}
+
+#define server_should_reconnect(server) \
+ ((server)->connection_lost && !(server)->no_reconnect && \
+ ((server)->connrec->chatnet != NULL || \
+ !(server)->banned))
+
+#define sserver_connect_ok(rec, net) \
+ (!(rec)->banned && (rec)->chatnet != NULL && \
+ g_ascii_strcasecmp((rec)->chatnet, (net)) == 0)
+
+static void sig_reconnect(SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ SERVER_SETUP_REC *sserver;
+ GSList *tmp;
+ int use_next, through;
+ time_t now;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ if (reconnect_time == -1 || !server_should_reconnect(server))
+ return;
+
+ sserver = server_setup_find(server->connrec->address, server->connrec->port,
+ server->connrec->chatnet);
+
+ conn = server_connect_copy_skeleton(server->connrec, sserver == NULL);
+ g_return_if_fail(conn != NULL);
+
+ /* save the server status */
+ if (server->connected) {
+ conn->reconnection = TRUE;
+
+ reconnect_save_status(conn, server);
+ }
+
+ if (sserver != NULL) {
+ /* save the last connection time/status */
+ sserver->last_connect = server->connect_time == 0 ?
+ time(NULL) : server->connect_time;
+ sserver->last_failed = !server->connected;
+ sserver->banned = server->banned;
+ sserver->dns_error = server->dns_error;
+ }
+
+ if (sserver == NULL || conn->chatnet == NULL) {
+ /* not in any chatnet, just reconnect back to same server */
+ conn->family = server->connrec->family;
+ conn->address = g_strdup(server->connrec->address);
+ conn->port = server->connrec->port;
+ conn->password = g_strdup(server->connrec->password);
+
+ if (strchr(conn->address, '/') != NULL)
+ conn->unix_socket = TRUE;
+
+ server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) :
+ server->connect_time) + reconnect_time);
+ server_connect_unref(conn);
+ return;
+ }
+
+ /* always try to first connect to the first on the list where we
+ haven't got unsuccessful connection attempts for the past half
+ an hour. */
+
+ now = time(NULL);
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (sserver_connect_ok(rec, conn->chatnet) &&
+ (!rec->last_connect || !rec->last_failed ||
+ rec->last_connect < now-FAILED_RECONNECT_WAIT)) {
+ if (rec == sserver)
+ conn->port = server->connrec->port;
+ sserver_connect(rec, conn);
+ return;
+ }
+ }
+
+ /* just try the next server in list */
+ use_next = through = FALSE;
+ for (tmp = setupservers; tmp != NULL; ) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (!use_next && server->connrec->port == rec->port &&
+ g_ascii_strcasecmp(rec->address, server->connrec->address) == 0)
+ use_next = TRUE;
+ else if (use_next && sserver_connect_ok(rec, conn->chatnet)) {
+ if (rec == sserver)
+ conn->port = server->connrec->port;
+ sserver_connect(rec, conn);
+ break;
+ }
+
+ if (tmp->next != NULL) {
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (through) {
+ /* shouldn't happen unless there's no servers in
+ this chatnet in setup.. */
+ server_connect_unref(conn);
+ break;
+ }
+
+ tmp = setupservers;
+ use_next = through = TRUE;
+ }
+}
+
+static void sig_connected(SERVER_REC *server)
+{
+ g_return_if_fail(IS_SERVER(server));
+ if (!server->connrec->reconnection)
+ return;
+
+ if (server->connrec->channels != NULL)
+ server->channels_join(server, server->connrec->channels, TRUE);
+}
+
+/* Remove all servers from reconnect list */
+/* SYNTAX: RMRECONNS */
+static void cmd_rmreconns(void)
+{
+ while (reconnects != NULL)
+ server_reconnect_destroy(reconnects->data);
+}
+
+static RECONNECT_REC *reconnect_find_tag(int tag)
+{
+ GSList *tmp;
+
+ for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ if (rec->tag == tag)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static void reconnect_all(void)
+{
+ GSList *list;
+ SERVER_CONNECT_REC *conn;
+ RECONNECT_REC *rec;
+
+ /* first move reconnects to another list so if server_connect()
+ fails and goes to reconnection list again, we won't get stuck
+ here forever */
+ list = NULL;
+ while (reconnects != NULL) {
+ rec = reconnects->data;
+
+ list = g_slist_append(list, rec->conn);
+ server_connect_ref(rec->conn);
+ server_reconnect_destroy(rec);
+ }
+
+
+ while (list != NULL) {
+ conn = list->data;
+
+ server_connect(conn);
+ server_connect_unref(conn);
+ list = g_slist_remove(list, conn);
+ }
+}
+
+/* SYNTAX: RECONNECT <tag> [<quit message>] */
+static void cmd_reconnect(const char *data, SERVER_REC *server)
+{
+ SERVER_CONNECT_REC *conn;
+ RECONNECT_REC *rec;
+ char *tag, *msg;
+ void *free_arg;
+ int tagnum;
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
+ return;
+
+ if (*tag != '\0' && g_strcmp0(tag, "*") != 0)
+ server = server_find_tag(tag);
+
+ if (server != NULL) {
+ /* reconnect connected server */
+ conn = server_connect_copy_skeleton(server->connrec, TRUE);
+
+ if (server->connected)
+ reconnect_save_status(conn, server);
+
+ msg = g_strconcat("* ", *msg == '\0' ?
+ "Reconnecting" : msg, NULL);
+ signal_emit("command disconnect", 2, msg, server);
+ g_free(msg);
+
+ conn->reconnection = TRUE;
+ server_connect(conn);
+ server_connect_unref(conn);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (g_ascii_strcasecmp(tag, "all") == 0) {
+ /* reconnect all servers in reconnect queue */
+ reconnect_all();
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (*data == '\0') {
+ /* reconnect to first server in reconnection list */
+ if (reconnects == NULL)
+ cmd_param_error(CMDERR_NOT_CONNECTED);
+ rec = reconnects->data;
+ } else {
+ if (g_ascii_strncasecmp(tag, "RECON-", 6) == 0)
+ tag += 6;
+
+ tagnum = atoi(tag);
+ rec = tagnum <= 0 ? NULL : reconnect_find_tag(tagnum);
+ }
+
+ if (rec == NULL) {
+ signal_emit("server reconnect not found", 1, data);
+ } else {
+ conn = rec->conn;
+ server_connect_ref(conn);
+ server_reconnect_destroy(rec);
+ server_connect(conn);
+ server_connect_unref(conn);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_disconnect(const char *data, SERVER_REC *server)
+{
+ RECONNECT_REC *rec;
+
+ if (g_ascii_strncasecmp(data, "RECON-", 6) != 0)
+ return; /* handle only reconnection removing */
+
+ rec = reconnect_find_tag(atoi(data+6));
+
+ if (rec == NULL)
+ signal_emit("server reconnect not found", 1, data);
+ else
+ server_reconnect_destroy(rec);
+ signal_stop();
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+ GSList *tmp, *next;
+
+ for (tmp = reconnects; tmp != NULL; tmp = next) {
+ RECONNECT_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->conn->chat_type == proto->id)
+ server_reconnect_destroy(rec);
+ }
+}
+
+static void read_settings(void)
+{
+ reconnect_time = settings_get_time("server_reconnect_time")/1000;
+ connect_timeout = settings_get_time("server_connect_timeout")/1000;
+}
+
+void servers_reconnect_init(void)
+{
+ settings_add_time("server", "server_reconnect_time", "5min");
+ settings_add_time("server", "server_connect_timeout", "5min");
+
+ reconnects = NULL;
+ last_reconnect_tag = 0;
+
+ reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL);
+ read_settings();
+
+ signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_add("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns);
+ command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect);
+ command_bind_first("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
+}
+
+void servers_reconnect_deinit(void)
+{
+ g_source_remove(reconnect_timeout_tag);
+
+ signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect);
+ signal_remove("event connected", (SIGNAL_FUNC) sig_connected);
+ signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+
+ command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns);
+ command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect);
+ command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
+}
diff --git a/src/core/servers-reconnect.h b/src/core/servers-reconnect.h
new file mode 100644
index 0000000..b98f517
--- /dev/null
+++ b/src/core/servers-reconnect.h
@@ -0,0 +1,23 @@
+#ifndef IRSSI_CORE_SERVERS_RECONNECT_H
+#define IRSSI_CORE_SERVERS_RECONNECT_H
+
+/* wait for half an hour before trying to reconnect to host where last
+ connection failed */
+#define FAILED_RECONNECT_WAIT (60*30)
+
+typedef struct {
+ int tag;
+ time_t next_connect;
+
+ SERVER_CONNECT_REC *conn;
+} RECONNECT_REC;
+
+extern GSList *reconnects;
+
+void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server);
+void server_reconnect_destroy(RECONNECT_REC *rec);
+
+void servers_reconnect_init(void);
+void servers_reconnect_deinit(void);
+
+#endif
diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c
new file mode 100644
index 0000000..82e5f6b
--- /dev/null
+++ b/src/core/servers-setup.c
@@ -0,0 +1,780 @@
+/*
+ servers-setup.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/network.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/chatnets.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+
+GSList *setupservers;
+
+static char *old_source_host;
+int source_host_ok; /* Use source_host_ip .. */
+IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+
+static void save_ips(IPADDR *ip4, IPADDR *ip6,
+ IPADDR **save_ip4, IPADDR **save_ip6)
+{
+ if (ip4->family == 0)
+ g_free_and_null(*save_ip4);
+ else {
+ if (*save_ip4 == NULL)
+ *save_ip4 = g_new(IPADDR, 1);
+ memcpy(*save_ip4, ip4, sizeof(IPADDR));
+ }
+
+ if (ip6->family == 0)
+ g_free_and_null(*save_ip6);
+ else {
+ if (*save_ip6 == NULL)
+ *save_ip6 = g_new(IPADDR, 1);
+ memcpy(*save_ip6, ip6, sizeof(IPADDR));
+ }
+}
+
+static void get_source_host_ip(void)
+{
+ const char *hostname;
+ IPADDR ip4, ip6;
+
+ if (source_host_ok)
+ return;
+
+ /* FIXME: This will block! */
+ hostname = settings_get_str("hostname");
+ source_host_ok = *hostname != '\0' &&
+ net_gethostbyname(hostname, &ip4, &ip6) == 0;
+
+ if (source_host_ok)
+ save_ips(&ip4, &ip6, &source_host_ip4, &source_host_ip6);
+ else {
+ g_free_and_null(source_host_ip4);
+ g_free_and_null(source_host_ip6);
+ }
+}
+
+static void conn_set_ip(SERVER_CONNECT_REC *conn, const char *own_host,
+ IPADDR **own_ip4, IPADDR **own_ip6)
+{
+ IPADDR ip4, ip6;
+
+ if (*own_ip4 == NULL && *own_ip6 == NULL) {
+ /* resolve the IP */
+ if (net_gethostbyname(own_host, &ip4, &ip6) == 0)
+ save_ips(&ip4, &ip6, own_ip4, own_ip6);
+ }
+
+ server_connect_own_ip_save(conn, *own_ip4, *own_ip6);
+}
+
+/* Fill information to connection from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+ if (sserver->own_host != NULL) {
+ conn_set_ip(conn, sserver->own_host,
+ &sserver->own_ip4, &sserver->own_ip6);
+ }
+
+ if (sserver->chatnet != NULL && conn->chatnet == NULL)
+ conn->chatnet = g_strdup(sserver->chatnet);
+
+ if (sserver->password != NULL && conn->password == NULL)
+ conn->password = g_strdup(sserver->password);
+
+ if (sserver->no_proxy)
+ g_free_and_null(conn->proxy);
+
+ if (sserver->family != 0 && conn->family == 0)
+ conn->family = sserver->family;
+ if (sserver->address && !conn->address)
+ conn->address = g_strdup(sserver->address);
+ if (sserver->port > 0 && conn->port <= 0)
+ conn->port = sserver->port;
+
+ conn->use_tls = sserver->use_tls;
+ if (conn->tls_cert == NULL && sserver->tls_cert != NULL && sserver->tls_cert[0] != '\0')
+ conn->tls_cert = g_strdup(sserver->tls_cert);
+ if (conn->tls_pkey == NULL && sserver->tls_pkey != NULL && sserver->tls_pkey[0] != '\0')
+ conn->tls_pkey = g_strdup(sserver->tls_pkey);
+ if (conn->tls_pass == NULL && sserver->tls_pass != NULL && sserver->tls_pass[0] != '\0')
+ conn->tls_pass = g_strdup(sserver->tls_pass);
+ conn->tls_verify = sserver->tls_verify;
+ if (conn->tls_cafile == NULL && sserver->tls_cafile != NULL && sserver->tls_cafile[0] != '\0')
+ conn->tls_cafile = g_strdup(sserver->tls_cafile);
+ if (conn->tls_capath == NULL && sserver->tls_capath != NULL && sserver->tls_capath[0] != '\0')
+ conn->tls_capath = g_strdup(sserver->tls_capath);
+ if (conn->tls_ciphers == NULL && sserver->tls_ciphers != NULL && sserver->tls_ciphers[0] != '\0')
+ conn->tls_ciphers = g_strdup(sserver->tls_ciphers);
+ if (conn->tls_pinned_cert == NULL && sserver->tls_pinned_cert != NULL && sserver->tls_pinned_cert[0] != '\0')
+ conn->tls_pinned_cert = g_strdup(sserver->tls_pinned_cert);
+ if (conn->tls_pinned_pubkey == NULL && sserver->tls_pinned_pubkey != NULL && sserver->tls_pinned_pubkey[0] != '\0')
+ conn->tls_pinned_pubkey = g_strdup(sserver->tls_pinned_pubkey);
+
+ signal_emit("server setup fill reconn", 2, conn, sserver);
+}
+
+static void server_setup_fill(SERVER_CONNECT_REC *conn, const char *address, int port,
+ GHashTable *optlist)
+{
+ g_return_if_fail(conn != NULL);
+ g_return_if_fail(address != NULL);
+
+ conn->type = module_get_uniq_id("SERVER CONNECT", 0);
+
+ conn->address = g_strdup(address);
+ if (port > 0) conn->port = port;
+
+ if (strchr(address, '/') != NULL)
+ conn->unix_socket = TRUE;
+
+ if (!conn->nick) conn->nick = g_strdup(settings_get_str("nick"));
+ conn->username = g_strdup(settings_get_str("user_name"));
+ conn->realname = g_strdup(settings_get_str("real_name"));
+
+ /* proxy settings */
+ if (settings_get_bool("use_proxy")) {
+ conn->proxy = g_strdup(settings_get_str("proxy_address"));
+ conn->proxy_port = settings_get_int("proxy_port");
+ conn->proxy_string = g_strdup(settings_get_str("proxy_string"));
+ conn->proxy_string_after = g_strdup(settings_get_str("proxy_string_after"));
+ conn->proxy_password = g_strdup(settings_get_str("proxy_password"));
+ }
+
+ /* source IP */
+ if (source_host_ip4 != NULL) {
+ conn->own_ip4 = g_new(IPADDR, 1);
+ memcpy(conn->own_ip4, source_host_ip4, sizeof(IPADDR));
+ }
+ if (source_host_ip6 != NULL) {
+ conn->own_ip6 = g_new(IPADDR, 1);
+ memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR));
+ }
+
+ signal_emit("server setup fill connect", 2, conn, optlist);
+}
+
+static void server_setup_fill_optlist(SERVER_CONNECT_REC *conn, GHashTable *optlist)
+{
+ char *tmp;
+
+ if (g_hash_table_lookup(optlist, "6") != NULL)
+ conn->family = AF_INET6;
+ else if (g_hash_table_lookup(optlist, "4") != NULL)
+ conn->family = AF_INET;
+
+ /* ad-hoc TLS settings from command optlist */
+ if ((tmp = g_hash_table_lookup(optlist, "tls_cert")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL) {
+ conn->tls_cert = g_strdup(tmp);
+ conn->use_tls = TRUE;
+ }
+ if ((tmp = g_hash_table_lookup(optlist, "tls_pkey")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL)
+ conn->tls_pkey = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_pass")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL)
+ conn->tls_pass = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_cafile")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL)
+ conn->tls_cafile = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_capath")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL)
+ conn->tls_capath = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL)
+ conn->tls_ciphers = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL)
+ conn->tls_pinned_cert = g_strdup(tmp);
+ if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL ||
+ (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL)
+ conn->tls_pinned_pubkey = g_strdup(tmp);
+ if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0') ||
+ (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0'))
+ conn->tls_verify = TRUE;
+ if (g_hash_table_lookup(optlist, "notls_verify") != NULL)
+ conn->tls_verify = FALSE;
+ if (g_hash_table_lookup(optlist, "tls_verify") != NULL ||
+ g_hash_table_lookup(optlist, "ssl_verify") != NULL) {
+ conn->tls_verify = TRUE;
+ conn->use_tls = TRUE;
+ }
+ if (g_hash_table_lookup(optlist, "notls") != NULL)
+ conn->use_tls = FALSE;
+ if (g_hash_table_lookup(optlist, "tls") != NULL ||
+ g_hash_table_lookup(optlist, "ssl") != NULL)
+ conn->use_tls = TRUE;
+
+ if (g_hash_table_lookup(optlist, "!") != NULL)
+ conn->no_autojoin_channels = TRUE;
+
+ if (g_hash_table_lookup(optlist, "noautosendcmd") != NULL)
+ conn->no_autosendcmd = TRUE;
+
+ if (g_hash_table_lookup(optlist, "noproxy") != NULL)
+ g_free_and_null(conn->proxy);
+
+ signal_emit("server setup fill optlist", 2, conn, optlist);
+}
+
+static void server_setup_fill_server(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_SERVER_SETUP(sserver));
+
+ sserver->last_connect = time(NULL);
+
+ server_setup_fill_reconn(conn, sserver);
+
+ signal_emit("server setup fill server", 2, conn, sserver);
+}
+
+static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn,
+ CHATNET_REC *chatnet)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+ g_return_if_fail(IS_CHATNET(chatnet));
+
+ if (chatnet->nick != NULL) {
+ g_free(conn->nick);
+ conn->nick = g_strdup(chatnet->nick);;
+ }
+ if (chatnet->username != NULL) {
+ g_free(conn->username);
+ conn->username = g_strdup(chatnet->username);;
+ }
+ if (chatnet->realname != NULL) {
+ g_free(conn->realname);
+ conn->realname = g_strdup(chatnet->realname);;
+ }
+ if (chatnet->own_host != NULL) {
+ conn_set_ip(conn, chatnet->own_host,
+ &chatnet->own_ip4, &chatnet->own_ip6);
+ }
+
+ signal_emit("server setup fill chatnet", 2, conn, chatnet);
+}
+
+static SERVER_CONNECT_REC *create_addr_conn(int chat_type, const char *address, int port,
+ const char *chatnet, const char *password,
+ const char *nick, GHashTable *optlist)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_CONNECT_REC *conn;
+ SERVER_SETUP_REC *sserver;
+ CHATNET_REC *chatnetrec;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ sserver = server_setup_find(address, port, chatnet);
+ if (sserver != NULL) {
+ if (chat_type < 0)
+ chat_type = sserver->chat_type;
+ else if (chat_type != sserver->chat_type)
+ sserver = NULL;
+ }
+
+ proto = chat_type >= 0 ? chat_protocol_find_id(chat_type) :
+ chat_protocol_get_default();
+
+ conn = proto->create_server_connect();
+ server_connect_ref(conn);
+
+ conn->chat_type = proto->id;
+ if (chatnet != NULL && *chatnet != '\0')
+ conn->chatnet = g_strdup(chatnet);
+
+ /* fill in the defaults */
+ server_setup_fill(conn, address, port, optlist);
+
+ /* fill the rest from chat network settings */
+ chatnetrec = chatnet != NULL ? chatnet_find(chatnet) :
+ (sserver == NULL || sserver->chatnet == NULL ? NULL :
+ chatnet_find(sserver->chatnet));
+ if (chatnetrec != NULL)
+ server_setup_fill_chatnet(conn, chatnetrec);
+
+ /* fill the information from setup */
+ if (sserver != NULL)
+ server_setup_fill_server(conn, sserver);
+
+ /* fill the optlist overrides */
+ if (g_hash_table_size(optlist))
+ server_setup_fill_optlist(conn, optlist);
+
+ /* nick / password given in command line overrides all settings */
+ if (password && *password) {
+ g_free_not_null(conn->password);
+ conn->password = g_strdup(password);
+ }
+ if (nick && *nick) {
+ g_free_not_null(conn->nick);
+ conn->nick = g_strdup(nick);
+ }
+
+ return conn;
+}
+
+/* Connect to server where last connect succeeded (or we haven't tried to
+ connect yet). If there's no such server, connect to server where we
+ haven't connected for the longest time */
+static SERVER_CONNECT_REC *create_chatnet_conn(const char *dest, int port, const char *password,
+ const char *nick, GHashTable *optlist)
+{
+ SERVER_SETUP_REC *bestrec;
+ GSList *tmp;
+ time_t now, besttime;
+
+ now = time(NULL);
+ bestrec = NULL; besttime = now;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (rec->chatnet == NULL ||
+ g_ascii_strcasecmp(rec->chatnet, dest) != 0)
+ continue;
+
+ if (!rec->last_failed) {
+ bestrec = rec;
+ break;
+ }
+
+ if (bestrec == NULL || besttime > rec->last_connect) {
+ bestrec = rec;
+ besttime = rec->last_connect;
+ }
+ }
+
+ return bestrec == NULL ? NULL :
+ create_addr_conn(bestrec->chat_type, bestrec->address, 0, dest,
+ NULL, nick, optlist);
+}
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *server_create_conn_opt(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password,
+ const char *nick, GHashTable *optlist)
+{
+ SERVER_CONNECT_REC *rec;
+ CHATNET_REC *chatrec;
+
+ g_return_val_if_fail(dest != NULL, NULL);
+
+ chatrec = chatnet_find(dest);
+ if (chatrec != NULL) {
+ rec = create_chatnet_conn(chatrec->name, port, password, nick, optlist);
+ /* If rec is NULL the chatnet has no url to connect to */
+ return rec;
+ }
+
+ chatrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+ if (chatrec != NULL)
+ chatnet = chatrec->name;
+
+ return create_addr_conn(chat_type, dest, port, chatnet, password, nick, optlist);
+}
+
+SERVER_CONNECT_REC *server_create_conn(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password, const char *nick)
+{
+ SERVER_CONNECT_REC *ret;
+ GHashTable *opt;
+
+ opt = g_hash_table_new(NULL, NULL);
+ ret = server_create_conn_opt(chat_type, dest, port, chatnet, password, nick, opt);
+ g_hash_table_destroy(opt);
+
+ return ret;
+}
+
+/* Find matching server from setup. Try to find record with a same port,
+ but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port,
+ const char *chatnet)
+{
+ SERVER_SETUP_REC *server;
+ GSList *tmp;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ server = NULL;
+ for (tmp = setupservers; tmp != NULL; tmp = tmp->next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ if (g_ascii_strcasecmp(rec->address, address) == 0 &&
+ (chatnet == NULL || rec->chatnet == NULL ||
+ g_ascii_strcasecmp(rec->chatnet, chatnet) == 0)) {
+ server = rec;
+ if (rec->port == port)
+ break;
+ }
+ }
+
+ return server;
+}
+
+static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node)
+{
+ SERVER_SETUP_REC *rec;
+ CHATNET_REC *chatnetrec;
+ char *server, *chatnet, *family;
+ int port;
+ char *value = NULL;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ server = config_node_get_str(node, "address", NULL);
+ if (server == NULL)
+ return NULL;
+
+ port = config_node_get_int(node, "port", 0);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+
+ if ((rec = server_setup_find(server, port, chatnet)) != NULL && rec->port == port) {
+ /* duplicate server setup */
+ server_setup_remove(rec);
+ }
+
+ rec = NULL;
+
+ chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet);
+ if (chatnetrec == NULL && chatnet != NULL) {
+ /* chat network not found, create it. */
+ chatnetrec = chat_protocol_get_default()->create_chatnet();
+ chatnetrec->chat_type = chat_protocol_get_default()->id;
+ chatnetrec->name = g_strdup(chatnet);
+ chatnet_create(chatnetrec);
+ }
+
+ family = config_node_get_str(node, "family", "");
+
+ rec = CHAT_PROTOCOL(chatnetrec)->create_server_setup();
+ rec->type = module_get_uniq_id("SERVER SETUP", 0);
+ rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id;
+ rec->chatnet = chatnetrec == NULL ? NULL : g_strdup(chatnetrec->name);
+ rec->family = g_ascii_strcasecmp(family, "inet6") == 0 ? AF_INET6 :
+ (g_ascii_strcasecmp(family, "inet") == 0 ? AF_INET : 0);
+ rec->address = g_strdup(server);
+ rec->password = g_strdup(config_node_get_str(node, "password", NULL));
+
+ rec->use_tls = config_node_get_bool(node, "use_tls", FALSE) || config_node_get_bool(node, "use_ssl", FALSE);
+ rec->tls_verify = config_node_find(node, "tls_verify") != NULL ?
+ config_node_get_bool(node, "tls_verify", TRUE) :
+ config_node_get_bool(node, "ssl_verify", TRUE);
+
+ value = config_node_get_str(node, "tls_cert", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_cert", NULL);
+ rec->tls_cert = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_pkey", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_pkey", NULL);
+ rec->tls_pkey = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_pass", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_pass", NULL);
+ rec->tls_pass = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_cafile", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_cafile", NULL);
+ rec->tls_cafile = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_capath", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_capath", NULL);
+ rec->tls_capath = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_ciphers", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_ciphers", NULL);
+ rec->tls_ciphers = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_pinned_cert", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_pinned_cert", NULL);
+ rec->tls_pinned_cert = g_strdup(value);
+
+ value = config_node_get_str(node, "tls_pinned_pubkey", NULL);
+ if (value == NULL)
+ value = config_node_get_str(node, "ssl_pinned_pubkey", NULL);
+ rec->tls_pinned_pubkey = g_strdup(value);
+
+ rec->port = port;
+ rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE);
+ rec->no_proxy = config_node_get_bool(node, "no_proxy", FALSE);
+ rec->own_host = g_strdup(config_node_get_str(node, "own_host", NULL));
+
+ signal_emit("server setup read", 2, rec, node);
+
+ setupservers = g_slist_append(setupservers, rec);
+ return rec;
+}
+
+static int compare_server_setup (CONFIG_NODE *node, SERVER_SETUP_REC *server)
+{
+ char *address, *chatnet;
+ int port;
+
+ /* skip comment nodes */
+ if (node->type == NODE_TYPE_COMMENT)
+ return -1;
+
+ address = config_node_get_str(node, "address", NULL);
+ chatnet = config_node_get_str(node, "chatnet", "");
+ port = config_node_get_int(node, "port", 0);
+
+ if (address == NULL || chatnet == NULL) {
+ return 0;
+ }
+
+ if (g_ascii_strcasecmp(address, server->address) != 0 ||
+ g_ascii_strcasecmp(chatnet, server->chatnet != NULL ? server->chatnet : "") != 0 ||
+ port != server->port) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void server_setup_save(SERVER_SETUP_REC *rec, int old_port, const char *old_chatnet)
+{
+ CONFIG_NODE *parent_node, *node;
+ SERVER_SETUP_REC search_rec = { 0 };
+ GSList *config_node;
+
+ parent_node = iconfig_node_traverse("(servers", TRUE);
+
+ /* Try to find this channel in the configuration */
+ search_rec.address = rec->address;
+ search_rec.chatnet = old_chatnet != NULL ? (char *) old_chatnet : rec->chatnet;
+ search_rec.port = old_port;
+ config_node = g_slist_find_custom(parent_node->value, &search_rec,
+ (GCompareFunc) compare_server_setup);
+ if (config_node != NULL)
+ /* Let's update this server record */
+ node = config_node->data;
+ else
+ /* Create a brand-new server record */
+ node = iconfig_node_section(parent_node, NULL, NODE_TYPE_BLOCK);
+
+ iconfig_node_clear(node);
+ iconfig_node_set_str(node, "address", rec->address);
+ iconfig_node_set_str(node, "chatnet", rec->chatnet);
+
+ iconfig_node_set_int(node, "port", rec->port);
+ iconfig_node_set_str(node, "password", rec->password);
+
+ iconfig_node_set_bool(node, "use_tls", rec->use_tls);
+ iconfig_node_set_str(node, "tls_cert", rec->tls_cert);
+ iconfig_node_set_str(node, "tls_pkey", rec->tls_pkey);
+ iconfig_node_set_str(node, "tls_pass", rec->tls_pass);
+ iconfig_node_set_bool(node, "tls_verify", rec->tls_verify);
+ iconfig_node_set_str(node, "tls_cafile", rec->tls_cafile);
+ iconfig_node_set_str(node, "tls_capath", rec->tls_capath);
+ iconfig_node_set_str(node, "tls_ciphers", rec->tls_ciphers);
+ iconfig_node_set_str(node, "tls_pinned_cert", rec->tls_pinned_cert);
+ iconfig_node_set_str(node, "tls_pinned_pubkey", rec->tls_pinned_pubkey);
+
+ iconfig_node_set_str(node, "own_host", rec->own_host);
+
+ iconfig_node_set_str(node, "family",
+ rec->family == AF_INET6 ? "inet6" :
+ rec->family == AF_INET ? "inet" : NULL);
+
+ if (rec->autoconnect)
+ iconfig_node_set_bool(node, "autoconnect", TRUE);
+ if (rec->no_proxy)
+ iconfig_node_set_bool(node, "no_proxy", TRUE);
+
+ signal_emit("server setup saved", 2, rec, node);
+}
+
+static void server_setup_remove_config(SERVER_SETUP_REC *rec)
+{
+ CONFIG_NODE *parent_node;
+ GSList *config_node;
+
+ parent_node = iconfig_node_traverse("servers", FALSE);
+
+ if (parent_node == NULL)
+ return;
+
+ /* Try to find this server in the configuration */
+ config_node = g_slist_find_custom(parent_node->value, rec,
+ (GCompareFunc)compare_server_setup);
+
+ if (config_node != NULL)
+ /* Delete the server from the configuration */
+ iconfig_node_remove(parent_node, config_node->data);
+}
+
+static void server_setup_destroy(SERVER_SETUP_REC *rec)
+{
+ setupservers = g_slist_remove(setupservers, rec);
+ signal_emit("server setup destroyed", 1, rec);
+
+ g_free_not_null(rec->own_host);
+ g_free_not_null(rec->own_ip4);
+ g_free_not_null(rec->own_ip6);
+ g_free_not_null(rec->chatnet);
+ g_free_not_null(rec->password);
+ g_free_not_null(rec->tls_cert);
+ g_free_not_null(rec->tls_pkey);
+ g_free_not_null(rec->tls_pass);
+ g_free_not_null(rec->tls_cafile);
+ g_free_not_null(rec->tls_capath);
+ g_free_not_null(rec->tls_ciphers);
+ g_free_not_null(rec->tls_pinned_cert);
+ g_free_not_null(rec->tls_pinned_pubkey);
+ g_free(rec->address);
+ g_free(rec);
+}
+
+void server_setup_modify(SERVER_SETUP_REC *rec, int old_port, const char *old_chatnet)
+{
+ g_return_if_fail(g_slist_find(setupservers, rec) != NULL);
+
+ rec->type = module_get_uniq_id("SERVER SETUP", 0);
+ server_setup_save(rec, old_port, old_chatnet);
+
+ signal_emit("server setup updated", 1, rec);
+}
+
+void server_setup_add(SERVER_SETUP_REC *rec)
+{
+ if (g_slist_find(setupservers, rec) == NULL)
+ setupservers = g_slist_append(setupservers, rec);
+ server_setup_modify(rec, -1, NULL);
+}
+
+void server_setup_remove_chatnet(const char *chatnet)
+{
+ GSList *tmp, *next;
+
+ g_return_if_fail(chatnet != NULL);
+
+ for (tmp = setupservers; tmp != NULL; tmp = next) {
+ SERVER_SETUP_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (g_ascii_strcasecmp(rec->chatnet, chatnet) == 0)
+ server_setup_remove(rec);
+ }
+}
+
+void server_setup_remove(SERVER_SETUP_REC *rec)
+{
+ server_setup_remove_config(rec);
+ server_setup_destroy(rec);
+}
+
+static void read_servers(void)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+
+ while (setupservers != NULL)
+ server_setup_destroy(setupservers->data);
+
+ /* Read servers */
+ node = iconfig_node_traverse("servers", FALSE);
+ if (node != NULL) {
+ int i = 0;
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp), i++) {
+ node = tmp->data;
+ if (node->type != NODE_TYPE_BLOCK) {
+ g_critical("Expected block node at `servers[%d]' was of %s type. "
+ "Corrupt config?",
+ i, node->type == NODE_TYPE_LIST ? "list" : "scalar");
+ } else {
+ server_setup_read(node);
+ }
+ }
+ }
+}
+
+static void read_settings(void)
+{
+ if (old_source_host == NULL ||
+ g_strcmp0(old_source_host, settings_get_str("hostname")) != 0) {
+ g_free_not_null(old_source_host);
+ old_source_host = g_strdup(settings_get_str("hostname"));
+
+ source_host_ok = FALSE;
+ get_source_host_ip();
+ }
+}
+
+void servers_setup_init(void)
+{
+ settings_add_str("server", "hostname", "");
+
+ settings_add_str("server", "nick", NULL);
+ settings_add_str("server", "user_name", NULL);
+ settings_add_str("server", "real_name", NULL);
+
+ settings_add_bool("proxy", "use_proxy", FALSE);
+ settings_add_str("proxy", "proxy_address", "");
+ settings_add_int("proxy", "proxy_port", 6667);
+ settings_add_str("proxy", "proxy_string", "CONNECT %s %d");
+ settings_add_str("proxy", "proxy_string_after", "");
+ settings_add_str("proxy", "proxy_password", "");
+
+ setupservers = NULL;
+ source_host_ip4 = source_host_ip6 = NULL;
+ old_source_host = NULL;
+ read_settings();
+
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) read_servers);
+ signal_add("irssi init read settings", (SIGNAL_FUNC) read_servers);
+}
+
+void servers_setup_deinit(void)
+{
+ g_free_not_null(source_host_ip4);
+ g_free_not_null(source_host_ip6);
+ g_free_not_null(old_source_host);
+
+ while (setupservers != NULL)
+ server_setup_destroy(setupservers->data);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) read_servers);
+ signal_remove("irssi init read settings", (SIGNAL_FUNC) read_servers);
+
+ module_uniq_destroy("SERVER SETUP");
+}
diff --git a/src/core/servers-setup.h b/src/core/servers-setup.h
new file mode 100644
index 0000000..742ed4d
--- /dev/null
+++ b/src/core/servers-setup.h
@@ -0,0 +1,53 @@
+#ifndef IRSSI_CORE_SERVERS_SETUP_H
+#define IRSSI_CORE_SERVERS_SETUP_H
+
+#include <irssi/src/core/modules.h>
+
+#define SERVER_SETUP(server) \
+ MODULE_CHECK_CAST(server, SERVER_SETUP_REC, type, "SERVER SETUP")
+
+#define IS_SERVER_SETUP(server) \
+ (SERVER_SETUP(server) ? TRUE : FALSE)
+
+/* servers */
+struct _SERVER_SETUP_REC {
+#include <irssi/src/core/server-setup-rec.h>
+};
+
+extern GSList *setupservers;
+
+extern IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */
+extern int source_host_ok; /* Use source_host_ip .. */
+
+/* Fill reconnection specific information to connection
+ from server setup record */
+void server_setup_fill_reconn(SERVER_CONNECT_REC *conn,
+ SERVER_SETUP_REC *sserver);
+
+/* Create server connection record. `dest' is required, rest can be NULL.
+ `dest' is either a server address or chat network */
+SERVER_CONNECT_REC *
+server_create_conn(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password,
+ const char *nick);
+
+SERVER_CONNECT_REC *server_create_conn_opt(int chat_type, const char *dest, int port,
+ const char *chatnet, const char *password,
+ const char *nick, GHashTable *optlist);
+
+/* Find matching server from setup. Try to find record with a same port,
+ but fallback to any server with the same address. */
+SERVER_SETUP_REC *server_setup_find(const char *address, int port,
+ const char *chatnet);
+
+void server_setup_add(SERVER_SETUP_REC *rec);
+void server_setup_modify(SERVER_SETUP_REC *rec, int old_port, const char *old_chatnet);
+void server_setup_remove(SERVER_SETUP_REC *rec);
+
+/* Remove servers attached to chatne */
+void server_setup_remove_chatnet(const char *chatnet);
+
+void servers_setup_init(void);
+void servers_setup_deinit(void);
+
+#endif
diff --git a/src/core/servers.c b/src/core/servers.c
new file mode 100644
index 0000000..30fc684
--- /dev/null
+++ b/src/core/servers.c
@@ -0,0 +1,785 @@
+/*
+ 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/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/net-disconnect.h>
+#include <irssi/src/core/net-nonblock.h>
+#include <irssi/src/core/net-sendbuffer.h>
+#include <irssi/src/core/rawlog.h>
+#include <irssi/src/core/refstrings.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-reconnect.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/queries.h>
+
+GSList *servers, *lookup_servers;
+
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg)
+{
+ g_return_if_fail(IS_SERVER(server));
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+
+ signal_emit("server connect failed", 2, server, msg);
+
+ if (server->connect_tag != -1) {
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+ }
+ if (server->handle != NULL) {
+ net_sendbuffer_destroy(server->handle, TRUE);
+ server->handle = NULL;
+ }
+
+ if (server->connect_pipe[0] != NULL) {
+ g_io_channel_shutdown(server->connect_pipe[0], TRUE, NULL);
+ g_io_channel_unref(server->connect_pipe[0]);
+ g_io_channel_shutdown(server->connect_pipe[1], TRUE, NULL);
+ g_io_channel_unref(server->connect_pipe[1]);
+ server->connect_pipe[0] = NULL;
+ server->connect_pipe[1] = NULL;
+ }
+
+ server_unref(server);
+}
+
+/* generate tag from server's address */
+static char *server_create_address_tag(const char *address)
+{
+ const char *start, *end;
+
+ g_return_val_if_fail(address != NULL, NULL);
+
+ /* try to generate a reasonable server tag */
+ if (strchr(address, '.') == NULL) {
+ start = end = NULL;
+ } else if (g_ascii_strncasecmp(address, "irc", 3) == 0 ||
+ g_ascii_strncasecmp(address, "chat", 4) == 0) {
+ /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
+ end = strrchr(address, '.');
+ start = end-1;
+ while (start > address && *start != '.') start--;
+ } else {
+ /* efnet.cs.hut.fi -> efnet */
+ end = strchr(address, '.');
+ start = end;
+ }
+
+ if (start == end) start = address; else start++;
+ if (end == NULL) end = address + strlen(address);
+
+ return g_strndup(start, (int) (end-start));
+}
+
+/* create unique tag for server. prefer ircnet's name or
+ generate it from server's address */
+static char *server_create_tag(SERVER_CONNECT_REC *conn)
+{
+ GString *str;
+ char *tag;
+ int num;
+
+ g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL);
+
+ tag = conn->chatnet != NULL && *conn->chatnet != '\0' ?
+ g_strdup(conn->chatnet) :
+ server_create_address_tag(conn->address);
+
+ if (conn->tag != NULL && server_find_tag(conn->tag) == NULL &&
+ server_find_lookup_tag(conn->tag) == NULL &&
+ strncmp(conn->tag, tag, strlen(tag)) == 0) {
+ /* use the existing tag if it begins with the same ID -
+ this is useful when you have several connections to
+ same server and you want to keep the same tags with
+ the servers (or it would cause problems when rejoining
+ /LAYOUT SAVEd channels). */
+ g_free(tag);
+ return g_strdup(conn->tag);
+ }
+
+
+ /* then just append numbers after tag until unused is found.. */
+ str = g_string_new(tag);
+
+ num = 2;
+ while (server_find_tag(str->str) != NULL ||
+ server_find_lookup_tag(str->str) != NULL) {
+ g_string_printf(str, "%s%d", tag, num);
+ num++;
+ }
+ g_free(tag);
+
+ tag = str->str;
+ g_string_free(str, FALSE);
+ return tag;
+}
+
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server)
+{
+ server->connect_time = time(NULL);
+
+ servers = g_slist_append(servers, server);
+ signal_emit("server connected", 1, server);
+}
+
+static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle)
+{
+ int error;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ error = net_geterror(handle);
+ if (error != 0) {
+ server->connection_lost = TRUE;
+ server->connrec->last_failed_family = server->connrec->chosen_family;
+ server_connect_failed(server, g_strerror(error));
+ return;
+ }
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+
+ server_connect_finished(server);
+}
+
+static void server_connect_callback_init_ssl(SERVER_REC *server, GIOChannel *handle)
+{
+ int error;
+
+ g_return_if_fail(IS_SERVER(server));
+
+ error = irssi_ssl_handshake(handle);
+ if (error == -1) {
+ server->connection_lost = TRUE;
+ server->connrec->last_failed_family = server->connrec->chosen_family;
+ server_connect_failed(server, NULL);
+ return;
+ }
+ if (error & 1) {
+ if (server->connect_tag != -1)
+ g_source_remove(server->connect_tag);
+ server->connect_tag =
+ i_input_add(handle, error == 1 ? I_INPUT_READ : I_INPUT_WRITE,
+ (GInputFunction) server_connect_callback_init_ssl, server);
+ return;
+ }
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+ if (server->connect_tag != -1) {
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+ }
+
+ server_connect_finished(server);
+}
+
+static void server_real_connect(SERVER_REC *server, IPADDR *ip,
+ const char *unix_socket)
+{
+ GIOChannel *handle;
+ IPADDR *own_ip = NULL;
+ const char *errmsg;
+ char *errmsg2;
+ char ipaddr[MAX_IP_LEN];
+ int port = 0;
+
+ g_return_if_fail(ip != NULL || unix_socket != NULL);
+
+ signal_emit("server connecting", 2, server, ip);
+
+ if (server->connrec->no_connect)
+ return;
+
+ if (ip != NULL) {
+ server->connrec->chosen_family = ip->family;
+ own_ip = IPADDR_IS_V6(ip) ? server->connrec->own_ip6 : server->connrec->own_ip4;
+ port = server->connrec->proxy != NULL ?
+ server->connrec->proxy_port : server->connrec->port;
+ handle = net_connect_ip(ip, port, own_ip);
+ } else {
+ handle = net_connect_unix(unix_socket);
+ }
+
+ if (server->connrec->use_tls && handle != NULL) {
+ server->handle = net_sendbuffer_create(handle, 0);
+ handle = net_start_ssl(server);
+ if (handle == NULL) {
+ net_sendbuffer_destroy(server->handle, TRUE);
+ server->handle = NULL;
+ } else {
+ server->handle->handle = handle;
+ }
+ }
+
+ if (handle == NULL) {
+ /* failed */
+ errmsg = g_strerror(errno);
+ errmsg2 = NULL;
+ if (errno == EADDRNOTAVAIL) {
+ if (own_ip != NULL) {
+ /* show the IP which is causing the error */
+ net_ip2host(own_ip, ipaddr);
+ errmsg2 = g_strconcat(errmsg, ": ", ipaddr, NULL);
+ }
+ server->no_reconnect = TRUE;
+ }
+ if (server->connrec->use_tls && errno == ENOSYS)
+ server->no_reconnect = TRUE;
+
+ server->connection_lost = TRUE;
+ if (ip != NULL) {
+ server->connrec->last_failed_family = ip->family;
+ }
+ server_connect_failed(server, errmsg2 ? errmsg2 : errmsg);
+ g_free(errmsg2);
+ } else {
+ server->connrec->last_failed_family = 0;
+ if (!server->connrec->use_tls)
+ server->handle = net_sendbuffer_create(handle, 0);
+ if (server->connrec->use_tls)
+ server_connect_callback_init_ssl(server, handle);
+ else
+ server->connect_tag =
+ i_input_add(handle, I_INPUT_WRITE | I_INPUT_READ,
+ (GInputFunction) server_connect_callback_init, server);
+ }
+}
+
+static void server_connect_callback_readpipe(SERVER_REC *server)
+{
+ RESOLVED_IP_REC iprec;
+ IPADDR *ip;
+ const char *errormsg;
+
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+
+ net_gethostbyname_return(server->connect_pipe[0], &iprec);
+
+ g_io_channel_shutdown(server->connect_pipe[0], TRUE, NULL);
+ g_io_channel_unref(server->connect_pipe[0]);
+ g_io_channel_shutdown(server->connect_pipe[1], TRUE, NULL);
+ g_io_channel_unref(server->connect_pipe[1]);
+
+ server->connect_pipe[0] = NULL;
+ server->connect_pipe[1] = NULL;
+
+ /* figure out if we should use IPv4 or v6 address */
+ if (iprec.error != 0) {
+ /* error */
+ ip = NULL;
+ } else if (server->connrec->family == AF_INET) {
+ /* force IPv4 connection */
+ ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
+ } else if (server->connrec->family == AF_INET6) {
+ /* force IPv6 connection */
+ ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
+ } else {
+ /* pick the one that was found. if both were found:
+ 1. disprefer the last one that failed
+ 2. prefer ipv4 over ipv6 unless resolve_prefer_ipv6 is set
+ */
+ if (iprec.ip4.family == 0 ||
+ (iprec.ip6.family != 0 &&
+ (server->connrec->last_failed_family == AF_INET ||
+ (settings_get_bool("resolve_prefer_ipv6") &&
+ server->connrec->last_failed_family != AF_INET6)))) {
+ ip = &iprec.ip6;
+ } else {
+ ip = &iprec.ip4;
+ }
+ }
+
+ if (ip != NULL) {
+ /* host lookup ok */
+ server_real_connect(server, ip, NULL);
+ errormsg = NULL;
+ } else {
+ if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
+ /* IP wasn't found for the host, don't try to
+ reconnect back to this server */
+ server->dns_error = TRUE;
+ }
+
+ if (iprec.error == 0) {
+ /* forced IPv4 or IPv6 address but it wasn't found */
+ errormsg = server->connrec->family == AF_INET ?
+ "IPv4 address not found for host" :
+ "IPv6 address not found for host";
+ } else {
+ /* gethostbyname() failed */
+ errormsg = iprec.errorstr != NULL ? iprec.errorstr :
+ "Host lookup failed";
+ }
+
+ server->connection_lost = TRUE;
+ server_connect_failed(server, errormsg);
+ }
+
+ g_free(iprec.errorstr);
+}
+
+SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_REC *server;
+
+ proto = CHAT_PROTOCOL(conn);
+ server = proto->server_init_connect(conn);
+ proto->server_connect(server);
+
+ return server;
+}
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server)
+{
+ const char *str;
+
+ g_return_if_fail(server != NULL);
+
+ MODULE_DATA_INIT(server);
+ server->type = module_get_uniq_id("SERVER", 0);
+ server_ref(server);
+ server->current_incoming_meta =
+ g_hash_table_new_full(g_str_hash, (GEqualFunc) g_str_equal,
+ (GDestroyNotify) i_refstr_release, (GDestroyNotify) g_free);
+
+ server->nick = g_strdup(server->connrec->nick);
+ if (server->connrec->username == NULL || *server->connrec->username == '\0') {
+ g_free_not_null(server->connrec->username);
+
+ str = g_get_user_name();
+ if (*str == '\0') str = "unknown";
+ server->connrec->username = g_strdup(str);
+ }
+ if (server->connrec->realname == NULL || *server->connrec->realname == '\0') {
+ g_free_not_null(server->connrec->realname);
+
+ str = g_get_real_name();
+ if (*str == '\0') str = server->connrec->username;
+ server->connrec->realname = g_strdup(str);
+ }
+
+ server->tag = server_create_tag(server->connrec);
+ server->connect_tag = -1;
+}
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server)
+{
+ const char *connect_address;
+ int fd[2];
+
+ g_return_val_if_fail(server != NULL, FALSE);
+ if (!server->connrec->unix_socket && server->connrec->port <= 0)
+ return FALSE;
+
+ server->rawlog = rawlog_create();
+
+ if (server->connrec->connect_handle != NULL) {
+ /* already connected */
+ GIOChannel *handle = server->connrec->connect_handle;
+
+ server->connrec->connect_handle = NULL;
+ server->handle = net_sendbuffer_create(handle, 0);
+ server_connect_finished(server);
+ } else if (server->connrec->unix_socket) {
+ /* connect with unix socket */
+ server_real_connect(server, NULL, server->connrec->address);
+ } else {
+ /* resolve host name */
+ if (pipe(fd) != 0) {
+ g_warning("server_connect(): pipe() failed.");
+ g_free(server->tag);
+ g_free(server->nick);
+ return FALSE;
+ }
+
+ server->connect_pipe[0] = i_io_channel_new(fd[0]);
+ server->connect_pipe[1] = i_io_channel_new(fd[1]);
+
+ connect_address = server->connrec->proxy != NULL ?
+ server->connrec->proxy : server->connrec->address;
+ server->connect_pid =
+ net_gethostbyname_nonblock(connect_address,
+ server->connect_pipe[1], 0);
+ server->connect_tag =
+ i_input_add(server->connect_pipe[0], I_INPUT_READ,
+ (GInputFunction) server_connect_callback_readpipe, server);
+
+ server->connect_time = time(NULL);
+ lookup_servers = g_slist_append(lookup_servers, server);
+
+ signal_emit("server looking", 1, server);
+ }
+ return TRUE;
+}
+
+static int server_remove_channels(SERVER_REC *server)
+{
+ GSList *tmp, *next;
+ int found;
+
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ found = FALSE;
+ for (tmp = server->channels; tmp != NULL; tmp = next) {
+ CHANNEL_REC *channel = tmp->data;
+
+ next = tmp->next;
+ channel_destroy(channel);
+ found = TRUE;
+ }
+
+ while (server->queries != NULL)
+ query_change_server(server->queries->data, NULL);
+
+ g_slist_free(server->channels);
+ g_slist_free(server->queries);
+
+ return found;
+}
+
+void server_disconnect(SERVER_REC *server)
+{
+ g_return_if_fail(IS_SERVER(server));
+
+ if (server->disconnected)
+ return;
+
+ if (server->connect_tag != -1) {
+ /* still connecting to server.. */
+ if (server->connect_pid != -1)
+ net_disconnect_nonblock(server->connect_pid);
+ server_connect_failed(server, NULL);
+ return;
+ }
+
+ servers = g_slist_remove(servers, server);
+
+ server->disconnected = TRUE;
+ signal_emit("server disconnected", 1, server);
+
+ /* we used to destroy the handle here but it may be still in
+ use during signal processing, so destroy it on unref
+ instead */
+
+ if (server->readtag > 0) {
+ g_source_remove(server->readtag);
+ server->readtag = -1;
+ }
+
+ server_unref(server);
+}
+
+void server_ref(SERVER_REC *server)
+{
+ g_return_if_fail(IS_SERVER(server));
+
+ server->refcount++;
+}
+
+int server_unref(SERVER_REC *server)
+{
+ int chans;
+
+ g_return_val_if_fail(IS_SERVER(server), FALSE);
+
+ if (--server->refcount > 0)
+ return TRUE;
+
+ if (g_slist_find(servers, server) != NULL) {
+ g_warning("Non-referenced server wasn't disconnected");
+ server_disconnect(server);
+ return TRUE;
+ }
+
+ /* close all channels */
+ chans = server_remove_channels(server);
+
+ /* since module initialisation uses server connected, only let
+ them know that the object got destroyed if the server was
+ disconnected */
+ if (server->disconnected) {
+ signal_emit("server destroyed", 1, server);
+ }
+
+ if (server->handle != NULL) {
+ if (!chans || server->connection_lost)
+ net_sendbuffer_destroy(server->handle, TRUE);
+ else {
+ /* we were on some channels, try to let the server
+ disconnect so that our quit message is guaranteed
+ to get displayed */
+ net_disconnect_later(net_sendbuffer_handle(server->handle));
+ net_sendbuffer_destroy(server->handle, FALSE);
+ }
+ server->handle = NULL;
+ }
+
+ MODULE_DATA_DEINIT(server);
+ server_connect_unref(server->connrec);
+ if (server->rawlog != NULL) rawlog_destroy(server->rawlog);
+ g_free(server->version);
+ g_free(server->away_reason);
+ g_free(server->nick);
+ g_free(server->tag);
+ g_hash_table_destroy(server->current_incoming_meta);
+
+ server->type = 0;
+ g_free(server);
+ return FALSE;
+}
+
+SERVER_REC *server_find_tag(const char *tag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(tag != NULL, NULL);
+ if (*tag == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (g_ascii_strcasecmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+SERVER_REC *server_find_lookup_tag(const char *tag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(tag != NULL, NULL);
+ if (*tag == '\0') return NULL;
+
+ for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (g_ascii_strcasecmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+SERVER_REC *server_find_chatnet(const char *chatnet)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(chatnet != NULL, NULL);
+ if (*chatnet == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (server->connrec->chatnet != NULL &&
+ g_ascii_strcasecmp(server->connrec->chatnet, chatnet) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+void server_connect_ref(SERVER_CONNECT_REC *conn)
+{
+ conn->refcount++;
+}
+
+void server_connect_unref(SERVER_CONNECT_REC *conn)
+{
+ g_return_if_fail(IS_SERVER_CONNECT(conn));
+
+ if (--conn->refcount > 0)
+ return;
+ if (conn->refcount < 0) {
+ g_warning("Connection '%s' refcount = %d",
+ conn->tag, conn->refcount);
+ }
+
+ CHAT_PROTOCOL(conn)->destroy_server_connect(conn);
+
+ if (conn->connect_handle != NULL)
+ net_disconnect(conn->connect_handle);
+
+ g_free_not_null(conn->proxy);
+ g_free_not_null(conn->proxy_string);
+ g_free_not_null(conn->proxy_string_after);
+ g_free_not_null(conn->proxy_password);
+
+ g_free_not_null(conn->tag);
+ g_free_not_null(conn->address);
+ g_free_not_null(conn->chatnet);
+
+ g_free_not_null(conn->own_ip4);
+ g_free_not_null(conn->own_ip6);
+
+ g_free_not_null(conn->password);
+ g_free_not_null(conn->nick);
+ g_free_not_null(conn->username);
+ g_free_not_null(conn->realname);
+
+ g_free_not_null(conn->tls_cert);
+ g_free_not_null(conn->tls_pkey);
+ g_free_not_null(conn->tls_pass);
+ g_free_not_null(conn->tls_cafile);
+ g_free_not_null(conn->tls_capath);
+ g_free_not_null(conn->tls_ciphers);
+ g_free_not_null(conn->tls_pinned_cert);
+ g_free_not_null(conn->tls_pinned_pubkey);
+
+ g_free_not_null(conn->channels);
+ g_free_not_null(conn->away_reason);
+
+ conn->type = 0;
+ g_free(conn);
+}
+
+void server_change_nick(SERVER_REC *server, const char *nick)
+{
+ g_free(server->nick);
+ server->nick = g_strdup(nick);
+
+ signal_emit("server nick changed", 1, server);
+}
+
+void server_meta_stash(SERVER_REC *server, const char *meta_key, const char *meta_value)
+{
+ g_hash_table_replace(server->current_incoming_meta, i_refstr_intern(meta_key),
+ g_strdup(meta_value));
+}
+
+const char *server_meta_stash_find(SERVER_REC *server, const char *meta_key)
+{
+ return g_hash_table_lookup(server->current_incoming_meta, meta_key);
+}
+
+void server_meta_clear_all(SERVER_REC *server)
+{
+ g_hash_table_remove_all(server->current_incoming_meta);
+}
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+ IPADDR *ip4, IPADDR *ip6)
+{
+ if (ip4 == NULL || ip4->family == 0)
+ g_free_and_null(conn->own_ip4);
+ if (ip6 == NULL || ip6->family == 0)
+ g_free_and_null(conn->own_ip6);
+
+ if (ip4 != NULL && ip4->family != 0) {
+ /* IPv4 address was found */
+ if (conn->own_ip4 == NULL)
+ conn->own_ip4 = g_new0(IPADDR, 1);
+ memcpy(conn->own_ip4, ip4, sizeof(IPADDR));
+ }
+
+ if (ip6 != NULL && ip6->family != 0) {
+ /* IPv6 address was found */
+ if (conn->own_ip6 == NULL)
+ conn->own_ip6 = g_new0(IPADDR, 1);
+ memcpy(conn->own_ip6, ip6, sizeof(IPADDR));
+ }
+}
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+ GHashTable *optlist,
+ SERVER_REC *defserver)
+{
+ SERVER_REC *server;
+ GList *list;
+
+ /* get all the options, then remove the known ones. there should
+ be only one left - the server tag. */
+ list = optlist_remove_known(cmd, optlist);
+ if (list == NULL)
+ return defserver;
+
+ server = server_find_tag(list->data);
+ if (server == NULL || list->next != NULL) {
+ /* unknown option (not server tag) */
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+ server == NULL ? list->data : list->next->data);
+ signal_stop();
+
+ server = NULL;
+ }
+
+ g_list_free(list);
+ return server;
+}
+
+static void disconnect_servers(GSList *servers, int chat_type)
+{
+ GSList *tmp, *next;
+
+ for (tmp = servers; tmp != NULL; tmp = next) {
+ SERVER_REC *rec = tmp->data;
+
+ next = tmp->next;
+ if (rec->chat_type == chat_type)
+ server_disconnect(rec);
+ }
+}
+
+static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto)
+{
+ disconnect_servers(servers, proto->id);
+ disconnect_servers(lookup_servers, proto->id);
+}
+
+void servers_init(void)
+{
+ settings_add_bool("server", "resolve_prefer_ipv6", FALSE);
+ lookup_servers = servers = NULL;
+
+ signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+ servers_reconnect_init();
+ servers_setup_init();
+}
+
+void servers_deinit(void)
+{
+ signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit);
+
+ servers_setup_deinit();
+ servers_reconnect_deinit();
+
+ module_uniq_destroy("SERVER");
+ module_uniq_destroy("SERVER CONNECT");
+}
diff --git a/src/core/servers.h b/src/core/servers.h
new file mode 100644
index 0000000..d52603e
--- /dev/null
+++ b/src/core/servers.h
@@ -0,0 +1,86 @@
+#ifndef IRSSI_CORE_SERVERS_H
+#define IRSSI_CORE_SERVERS_H
+
+#include <irssi/src/core/modules.h>
+
+/* Returns SERVER_REC if it's server, NULL if it isn't. */
+#define SERVER(server) \
+ MODULE_CHECK_CAST(server, SERVER_REC, type, "SERVER")
+
+/* Returns SERVER_CONNECT_REC if it's server connection, NULL if it isn't. */
+#define SERVER_CONNECT(conn) \
+ MODULE_CHECK_CAST(conn, SERVER_CONNECT_REC, type, "SERVER CONNECT")
+
+#define IS_SERVER(server) \
+ (SERVER(server) ? TRUE : FALSE)
+
+#define IS_SERVER_CONNECT(conn) \
+ (SERVER_CONNECT(conn) ? TRUE : FALSE)
+
+#define server_ischannel(server, channel) \
+ ((server)->ischannel(server, channel))
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+struct _SERVER_CONNECT_REC {
+#include <irssi/src/core/server-connect-rec.h>
+};
+
+#define STRUCT_SERVER_CONNECT_REC SERVER_CONNECT_REC
+struct _SERVER_REC {
+#include <irssi/src/core/server-rec.h>
+};
+
+#define SEND_TARGET_CHANNEL 0
+#define SEND_TARGET_NICK 1
+
+extern GSList *servers, *lookup_servers;
+
+void servers_init(void);
+void servers_deinit(void);
+
+/* Disconnect from server */
+void server_disconnect(SERVER_REC *server);
+
+void server_ref(SERVER_REC *server);
+int server_unref(SERVER_REC *server);
+
+SERVER_REC *server_find_tag(const char *tag);
+SERVER_REC *server_find_lookup_tag(const char *tag);
+SERVER_REC *server_find_chatnet(const char *chatnet);
+
+/* starts connecting to server */
+int server_start_connect(SERVER_REC *server);
+void server_connect_ref(SERVER_CONNECT_REC *conn);
+void server_connect_unref(SERVER_CONNECT_REC *conn);
+
+SERVER_REC *server_connect(SERVER_CONNECT_REC *conn);
+
+/* initializes server record but doesn't start connecting */
+void server_connect_init(SERVER_REC *server);
+/* Connection to server finished, fill the rest of the fields */
+void server_connect_finished(SERVER_REC *server);
+/* connection to server failed */
+void server_connect_failed(SERVER_REC *server, const char *msg);
+
+/* Change your nick */
+void server_change_nick(SERVER_REC *server, const char *nick);
+
+/* Push meta data onto the server stash */
+void server_meta_stash(SERVER_REC *server, const char *meta_key, const char *meta_value);
+/* Get a value from the stash */
+const char *server_meta_stash_find(SERVER_REC *server, const char *meta_key);
+/* clear meta stash */
+void server_meta_clear_all(SERVER_REC *server);
+
+/* Update own IPv4 and IPv6 records */
+void server_connect_own_ip_save(SERVER_CONNECT_REC *conn,
+ IPADDR *ip4, IPADDR *ip6);
+
+/* `optlist' should contain only one unknown key - the server tag.
+ returns NULL if there was unknown -option */
+SERVER_REC *cmd_options_get_server(const char *cmd,
+ GHashTable *optlist,
+ SERVER_REC *defserver);
+
+#endif
diff --git a/src/core/session.c b/src/core/session.c
new file mode 100644
index 0000000..3a63a78
--- /dev/null
+++ b/src/core/session.c
@@ -0,0 +1,390 @@
+/*
+ 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/commands.h>
+#include <irssi/src/core/args.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/net-sendbuffer.h>
+#include <irssi/src/core/pidwait.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/core/chat-protocols.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/servers-setup.h>
+#include <irssi/src/core/channels.h>
+#include <irssi/src/core/nicklist.h>
+
+static char *session_file;
+char *irssi_binary = NULL;
+
+static char **session_args;
+
+void session_set_binary(const char *path)
+{
+ g_free_and_null(irssi_binary);
+
+ irssi_binary = g_find_program_in_path(path);
+}
+
+void session_upgrade(void)
+{
+ if (session_args == NULL)
+ return;
+
+ execv(session_args[0], session_args);
+ fprintf(stderr, "exec failed: %s: %s\n",
+ session_args[0], g_strerror(errno));
+}
+
+/* SYNTAX: UPGRADE [<irssi binary path>] */
+static void cmd_upgrade(const char *data)
+{
+ CONFIG_REC *session;
+ char *session_file, *str, *name;
+ char *binary;
+
+ if (*data == '\0')
+ name = irssi_binary;
+ else
+ name = convert_home(data);
+
+ binary = g_find_program_in_path(name);
+ if (name != irssi_binary)
+ g_free(name);
+
+ if (binary == NULL)
+ cmd_return_error(CMDERR_PROGRAM_NOT_FOUND);
+
+ /* save the session */
+ session_file = g_strdup_printf("%s/session", get_irssi_dir());
+ session = config_open(session_file, 0600);
+ unlink(session_file);
+
+ signal_emit("session save", 1, session);
+ config_write(session, NULL, -1);
+ config_close(session);
+
+ /* data may contain some other program as well, like
+ /UPGRADE /usr/bin/screen irssi */
+ str = g_strdup_printf("%s --noconnect --session=%s --home=%s --config=%s",
+ binary, session_file, get_irssi_dir(), get_irssi_config());
+ g_free(binary);
+ g_free(session_file);
+ session_args = g_strsplit(str, " ", -1);
+ g_free(str);
+
+ signal_emit("gui exit", 0);
+}
+
+static void session_save_nick(CHANNEL_REC *channel, NICK_REC *nick,
+ CONFIG_REC *config, CONFIG_NODE *node)
+{
+ node = config_node_section(config, node, NULL, NODE_TYPE_BLOCK);
+
+ config_node_set_str(config, node, "nick", nick->nick);
+ config_node_set_bool(config, node, "op", nick->op);
+ config_node_set_bool(config, node, "halfop", nick->halfop);
+ config_node_set_bool(config, node, "voice", nick->voice);
+
+ config_node_set_str(config, node, "prefixes", nick->prefixes);
+
+ signal_emit("session save nick", 4, channel, nick, config, node);
+}
+
+static void session_save_channel_nicks(CHANNEL_REC *channel, CONFIG_REC *config,
+ CONFIG_NODE *node)
+{
+ GSList *tmp, *nicks;
+
+ node = config_node_section(config, node, "nicks", NODE_TYPE_LIST);
+ nicks = nicklist_getnicks(channel);
+ for (tmp = nicks; tmp != NULL; tmp = tmp->next)
+ session_save_nick(channel, tmp->data, config, node);
+ g_slist_free(nicks);
+}
+
+static void session_save_channel(CHANNEL_REC *channel, CONFIG_REC *config,
+ CONFIG_NODE *node)
+{
+ node = config_node_section(config, node, NULL, NODE_TYPE_BLOCK);
+
+ config_node_set_str(config, node, "name", channel->name);
+ config_node_set_str(config, node, "visible_name", channel->visible_name);
+ config_node_set_str(config, node, "topic", channel->topic);
+ config_node_set_str(config, node, "topic_by", channel->topic_by);
+ config_node_set_int(config, node, "topic_time", channel->topic_time);
+ config_node_set_str(config, node, "key", channel->key);
+
+ signal_emit("session save channel", 3, channel, config, node);
+}
+
+static void session_save_server_channels(SERVER_REC *server,
+ CONFIG_REC *config,
+ CONFIG_NODE *node)
+{
+ GSList *tmp;
+
+ /* save channels */
+ node = config_node_section(config, node, "channels", NODE_TYPE_LIST);
+ for (tmp = server->channels; tmp != NULL; tmp = tmp->next)
+ session_save_channel(tmp->data, config, node);
+}
+
+static void session_save_server(SERVER_REC *server, CONFIG_REC *config,
+ CONFIG_NODE *node)
+{
+ int handle;
+
+ node = config_node_section(config, node, NULL, NODE_TYPE_BLOCK);
+
+ config_node_set_str(config, node, "chat_type", chat_protocol_find_id(server->chat_type)->name);
+ config_node_set_str(config, node, "address", server->connrec->address);
+ config_node_set_int(config, node, "port", server->connrec->port);
+ config_node_set_str(config, node, "chatnet", server->connrec->chatnet);
+ config_node_set_str(config, node, "password", server->connrec->password);
+ config_node_set_str(config, node, "nick", server->nick);
+ config_node_set_str(config, node, "version", server->version);
+
+ config_node_set_bool(config, node, "use_tls", server->connrec->use_tls);
+ config_node_set_str(config, node, "tls_cert", server->connrec->tls_cert);
+ config_node_set_str(config, node, "tls_pkey", server->connrec->tls_pkey);
+ config_node_set_bool(config, node, "tls_verify", server->connrec->tls_verify);
+ config_node_set_str(config, node, "tls_cafile", server->connrec->tls_cafile);
+ config_node_set_str(config, node, "tls_capath", server->connrec->tls_capath);
+ config_node_set_str(config, node, "tls_ciphers", server->connrec->tls_ciphers);
+ config_node_set_str(config, node, "tls_pinned_cert", server->connrec->tls_pinned_cert);
+ config_node_set_str(config, node, "tls_pinned_pubkey", server->connrec->tls_pinned_pubkey);
+
+ handle = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle));
+ config_node_set_int(config, node, "handle", handle);
+
+ signal_emit("session save server", 3, server, config, node);
+
+ /* fake the server disconnection */
+ g_io_channel_unref(net_sendbuffer_handle(server->handle));
+ net_sendbuffer_destroy(server->handle, FALSE);
+ server->handle = NULL;
+
+ server->connection_lost = TRUE;
+ server->no_reconnect = TRUE;
+ server_disconnect(server);
+}
+
+static void session_restore_channel_nicks(CHANNEL_REC *channel,
+ CONFIG_NODE *node)
+{
+ GSList *tmp;
+
+ /* restore nicks */
+ node = config_node_section(NULL, node, "nicks", -1);
+ if (node != NULL && node->type == NODE_TYPE_LIST) {
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ signal_emit("session restore nick", 2,
+ channel, tmp->data);
+ }
+ }
+}
+
+static void session_restore_channel(SERVER_REC *server, CONFIG_NODE *node)
+{
+ CHANNEL_REC *channel;
+ const char *name, *visible_name;
+
+ name = config_node_get_str(node, "name", NULL);
+ if (name == NULL)
+ return;
+
+ visible_name = config_node_get_str(node, "visible_name", NULL);
+ channel = CHAT_PROTOCOL(server)->channel_create(server, name, visible_name, TRUE);
+ channel->topic = g_strdup(config_node_get_str(node, "topic", NULL));
+ channel->topic_by = g_strdup(config_node_get_str(node, "topic_by", NULL));
+ channel->topic_time = config_node_get_int(node, "topic_time", 0);
+ channel->key = g_strdup(config_node_get_str(node, "key", NULL));
+ channel->session_rejoin = TRUE;
+
+ signal_emit("session restore channel", 2, channel, node);
+}
+
+static void session_restore_server_channels(SERVER_REC *server,
+ CONFIG_NODE *node)
+{
+ GSList *tmp;
+
+ /* restore channels */
+ node = config_node_section(NULL, node, "channels", -1);
+ if (node != NULL && node->type == NODE_TYPE_LIST) {
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp))
+ session_restore_channel(server, tmp->data);
+ }
+}
+
+static void session_restore_server(CONFIG_NODE *node)
+{
+ CHAT_PROTOCOL_REC *proto;
+ SERVER_CONNECT_REC *conn;
+ SERVER_REC *server;
+ const char *chat_type, *address, *chatnet, *password, *nick;
+ int port, handle;
+
+ chat_type = config_node_get_str(node, "chat_type", NULL);
+ address = config_node_get_str(node, "address", NULL);
+ port = config_node_get_int(node, "port", 0);
+ chatnet = config_node_get_str(node, "chatnet", NULL);
+ password = config_node_get_str(node, "password", NULL);
+ nick = config_node_get_str(node, "nick", NULL);
+ handle = config_node_get_int(node, "handle", -1);
+
+ if (chat_type == NULL || address == NULL || nick == NULL || handle < 0)
+ return;
+
+ proto = chat_protocol_find(chat_type);
+ if (proto == NULL || proto->not_initialized) {
+ if (handle >= 0)
+ close(handle);
+ return;
+ }
+
+ conn = server_create_conn(proto->id, address, port,
+ chatnet, password, nick);
+ if (conn == NULL)
+ return;
+
+ conn->use_tls = config_node_get_bool(node, "use_tls", FALSE);
+ conn->tls_cert = g_strdup(config_node_get_str(node, "tls_cert", NULL));
+ conn->tls_pkey = g_strdup(config_node_get_str(node, "tls_pkey", NULL));
+ conn->tls_verify = config_node_get_bool(node, "tls_verify", TRUE);
+ conn->tls_cafile = g_strdup(config_node_get_str(node, "tls_cafile", NULL));
+ conn->tls_capath = g_strdup(config_node_get_str(node, "tls_capath", NULL));
+ conn->tls_ciphers = g_strdup(config_node_get_str(node, "tls_ciphers", NULL));
+ conn->tls_pinned_cert = g_strdup(config_node_get_str(node, "tls_pinned_cert", NULL));
+ conn->tls_pinned_pubkey = g_strdup(config_node_get_str(node, "tls_pinned_pubkey", NULL));
+
+ conn->reconnection = TRUE;
+ conn->connect_handle = i_io_channel_new(handle);
+
+ server = proto->server_init_connect(conn);
+ server->version = g_strdup(config_node_get_str(node, "version", NULL));
+ server->session_reconnect = TRUE;
+ signal_emit("session restore server", 2, server, node);
+
+ proto->server_connect(server);
+}
+
+static void sig_session_save(CONFIG_REC *config)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+ GString *str;
+
+ /* save servers */
+ node = config_node_traverse(config, "(servers", TRUE);
+ while (servers != NULL)
+ session_save_server(servers->data, config, node);
+
+ /* save pids */
+ str = g_string_new(NULL);
+ for (tmp = pidwait_get_pids(); tmp != NULL; tmp = tmp->next)
+ g_string_append_printf(str, "%d ", GPOINTER_TO_INT(tmp->data));
+ config_node_set_str(config, config->mainnode, "pids", str->str);
+ g_string_free(str, TRUE);
+}
+
+static void sig_session_restore(CONFIG_REC *config)
+{
+ CONFIG_NODE *node;
+ GSList *tmp;
+ char **pids, **pid;
+
+ /* restore servers */
+ node = config_node_traverse(config, "(servers", FALSE);
+ if (node != NULL) {
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp))
+ session_restore_server(tmp->data);
+ }
+
+ /* restore pids (so we don't leave zombies) */
+ pids = g_strsplit(config_node_get_str(config->mainnode, "pids", ""), " ", -1);
+ for (pid = pids; *pid != NULL; pid++)
+ pidwait_add(atoi(*pid));
+ g_strfreev(pids);
+}
+
+static void sig_init_finished(void)
+{
+ CONFIG_REC *session;
+
+ if (session_file == NULL)
+ return;
+
+ session = config_open(session_file, -1);
+ if (session == NULL)
+ return;
+
+ config_parse(session);
+ signal_emit("session restore", 1, session);
+ config_close(session);
+
+ unlink(session_file);
+}
+
+void session_register_options(void)
+{
+ static GOptionEntry options[] = {
+ { "session", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &session_file, "Used by /UPGRADE command", "PATH" },
+ { NULL }
+ };
+
+ session_file = NULL;
+ args_register(options);
+}
+
+void session_init(void)
+{
+ command_bind("upgrade", NULL, (SIGNAL_FUNC) cmd_upgrade);
+
+ signal_add("session save", (SIGNAL_FUNC) sig_session_save);
+ signal_add("session restore", (SIGNAL_FUNC) sig_session_restore);
+ signal_add("session save server", (SIGNAL_FUNC) session_save_server_channels);
+ signal_add("session restore server", (SIGNAL_FUNC) session_restore_server_channels);
+ signal_add("session save channel", (SIGNAL_FUNC) session_save_channel_nicks);
+ signal_add("session restore channel", (SIGNAL_FUNC) session_restore_channel_nicks);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+}
+
+void session_deinit(void)
+{
+ g_free_not_null(irssi_binary);
+
+ command_unbind("upgrade", (SIGNAL_FUNC) cmd_upgrade);
+
+ signal_remove("session save", (SIGNAL_FUNC) sig_session_save);
+ signal_remove("session restore", (SIGNAL_FUNC) sig_session_restore);
+ signal_remove("session save server", (SIGNAL_FUNC) session_save_server_channels);
+ signal_remove("session restore server", (SIGNAL_FUNC) session_restore_server_channels);
+ signal_remove("session save channel", (SIGNAL_FUNC) session_save_channel_nicks);
+ signal_remove("session restore channel", (SIGNAL_FUNC) session_restore_channel_nicks);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+}
diff --git a/src/core/session.h b/src/core/session.h
new file mode 100644
index 0000000..28c5cec
--- /dev/null
+++ b/src/core/session.h
@@ -0,0 +1,13 @@
+#ifndef IRSSI_CORE_SESSION_H
+#define IRSSI_CORE_SESSION_H
+
+extern char *irssi_binary;
+
+void session_set_binary(const char *path);
+void session_upgrade(void);
+
+void session_register_options(void);
+void session_init(void);
+void session_deinit(void);
+
+#endif
diff --git a/src/core/settings.c b/src/core/settings.c
new file mode 100644
index 0000000..1e7ef2e
--- /dev/null
+++ b/src/core/settings.c
@@ -0,0 +1,934 @@
+/*
+ settings.c : Irssi settings
+
+ 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/levels.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/recode.h>
+#include <irssi/src/core/settings.h>
+#include "default-config.h"
+
+#include <signal.h>
+
+#define SETTINGS_AUTOSAVE_TIMEOUT (1000*60*60) /* 1 hour */
+
+CONFIG_REC *mainconfig;
+
+static GString *last_errors;
+static GSList *last_invalid_modules;
+static int fe_initialized;
+static int config_changed; /* FIXME: remove after .98 (unless needed again) */
+static unsigned int user_settings_changed;
+
+static GHashTable *settings;
+static int timeout_tag;
+
+static int config_last_modifycounter;
+static time_t config_last_mtime;
+static long config_last_size;
+static unsigned int config_last_checksum;
+
+static SETTINGS_REC *settings_get(const char *key, SettingType type)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) {
+ g_warning("settings_get(%s) : not found", key);
+ return NULL;
+ }
+ if (type != SETTING_TYPE_ANY && rec->type != type) {
+ g_warning("settings_get(%s) : invalid type", key);
+ return NULL;
+ }
+
+ return rec;
+}
+
+static const char *
+settings_get_str_type(const char *key, SettingType type)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+
+ rec = settings_get(key, type);
+ if (rec == NULL) return NULL;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ node = node == NULL ? NULL : iconfig_node_section(node, rec->module, -1);
+
+ return node == NULL ? rec->default_value.v_string :
+ config_node_get_str(node, key, rec->default_value.v_string);
+}
+
+const char *settings_get_str(const char *key)
+{
+ return settings_get_str_type(key, SETTING_TYPE_ANY);
+}
+
+int settings_get_int(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+
+ rec = settings_get(key, SETTING_TYPE_INT);
+ if (rec == NULL) return 0;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ node = node == NULL ? NULL : iconfig_node_section(node, rec->module, -1);
+
+ return node == NULL ? rec->default_value.v_int :
+ config_node_get_int(node, key, rec->default_value.v_int);
+}
+
+int settings_get_bool(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+
+ rec = settings_get(key, SETTING_TYPE_BOOLEAN);
+ if (rec == NULL) return FALSE;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ node = node == NULL ? NULL : iconfig_node_section(node, rec->module, -1);
+
+ return node == NULL ? rec->default_value.v_bool :
+ config_node_get_bool(node, key, rec->default_value.v_bool);
+}
+
+int settings_get_time(const char *key)
+{
+ const char *str;
+ int msecs = 0;
+
+ str = settings_get_str_type(key, SETTING_TYPE_TIME);
+ if (str != NULL && !parse_time_interval(str, &msecs))
+ g_warning("settings_get_time(%s) : Invalid time '%s'", key, str);
+ return str == NULL ? 0 : msecs;
+}
+
+int settings_get_level(const char *key)
+{
+ const char *str;
+
+ str = settings_get_str_type(key, SETTING_TYPE_LEVEL);
+ return str == NULL ? 0 : level2bits(str, NULL);
+}
+
+int settings_get_level_negative(const char *key)
+{
+ const char *str, *tmp, *all_levels;
+ int levels;
+
+ str = settings_get_str_type(key, SETTING_TYPE_LEVEL);
+ if (str == NULL)
+ return 0;
+
+ all_levels = bits2level(~0);
+ tmp = g_strdup_printf("%s %s", all_levels, str);
+ levels = level2bits(tmp, NULL) ^ level2bits(all_levels, NULL);
+ g_free((char *) tmp);
+ g_free((char *) all_levels);
+ return levels;
+}
+
+int settings_get_size(const char *key)
+{
+ const char *str;
+ int bytes = 0;
+
+ str = settings_get_str_type(key, SETTING_TYPE_SIZE);
+ if (str != NULL && !parse_size(str, &bytes))
+ g_warning("settings_get_size(%s) : Invalid size '%s'", key, str);
+ return str == NULL ? 0 : bytes;
+}
+
+int settings_get_choice(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+ char *str;
+ int index;
+
+ rec = settings_get(key, SETTING_TYPE_CHOICE);
+ if (rec == NULL) return -1;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ node = node == NULL ? NULL : iconfig_node_section(node, rec->module, -1);
+
+ str = node == NULL ? rec->default_value.v_string :
+ config_node_get_str(node, key, rec->default_value.v_string);
+
+ if (str == NULL || (index = strarray_find(rec->choices, str)) < 0)
+ return rec->default_value.v_int;
+
+ return index;
+}
+
+char *settings_get_print(SETTINGS_REC *rec)
+{
+ char *value = NULL;
+
+ switch(rec->type) {
+ case SETTING_TYPE_CHOICE:
+ value = g_strdup(rec->choices[settings_get_choice(rec->key)]);
+ break;
+ case SETTING_TYPE_BOOLEAN:
+ value = g_strdup(settings_get_bool(rec->key) ? "ON" : "OFF");
+ break;
+ case SETTING_TYPE_INT:
+ value = g_strdup_printf("%d", settings_get_int(rec->key));
+ break;
+ case SETTING_TYPE_STRING:
+ case SETTING_TYPE_TIME:
+ case SETTING_TYPE_LEVEL:
+ case SETTING_TYPE_SIZE:
+ case SETTING_TYPE_ANY:
+ value = g_strdup(settings_get_str(rec->key));
+ break;
+ }
+ return value;
+}
+
+static void settings_add(const char *module, const char *section,
+ const char *key, SettingType type,
+ const SettingValue *default_value,
+ const char *choices)
+{
+ SETTINGS_REC *rec;
+ char **choices_vec = NULL;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ if (type == SETTING_TYPE_CHOICE) {
+ if (choices == NULL) {
+ g_warning("Trying to add setting '%s' with no choices.", key);
+ return;
+ }
+
+ choices_vec = g_strsplit(choices, ";", -1);
+
+ /* validate the default value */
+ if (default_value->v_int < 0 || default_value->v_int >= g_strv_length(choices_vec)) {
+ g_warning("Trying to add setting '%s' with an invalid default value.", key);
+ g_strfreev(choices_vec);
+ return;
+ }
+ }
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec != NULL) {
+ /* Already exists, make sure it's correct type */
+ if (rec->type != type) {
+ g_warning("Trying to add already existing "
+ "setting '%s' with different type.", key);
+ g_strfreev(choices_vec);
+ return;
+ }
+ rec->refcount++;
+ } else {
+ rec = g_new(SETTINGS_REC, 1);
+ rec->refcount = 1;
+ rec->module = g_strdup(module);
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->type = type;
+
+ rec->default_value = *default_value;
+ rec->choices = choices_vec;
+ g_hash_table_insert(settings, rec->key, rec);
+ }
+}
+
+void settings_add_str_module(const char *module, const char *section,
+ const char *key, const char *def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_string = g_strdup(def);
+ settings_add(module, section, key, SETTING_TYPE_STRING, &default_value, NULL);
+}
+
+void settings_add_choice_module(const char *module, const char *section,
+ const char *key, int def, const char *choices)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_int = def;
+ settings_add(module, section, key, SETTING_TYPE_CHOICE, &default_value, choices);
+}
+
+void settings_add_int_module(const char *module, const char *section,
+ const char *key, int def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_int = def;
+ settings_add(module, section, key, SETTING_TYPE_INT, &default_value, NULL);
+}
+
+void settings_add_bool_module(const char *module, const char *section,
+ const char *key, int def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_bool = def;
+ settings_add(module, section, key, SETTING_TYPE_BOOLEAN, &default_value, NULL);
+}
+
+void settings_add_time_module(const char *module, const char *section,
+ const char *key, const char *def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_string = g_strdup(def);
+ settings_add(module, section, key, SETTING_TYPE_TIME, &default_value, NULL);
+}
+
+void settings_add_level_module(const char *module, const char *section,
+ const char *key, const char *def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_string = g_strdup(def);
+ settings_add(module, section, key, SETTING_TYPE_LEVEL, &default_value, NULL);
+}
+
+void settings_add_size_module(const char *module, const char *section,
+ const char *key, const char *def)
+{
+ SettingValue default_value;
+
+ memset(&default_value, 0, sizeof(default_value));
+ default_value.v_string = g_strdup(def);
+ settings_add(module, section, key, SETTING_TYPE_SIZE, &default_value, NULL);
+}
+
+static void settings_destroy(SETTINGS_REC *rec)
+{
+ if (rec->type != SETTING_TYPE_INT &&
+ rec->type != SETTING_TYPE_BOOLEAN &&
+ rec->type != SETTING_TYPE_CHOICE)
+ g_free(rec->default_value.v_string);
+ g_strfreev(rec->choices);
+ g_free(rec->module);
+ g_free(rec->section);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+static void settings_unref(SETTINGS_REC *rec, int remove_hash)
+{
+ if (--rec->refcount == 0) {
+ if (remove_hash)
+ g_hash_table_remove(settings, rec->key);
+ settings_destroy(rec);
+ }
+}
+
+void settings_remove(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec != NULL)
+ settings_unref(rec, TRUE);
+}
+
+static int settings_remove_hash(const char *key, SETTINGS_REC *rec,
+ const char *module)
+{
+ if (g_strcmp0(rec->module, module) == 0) {
+ settings_unref(rec, FALSE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void settings_remove_module(const char *module)
+{
+ g_hash_table_foreach_remove(settings,
+ (GHRFunc) settings_remove_hash,
+ (void *) module);
+}
+
+static CONFIG_NODE *settings_get_node(const char *key)
+{
+ SETTINGS_REC *rec;
+ CONFIG_NODE *node;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) {
+ g_warning("Changing unknown setting '%s'", key);
+ return NULL;
+ }
+
+ node = iconfig_node_traverse("settings", TRUE);
+ return iconfig_node_section(node, rec->module, NODE_TYPE_BLOCK);
+}
+
+gboolean settings_set_choice(const char *key, const char *value)
+{
+ SETTINGS_REC *rec;
+
+ rec = settings_get_record(key);
+
+ if (rec != NULL && strarray_find(rec->choices, value) < 0)
+ return FALSE;
+
+ settings_set_str(key, value);
+
+ return TRUE;
+}
+
+void settings_set_str(const char *key, const char *value)
+{
+ iconfig_node_set_str(settings_get_node(key), key, value);
+}
+
+void settings_set_int(const char *key, int value)
+{
+ iconfig_node_set_int(settings_get_node(key), key, value);
+}
+
+void settings_set_bool(const char *key, int value)
+{
+ iconfig_node_set_bool(settings_get_node(key), key, value);
+}
+
+gboolean settings_set_time(const char *key, const char *value)
+{
+ int msecs;
+
+ if (!parse_time_interval(value, &msecs))
+ return FALSE;
+
+ iconfig_node_set_str(settings_get_node(key), key, value);
+ return TRUE;
+}
+
+gboolean settings_set_level(const char *key, const char *value)
+{
+ int iserror;
+
+ (void)level2bits(value, &iserror);
+ if (iserror)
+ return FALSE;
+
+ iconfig_node_set_str(settings_get_node(key), key, value);
+ return TRUE;
+}
+
+gboolean settings_set_size(const char *key, const char *value)
+{
+ int size;
+
+ if (!parse_size(value, &size))
+ return FALSE;
+
+ iconfig_node_set_str(settings_get_node(key), key, value);
+ return TRUE;
+}
+
+SettingType settings_get_type(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, SETTING_TYPE_ANY);
+
+ rec = g_hash_table_lookup(settings, key);
+ return rec == NULL ? SETTING_TYPE_ANY : rec->type;
+}
+
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key)
+{
+ g_return_val_if_fail(key != NULL, NULL);
+
+ return g_hash_table_lookup(settings, key);
+}
+
+static void sig_init_userinfo_changed(gpointer changedp)
+{
+ user_settings_changed |= GPOINTER_TO_UINT(changedp);
+}
+
+static void sig_init_finished(void)
+{
+ fe_initialized = TRUE;
+ if (last_errors != NULL) {
+ signal_emit("settings errors", 1, last_errors->str);
+ g_string_free(last_errors, TRUE);
+ }
+
+ if (config_changed) {
+ /* some backwards compatibility changes were made to
+ config file, reload it */
+ g_warning("Some settings were automatically "
+ "updated, please /SAVE");
+ signal_emit("setup changed", 0);
+ }
+
+ signal_emit("settings userinfo changed", 1, GUINT_TO_POINTER(user_settings_changed));
+}
+
+static void settings_clean_invalid_module(const char *module)
+{
+ CONFIG_NODE *node;
+ SETTINGS_REC *set;
+ GSList *tmp, *next;
+
+ node = iconfig_node_traverse("settings", FALSE);
+ if (node == NULL) return;
+
+ node = iconfig_node_section(node, module, -1);
+ if (node == NULL) return;
+
+ for (tmp = config_node_first(node->value); tmp != NULL; tmp = next) {
+ CONFIG_NODE *subnode = tmp->data;
+ next = config_node_next(tmp);
+
+ set = g_hash_table_lookup(settings, subnode->key);
+ if (set == NULL || g_strcmp0(set->module, module) != 0)
+ iconfig_node_remove(node, subnode);
+ }
+}
+
+/* remove all invalid settings from config file. works only with the
+ modules that have already called settings_check() */
+void settings_clean_invalid(void)
+{
+ while (last_invalid_modules != NULL) {
+ char *module = last_invalid_modules->data;
+
+ settings_clean_invalid_module(module);
+
+ last_invalid_modules =
+ g_slist_remove(last_invalid_modules, module);
+ g_free(module);
+ }
+}
+
+static int backwards_compatibility(const char *module, CONFIG_NODE *node,
+ CONFIG_NODE *parent)
+{
+ const char *new_key, *new_module;
+ CONFIG_NODE *new_node;
+ char *new_value;
+
+ new_value = NULL; new_key = NULL; new_module = NULL;
+
+ /* fe-text term_type -> fe-common/core term_charset - for 0.8.10-> */
+ if (g_strcmp0(module, "fe-text") == 0) {
+ if (g_ascii_strcasecmp(node->key, "term_type") == 0 ||
+ /* kludge for cvs-version where term_charset was in fe-text */
+ g_ascii_strcasecmp(node->key, "term_charset") == 0) {
+ new_module = "fe-common/core";
+ new_key = "term_charset";
+ new_value = !is_valid_charset(node->value) ? NULL :
+ g_strdup(node->value);
+ new_node = iconfig_node_traverse("settings", FALSE);
+ new_node = new_node == NULL ? NULL :
+ iconfig_node_section(new_node, new_module, -1);
+
+ config_node_set_str(mainconfig, new_node,
+ new_key, new_value);
+ /* remove old */
+ config_node_set_str(mainconfig, parent,
+ node->key, NULL);
+ g_free(new_value);
+ config_changed = TRUE;
+ return new_key != NULL;
+ }
+ if (g_ascii_strcasecmp(node->key, "actlist_moves") == 0 &&
+ node->value != NULL && g_ascii_strcasecmp(node->value, "yes") == 0) {
+ config_node_set_str(mainconfig, parent, "actlist_sort", "recent");
+ config_node_set_str(mainconfig, parent, node->key, NULL);
+ config_changed = TRUE;
+ return TRUE;
+ }
+ }
+ if (g_strcmp0(module, "core") == 0 &&
+ g_strcmp0(node->key, "resolve_reverse_lookup") == 0) {
+ config_node_set_str(mainconfig, parent, node->key, NULL);
+ config_changed = TRUE;
+ return TRUE;
+ }
+ return new_key != NULL;
+}
+
+/* verify that all settings in config file for `module' are actually found
+ from /SET list */
+void settings_check_module(const char *module)
+{
+ SETTINGS_REC *set;
+ CONFIG_NODE *node, *parent;
+ GString *errors;
+ GSList *tmp, *next;
+ int count;
+
+ g_return_if_fail(module != NULL);
+
+ node = iconfig_node_traverse("settings", FALSE);
+ node = node == NULL ? NULL : iconfig_node_section(node, module, -1);
+ if (node == NULL) return;
+
+ errors = g_string_new(NULL);
+ g_string_printf(errors, "Unknown settings in configuration "
+ "file for module %s:", module);
+
+ count = 0;
+ parent = node;
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = next) {
+ node = tmp->data;
+ next = config_node_next(tmp);
+ if (node->key == NULL) continue;
+
+ set = g_hash_table_lookup(settings, node->key);
+ if (backwards_compatibility(module, node, parent))
+ continue;
+
+ if (set == NULL || g_strcmp0(set->module, module) != 0) {
+ g_string_append_printf(errors, " %s", node->key);
+ count++;
+ }
+ }
+ if (count > 0) {
+ if (i_slist_find_icase_string(last_invalid_modules, module) == NULL) {
+ /* mark this module having invalid settings */
+ last_invalid_modules =
+ g_slist_append(last_invalid_modules,
+ g_strdup(module));
+ }
+ if (fe_initialized)
+ signal_emit("settings errors", 1, errors->str);
+ else {
+ if (last_errors == NULL)
+ last_errors = g_string_new(NULL);
+ else
+ g_string_append_c(last_errors, '\n');
+ g_string_append(last_errors, errors->str);
+ }
+ }
+ g_string_free(errors, TRUE);
+}
+
+static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2)
+{
+ int cmp = g_strcmp0(v1->section, v2->section);
+ if (!cmp)
+ cmp = g_strcmp0(v1->key, v2->key);
+ return cmp;
+}
+
+static void settings_hash_get(const char *key, SETTINGS_REC *rec,
+ GSList **list)
+{
+ *list = g_slist_insert_sorted(*list, rec,
+ (GCompareFunc) settings_compare);
+}
+
+GSList *settings_get_sorted(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list);
+ return list;
+}
+
+void sig_term(int n)
+{
+ /* if we get SIGTERM after this, just die instead of coming back here. */
+ signal(SIGTERM, SIG_DFL);
+
+ /* quit from all servers too.. */
+ signal_emit("command quit", 1, "");
+
+ /* and die */
+ raise(SIGTERM);
+}
+
+/* Yes, this is my own stupid checksum generator, some "real" algorithm
+ would be nice but would just take more space without much real benefit */
+static unsigned int file_checksum(const char *fname)
+{
+ char buf[512];
+ int f, ret, n;
+ unsigned int checksum = 0;
+
+ f = open(fname, O_RDONLY);
+ if (f == -1) return 0;
+
+ n = 0;
+ while ((ret = read(f, buf, sizeof(buf))) > 0) {
+ while (ret-- > 0)
+ checksum += buf[ret] << ((n++ & 3)*8);
+ }
+ close(f);
+ return checksum;
+}
+
+static void irssi_config_save_state(const char *fname)
+{
+ struct stat statbuf;
+
+ g_return_if_fail(fname != NULL);
+
+ if (stat(fname, &statbuf) != 0)
+ return;
+
+ /* save modify time, file size and checksum */
+ config_last_mtime = statbuf.st_mtime;
+ config_last_size = statbuf.st_size;
+ config_last_checksum = file_checksum(fname);
+}
+
+int irssi_config_is_changed(const char *fname)
+{
+ struct stat statbuf;
+
+ if (fname == NULL)
+ fname = mainconfig->fname;
+
+ if (stat(fname, &statbuf) != 0)
+ return FALSE;
+
+ return config_last_mtime != statbuf.st_mtime &&
+ (config_last_size != statbuf.st_size ||
+ config_last_checksum != file_checksum(fname));
+}
+
+static CONFIG_REC *parse_configfile(const char *fname)
+{
+ CONFIG_REC *config;
+ struct stat statbuf;
+ const char *path;
+ char *str;
+
+ if (fname == NULL)
+ fname = get_irssi_config();
+
+ if (stat(fname, &statbuf) == 0)
+ path = fname;
+ else {
+ /* user configuration file not found, use the default one
+ from sysconfdir */
+ path = SYSCONFDIR"/"IRSSI_GLOBAL_CONFIG;
+ if (stat(path, &statbuf) != 0) {
+ /* no configuration file in sysconfdir ..
+ use the build-in configuration */
+ path = NULL;
+ }
+ }
+
+ config = config_open(path, -1);
+ if (config == NULL) {
+ str = g_strdup_printf("Error opening configuration file %s: %s",
+ path, g_strerror(errno));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+
+ config = config_open(NULL, -1);
+ }
+
+ if (config->fname != NULL)
+ config_parse(config);
+ else
+ config_parse_data(config, default_config, "internal");
+
+ config_change_file_name(config, fname, 0660);
+ irssi_config_save_state(fname);
+ return config;
+}
+
+static void init_configfile(void)
+{
+ struct stat statbuf;
+ char *str;
+
+ if (stat(get_irssi_dir(), &statbuf) != 0) {
+ /* ~/.irssi not found, create it. */
+ if (g_mkdir_with_parents(get_irssi_dir(), 0700) != 0) {
+ g_error("Couldn't create %s directory: %s",
+ get_irssi_dir(), g_strerror(errno));
+ }
+ } else if (!S_ISDIR(statbuf.st_mode)) {
+ g_error("%s is not a directory.\n"
+ "You should remove it with command: rm %s",
+ get_irssi_dir(), get_irssi_dir());
+ }
+
+ mainconfig = parse_configfile(NULL);
+ config_last_modifycounter = mainconfig->modifycounter;
+
+ /* any errors? */
+ if (config_last_error(mainconfig) != NULL) {
+ str = g_strdup_printf("Ignored errors in configuration file:\n%s",
+ config_last_error(mainconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+ }
+
+ signal(SIGTERM, sig_term);
+}
+
+int settings_reread(const char *fname)
+{
+ CONFIG_REC *tempconfig;
+ char *str;
+
+ str = fname == NULL ? NULL : convert_home(fname);
+ tempconfig = parse_configfile(str);
+ g_free_not_null(str);
+
+ if (tempconfig == NULL) {
+ signal_emit("gui dialog", 2, "error", g_strerror(errno));
+ return FALSE;
+ }
+
+ if (config_last_error(tempconfig) != NULL) {
+ str = g_strdup_printf("Errors in configuration file:\n%s",
+ config_last_error(tempconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+
+ config_close(tempconfig);
+ return FALSE;
+ }
+
+ config_close(mainconfig);
+ mainconfig = tempconfig;
+ config_last_modifycounter = mainconfig->modifycounter;
+
+ signal_emit("setup changed", 0);
+ signal_emit("setup reread", 1, mainconfig->fname);
+ return TRUE;
+}
+
+int settings_save(const char *fname, int autosave)
+{
+ char *str;
+ int error;
+
+ if (fname == NULL)
+ fname = mainconfig->fname;
+
+ error = config_write(mainconfig, fname, 0660) != 0;
+ irssi_config_save_state(fname);
+ config_last_modifycounter = mainconfig->modifycounter;
+ if (error) {
+ str = g_strdup_printf("Couldn't save configuration file: %s",
+ config_last_error(mainconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+ }
+ signal_emit("setup saved", 2, fname, GINT_TO_POINTER(autosave));
+ return !error;
+}
+
+static int sig_autosave(void)
+{
+ char *fname, *str;
+
+ if (!settings_get_bool("settings_autosave") ||
+ config_last_modifycounter == mainconfig->modifycounter)
+ return 1;
+
+ if (!irssi_config_is_changed(NULL))
+ settings_save(NULL, TRUE);
+ else {
+ fname = g_strconcat(mainconfig->fname, ".autosave", NULL);
+ str = g_strdup_printf("Configuration file was modified "
+ "while irssi was running. Saving "
+ "configuration to file '%s' instead. "
+ "Use /SAVE or /RELOAD to get rid of "
+ "this message.", fname);
+ signal_emit("gui dialog", 2, "warning", str);
+ g_free(str);
+
+ settings_save(fname, TRUE);
+ g_free(fname);
+ }
+
+ return 1;
+}
+
+void settings_init(void)
+{
+ settings = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal);
+
+ last_errors = NULL;
+ last_invalid_modules = NULL;
+ fe_initialized = FALSE;
+ config_changed = FALSE;
+
+ config_last_mtime = 0;
+ config_last_modifycounter = 0;
+ init_configfile();
+
+ settings_add_bool("misc", "settings_autosave", TRUE);
+ timeout_tag = g_timeout_add(SETTINGS_AUTOSAVE_TIMEOUT,
+ (GSourceFunc) sig_autosave, NULL);
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+ signal_add("irssi init userinfo changed", (SIGNAL_FUNC) sig_init_userinfo_changed);
+ signal_add("gui exit", (SIGNAL_FUNC) sig_autosave);
+}
+
+static void settings_hash_free(const char *key, SETTINGS_REC *rec)
+{
+ settings_destroy(rec);
+}
+
+void settings_deinit(void)
+{
+ g_source_remove(timeout_tag);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
+ signal_remove("irssi init userinfo changed", (SIGNAL_FUNC) sig_init_userinfo_changed);
+ signal_remove("gui exit", (SIGNAL_FUNC) sig_autosave);
+
+ g_slist_foreach(last_invalid_modules, (GFunc) g_free, NULL);
+ g_slist_free(last_invalid_modules);
+
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL);
+ g_hash_table_destroy(settings);
+ settings = NULL;
+
+ if (mainconfig != NULL) config_close(mainconfig);
+}
diff --git a/src/core/settings.h b/src/core/settings.h
new file mode 100644
index 0000000..9c78b3d
--- /dev/null
+++ b/src/core/settings.h
@@ -0,0 +1,139 @@
+#ifndef IRSSI_CORE_SETTINGS_H
+#define IRSSI_CORE_SETTINGS_H
+
+typedef enum {
+ SETTING_TYPE_STRING,
+ SETTING_TYPE_INT,
+ SETTING_TYPE_BOOLEAN,
+ SETTING_TYPE_TIME,
+ SETTING_TYPE_LEVEL,
+ SETTING_TYPE_SIZE,
+ SETTING_TYPE_CHOICE,
+ SETTING_TYPE_ANY
+} SettingType;
+
+typedef struct {
+ char *v_string;
+ int v_int;
+ unsigned int v_bool:1;
+} SettingValue;
+
+typedef struct {
+ int refcount;
+
+ char *module;
+ char *key;
+ char *section;
+
+ SettingType type;
+ SettingValue default_value;
+ char **choices;
+} SETTINGS_REC;
+
+enum {
+ USER_SETTINGS_REAL_NAME = 0x1,
+ USER_SETTINGS_USER_NAME = 0x2,
+ USER_SETTINGS_NICK = 0x4,
+ USER_SETTINGS_HOSTNAME = 0x8,
+};
+
+/* macros for handling the default Irssi configuration */
+#define iconfig_get_str(a, b, c) config_get_str(mainconfig, a, b, c)
+#define iconfig_get_int(a, b, c) config_get_int(mainconfig, a, b, c)
+#define iconfig_get_bool(a, b, c) config_get_bool(mainconfig, a, b, c)
+
+#define iconfig_set_str(a, b, c) config_set_str(mainconfig, a, b, c)
+#define iconfig_set_int(a, b, c) config_set_int(mainconfig, a, b, c)
+#define iconfig_set_bool(a, b, c) config_set_bool(mainconfig, a, b, c)
+
+#define iconfig_node_section(a, b, c) config_node_section(mainconfig, a, b, c)
+#define iconfig_node_section_index(a, b, c, d) config_node_section_index(mainconfig, a, b, c, d)
+#define iconfig_node_traverse(a, b) config_node_traverse(mainconfig, a, b)
+#define iconfig_node_set_str(a, b, c) config_node_set_str(mainconfig, a, b, c)
+#define iconfig_node_set_int(a, b, c) config_node_set_int(mainconfig, a, b, c)
+#define iconfig_node_set_bool(a, b, c) config_node_set_bool(mainconfig, a, b, c)
+#define iconfig_node_list_remove(a, b) config_node_list_remove(mainconfig, a, b)
+#define iconfig_node_remove(a, b) config_node_remove(mainconfig, a, b)
+#define iconfig_node_clear(a) config_node_clear(mainconfig, a)
+#define iconfig_node_add_list(a, b) config_node_add_list(mainconfig, a, b)
+
+extern struct _CONFIG_REC *mainconfig;
+extern const char *default_config;
+
+/* Functions for handling the "settings" node of Irssi configuration */
+const char *settings_get_str(const char *key);
+int settings_get_int(const char *key);
+int settings_get_bool(const char *key);
+int settings_get_time(const char *key); /* as milliseconds */
+int settings_get_level(const char *key);
+int settings_get_level_negative(const char *key);
+int settings_get_size(const char *key); /* as bytes */
+int settings_get_choice(const char *key);
+char *settings_get_print(SETTINGS_REC *rec);
+
+/* Functions to add/remove settings */
+void settings_add_str_module(const char *module, const char *section,
+ const char *key, const char *def);
+void settings_add_int_module(const char *module, const char *section,
+ const char *key, int def);
+void settings_add_bool_module(const char *module, const char *section,
+ const char *key, int def);
+void settings_add_time_module(const char *module, const char *section,
+ const char *key, const char *def);
+void settings_add_level_module(const char *module, const char *section,
+ const char *key, const char *def);
+void settings_add_size_module(const char *module, const char *section,
+ const char *key, const char *def);
+void settings_add_choice_module(const char *module, const char *section,
+ const char *key, int def, const char *choices);
+void settings_remove(const char *key);
+void settings_remove_module(const char *module);
+
+#define settings_add_str(section, key, def) \
+ settings_add_str_module(MODULE_NAME, section, key, def)
+#define settings_add_int(section, key, def) \
+ settings_add_int_module(MODULE_NAME, section, key, def)
+#define settings_add_bool(section, key, def) \
+ settings_add_bool_module(MODULE_NAME, section, key, def)
+#define settings_add_time(section, key, def) \
+ settings_add_time_module(MODULE_NAME, section, key, def)
+#define settings_add_level(section, key, def) \
+ settings_add_level_module(MODULE_NAME, section, key, def)
+#define settings_add_size(section, key, def) \
+ settings_add_size_module(MODULE_NAME, section, key, def)
+#define settings_add_choice(section, key, def, choices) \
+ settings_add_choice_module(MODULE_NAME, section, key, def, choices)
+
+void settings_set_str(const char *key, const char *value);
+void settings_set_int(const char *key, int value);
+void settings_set_bool(const char *key, int value);
+gboolean settings_set_time(const char *key, const char *value);
+gboolean settings_set_level(const char *key, const char *value);
+gboolean settings_set_size(const char *key, const char *value);
+gboolean settings_set_choice(const char *key, const char *value);
+
+/* Get the type (SETTING_TYPE_xxx) of `key' */
+SettingType settings_get_type(const char *key);
+/* Get all settings sorted by section. Free the result with g_slist_free() */
+GSList *settings_get_sorted(void);
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key);
+
+/* verify that all settings in config file for `module' are actually found
+ from /SET list */
+void settings_check_module(const char *module);
+#define settings_check() settings_check_module(MODULE_NAME)
+
+/* remove all invalid settings from config file. works only with the
+ modules that have already called settings_check() */
+void settings_clean_invalid(void);
+
+/* if `fname' is NULL, the default is used */
+int settings_reread(const char *fname);
+int settings_save(const char *fname, int autosave);
+int irssi_config_is_changed(const char *fname);
+
+void settings_init(void);
+void settings_deinit(void);
+
+#endif
diff --git a/src/core/signals.c b/src/core/signals.c
new file mode 100644
index 0000000..e52e690
--- /dev/null
+++ b/src/core/signals.c
@@ -0,0 +1,439 @@
+/*
+ signals.c : irssi
+
+ Copyright (C) 1999-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/signals.h>
+#include <irssi/src/core/modules.h>
+
+typedef struct _SignalHook {
+ struct _SignalHook *next;
+
+ int priority;
+ const char *module;
+ SIGNAL_FUNC func;
+ void *user_data;
+} SignalHook;
+
+typedef struct {
+ int id; /* signal id */
+ int refcount;
+
+ int emitting; /* signal is being emitted */
+ int stop_emit; /* this signal was stopped */
+ int continue_emit; /* this signal emit was continued elsewhere */
+ int remove_count; /* hooks were removed from signal */
+
+ SignalHook *hooks;
+} Signal;
+
+void *signal_user_data;
+
+static GHashTable *signals;
+static Signal *current_emitted_signal;
+static SignalHook *current_emitted_hook;
+
+#define signal_ref(signal) ++(signal)->refcount
+#define signal_unref(signal) (signal_unref_full(signal, TRUE))
+
+static int signal_unref_full(Signal *rec, int remove)
+{
+ g_assert(rec->refcount > 0);
+
+ if (--rec->refcount != 0)
+ return TRUE;
+
+ /* remove whole signal from memory */
+ if (rec->hooks != NULL) {
+ g_error("signal_unref(%s) : BUG - hook list wasn't empty",
+ signal_get_id_str(rec->id));
+ }
+
+ if (remove)
+ g_hash_table_remove(signals, GINT_TO_POINTER(rec->id));
+ g_free(rec);
+
+ return FALSE;
+}
+
+static void signal_hash_ref(void *key, Signal *rec)
+{
+ signal_ref(rec);
+}
+
+static int signal_hash_unref(void *key, Signal *rec)
+{
+ return !signal_unref_full(rec, FALSE);
+}
+
+void signal_add_full(const char *module, int priority,
+ const char *signal, SIGNAL_FUNC func, void *user_data)
+{
+ signal_add_full_id(module, priority, signal_get_uniq_id(signal),
+ func, user_data);
+}
+
+/* bind a signal */
+void signal_add_full_id(const char *module, int priority,
+ int signal_id, SIGNAL_FUNC func, void *user_data)
+{
+ Signal *signal;
+ SignalHook *hook, **tmp;
+
+ g_return_if_fail(signal_id >= 0);
+ g_return_if_fail(func != NULL);
+
+ signal = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (signal == NULL) {
+ /* new signal */
+ signal = g_new0(Signal, 1);
+ signal->id = signal_id;
+ g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), signal);
+ }
+
+ hook = g_new0(SignalHook, 1);
+ hook->priority = priority;
+ hook->module = module;
+ hook->func = func;
+ hook->user_data = user_data;
+
+ /* insert signal to proper position in list */
+ for (tmp = &signal->hooks; ; tmp = &(*tmp)->next) {
+ if (*tmp == NULL) {
+ /* last in list */
+ *tmp = hook;
+ break;
+ } else if (priority <= (*tmp)->priority) {
+ /* insert before others with same priority */
+ hook->next = *tmp;
+ *tmp = hook;
+ break;
+ }
+ }
+
+ signal_ref(signal);
+}
+
+static void signal_remove_hook(Signal *rec, SignalHook **hook_pos)
+{
+ SignalHook *hook;
+
+ hook = *hook_pos;
+ *hook_pos = hook->next;
+
+ g_free(hook);
+
+ signal_unref(rec);
+}
+
+/* Remove function from signal's emit list */
+static int signal_remove_func(Signal *rec, SIGNAL_FUNC func, void *user_data)
+{
+ SignalHook **hook;
+
+ for (hook = &rec->hooks; *hook != NULL; hook = &(*hook)->next) {
+ if ((*hook)->func == func && (*hook)->user_data == user_data) {
+ if (rec->emitting) {
+ /* mark it removed after emitting is done */
+ (*hook)->func = NULL;
+ rec->remove_count++;
+ } else {
+ /* remove the function from emit list */
+ signal_remove_hook(rec, hook);
+ }
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void signal_remove_id(int signal_id, SIGNAL_FUNC func, void *user_data)
+{
+ Signal *rec;
+
+ g_return_if_fail(signal_id >= 0);
+ g_return_if_fail(func != NULL);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL)
+ signal_remove_func(rec, func, user_data);
+}
+
+/* unbind signal */
+void signal_remove_full(const char *signal, SIGNAL_FUNC func, void *user_data)
+{
+ g_return_if_fail(signal != NULL);
+
+ signal_remove_id(signal_get_uniq_id(signal), func, user_data);
+}
+
+static void signal_hooks_clean(Signal *rec)
+{
+ SignalHook **hook, **next;
+ int count;
+
+ count = rec->remove_count;
+ rec->remove_count = 0;
+
+ for (hook = &rec->hooks; *hook != NULL; hook = next) {
+ next = &(*hook)->next;
+
+ if ((*hook)->func == NULL) {
+ next = hook;
+ signal_remove_hook(rec, hook);
+
+ if (--count == 0)
+ break;
+ }
+ }
+}
+
+static int signal_emit_real(Signal *rec, int params, va_list va,
+ SignalHook *first_hook)
+{
+ const void *arglist[SIGNAL_MAX_ARGUMENTS];
+ Signal *prev_emitted_signal;
+ SignalHook *hook, *prev_emitted_hook;
+ int i, stopped, stop_emit_count, continue_emit_count;
+
+ for (i = 0; i < SIGNAL_MAX_ARGUMENTS; i++)
+ arglist[i] = i >= params ? NULL : va_arg(va, const void *);
+
+ /* signal_stop_by_name("signal"); signal_emit("signal", ...);
+ fails if we compare rec->stop_emit against 0. */
+ stop_emit_count = rec->stop_emit;
+ continue_emit_count = rec->continue_emit;
+
+ signal_ref(rec);
+
+ stopped = FALSE;
+ rec->emitting++;
+
+ prev_emitted_signal = current_emitted_signal;
+ prev_emitted_hook = current_emitted_hook;
+ current_emitted_signal = rec;
+
+ for (hook = first_hook; hook != NULL; hook = hook->next) {
+ if (hook->func == NULL)
+ continue; /* removed */
+
+ current_emitted_hook = hook;
+#if SIGNAL_MAX_ARGUMENTS != 6
+# error SIGNAL_MAX_ARGUMENTS changed - update code
+#endif
+ signal_user_data = hook->user_data;
+ hook->func(arglist[0], arglist[1], arglist[2], arglist[3],
+ arglist[4], arglist[5]);
+
+ if (rec->continue_emit != continue_emit_count)
+ rec->continue_emit--;
+
+ if (rec->stop_emit != stop_emit_count) {
+ stopped = TRUE;
+ rec->stop_emit--;
+ break;
+ }
+ }
+
+ current_emitted_signal = prev_emitted_signal;
+ current_emitted_hook = prev_emitted_hook;
+
+ rec->emitting--;
+ signal_user_data = NULL;
+
+ if (!rec->emitting) {
+ g_assert(rec->stop_emit == 0);
+ g_assert(rec->continue_emit == 0);
+
+ if (rec->remove_count > 0)
+ signal_hooks_clean(rec);
+ }
+
+ signal_unref(rec);
+ return stopped;
+}
+
+int signal_emit(const char *signal, int params, ...)
+{
+ Signal *rec;
+ va_list va;
+ int signal_id;
+
+ g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
+
+ signal_id = signal_get_uniq_id(signal);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL) {
+ va_start(va, params);
+ signal_emit_real(rec, params, va, rec->hooks);
+ va_end(va);
+ }
+
+ return rec != NULL;
+}
+
+int signal_emit_id(int signal_id, int params, ...)
+{
+ Signal *rec;
+ va_list va;
+
+ g_return_val_if_fail(signal_id >= 0, FALSE);
+ g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL) {
+ va_start(va, params);
+ signal_emit_real(rec, params, va, rec->hooks);
+ va_end(va);
+ }
+
+ return rec != NULL;
+}
+
+void signal_continue(int params, ...)
+{
+ Signal *rec;
+ va_list va;
+
+ rec = current_emitted_signal;
+ if (rec == NULL || rec->emitting <= rec->continue_emit)
+ g_warning("signal_continue() : no signals are being emitted currently");
+ else {
+ va_start(va, params);
+
+ /* stop the signal */
+ if (rec->emitting > rec->stop_emit)
+ rec->stop_emit++;
+
+ /* re-emit */
+ rec->continue_emit++;
+ signal_emit_real(rec, params, va, current_emitted_hook->next);
+ va_end(va);
+ }
+}
+
+/* stop the current ongoing signal emission */
+void signal_stop(void)
+{
+ Signal *rec;
+
+ rec = current_emitted_signal;
+ if (rec == NULL)
+ g_warning("signal_stop() : no signals are being emitted currently");
+ else if (rec->emitting > rec->stop_emit)
+ rec->stop_emit++;
+}
+
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal)
+{
+ Signal *rec;
+ int signal_id;
+
+ signal_id = signal_get_uniq_id(signal);
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec == NULL)
+ g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal);
+ else if (rec->emitting > rec->stop_emit)
+ rec->stop_emit++;
+}
+
+/* return the name of the signal that is currently being emitted */
+const char *signal_get_emitted(void)
+{
+ return signal_get_id_str(signal_get_emitted_id());
+}
+
+/* return the ID of the signal that is currently being emitted */
+int signal_get_emitted_id(void)
+{
+ Signal *rec;
+
+ rec = current_emitted_signal;
+ g_return_val_if_fail(rec != NULL, -1);
+ return rec->id;
+}
+
+/* return TRUE if specified signal was stopped */
+int signal_is_stopped(int signal_id)
+{
+ Signal *rec;
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ g_return_val_if_fail(rec != NULL, FALSE);
+
+ return rec->emitting <= rec->stop_emit;
+}
+
+static void signal_remove_module(void *signal, Signal *rec,
+ const char *module)
+{
+ SignalHook **hook, **next;
+
+ for (hook = &rec->hooks; *hook != NULL; hook = next) {
+ next = &(*hook)->next;
+
+ if (strcasecmp((*hook)->module, module) == 0) {
+ next = hook;
+ signal_remove_hook(rec, hook);
+ }
+ }
+}
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module)
+{
+ g_return_if_fail(module != NULL);
+
+ g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
+ g_hash_table_foreach(signals, (GHFunc) signal_remove_module,
+ (void *) module);
+ g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
+}
+
+void signals_init(void)
+{
+ signals = g_hash_table_new(NULL, NULL);
+}
+
+static void signal_free(void *key, Signal *rec)
+{
+ /* refcount-1 because we just referenced it ourself */
+ g_warning("signal_free(%s) : signal still has %d references:",
+ signal_get_id_str(rec->id), rec->refcount-1);
+
+ while (rec->hooks != NULL) {
+ g_warning(" - module '%s' function %p",
+ rec->hooks->module, rec->hooks->func);
+
+ signal_remove_hook(rec, &rec->hooks);
+ }
+}
+
+void signals_deinit(void)
+{
+ g_hash_table_foreach(signals, (GHFunc) signal_hash_ref, NULL);
+ g_hash_table_foreach(signals, (GHFunc) signal_free, NULL);
+ g_hash_table_foreach_remove(signals, (GHRFunc) signal_hash_unref, NULL);
+ g_hash_table_destroy(signals);
+
+ module_uniq_destroy("signals");
+}
diff --git a/src/core/signals.h b/src/core/signals.h
new file mode 100644
index 0000000..0fa005a
--- /dev/null
+++ b/src/core/signals.h
@@ -0,0 +1,76 @@
+#ifndef IRSSI_CORE_SIGNALS_H
+#define IRSSI_CORE_SIGNALS_H
+
+#define SIGNAL_PRIORITY_LOW 100
+#define SIGNAL_PRIORITY_DEFAULT 0
+#define SIGNAL_PRIORITY_HIGH -100
+
+#define SIGNAL_MAX_ARGUMENTS 6
+typedef void (*SIGNAL_FUNC) (const void *, const void *,
+ const void *, const void *,
+ const void *, const void *);
+
+extern void *signal_user_data; /* use signal_get_user_data() macro to access */
+
+/* bind a signal */
+void signal_add_full(const char *module, int priority,
+ const char *signal, SIGNAL_FUNC func, void *user_data);
+void signal_add_full_id(const char *module, int priority,
+ int signal, SIGNAL_FUNC func, void *user_data);
+#define signal_add(signal, func) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, (signal), (SIGNAL_FUNC) (func), NULL)
+#define signal_add_first(signal, func) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_HIGH, (signal), (SIGNAL_FUNC) (func), NULL)
+#define signal_add_last(signal, func) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_LOW, (signal), (SIGNAL_FUNC) (func), NULL)
+
+#define signal_add_data(signal, func, data) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_DEFAULT, (signal), (SIGNAL_FUNC) (func), data)
+#define signal_add_first_data(signal, func, data) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_HIGH, (signal), (SIGNAL_FUNC) (func), data)
+#define signal_add_last_data(signal, func, data) \
+ signal_add_full(MODULE_NAME, SIGNAL_PRIORITY_LOW, (signal), (SIGNAL_FUNC) (func), data)
+
+/* unbind signal */
+void signal_remove_full(const char *signal, SIGNAL_FUNC func, void *user_data);
+#define signal_remove(signal, func) \
+ signal_remove_full((signal), (SIGNAL_FUNC) (func), NULL)
+#define signal_remove_data(signal, func, data) \
+ signal_remove_full((signal), (SIGNAL_FUNC) (func), data)
+void signal_remove_id(int signal_id, SIGNAL_FUNC func, void *user_data);
+
+/* emit signal */
+int signal_emit(const char *signal, int params, ...);
+int signal_emit_id(int signal_id, int params, ...);
+
+/* continue currently emitted signal with different parameters */
+void signal_continue(int params, ...);
+
+/* stop the current ongoing signal emission */
+void signal_stop(void);
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal);
+
+/* return the name of the signal that is currently being emitted */
+const char *signal_get_emitted(void);
+/* return the ID of the signal that is currently being emitted */
+int signal_get_emitted_id(void);
+/* return TRUE if specified signal was stopped */
+int signal_is_stopped(int signal_id);
+/* return the user data of the signal function currently being emitted */
+#define signal_get_user_data() signal_user_data
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module);
+
+/* signal name -> ID */
+#define signal_get_uniq_id(signal) \
+ module_get_uniq_id_str("signals", signal)
+/* signal ID -> name */
+#define signal_get_id_str(signal_id) \
+ module_find_id_str("signals", signal_id)
+
+void signals_init(void);
+void signals_deinit(void);
+
+#endif
diff --git a/src/core/special-vars.c b/src/core/special-vars.c
new file mode 100644
index 0000000..802fcb3
--- /dev/null
+++ b/src/core/special-vars.c
@@ -0,0 +1,843 @@
+/*
+ special-vars.c : irssi
+
+ 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/expandos.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/refstrings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/utf8.h>
+
+#define isvarchar(c) \
+ (i_isalnum(c) || (c) == '_')
+
+#define isarg(c) \
+ (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-')
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* fuzzer should not exhaust memory here */
+#define ALIGN_MAX 512
+#else
+#define ALIGN_MAX 222488
+#endif
+
+static SPECIAL_HISTORY_FUNC history_func = NULL;
+static GSList *special_collector;
+static GSList *special_cache;
+
+static char *get_argument(char **cmd, char **arglist)
+{
+ GString *str;
+ char *ret;
+ int max, arg, argcount;
+
+ arg = 0;
+ max = -1;
+
+ argcount = arglist == NULL ? 0 : g_strv_length(arglist);
+
+ if (**cmd == '*') {
+ /* get all arguments */
+ } else if (**cmd == '~') {
+ /* get last argument */
+ arg = max = argcount-1;
+ } else {
+ if (i_isdigit(**cmd)) {
+ /* first argument */
+ arg = max = (**cmd)-'0';
+ (*cmd)++;
+ }
+
+ if (**cmd == '-') {
+ /* get more than one argument */
+ (*cmd)++;
+ if (!i_isdigit(**cmd))
+ max = -1; /* get all the rest */
+ else {
+ max = (**cmd)-'0';
+ (*cmd)++;
+ }
+ }
+ (*cmd)--;
+ }
+
+ str = g_string_new(NULL);
+ while (arg >= 0 && arg < argcount && (arg <= max || max == -1)) {
+ g_string_append(str, arglist[arg]);
+ g_string_append_c(str, ' ');
+ arg++;
+ }
+ if (str->len > 0) g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *get_long_variable_value(const char *key, SERVER_REC *server,
+ void *item, int *free_ret)
+{
+ EXPANDO_FUNC func;
+ const char *ret;
+ SETTINGS_REC *rec;
+
+ *free_ret = FALSE;
+
+ /* expando? */
+ func = expando_find_long(key);
+ if (func != NULL) {
+ current_expando = key;
+ return func(server, item, free_ret);
+ }
+
+ /* internal setting? */
+ rec = settings_get_record(key);
+ if (rec != NULL) {
+ *free_ret = TRUE;
+ return settings_get_print(rec);
+ }
+
+ /* environment variable? */
+ ret = g_getenv(key);
+ if (ret != NULL)
+ return (char *) ret;
+
+ return NULL;
+}
+
+static gboolean cache_find(GSList **cache, const char *var, char **ret)
+{
+ GSList *tmp;
+ GSList *prev = NULL;
+
+ if (cache == NULL)
+ return FALSE;
+
+ for (tmp = *cache; tmp;) {
+ if (g_strcmp0(var, tmp->data) == 0) {
+ *ret = tmp->next->data;
+ if (prev != NULL)
+ prev->next->next = tmp->next->next;
+ else
+ *cache = tmp->next->next;
+
+ g_slist_free_1(tmp->next);
+ g_slist_free_1(tmp);
+ return TRUE;
+ }
+ prev = tmp;
+ tmp = tmp->next->next;
+ }
+ return FALSE;
+}
+
+static gboolean cache_find_char(GSList **cache, char var, char **ret)
+{
+ char varn[] = { var, '\0' };
+ return cache_find(cache, varn, ret);
+}
+
+static char *get_long_variable(char **cmd, SERVER_REC *server, void *item, int *free_ret,
+ int getname, GSList **collector, GSList **cache)
+{
+ char *start, *var, *ret;
+
+ /* get variable name */
+ start = *cmd;
+ while (isvarchar((*cmd)[1])) (*cmd)++;
+
+ var = g_strndup(start, (int) (*cmd-start)+1);
+ if (getname) {
+ *free_ret = TRUE;
+ return var;
+ }
+ if (cache_find(cache, var, &ret)) {
+ g_free(var);
+ return ret;
+ }
+ ret = get_long_variable_value(var, server, item, free_ret);
+ if (collector != NULL) {
+ *collector = g_slist_prepend(*collector, g_strdup(ret));
+ *collector = g_slist_prepend(*collector, i_refstr_intern(var));
+ }
+ g_free(var);
+ return ret;
+}
+
+/* return the value of the variable found from `cmd'.
+ if 'getname' is TRUE, return the name of the variable instead it's value */
+static char *get_variable(char **cmd, SERVER_REC *server, void *item, char **arglist, int *free_ret,
+ int *arg_used, int getname, GSList **collector, GSList **cache)
+{
+ EXPANDO_FUNC func;
+
+ if (isarg(**cmd)) {
+ /* argument */
+ *free_ret = TRUE;
+ if (arg_used != NULL) *arg_used = TRUE;
+ return getname ? g_strdup_printf("%c", **cmd) :
+ get_argument(cmd, arglist);
+ }
+
+ if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
+ /* long variable name.. */
+ return get_long_variable(cmd, server, item, free_ret, getname, collector, cache);
+ }
+
+ /* single character variable. */
+ if (getname) {
+ *free_ret = TRUE;
+ return g_strdup_printf("%c", **cmd);
+ }
+ *free_ret = FALSE;
+ {
+ char *ret;
+ if (cache_find_char(cache, **cmd, &ret)) {
+ return ret;
+ }
+ }
+ func = expando_find_char(**cmd);
+ if (func == NULL)
+ return NULL;
+ else {
+ char str[2];
+ char *ret;
+
+ str[0] = **cmd; str[1] = '\0';
+ current_expando = str;
+ ret = func(server, item, free_ret);
+ if (**cmd != 'Z' && collector != NULL) {
+ *collector = g_slist_prepend(*collector, g_strdup(ret));
+ *collector = g_slist_prepend(*collector, i_refstr_intern(str));
+ }
+ return ret;
+ }
+}
+
+static char *get_history(char **cmd, void *item, int *free_ret)
+{
+ char *start, *text, *ret;
+
+ /* get variable name */
+ start = ++(*cmd);
+ while (**cmd != '\0' && **cmd != '!') (*cmd)++;
+
+ if (history_func == NULL)
+ ret = NULL;
+ else {
+ text = g_strndup(start, (int) (*cmd-start));
+ ret = history_func(text, item, free_ret);
+ g_free(text);
+ }
+
+ if (**cmd == '\0') (*cmd)--;
+ return ret;
+}
+
+static char *get_special_value(char **cmd, SERVER_REC *server, void *item, char **arglist,
+ int *free_ret, int *arg_used, int flags, GSList **collector,
+ GSList **cache)
+{
+ char command, *value, *p;
+ int len;
+
+ if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
+ *free_ret = TRUE;
+ return g_strdup_printf("$%c", **cmd);
+ }
+
+ if (**cmd == '!') {
+ /* find text from command history */
+ if (flags & PARSE_FLAG_GETNAME)
+ return "!";
+
+ return get_history(cmd, item, free_ret);
+ }
+
+ command = 0;
+ if (**cmd == '#' || **cmd == '@') {
+ command = **cmd;
+ if ((*cmd)[1] != '\0')
+ (*cmd)++;
+ else {
+ /* default to $* */
+ char *temp_cmd = "*";
+
+ if (flags & PARSE_FLAG_GETNAME)
+ return "*";
+
+ *free_ret = TRUE;
+ return get_argument(&temp_cmd, arglist);
+ }
+ }
+
+ value = get_variable(cmd, server, item, arglist, free_ret, arg_used,
+ flags & PARSE_FLAG_GETNAME, collector, cache);
+
+ if (flags & PARSE_FLAG_GETNAME)
+ return value;
+
+ if (command == '#') {
+ /* number of words */
+ if (value == NULL || *value == '\0') {
+ if (value != NULL && *free_ret) {
+ g_free(value);
+ *free_ret = FALSE;
+ }
+ return "0";
+ }
+
+ len = 1;
+ for (p = value; *p != '\0'; p++) {
+ if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
+ len++;
+ }
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ if (command == '@') {
+ /* number of characters */
+ if (value == NULL) return "0";
+
+ len = strlen(value);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ return value;
+}
+
+/* get alignment arguments (inside the []) */
+static int get_alignment_args(char **data, int *align, int *flags, char *pad)
+{
+ char *str;
+ char *endptr;
+ guint align_;
+
+ *align = 0;
+ *flags = ALIGN_CUT|ALIGN_PAD;
+ *pad = ' ';
+
+ /* '!' = don't cut, '-' = right padding */
+ str = *data;
+ while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
+ if (*str == '!')
+ *flags &= ~ALIGN_CUT;
+ else if (*str == '-')
+ *flags |= ALIGN_RIGHT;
+ else if (*str == '.')
+ *flags &= ~ALIGN_PAD;
+ str++;
+ }
+ if (!i_isdigit(*str))
+ return FALSE; /* expecting number */
+
+ /* get the alignment size */
+ if (!parse_uint(str, &endptr, 10, &align_)) {
+ return FALSE;
+ }
+ /* alignment larger than supported */
+ if (align_ > ALIGN_MAX) {
+ return FALSE;
+ }
+ str = endptr;
+ *align = align_;
+
+ /* get the pad character */
+ while (*str != '\0' && *str != ']') {
+ *pad = *str;
+ str++;
+ }
+
+ if (*str++ != ']') return FALSE;
+
+ *data = str;
+ return TRUE;
+}
+
+/* return the aligned text */
+char *get_alignment(const char *text, int align, int flags, char pad)
+{
+ GString *str;
+ char *ret;
+ int policy;
+ unsigned int cut_bytes;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ policy = string_policy(text);
+
+ str = g_string_new(text);
+
+ /* cut */
+ if ((flags & ALIGN_CUT) && align > 0 && string_width(text, policy) > align) {
+ string_chars_for_width(text, policy, align, &cut_bytes);
+ g_string_truncate(str, cut_bytes);
+ }
+
+ /* add pad characters */
+ if (flags & ALIGN_PAD) {
+ int pad_len = align - string_width(str->str, policy);
+ if (pad_len > 0) {
+ char *pad_full = g_strnfill(pad_len, pad);
+ if (flags & ALIGN_RIGHT)
+ g_string_prepend(str, pad_full);
+ else
+ g_string_append(str, pad_full);
+ g_free(pad_full);
+ }
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used, int flags)
+{
+ static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
+ char command, *value;
+
+ char align_pad = '\0';
+ int align = 0, align_flags = 0;
+
+ char *nest_value;
+ int brackets, nest_free;
+
+ *free_ret = FALSE;
+ if (**cmd == '\0')
+ return NULL;
+
+ command = **cmd; (*cmd)++;
+ switch (command) {
+ case '[':
+ /* alignment */
+ if (!get_alignment_args(cmd, &align, &align_flags,
+ &align_pad) || **cmd == '\0') {
+ (*cmd)--;
+ return NULL;
+ }
+ break;
+ default:
+ command = 0;
+ (*cmd)--;
+ }
+
+ nest_free = FALSE; nest_value = NULL;
+#if 0 /* this code is disabled due to security issues until it is fixed */
+ if (**cmd == '(' && (*cmd)[1] != '\0') {
+ /* subvariable */
+ int toplevel = nested_orig_cmd == NULL;
+
+ if (toplevel) nested_orig_cmd = cmd;
+ (*cmd)++;
+ if (**cmd != '$') {
+ /* ... */
+ nest_value = *cmd;
+ } else {
+ (*cmd)++;
+ nest_value = parse_special(cmd, server, item, arglist,
+ &nest_free, arg_used,
+ flags);
+ }
+
+ if (nest_value == NULL || *nest_value == '\0')
+ return NULL;
+
+ while ((*nested_orig_cmd)[1] != '\0') {
+ (*nested_orig_cmd)++;
+ if (**nested_orig_cmd == ')')
+ break;
+ }
+ cmd = &nest_value;
+
+ if (toplevel) nested_orig_cmd = NULL;
+ }
+#else
+ if (nested_orig_cmd) nested_orig_cmd = NULL;
+#endif
+
+ if (**cmd != '{')
+ brackets = FALSE;
+ else {
+ /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
+ if ((*cmd)[1] == '\0')
+ return NULL;
+ (*cmd)++;
+ brackets = TRUE;
+ }
+
+ value = get_special_value(cmd, server, item, arglist, free_ret, arg_used, flags,
+ special_collector != NULL ? special_collector->data : NULL,
+ &special_cache);
+ if (**cmd == '\0')
+ g_error("parse_special() : buffer overflow!");
+
+ if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
+ *arg_used = TRUE;
+
+ if (brackets) {
+ while (**cmd != '}' && (*cmd)[1] != '\0')
+ (*cmd)++;
+ }
+
+ if (nest_free) g_free(nest_value);
+
+ if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
+ /* alignment */
+ char *p;
+
+ if (value == NULL) return "";
+
+ p = get_alignment(value, align, align_flags, align_pad);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return p;
+ }
+
+ return value;
+}
+
+static void gstring_append_escaped(GString *str, const char *text, int flags)
+{
+ char esc[4], *escpos;
+
+ escpos = esc;
+ if (flags & PARSE_FLAG_ESCAPE_VARS)
+ *escpos++ = '%';
+ if (flags & PARSE_FLAG_ESCAPE_THEME) {
+ *escpos++ = '{';
+ *escpos++ = '}';
+ }
+
+ if (escpos == esc) {
+ g_string_append(str, text);
+ return;
+ }
+
+ *escpos = '\0';
+ while (*text != '\0') {
+ for (escpos = esc; *escpos != '\0'; escpos++) {
+ if (*text == *escpos) {
+ g_string_append_c(str, '%');
+ break;
+ }
+ }
+ g_string_append_c(str, *text);
+ text++;
+ }
+}
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+ const char *data, int *arg_used, int flags)
+{
+ char code, **arglist, *ret;
+ GString *str;
+ int need_free, chr;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+ g_return_val_if_fail(data != NULL, NULL);
+
+ /* create the argument list */
+ arglist = g_strsplit(data, " ", -1);
+
+ if (arg_used != NULL) *arg_used = FALSE;
+ code = 0;
+ str = g_string_new(NULL);
+ while (*cmd != '\0') {
+ if (code == '\\') {
+ if (*cmd == ';')
+ g_string_append_c(str, ';');
+ else {
+ chr = expand_escape(&cmd);
+ g_string_append_c(str, chr != -1 ? chr : *cmd);
+ }
+ code = 0;
+ } else if (code == '$') {
+ char *ret;
+
+ ret = parse_special((char **) &cmd, server, item,
+ arglist, &need_free, arg_used,
+ flags);
+ if (ret != NULL) {
+ gstring_append_escaped(str, ret, flags);
+ if (need_free) g_free(ret);
+ }
+ code = 0;
+ } else {
+ if (*cmd == '\\' || *cmd == '$')
+ code = *cmd;
+ else
+ g_string_append_c(str, *cmd);
+ }
+
+ cmd++;
+ }
+ g_strfreev(arglist);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+#define is_split_char(str, start) \
+ ((str)[0] == ';' && ((start) == (str) || \
+ ((str)[-1] != '\\' && (str)[-1] != '$')))
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+ SERVER_REC *server, void *item)
+{
+ const char *cmdchars;
+ char *orig, *str, *start, *ret;
+ int arg_used, arg_used_ever;
+ GSList *commands;
+
+ commands = NULL;
+ arg_used_ever = FALSE;
+ cmdchars = settings_get_str("cmdchars");
+
+ /* get a list of all the commands to run */
+ orig = start = str = g_strdup(cmd);
+ do {
+ if (is_split_char(str, start)) {
+ *str++ = '\0';
+ while (*str == ' ') str++;
+ } else if (*str != '\0') {
+ str++;
+ continue;
+ }
+
+ ret = parse_special_string(start, server, item,
+ data, &arg_used, 0);
+ if (*ret != '\0') {
+ if (arg_used) arg_used_ever = TRUE;
+
+ if (strchr(cmdchars, *ret) == NULL) {
+ /* no command char - let's put it there.. */
+ char *old = ret;
+
+ ret = g_strdup_printf("%c%s", *cmdchars, old);
+ g_free(old);
+ }
+ commands = g_slist_append(commands, ret);
+ }
+ start = str;
+ } while (*start != '\0');
+
+ /* run the command, if no arguments were ever used, append all of them
+ after each command */
+ while (commands != NULL) {
+ ret = commands->data;
+
+ if (!arg_used_ever && *data != '\0') {
+ char *old = ret;
+
+ ret = g_strconcat(old, " ", data, NULL);
+ g_free(old);
+ }
+
+ if (server != NULL)
+ server_ref(server);
+ signal_emit("send command", 3, ret, server, item);
+
+ if (server != NULL && !server_unref(server)) {
+ /* the server was destroyed */
+ server = NULL;
+ item = NULL;
+ }
+
+ /* FIXME: window item would need reference counting as well,
+ eg. "/EVAL win close;say hello" wouldn't work now.. */
+
+ commands = g_slist_remove(commands, commands->data);
+ g_free(ret);
+ }
+ g_free(orig);
+}
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func)
+{
+ history_func = func;
+}
+
+void special_push_collector(GSList **list)
+{
+ special_collector = g_slist_prepend(special_collector, list);
+}
+
+void special_pop_collector(void)
+{
+ special_collector = g_slist_delete_link(special_collector, special_collector);
+}
+
+void special_fill_cache(GSList *list)
+{
+ g_slist_free(special_cache);
+ special_cache = g_slist_copy(list);
+}
+
+static void update_signals_hash(GHashTable **hash, int *signals)
+{
+ void *signal_id;
+ int arg_type;
+
+ if (*hash == NULL) {
+ *hash = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ }
+
+ while (*signals != -1) {
+ signal_id = GINT_TO_POINTER(*signals);
+ arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id));
+ if (arg_type != 0 && arg_type != signals[1]) {
+ /* same signal is used for different purposes ..
+ not sure if this should ever happen, but change
+ the argument type to none so it will at least
+ work. */
+ arg_type = EXPANDO_ARG_NONE;
+ }
+
+ if (arg_type == 0) arg_type = signals[1];
+ g_hash_table_insert(*hash, signal_id,
+ GINT_TO_POINTER(arg_type));
+ signals += 2;
+ }
+}
+
+static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
+{
+ (*pos)[0] = GPOINTER_TO_INT(signal_id);
+ (*pos)[1] = GPOINTER_TO_INT(arg_type);
+ (*pos) += 2;
+}
+
+static int *get_signals_list(GHashTable *hash)
+{
+ int *signals, *pos;
+
+ if (hash == NULL) {
+ /* no expandos in text - never needs updating */
+ return NULL;
+ }
+
+ pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
+ g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
+ *pos = -1;
+
+ g_hash_table_destroy(hash);
+ return signals;
+
+}
+
+#define TASK_BIND 1
+#define TASK_UNBIND 2
+#define TASK_GET_SIGNALS 3
+
+static int *special_vars_signals_task(const char *text, int funccount,
+ SIGNAL_FUNC *funcs, int task)
+{
+ GHashTable *signals;
+ char *expando;
+ int need_free, *expando_signals;
+
+ signals = NULL;
+ while (*text != '\0') {
+ if (*text == '\\' && text[1] != '\0') {
+ /* escape */
+ text += 2;
+ } else if (*text == '$' && text[1] != '\0') {
+ /* expando */
+ text++;
+ expando = parse_special((char **) &text, NULL, NULL,
+ NULL, &need_free, NULL,
+ PARSE_FLAG_GETNAME);
+ if (expando == NULL)
+ continue;
+
+ switch (task) {
+ case TASK_BIND:
+ expando_bind(expando, funccount, funcs);
+ break;
+ case TASK_UNBIND:
+ expando_unbind(expando, funccount, funcs);
+ break;
+ case TASK_GET_SIGNALS:
+ expando_signals = expando_get_signals(expando);
+ if (expando_signals != NULL) {
+ update_signals_hash(&signals,
+ expando_signals);
+ g_free(expando_signals);
+ }
+ break;
+ }
+ if (need_free) g_free(expando);
+ } else {
+ /* just a char */
+ text++;
+ }
+ }
+
+ if (task == TASK_GET_SIGNALS)
+ return get_signals_list(signals);
+
+ return NULL;
+}
+
+void special_vars_add_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs)
+{
+ special_vars_signals_task(text, funccount, funcs, TASK_BIND);
+}
+
+void special_vars_remove_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs)
+{
+ special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
+}
+
+int *special_vars_get_signals(const char *text)
+{
+ return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);
+}
+
+void special_vars_init(void)
+{
+ special_cache = NULL;
+ special_collector = NULL;
+}
+
+void special_vars_deinit(void)
+{
+ g_slist_free(special_cache);
+ g_slist_free(special_collector);
+}
diff --git a/src/core/special-vars.h b/src/core/special-vars.h
new file mode 100644
index 0000000..87aba7d
--- /dev/null
+++ b/src/core/special-vars.h
@@ -0,0 +1,53 @@
+#ifndef IRSSI_CORE_SPECIAL_VARS_H
+#define IRSSI_CORE_SPECIAL_VARS_H
+
+#include <irssi/src/core/signals.h>
+
+#define PARSE_FLAG_GETNAME 0x01 /* return argument name instead of it's value */
+#define PARSE_FLAG_ISSET_ANY 0x02 /* arg_used field specifies that at least one of the $variables was non-empty */
+#define PARSE_FLAG_ESCAPE_VARS 0x04 /* if any arguments/variables contain % chars, escape them with another % */
+#define PARSE_FLAG_ESCAPE_THEME 0x08 /* if any arguments/variables contain { or } chars, escape them with % */
+#define PARSE_FLAG_ONLY_ARGS 0x10 /* expand only arguments ($0 $1 etc.) but no other $variables */
+
+#define ALIGN_RIGHT 0x01
+#define ALIGN_CUT 0x02
+#define ALIGN_PAD 0x04
+
+typedef char* (*SPECIAL_HISTORY_FUNC)
+ (const char *text, void *item, int *free_ret);
+
+/* Cut and/or pad text so it takes exactly "align" characters on the screen */
+char *get_alignment(const char *text, int align, int flags, char pad);
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, SERVER_REC *server, void *item,
+ char **arglist, int *free_ret, int *arg_used, int flags);
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
+ const char *data, int *arg_used, int flags);
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data,
+ SERVER_REC *server, void *item);
+
+void special_push_collector(GSList **list);
+void special_pop_collector(void);
+
+void special_fill_cache(GSList *list);
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func);
+
+void special_vars_add_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs);
+void special_vars_remove_signals(const char *text,
+ int funccount, SIGNAL_FUNC *funcs);
+/* Returns [<signal id>, EXPANDO_ARG_xxx, <signal id>, ..., -1] */
+int *special_vars_get_signals(const char *text);
+
+void special_vars_init(void);
+
+void special_vars_deinit(void);
+
+#endif
diff --git a/src/core/tls.c b/src/core/tls.c
new file mode 100644
index 0000000..bd5c832
--- /dev/null
+++ b/src/core/tls.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2015 Alexander Færøy <ahf@irssi.org>
+ *
+ * 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/tls.h>
+
+TLS_REC *tls_create_rec()
+{
+ TLS_REC *rec = g_new0(TLS_REC, 1);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return rec;
+}
+
+void tls_rec_free(TLS_REC *tls_rec)
+{
+ if (tls_rec == NULL)
+ return;
+
+ g_free_and_null(tls_rec->protocol_version);
+ g_free_and_null(tls_rec->cipher);
+ g_free_and_null(tls_rec->public_key_algorithm);
+ g_free_and_null(tls_rec->public_key_fingerprint);
+ g_free_and_null(tls_rec->public_key_fingerprint_algorithm);
+ g_free_and_null(tls_rec->certificate_fingerprint);
+ g_free_and_null(tls_rec->certificate_fingerprint_algorithm);
+ g_free_and_null(tls_rec->not_after);
+ g_free_and_null(tls_rec->not_before);
+ g_free_and_null(tls_rec->ephemeral_key_algorithm);
+
+ if (tls_rec->certs != NULL) {
+ g_slist_foreach(tls_rec->certs, (GFunc)tls_cert_rec_free, NULL);
+ g_slist_free(tls_rec->certs);
+ tls_rec->certs = NULL;
+ }
+
+ g_free(tls_rec);
+}
+
+void tls_rec_set_protocol_version(TLS_REC *tls_rec, const char *protocol_version)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->protocol_version = g_strdup(protocol_version);
+}
+
+void tls_rec_set_cipher(TLS_REC *tls_rec, const char *cipher)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->cipher = g_strdup(cipher);
+}
+
+void tls_rec_set_cipher_size(TLS_REC *tls_rec, size_t size)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->cipher_size = size;
+}
+
+void tls_rec_set_public_key_algorithm(TLS_REC *tls_rec, const char *algorithm)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->public_key_algorithm = g_strdup(algorithm);
+}
+
+void tls_rec_set_public_key_fingerprint(TLS_REC *tls_rec, const char *fingerprint)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->public_key_fingerprint = g_strdup(fingerprint);
+}
+
+void tls_rec_set_public_key_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->public_key_fingerprint_algorithm = g_strdup(algorithm);
+}
+
+void tls_rec_set_public_key_size(TLS_REC *tls_rec, size_t size)
+{
+ g_return_if_fail(tls_rec != NULL);
+ tls_rec->public_key_size = size;
+}
+
+void tls_rec_set_certificate_fingerprint(TLS_REC *tls_rec, const char *fingerprint)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->certificate_fingerprint = g_strdup(fingerprint);
+}
+
+void tls_rec_set_certificate_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm)
+{
+ g_return_if_fail(tls_rec != NULL);
+
+ tls_rec->certificate_fingerprint_algorithm = g_strdup(algorithm);
+}
+
+void tls_rec_set_not_after(TLS_REC *tls_rec, const char *not_after)
+{
+ g_return_if_fail(tls_rec != NULL);
+ tls_rec->not_after = g_strdup(not_after);
+}
+
+void tls_rec_set_not_before(TLS_REC *tls_rec, const char *not_before)
+{
+ g_return_if_fail(tls_rec != NULL);
+ tls_rec->not_before = g_strdup(not_before);
+}
+
+void tls_rec_set_ephemeral_key_algorithm(TLS_REC *tls_rec, const char *algorithm)
+{
+ g_return_if_fail(tls_rec != NULL);
+ tls_rec->ephemeral_key_algorithm = g_strdup(algorithm);
+}
+
+void tls_rec_set_ephemeral_key_size(TLS_REC *tls_rec, size_t size)
+{
+ g_return_if_fail(tls_rec != NULL);
+ tls_rec->ephemeral_key_size = size;
+}
+
+void tls_rec_append_cert(TLS_REC *tls_rec, TLS_CERT_REC *tls_cert_rec)
+{
+ g_return_if_fail(tls_rec != NULL);
+ g_return_if_fail(tls_cert_rec != NULL);
+
+ tls_rec->certs = g_slist_append(tls_rec->certs, tls_cert_rec);
+}
+
+TLS_CERT_REC *tls_cert_create_rec()
+{
+ TLS_CERT_REC *rec = g_new0(TLS_CERT_REC, 1);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ return rec;
+}
+
+void tls_cert_rec_append_subject_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec)
+{
+ g_return_if_fail(tls_cert_rec != NULL);
+ g_return_if_fail(tls_cert_entry_rec != NULL);
+
+ tls_cert_rec->subject = g_slist_append(tls_cert_rec->subject, tls_cert_entry_rec);
+}
+
+void tls_cert_rec_append_issuer_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec)
+{
+ g_return_if_fail(tls_cert_rec != NULL);
+ g_return_if_fail(tls_cert_entry_rec != NULL);
+
+ tls_cert_rec->issuer = g_slist_append(tls_cert_rec->issuer, tls_cert_entry_rec);
+}
+
+void tls_cert_rec_free(TLS_CERT_REC *tls_cert_rec)
+{
+ if (tls_cert_rec == NULL)
+ return;
+
+ if (tls_cert_rec->subject != NULL) {
+ g_slist_foreach(tls_cert_rec->subject, (GFunc)tls_cert_entry_rec_free, NULL);
+ g_slist_free(tls_cert_rec->subject);
+ tls_cert_rec->subject = NULL;
+ }
+
+ if (tls_cert_rec->issuer != NULL) {
+ g_slist_foreach(tls_cert_rec->issuer, (GFunc)tls_cert_entry_rec_free, NULL);
+ g_slist_free(tls_cert_rec->issuer);
+ tls_cert_rec->issuer = NULL;
+ }
+
+ g_free(tls_cert_rec);
+}
+
+TLS_CERT_ENTRY_REC *tls_cert_entry_create_rec(const char *name, const char *value)
+{
+ TLS_CERT_ENTRY_REC *rec = g_new0(TLS_CERT_ENTRY_REC, 1);
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ rec->name = g_strdup(name);
+ rec->value = g_strdup(value);
+
+ return rec;
+}
+
+void tls_cert_entry_rec_free(TLS_CERT_ENTRY_REC *tls_cert_entry)
+{
+ if (tls_cert_entry == NULL)
+ return;
+
+ g_free_and_null(tls_cert_entry->name);
+ g_free_and_null(tls_cert_entry->value);
+
+ g_free(tls_cert_entry);
+}
diff --git a/src/core/tls.h b/src/core/tls.h
new file mode 100644
index 0000000..1e1a103
--- /dev/null
+++ b/src/core/tls.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2015 Alexander Færøy <ahf@irssi.org>
+ *
+ * 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_CORE_TLS_H
+#define IRSSI_CORE_TLS_H
+
+#include <openssl/ssl.h>
+
+typedef struct _TLS_REC TLS_REC;
+typedef struct _TLS_CERT_REC TLS_CERT_REC;
+typedef struct _TLS_CERT_ENTRY_REC TLS_CERT_ENTRY_REC;
+
+struct _TLS_REC {
+ char *protocol_version;
+ char *cipher;
+ size_t cipher_size;
+
+ char *public_key_algorithm;
+ char *public_key_fingerprint;
+ char *public_key_fingerprint_algorithm;
+ size_t public_key_size;
+
+ char *certificate_fingerprint;
+ char *certificate_fingerprint_algorithm;
+
+ char *not_after;
+ char *not_before;
+
+ char *ephemeral_key_algorithm;
+ size_t ephemeral_key_size;
+
+ GSList *certs;
+};
+
+struct _TLS_CERT_REC {
+ GSList *subject;
+ GSList *issuer;
+};
+
+struct _TLS_CERT_ENTRY_REC {
+ char *name;
+ char *value;
+};
+
+TLS_REC *tls_create_rec();
+void tls_rec_free(TLS_REC *tls_rec);
+
+void tls_rec_set_protocol_version(TLS_REC *tls_rec, const char *protocol_version);
+void tls_rec_set_cipher(TLS_REC *tls_rec, const char *cipher);
+void tls_rec_set_cipher_size(TLS_REC *tls_rec, size_t size);
+void tls_rec_set_public_key_algorithm(TLS_REC *tls_rec, const char *algorithm);
+void tls_rec_set_public_key_fingerprint(TLS_REC *tls_rec, const char *fingerprint);
+void tls_rec_set_public_key_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm);
+void tls_rec_set_public_key_size(TLS_REC *tls_rec, size_t size);
+void tls_rec_set_certificate_fingerprint(TLS_REC *tls_rec, const char *fingerprint);
+void tls_rec_set_certificate_fingerprint_algorithm(TLS_REC *tls_rec, const char *algorithm);
+void tls_rec_set_not_after(TLS_REC *tls_rec, const char *not_after);
+void tls_rec_set_not_before(TLS_REC *tls_rec, const char *not_before);
+void tls_rec_set_ephemeral_key_algorithm(TLS_REC *tls_rec, const char *algorithm);
+void tls_rec_set_ephemeral_key_size(TLS_REC *tls_rec, size_t size);
+
+void tls_rec_append_cert(TLS_REC *tls_rec, TLS_CERT_REC *tls_cert_rec);
+
+TLS_CERT_REC *tls_cert_create_rec();
+void tls_cert_rec_free(TLS_CERT_REC *tls_cert_rec);
+
+void tls_cert_rec_append_subject_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec);
+void tls_cert_rec_append_issuer_entry(TLS_CERT_REC *tls_cert_rec, TLS_CERT_ENTRY_REC *tls_cert_entry_rec);
+
+TLS_CERT_ENTRY_REC *tls_cert_entry_create_rec(const char *name, const char *value);
+void tls_cert_entry_rec_free(TLS_CERT_ENTRY_REC *tls_cert_entry);
+
+#endif
diff --git a/src/core/utf8.c b/src/core/utf8.c
new file mode 100644
index 0000000..5ef030d
--- /dev/null
+++ b/src/core/utf8.c
@@ -0,0 +1,135 @@
+/* utf8.c - Operations on UTF-8 strings.
+ *
+ * Copyright (C) 2002 Timo Sirainen
+ *
+ * Based on GLib code by
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser 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 <irssi/src/core/utf8.h>
+#include "module.h"
+
+/* Provide is_utf8(): */
+#include <irssi/src/core/recode.h>
+
+int string_advance(char const **str, int policy)
+{
+ if (policy == TREAT_STRING_AS_UTF8) {
+ gunichar c;
+
+ c = g_utf8_get_char(*str);
+ *str = g_utf8_next_char(*str);
+
+ return unichar_isprint(c) ? i_wcwidth(c) : 1;
+ } else {
+ /* Assume TREAT_STRING_AS_BYTES: */
+ *str += 1;
+
+ return 1;
+ }
+}
+
+int string_policy(const char *str)
+{
+ if (is_utf8()) {
+ if (str == NULL || g_utf8_validate(str, -1, NULL)) {
+ /* No string provided or valid UTF-8 string: treat as UTF-8: */
+ return TREAT_STRING_AS_UTF8;
+ }
+ }
+ return TREAT_STRING_AS_BYTES;
+}
+
+int string_length(const char *str, int policy)
+{
+ g_return_val_if_fail(str != NULL, 0);
+
+ if (policy == -1) {
+ policy = string_policy(str);
+ }
+
+ if (policy == TREAT_STRING_AS_UTF8) {
+ return g_utf8_strlen(str, -1);
+ }
+ else {
+ /* Assume TREAT_STRING_AS_BYTES: */
+ return strlen(str);
+ }
+}
+
+int string_width(const char *str, int policy)
+{
+ int len;
+
+ g_return_val_if_fail(str != NULL, 0);
+
+ if (policy == -1) {
+ policy = string_policy(str);
+ }
+
+ len = 0;
+ while (*str != '\0') {
+ len += string_advance(&str, policy);
+ }
+ return len;
+}
+
+int string_chars_for_width(const char *str, int policy, unsigned int n, unsigned int *bytes)
+{
+ const char *c, *previous_c;
+ int str_width, char_width, char_count;
+
+ g_return_val_if_fail(str != NULL, -1);
+
+ /* Handle the dummy case where n is 0: */
+ if (n == 0) {
+ if (bytes != NULL) {
+ *bytes = 0;
+ }
+ return 0;
+ }
+
+ if (policy == -1) {
+ policy = string_policy(str);
+ }
+
+ /* Iterate over characters until we reach n: */
+ char_count = 0;
+ str_width = 0;
+ c = str;
+ while (*c != '\0') {
+ previous_c = c;
+ char_width = string_advance(&c, policy);
+ if (str_width + char_width > n) {
+ /* We stepped beyond n, get one step back and stop there: */
+ c = previous_c;
+ break;
+ }
+ ++ char_count;
+ str_width += char_width;
+ }
+ /* At this point, we know that char_count characters reach str_width
+ * columns, which is less than or equal to n. */
+
+ /* Optionally provide the equivalent amount of bytes: */
+ if (bytes != NULL) {
+ *bytes = c - str;
+ }
+ return char_count;
+}
diff --git a/src/core/utf8.h b/src/core/utf8.h
new file mode 100644
index 0000000..372c5d2
--- /dev/null
+++ b/src/core/utf8.h
@@ -0,0 +1,62 @@
+#ifndef IRSSI_CORE_UTF8_H
+#define IRSSI_CORE_UTF8_H
+
+/* XXX I didn't check the encoding range of big5+. This is standard big5. */
+#define is_big5_los(lo) (0x40 <= (lo) && (lo) <= 0x7E) /* standard */
+#define is_big5_lox(lo) (0x80 <= (lo) && (lo) <= 0xFE) /* extended */
+#define is_big5_lo(lo) ((is_big5_los(lo) || is_big5_lox(lo)))
+#define is_big5_hi(hi) (0x81 <= (hi) && (hi) <= 0xFE)
+#define is_big5(hi,lo) (is_big5_hi(hi) && is_big5_lo(lo))
+
+#include <glib.h>
+typedef guint32 unichar;
+
+/* Returns width for character (0-2). */
+int i_wcwidth(unichar c);
+
+/* Older variant of the above */
+int mk_wcwidth(unichar c);
+
+/* Signature for wcwidth implementations */
+typedef int (*WCWIDTH_FUNC) (unichar ucs);
+
+/* Advance the str pointer one character further; return the number of columns
+ * occupied by the skipped character.
+ */
+int string_advance(char const **str, int policy);
+
+/* TREAT_STRING_AS_BYTES means strings are to be treated using strncpy,
+ * strnlen, etc.
+ * TREAT_STRING_AS_UTF8 means strings are to be treated using g_utf8_*
+ * functions.
+ */
+enum str_policy {
+ TREAT_STRING_AS_BYTES,
+ TREAT_STRING_AS_UTF8
+};
+
+/* Return how the str string ought to be treated: TREAT_STRING_AS_UTF8 if the
+ * terminal handles UTF-8 and if the string appears to be a valid UTF-8 string;
+ * TREAT_STRING_AS_BYTES otherwise.
+ */
+int string_policy(const char *str);
+
+/* Return the length of the str string according to the given policy; if policy
+ * is -1, this function will call string_policy().
+ */
+int string_length(const char *str, int policy);
+/* Return the screen width of the str string according to the given policy; if
+ * policy is -1, this function will call string_policy().
+ */
+int string_width(const char *str, int policy);
+
+/* Return the amount of characters from str it takes to reach n columns, or -1 if
+ * str is NULL. Optionally return the equivalent amount of bytes.
+ * If policy is -1, this function will call string_policy().
+ */
+int string_chars_for_width(const char *str, int policy, unsigned int n, unsigned int *bytes);
+
+#define unichar_isprint(c) (((c) & ~0x80) >= 32)
+#define is_utf8_leading(c) (((c) & 0xc0) != 0x80)
+
+#endif
diff --git a/src/core/wcwidth-wrapper.c b/src/core/wcwidth-wrapper.c
new file mode 100644
index 0000000..b3881d3
--- /dev/null
+++ b/src/core/wcwidth-wrapper.c
@@ -0,0 +1,141 @@
+/*
+ wcwidth-wrapper.c : irssi
+
+ Copyright (C) 2018 dequis
+
+ 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.
+*/
+
+#define _GNU_SOURCE
+#include <wchar.h>
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/utf8.h>
+
+#ifdef HAVE_LIBUTF8PROC
+#include <utf8proc.h>
+#endif
+
+/* wcwidth=2 since unicode 5.2.0 */
+#define UNICODE_SQUARE_HIRAGANA_HOKA 0x1F200
+
+/* wcwidth=2 since unicode 9.0.0 */
+#define UNICODE_IRSSI_LOGO 0x1F525
+
+enum {
+ WCWIDTH_IMPL_OLD,
+ WCWIDTH_IMPL_SYSTEM
+#ifdef HAVE_LIBUTF8PROC
+ ,WCWIDTH_IMPL_JULIA
+#endif
+};
+
+WCWIDTH_FUNC wcwidth_impl_func = mk_wcwidth;
+
+int i_wcwidth(unichar ucs)
+{
+ return (*wcwidth_impl_func)(ucs);
+}
+
+static int system_wcwidth(unichar ucs)
+{
+ int retval = wcwidth((wchar_t) ucs);
+
+ if (retval < 0) {
+ /* Treat all unknown characters as taking one cell. This is
+ * the reason mk_wcwidth and other outdated implementations
+ * mostly worked with newer unicode, while glibc's wcwidth
+ * needs updating to recognize new characters.
+ *
+ * Instead of relying on that, we keep the behavior of assuming
+ * one cell even for glibc's implementation, which is still
+ * highly accurate and less of a headache overall.
+ */
+ return 1;
+ }
+
+ return retval;
+}
+
+#ifdef HAVE_LIBUTF8PROC
+/* wrapper because the function signatures are different
+ * (the parameter is unsigned for us, signed for them) */
+static int julia_wcwidth(unichar ucs)
+{
+ return utf8proc_charwidth(ucs);
+}
+#endif
+
+static void read_settings(void)
+{
+ static int choice = -1;
+ int newchoice;
+
+ newchoice = settings_get_choice("wcwidth_implementation");
+
+ if (choice == newchoice) {
+ return;
+ }
+
+ choice = newchoice;
+
+ switch (choice) {
+ case WCWIDTH_IMPL_OLD:
+ wcwidth_impl_func = &mk_wcwidth;
+ break;
+
+ case WCWIDTH_IMPL_SYSTEM:
+ wcwidth_impl_func = &system_wcwidth;
+ break;
+
+#ifdef HAVE_LIBUTF8PROC
+ case WCWIDTH_IMPL_JULIA:
+ wcwidth_impl_func = &julia_wcwidth;
+ break;
+#endif
+ }
+
+}
+
+void wcwidth_wrapper_init(void)
+{
+ int wcwidth_impl_default = 0;
+ /* Test against characters that have wcwidth=2
+ * since unicode 5.2 and 9.0 respectively */
+
+ if (system_wcwidth(UNICODE_SQUARE_HIRAGANA_HOKA) == 2 ||
+ system_wcwidth(UNICODE_IRSSI_LOGO) == 2) {
+ wcwidth_impl_default = WCWIDTH_IMPL_SYSTEM;
+ } else {
+ /* Fall back to our own (which implements 5.0) */
+ wcwidth_impl_default = WCWIDTH_IMPL_OLD;
+ }
+
+#ifdef HAVE_LIBUTF8PROC
+ settings_add_choice("misc", "wcwidth_implementation", wcwidth_impl_default, "old;system;julia");
+#else
+ settings_add_choice("misc", "wcwidth_implementation", wcwidth_impl_default, "old;system");
+#endif
+
+ read_settings();
+ signal_add_first("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void wcwidth_wrapper_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/core/wcwidth.c b/src/core/wcwidth.c
new file mode 100644
index 0000000..5226d69
--- /dev/null
+++ b/src/core/wcwidth.c
@@ -0,0 +1,220 @@
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-25 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <irssi/src/core/utf8.h>
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(unichar ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_wcwidth(unichar ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+
+
+#if 0
+int mk_wcswidth(const unichar *pwcs, size_t n)
+{
+ int w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+
+ return width;
+}
+#endif
diff --git a/src/core/window-item-def.h b/src/core/window-item-def.h
new file mode 100644
index 0000000..6944c92
--- /dev/null
+++ b/src/core/window-item-def.h
@@ -0,0 +1,9 @@
+#ifndef IRSSI_CORE_WINDOW_ITEM_DEF_H
+#define IRSSI_CORE_WINDOW_ITEM_DEF_H
+
+#define STRUCT_SERVER_REC SERVER_REC
+struct _WI_ITEM_REC {
+#include <irssi/src/core/window-item-rec.h>
+};
+
+#endif
diff --git a/src/core/window-item-rec.h b/src/core/window-item-rec.h
new file mode 100644
index 0000000..7bf5ba5
--- /dev/null
+++ b/src/core/window-item-rec.h
@@ -0,0 +1,21 @@
+/* WI_ITEM_REC definition, used for inheritance */
+
+int type; /* module_get_uniq_id("CHANNEL/QUERY/xxx", 0) */
+int chat_type; /* chat_protocol_lookup(xx) */
+GHashTable *module_data;
+
+void *window;
+STRUCT_SERVER_REC *server;
+char *visible_name;
+char *name;
+time_t createtime;
+int data_level;
+char *hilight_color;
+
+void (*destroy)(WI_ITEM_REC *item);
+
+const char *(*get_target)(WI_ITEM_REC *item);
+#define window_item_get_target(item) \
+ ((item)->get_target(item))
+
+#undef STRUCT_SERVER_REC
diff --git a/src/core/write-buffer.c b/src/core/write-buffer.c
new file mode 100644
index 0000000..a140154
--- /dev/null
+++ b/src/core/write-buffer.c
@@ -0,0 +1,191 @@
+/*
+ write-buffer.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/commands.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/write-buffer.h>
+
+#define BUFFER_BLOCK_SIZE 2048
+
+typedef struct {
+ char *active_block;
+ int active_block_pos;
+
+ GSList *blocks;
+} BUFFER_REC;
+
+static GSList *empty_blocks;
+static GHashTable *buffers;
+static int block_count;
+
+static int write_buffer_max_blocks;
+static int timeout_tag;
+
+static void write_buffer_new_block(BUFFER_REC *rec)
+{
+ char *block;
+
+ if (empty_blocks == NULL)
+ block = g_malloc(BUFFER_BLOCK_SIZE);
+ else {
+ block = empty_blocks->data;
+ empty_blocks = g_slist_remove(empty_blocks, block);
+ }
+
+ block_count++;
+ rec->active_block = block;
+ rec->active_block_pos = 0;
+ rec->blocks = g_slist_append(rec->blocks, block);
+}
+
+int write_buffer(int handle, const void *data, int size)
+{
+ BUFFER_REC *rec;
+ const char *cdata = data;
+ int next_size;
+
+ if (size <= 0)
+ return size;
+
+ if (write_buffer_max_blocks <= 0) {
+ /* no write buffer */
+ return write(handle, data, size);
+ }
+
+ rec = g_hash_table_lookup(buffers, GINT_TO_POINTER(handle));
+ if (rec == NULL) {
+ rec = g_new0(BUFFER_REC, 1);
+ write_buffer_new_block(rec);
+ g_hash_table_insert(buffers, GINT_TO_POINTER(handle), rec);
+ }
+
+ while (size > 0) {
+ if (rec->active_block_pos == BUFFER_BLOCK_SIZE)
+ write_buffer_new_block(rec);
+
+ next_size = size < BUFFER_BLOCK_SIZE-rec->active_block_pos ?
+ size : BUFFER_BLOCK_SIZE-rec->active_block_pos;
+ memcpy(rec->active_block+rec->active_block_pos,
+ cdata, next_size);
+
+ rec->active_block_pos += next_size;
+ cdata += next_size;
+ size -= next_size;
+ }
+
+ if (block_count > write_buffer_max_blocks)
+ write_buffer_flush();
+
+ return size;
+}
+
+static int write_buffer_flush_rec(void *handlep, BUFFER_REC *rec)
+{
+ GSList *tmp;
+ int handle, size;
+
+ handle = GPOINTER_TO_INT(handlep);
+ for (tmp = rec->blocks; tmp != NULL; tmp = tmp->next) {
+ size = tmp->data != rec->active_block ? BUFFER_BLOCK_SIZE :
+ rec->active_block_pos;
+ if (write(handle, tmp->data, size) != size) {
+ g_warning("Failed to write(): %s", strerror(errno));
+ }
+ }
+
+ empty_blocks = g_slist_concat(empty_blocks, rec->blocks);
+ g_free(rec);
+ return TRUE;
+}
+
+void write_buffer_flush(void)
+{
+ g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+ g_slist_free(empty_blocks);
+ empty_blocks = NULL;
+
+ g_hash_table_foreach_remove(buffers,
+ (GHRFunc) write_buffer_flush_rec, NULL);
+ block_count = 0;
+}
+
+static int flush_timeout(void)
+{
+ write_buffer_flush();
+ return 1;
+}
+
+static void read_settings(void)
+{
+ write_buffer_flush();
+
+ write_buffer_max_blocks =
+ settings_get_size("write_buffer_size") / BUFFER_BLOCK_SIZE;
+
+ if (settings_get_time("write_buffer_timeout") > 0) {
+ if (timeout_tag == -1) {
+ timeout_tag = g_timeout_add(settings_get_time("write_buffer_timeout"),
+ (GSourceFunc) flush_timeout,
+ NULL);
+ }
+ } else if (timeout_tag != -1) {
+ g_source_remove(timeout_tag);
+ timeout_tag = -1;
+ }
+}
+
+static void cmd_flushbuffer(void)
+{
+ write_buffer_flush();
+}
+
+void write_buffer_init(void)
+{
+ settings_add_time("misc", "write_buffer_timeout", "0");
+ settings_add_size("misc", "write_buffer_size", "0");
+
+ buffers = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+
+ empty_blocks = NULL;
+ block_count = 0;
+
+ timeout_tag = -1;
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ command_bind("flushbuffer", NULL, (SIGNAL_FUNC) cmd_flushbuffer);
+}
+
+void write_buffer_deinit(void)
+{
+ if (timeout_tag != -1)
+ g_source_remove(timeout_tag);
+
+ write_buffer_flush();
+ g_hash_table_destroy(buffers);
+
+ g_slist_foreach(empty_blocks, (GFunc) g_free, NULL);
+ g_slist_free(empty_blocks);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ command_unbind("flushbuffer", (SIGNAL_FUNC) cmd_flushbuffer);
+}
diff --git a/src/core/write-buffer.h b/src/core/write-buffer.h
new file mode 100644
index 0000000..8c60dc7
--- /dev/null
+++ b/src/core/write-buffer.h
@@ -0,0 +1,10 @@
+#ifndef IRSSI_CORE_WRITE_BUFFER_H
+#define IRSSI_CORE_WRITE_BUFFER_H
+
+int write_buffer(int handle, const void *data, int size);
+void write_buffer_flush(void);
+
+void write_buffer_init(void);
+void write_buffer_deinit(void);
+
+#endif