diff options
Diffstat (limited to 'src/core')
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(©, "/"); + 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 |