diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:18:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:18:39 +0000 |
commit | fff5217f02d91268ce90c8c05665602c059faaef (patch) | |
tree | 2ba24d32dc96eafe7ed0a85269548e76796d849d /src/irc/dcc | |
parent | Initial commit. (diff) | |
download | irssi-upstream.tar.xz irssi-upstream.zip |
Adding upstream version 1.4.5.upstream/1.4.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/irc/dcc')
-rw-r--r-- | src/irc/dcc/Makefile.am | 30 | ||||
-rw-r--r-- | src/irc/dcc/Makefile.in | 758 | ||||
-rw-r--r-- | src/irc/dcc/dcc-autoget.c | 97 | ||||
-rw-r--r-- | src/irc/dcc/dcc-chat.c | 873 | ||||
-rw-r--r-- | src/irc/dcc/dcc-chat.h | 40 | ||||
-rw-r--r-- | src/irc/dcc/dcc-file-rec.h | 9 | ||||
-rw-r--r-- | src/irc/dcc/dcc-file.h | 10 | ||||
-rw-r--r-- | src/irc/dcc/dcc-get.c | 627 | ||||
-rw-r--r-- | src/irc/dcc/dcc-get.h | 45 | ||||
-rw-r--r-- | src/irc/dcc/dcc-queue.c | 227 | ||||
-rw-r--r-- | src/irc/dcc/dcc-queue.h | 45 | ||||
-rw-r--r-- | src/irc/dcc/dcc-rec.h | 27 | ||||
-rw-r--r-- | src/irc/dcc/dcc-resume.c | 248 | ||||
-rw-r--r-- | src/irc/dcc/dcc-send.c | 487 | ||||
-rw-r--r-- | src/irc/dcc/dcc-send.h | 27 | ||||
-rw-r--r-- | src/irc/dcc/dcc-server.c | 413 | ||||
-rw-r--r-- | src/irc/dcc/dcc-server.h | 29 | ||||
-rw-r--r-- | src/irc/dcc/dcc.c | 600 | ||||
-rw-r--r-- | src/irc/dcc/dcc.h | 67 | ||||
-rw-r--r-- | src/irc/dcc/meson.build | 31 | ||||
-rw-r--r-- | src/irc/dcc/module.h | 4 |
21 files changed, 4694 insertions, 0 deletions
diff --git a/src/irc/dcc/Makefile.am b/src/irc/dcc/Makefile.am new file mode 100644 index 0000000..c5592f7 --- /dev/null +++ b/src/irc/dcc/Makefile.am @@ -0,0 +1,30 @@ +noinst_LIBRARIES = libirc_dcc.a + +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_dcc_a_SOURCES = \ + dcc.c \ + dcc-chat.c \ + dcc-get.c \ + dcc-send.c \ + dcc-resume.c \ + dcc-autoget.c \ + dcc-queue.c \ + dcc-server.c + +pkginc_irc_dccdir=$(pkgincludedir)/src/irc/dcc +pkginc_irc_dcc_HEADERS = \ + dcc-rec.h \ + dcc-file-rec.h \ + dcc.h \ + dcc-file.h \ + dcc-chat.h \ + dcc-get.h \ + dcc-send.h \ + dcc-queue.h \ + module.h \ + dcc-server.h + +EXTRA_DIST = meson.build diff --git a/src/irc/dcc/Makefile.in b/src/irc/dcc/Makefile.in new file mode 100644 index 0000000..0a0bfa4 --- /dev/null +++ b/src/irc/dcc/Makefile.in @@ -0,0 +1,758 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/irc/dcc +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \ + $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_irc_dcc_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/irssi-config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LIBRARIES = $(noinst_LIBRARIES) +ARFLAGS = cru +AM_V_AR = $(am__v_AR_@AM_V@) +am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@) +am__v_AR_0 = @echo " AR " $@; +am__v_AR_1 = +libirc_dcc_a_AR = $(AR) $(ARFLAGS) +libirc_dcc_a_LIBADD = +am_libirc_dcc_a_OBJECTS = dcc.$(OBJEXT) dcc-chat.$(OBJEXT) \ + dcc-get.$(OBJEXT) dcc-send.$(OBJEXT) dcc-resume.$(OBJEXT) \ + dcc-autoget.$(OBJEXT) dcc-queue.$(OBJEXT) dcc-server.$(OBJEXT) +libirc_dcc_a_OBJECTS = $(am_libirc_dcc_a_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/dcc-autoget.Po \ + ./$(DEPDIR)/dcc-chat.Po ./$(DEPDIR)/dcc-get.Po \ + ./$(DEPDIR)/dcc-queue.Po ./$(DEPDIR)/dcc-resume.Po \ + ./$(DEPDIR)/dcc-send.Po ./$(DEPDIR)/dcc-server.Po \ + ./$(DEPDIR)/dcc.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libirc_dcc_a_SOURCES) +DIST_SOURCES = $(libirc_dcc_a_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(pkginc_irc_dccdir)" +HEADERS = $(pkginc_irc_dcc_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHAT_MODULES = @CHAT_MODULES@ +COMMON_LIBS = @COMMON_LIBS@ +COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +FUZZER_LIBS = @FUZZER_LIBS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GLIB_LIBS = @GLIB_LIBS@ +GLIB_MKENUMS = @GLIB_MKENUMS@ +GOBJECT_QUERY = @GOBJECT_QUERY@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBOTR_CFLAGS = @LIBOTR_CFLAGS@ +LIBOTR_LIBS = @LIBOTR_LIBS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +OTR_CFLAGS = @OTR_CFLAGS@ +OTR_LDFLAGS = @OTR_LDFLAGS@ +OTR_LINK_FLAGS = @OTR_LINK_FLAGS@ +OTR_LINK_LIBS = @OTR_LINK_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL_CFLAGS = @PERL_CFLAGS@ +PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@ +PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@ +PERL_LDFLAGS = @PERL_LDFLAGS@ +PERL_LINK_FLAGS = @PERL_LINK_FLAGS@ +PERL_LINK_LIBS = @PERL_LINK_LIBS@ +PERL_MM_OPT = @PERL_MM_OPT@ +PERL_MM_PARAMS = @PERL_MM_PARAMS@ +PERL_STATIC_LIBS = @PERL_STATIC_LIBS@ +PERL_USE_LIB = @PERL_USE_LIB@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROG_LIBS = @PROG_LIBS@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TEXTUI_LIBS = @TEXTUI_LIBS@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +installed_test_metadir = @installed_test_metadir@ +installed_testdir = @installed_testdir@ +irc_MODULES = @irc_MODULES@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +otr_module_lib = @otr_module_lib@ +otr_static_lib = @otr_static_lib@ +pdfdir = @pdfdir@ +perl_module_fe_lib = @perl_module_fe_lib@ +perl_module_lib = @perl_module_lib@ +perl_static_fe_lib = @perl_static_fe_lib@ +perl_static_lib = @perl_static_lib@ +perlpath = @perlpath@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sedpath = @sedpath@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LIBRARIES = libirc_dcc.a +AM_CPPFLAGS = \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) + +libirc_dcc_a_SOURCES = \ + dcc.c \ + dcc-chat.c \ + dcc-get.c \ + dcc-send.c \ + dcc-resume.c \ + dcc-autoget.c \ + dcc-queue.c \ + dcc-server.c + +pkginc_irc_dccdir = $(pkgincludedir)/src/irc/dcc +pkginc_irc_dcc_HEADERS = \ + dcc-rec.h \ + dcc-file-rec.h \ + dcc.h \ + dcc-file.h \ + dcc-chat.h \ + dcc-get.h \ + dcc-send.h \ + dcc-queue.h \ + module.h \ + dcc-server.h + +EXTRA_DIST = meson.build +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/irc/dcc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/irc/dcc/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +libirc_dcc.a: $(libirc_dcc_a_OBJECTS) $(libirc_dcc_a_DEPENDENCIES) $(EXTRA_libirc_dcc_a_DEPENDENCIES) + $(AM_V_at)-rm -f libirc_dcc.a + $(AM_V_AR)$(libirc_dcc_a_AR) libirc_dcc.a $(libirc_dcc_a_OBJECTS) $(libirc_dcc_a_LIBADD) + $(AM_V_at)$(RANLIB) libirc_dcc.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-autoget.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-chat.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-get.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-resume.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-send.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dcc.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-pkginc_irc_dccHEADERS: $(pkginc_irc_dcc_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginc_irc_dcc_HEADERS)'; test -n "$(pkginc_irc_dccdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_irc_dccdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkginc_irc_dccdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_irc_dccdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_irc_dccdir)" || exit $$?; \ + done + +uninstall-pkginc_irc_dccHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginc_irc_dcc_HEADERS)'; test -n "$(pkginc_irc_dccdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkginc_irc_dccdir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkginc_irc_dccdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dcc-autoget.Po + -rm -f ./$(DEPDIR)/dcc-chat.Po + -rm -f ./$(DEPDIR)/dcc-get.Po + -rm -f ./$(DEPDIR)/dcc-queue.Po + -rm -f ./$(DEPDIR)/dcc-resume.Po + -rm -f ./$(DEPDIR)/dcc-send.Po + -rm -f ./$(DEPDIR)/dcc-server.Po + -rm -f ./$(DEPDIR)/dcc.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-pkginc_irc_dccHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/dcc-autoget.Po + -rm -f ./$(DEPDIR)/dcc-chat.Po + -rm -f ./$(DEPDIR)/dcc-get.Po + -rm -f ./$(DEPDIR)/dcc-queue.Po + -rm -f ./$(DEPDIR)/dcc-resume.Po + -rm -f ./$(DEPDIR)/dcc-send.Po + -rm -f ./$(DEPDIR)/dcc-server.Po + -rm -f ./$(DEPDIR)/dcc.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-pkginc_irc_dccHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-pkginc_irc_dccHEADERS install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-pkginc_irc_dccHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/irc/dcc/dcc-autoget.c b/src/irc/dcc/dcc-autoget.c new file mode 100644 index 0000000..ba0bed0 --- /dev/null +++ b/src/irc/dcc/dcc-autoget.c @@ -0,0 +1,97 @@ +/* + dcc-autoget.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/masks.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/servers.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/dcc/dcc-get.h> + +static void sig_dcc_request(GET_DCC_REC *dcc, const char *nickaddr) +{ + struct stat statbuf; + const char *masks; + char *str, *file, *esc_arg; + int max_size; + + if (!IS_DCC_GET(dcc)) return; + + /* check if we want to autoget file offer */ + if (!settings_get_bool("dcc_autoget")) + return; + + /* check for lowports */ + if (dcc->port < 1024 && !settings_get_bool("dcc_autoaccept_lowports")) + return; + + /* check that autoget masks match */ + masks = settings_get_str("dcc_autoget_masks"); + if (*masks != '\0' && + !masks_match(SERVER(dcc->server), masks, dcc->nick, nickaddr)) + return; + + /* Unless specifically said in dcc_autoget_masks, don't do autogets + sent to channels. */ + if (*masks == '\0' && dcc->target != NULL && server_ischannel(SERVER(dcc->server), dcc->target)) + return; + + /* don't autoget files beginning with a dot, if download dir is + our home dir (stupid kludge for stupid people) */ + if (*dcc->arg == '.' && + g_strcmp0(settings_get_str("dcc_download_path"), "~") == 0) + return; + + /* check file size limit, NOTE: it's still possible to send a + bogus file size and then just send what ever sized file.. */ + max_size = settings_get_size("dcc_autoget_max_size"); + if (max_size > 0 && (uoff_t)max_size < dcc->size) + return; + + /* ok. but do we want/need to resume? */ + file = dcc_get_download_path(dcc->arg); + esc_arg = escape_string(dcc->arg); + str = g_strdup_printf(settings_get_bool("dcc_autoresume") && + stat(file, &statbuf) == 0 ? + "RESUME %s \"%s\"" : "GET %s \"%s\"", + dcc->nick, esc_arg); + signal_emit("command dcc", 2, str, dcc->server); + g_free(esc_arg); + g_free(file); + g_free(str); +} + +void dcc_autoget_init(void) +{ + settings_add_bool("dcc", "dcc_autoget", FALSE); + settings_add_bool("dcc", "dcc_autoaccept_lowports", FALSE); + settings_add_bool("dcc", "dcc_autoresume", FALSE); + settings_add_size("dcc", "dcc_autoget_max_size", "0k"); + settings_add_str("dcc", "dcc_autoget_masks", ""); + + signal_add_last("dcc request", (SIGNAL_FUNC) sig_dcc_request); +} + +void dcc_autoget_deinit(void) +{ + signal_remove("dcc request", (SIGNAL_FUNC) sig_dcc_request); +} diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c new file mode 100644 index 0000000..68da0e2 --- /dev/null +++ b/src/irc/dcc/dcc-chat.c @@ -0,0 +1,873 @@ +/* + dcc-chat.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/recode.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/irc/core/irc-queries.h> +#include <irssi/src/core/masks.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> + +static char *dcc_chat_get_new_id(const char *nick) +{ + char *id; + int num; + + g_return_val_if_fail(nick != NULL, NULL); + + if (dcc_chat_find_id(nick) == NULL) { + /* same as nick, good */ + return g_strdup(nick); + } + + /* keep adding numbers after nick until some of them isn't found */ + for (num = 2;; num++) { + id = g_strdup_printf("%s%d", nick, num); + if (dcc_chat_find_id(id) == NULL) + return id; + g_free(id); + } +} + +CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, + CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + CHAT_DCC_REC *dcc; + + dcc = g_new0(CHAT_DCC_REC, 1); + dcc->orig_type = dcc->type = DCC_CHAT_TYPE; + dcc->mirc_ctcp = settings_get_bool("dcc_mirc_ctcp"); + dcc->id = dcc_chat_get_new_id(nick); + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change init_rec API */ + g_free(dcc->id); + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void dcc_remove_chat_refs(CHAT_DCC_REC *dcc) +{ + GSList *tmp; + + g_return_if_fail(dcc != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *rec = tmp->data; + + if (rec->chat == dcc) + rec->chat = NULL; + } +} + +static void sig_dcc_destroyed(CHAT_DCC_REC *dcc) +{ + if (!IS_DCC_CHAT(dcc)) return; + + dcc_remove_chat_refs(dcc); + + if (dcc->sendbuf != NULL) net_sendbuffer_destroy(dcc->sendbuf, FALSE); + g_free(dcc->id); +} + +CHAT_DCC_REC *dcc_chat_find_id(const char *id) +{ + GSList *tmp; + + g_return_val_if_fail(id != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + CHAT_DCC_REC *dcc = tmp->data; + + if (IS_DCC_CHAT(dcc) && dcc->id != NULL && + g_ascii_strcasecmp(dcc->id, id) == 0) + return dcc; + } + + return NULL; +} + +static CHAT_DCC_REC *dcc_chat_find_nick(IRC_SERVER_REC *server, + const char *nick) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + CHAT_DCC_REC *dcc = tmp->data; + + if (IS_DCC_CHAT(dcc) && dcc->server == server && + g_ascii_strcasecmp(dcc->nick, nick) == 0) + return dcc; + } + + return NULL; +} + +/* Send `data' to dcc chat. */ +void dcc_chat_send(CHAT_DCC_REC *dcc, const char *data) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + g_return_if_fail(dcc->sendbuf != NULL); + g_return_if_fail(data != NULL); + + net_sendbuffer_send(dcc->sendbuf, data, strlen(data)); + net_sendbuffer_send(dcc->sendbuf, "\n", 1); +} + +/* Send a CTCP message/notify to target. + Send the CTCP via DCC chat if `chat' is specified. */ +void dcc_ctcp_message(IRC_SERVER_REC *server, const char *target, + CHAT_DCC_REC *chat, int notice, const char *msg) +{ + char *str; + char *recoded; + + if (chat != NULL && chat->sendbuf != NULL) { + /* send it via open DCC chat */ + recoded = recode_out(SERVER(server), msg, chat->nick); + str = g_strdup_printf("%s\001%s\001", chat->mirc_ctcp ? "" : + notice ? "CTCP_REPLY " : + "CTCP_MESSAGE ", recoded); + dcc_chat_send(chat, str); + g_free(str); + } else { + recoded = recode_out(SERVER(server), msg, target); + irc_send_cmdv(server, "%s %s :\001%s\001", + notice ? "NOTICE" : "PRIVMSG", target, recoded); + } + g_free(recoded); +} + +/* If `item' is a query of a =nick, return DCC chat record of nick */ +CHAT_DCC_REC *item_get_dcc(WI_ITEM_REC *item) +{ + QUERY_REC *query; + + query = IRC_QUERY(item); + if (query == NULL || *query->name != '=') + return NULL; + + return dcc_chat_find_id(query->name+1); +} + +/* Send text to DCC chat */ +static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + GHashTable *optlist; + char *text, *target; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_UNKNOWN_OPTIONS | + PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "msg", + &optlist, &target, &text)) + return; + + /* handle only DCC messages */ + if (g_strcmp0(target, "*") == 0) + dcc = item_get_dcc(item); + else if (*target == '=') + dcc = dcc_chat_find_id(target+1); + else + dcc = NULL; + + if (dcc != NULL && dcc->sendbuf != NULL) { + char *recoded; + + recoded = recode_out(server, text, dcc->nick); + dcc_chat_send(dcc, recoded); + g_free(recoded); + } + + if (dcc != NULL || *target == '=') + signal_stop(); + + cmd_params_free(free_arg); +} + +static void cmd_me(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + char *str; + + g_return_if_fail(data != NULL); + + dcc = item_get_dcc(item); + if (dcc == NULL) return; + + str = g_strconcat("ACTION ", data, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + + signal_stop(); +} + +static void cmd_action(const char *data, IRC_SERVER_REC *server) +{ + CHAT_DCC_REC *dcc; + char *target, *text, *str; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (*data != '=') { + /* handle only DCC actions */ + return; + } + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, + &target, &text)) + return; + if (*target == '\0' || *text == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc = dcc_chat_find_id(target+1); + if (dcc != NULL) { + str = g_strconcat("ACTION ", text, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + } + + cmd_params_free(free_arg); + signal_stop(); +} + +static void cmd_ctcp(const char *data, IRC_SERVER_REC *server) +{ + CHAT_DCC_REC *dcc; + char *target, *ctcpcmd, *ctcpdata, *str; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &target, &ctcpcmd, &ctcpdata)) + return; + if (*target == '\0' || *ctcpcmd == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*target != '=') { + /* handle only DCC CTCPs */ + cmd_params_free(free_arg); + return; + } + + dcc = dcc_chat_find_id(target+1); + if (dcc != NULL) { + ascii_strup(ctcpcmd); + + str = g_strconcat(ctcpcmd, " ", ctcpdata, NULL); + dcc_ctcp_message(server, dcc->nick, dcc, FALSE, str); + g_free(str); + } + + cmd_params_free(free_arg); + signal_stop(); +} + +/* input function: DCC CHAT received some data.. */ +void dcc_chat_input(CHAT_DCC_REC *dcc) +{ + char *str; + int ret; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + do { + ret = net_sendbuffer_receive_line(dcc->sendbuf, &str, 1); + + if (ret == -1) { + /* connection lost */ + dcc->connection_lost = TRUE; + dcc_close(DCC(dcc)); + break; + } + + if (ret > 0) { + SERVER_REC *server; + char *recoded; + + dcc->transfd += ret; + + server = SERVER(dcc->server); + recoded = recode_in(server, str, dcc->nick); + signal_emit("dcc chat message", 2, dcc, recoded); + g_free(recoded); + if (server != NULL) + server_meta_clear_all(server); + } + } while (ret > 0); +} + +/* input function: DCC CHAT - someone tried to connect to our socket */ +static void dcc_chat_listen(CHAT_DCC_REC *dcc) +{ + IPADDR ip; + GIOChannel *handle; + int port; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + /* accept connection */ + handle = net_accept(dcc->handle, &ip, &port); + if (handle == NULL) + return; + + /* TODO: add paranoia check - see dcc-files.c */ + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + dcc->sendbuf = net_sendbuffer_create(handle, 0); + memcpy(&dcc->addr, &ip, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + dcc->tagread = i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* callback: DCC CHAT - connect finished */ +static void sig_chat_connected(CHAT_DCC_REC *dcc) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (net_geterror(dcc->handle) != 0) { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + return; + } + + /* connect ok. */ + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->sendbuf = net_sendbuffer_create(dcc->handle, 0); + dcc->tagread = i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_input, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +static void dcc_chat_connect(CHAT_DCC_REC *dcc) +{ + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (dcc->addrstr[0] == '\0' || + dcc->starttime != 0 || dcc->handle != NULL) { + /* already sent a chat request / already chatting */ + return; + } + + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + if (dcc->handle != NULL) { + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_WRITE | I_INPUT_READ, + (GInputFunction) sig_chat_connected, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static void dcc_chat_passive(CHAT_DCC_REC *dcc) +{ + IPADDR own_ip; + int port; + GIOChannel *handle; + char host[MAX_IP_LEN]; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + + if (dcc->addrstr[0] == '\0' || + dcc->starttime != 0 || dcc->handle != NULL) { + /* already sent a chat request / already chatting */ + return; + } + + handle = dcc_listen(net_sendbuffer_handle(dcc->server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_return_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_listen, dcc); + + /* Let's send the reply to the other client! */ + dcc_ip2str(&own_ip, host); + irc_send_cmdv(dcc->server, "PRIVMSG %s :\001DCC CHAT CHAT %s %d %d\001", + dcc->nick, host, port, dcc->pasv_id); + +} + +/* SYNTAX: DCC CHAT [-passive] [<nick>] */ +static void cmd_dcc_chat(const char *data, IRC_SERVER_REC *server) +{ + void *free_arg; + CHAT_DCC_REC *dcc; + IPADDR own_ip; + GIOChannel *handle; + GHashTable *optlist; + int p_id; + char *nick, host[MAX_IP_LEN]; + int port; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "dcc chat", &optlist, &nick)) + return; + + if (*nick == '\0') { + dcc = DCC_CHAT(dcc_find_request_latest(DCC_CHAT_TYPE)); + if (dcc != NULL) { + if (!dcc_is_passive(dcc)) + dcc_chat_connect(dcc); + else + dcc_chat_passive(dcc); + } + cmd_params_free(free_arg); + return; + } + + dcc = dcc_chat_find_id(nick); + if (dcc != NULL && dcc_is_waiting_user(dcc)) { + if (!dcc_is_passive(dcc)) { + /* found from dcc chat requests, + we're the connecting side */ + dcc_chat_connect(dcc); + } else { + /* We are accepting a passive DCC CHAT. */ + dcc_chat_passive(dcc); + } + cmd_params_free(free_arg); + return; + } + + if (dcc != NULL && dcc_is_listening(dcc) && + dcc->server == server) { + /* sending request again even while old request is + still waiting, remove it. */ + dcc_destroy(DCC(dcc)); + } + + if (!IS_IRC_SERVER(server) || !server->connected) + cmd_param_error(CMDERR_NOT_CONNECTED); + + dcc = dcc_chat_create(server, NULL, nick, "chat"); + if (dcc == NULL) { + cmd_params_free(free_arg); + g_warn_if_reached(); + return; + } + + if (g_hash_table_lookup(optlist, "passive") == NULL) { + /* Standard DCC CHAT... let's listen for incoming connections */ + handle = dcc_listen(net_sendbuffer_handle(server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_param_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_chat_listen, dcc); + + /* send the chat request */ + signal_emit("dcc request send", 1, dcc); + + dcc_ip2str(&own_ip, host); + irc_send_cmdv(server, "PRIVMSG %s :\001DCC CHAT CHAT %s %d\001", + nick, host, port); + } else { + /* Passive protocol... we want the other side to listen */ + /* send the chat request */ + dcc->port = 0; + signal_emit("dcc request send", 1, dcc); + + /* generate a random id */ + p_id = rand() % 64; + dcc->pasv_id = p_id; + + /* 16843009 is the long format of 1.1.1.1, we use a fake IP + since the other side shouldn't care of it: they will send + the address for us to connect to in the reply */ + irc_send_cmdv(server, + "PRIVMSG %s :\001DCC CHAT CHAT 16843009 0 %d\001", + nick, p_id); + } + cmd_params_free(free_arg); +} + +/* SYNTAX: MIRCDCC ON|OFF */ +static void cmd_mircdcc(const char *data, SERVER_REC *server, + QUERY_REC *item) +{ + CHAT_DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + dcc = item_get_dcc((WI_ITEM_REC *) item); + if (dcc == NULL) return; + + dcc->mirc_ctcp = i_toupper(*data) != 'N' && + g_ascii_strncasecmp(data, "OF", 2) != 0; +} + +/* DCC CLOSE CHAT <nick> - check only from chat_ids in open DCC chats, + the default handler will check from DCC chat requests */ +static void cmd_dcc_close(char *data, SERVER_REC *server) +{ + GSList *tmp, *next; + char *nick; + void *free_arg; + int found; + + g_return_if_fail(data != NULL); + + if (g_ascii_strncasecmp(data, "CHAT ", 5) != 0 || + !cmd_get_params(data, &free_arg, 2, NULL, &nick)) + return; + + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + CHAT_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_CHAT(dcc) && dcc->id != NULL && + g_ascii_strcasecmp(dcc->id, nick) == 0) { + found = TRUE; + if (!dcc_is_connected(dcc) && IS_IRC_SERVER(server)) + dcc_reject(DCC(dcc), IRC_SERVER(server)); + else { + /* don't send DCC REJECT after DCC chat + is already open */ + dcc_close(DCC(dcc)); + } + } + } + + if (found) signal_stop(); + + cmd_params_free(free_arg); +} + +static void cmd_whois(const char *data, SERVER_REC *server, + WI_ITEM_REC *item) +{ + CHAT_DCC_REC *dcc; + + g_return_if_fail(data != NULL); + + /* /WHOIS without target in DCC CHAT query? */ + if (*data == '\0') { + dcc = item_get_dcc(item); + if (dcc != NULL) { + signal_emit("command whois", 3, + dcc->nick, server, item); + signal_stop(); + } + } +} + +#define DCC_AUTOACCEPT_PORT(dcc) \ + ((dcc)->port >= 1024 || settings_get_bool("dcc_autoaccept_lowports")) + +#define DCC_CHAT_AUTOACCEPT(dcc, server, nick, addr) \ + (DCC_AUTOACCEPT_PORT(dcc) && \ + masks_match(SERVER(server), \ + settings_get_str("dcc_autochat_masks"), (nick), (addr))) + + +/* CTCP: DCC CHAT */ +static void ctcp_msg_dcc_chat(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, CHAT_DCC_REC *chat) +{ + CHAT_DCC_REC *dcc; + char **params; + int paramcount; + int passive, autoallow = FALSE; + + /* CHAT <unused> <address> <port> */ + /* CHAT <unused> <address> 0 <id> (DCC CHAT passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + return; + } + passive = paramcount == 4 && g_strcmp0(params[2], "0") == 0; + + if (nick == NULL) + nick = ""; + + dcc = DCC_CHAT(dcc_find_request(DCC_CHAT_TYPE, nick, NULL)); + if (dcc != NULL) { + if (dcc_is_listening(dcc)) { + /* we requested dcc chat, they requested + dcc chat from us .. allow it. */ + dcc_destroy(DCC(dcc)); + autoallow = TRUE; + } else if (!dcc_is_passive(dcc)) { + /* we already have one dcc chat request + from this nick, remove it. */ + dcc_destroy(DCC(dcc)); + } else if (passive) { + if (dcc->pasv_id != atoi(params[3])) { + /* IDs don't match! */ + dcc_destroy(DCC(dcc)); + } else { + /* IDs are ok! Update address and port and + connect! */ + dcc->target = g_strdup(target); + dcc->port = atoi(params[2]); + dcc_str2ip(params[1], &dcc->addr); + net_ip2host(&dcc->addr, dcc->addrstr); + + dcc_chat_connect(dcc); + g_strfreev(params); + return; + } + } + } + + dcc = dcc_chat_create(server, chat, nick, params[0]); + if (dcc == NULL) { + g_strfreev(params); + g_warn_if_reached(); + return; + } + dcc->target = g_strdup(target); + dcc->port = atoi(params[2]); + + if (passive) + dcc->pasv_id = atoi(params[3]); + + dcc_str2ip(params[1], &dcc->addr); + net_ip2host(&dcc->addr, dcc->addrstr); + + signal_emit("dcc request", 2, dcc, addr); + + if (autoallow || DCC_CHAT_AUTOACCEPT(dcc, server, nick, addr)) { + if (passive) { + /* Passive DCC... let's set up a listening socket + and send reply back */ + dcc_chat_passive(dcc); + } else { + dcc_chat_connect(dcc); + } + } + g_strfreev(params); +} + +/* DCC CHAT: text received */ +static void dcc_chat_msg(CHAT_DCC_REC *dcc, const char *msg) +{ + char *event, *cmd, *ptr; + int reply; + + g_return_if_fail(IS_DCC_CHAT(dcc)); + g_return_if_fail(msg != NULL); + + reply = FALSE; + if (g_ascii_strncasecmp(msg, "CTCP_MESSAGE ", 13) == 0) { + /* bitchx (and ircii?) sends this */ + msg += 13; + dcc->mirc_ctcp = FALSE; + } else if (g_ascii_strncasecmp(msg, "CTCP_REPLY ", 11) == 0) { + /* bitchx (and ircii?) sends this */ + msg += 11; + reply = TRUE; + dcc->mirc_ctcp = FALSE; + } else if (*msg == 1) { + /* Use the mirc style CTCPs from now on.. */ + dcc->mirc_ctcp = TRUE; + } + + /* Handle only DCC CTCPs */ + if (*msg != 1) + return; + + /* get ctcp command, remove \001 chars */ + event = g_strconcat(reply ? "dcc reply " : "dcc ctcp ", msg+1, NULL); + if (event[strlen(event)-1] == 1) event[strlen(event)-1] = '\0'; + + cmd = event + (reply ? 10 : 9); + ptr = strchr(cmd, ' '); + if (ptr != NULL) *ptr++ = '\0'; else ptr = ""; + + cmd = g_ascii_strup(cmd, -1); + + ascii_strdown(event+9); + if (!signal_emit(event, 2, dcc, ptr)) { + signal_emit(reply ? "default dcc reply" : + "default dcc ctcp", 3, dcc, cmd, ptr); + } + + g_free(cmd); + g_free(event); + + signal_stop(); +} + +static void dcc_ctcp_redirect(CHAT_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(IS_DCC_CHAT(dcc)); + + signal_emit("ctcp msg dcc", 6, dcc->server, msg, + dcc->nick, "dcc", dcc->mynick, dcc); +} + +static void dcc_ctcp_reply_redirect(CHAT_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(msg != NULL); + g_return_if_fail(IS_DCC_CHAT(dcc)); + + signal_emit("ctcp reply dcc", 6, dcc->server, msg, + dcc->nick, "dcc", dcc->mynick, dcc); +} + +/* CTCP REPLY: REJECT */ +static void ctcp_reply_dcc_reject(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + DCC_REC *chat) +{ + DCC_REC *dcc; + + /* default REJECT handler checks args too - + we don't care about it in DCC chats. */ + if (g_ascii_strncasecmp(data, "CHAT", 4) == 0 && + (data[4] == '\0' || data[4] == ' ')) { + dcc = dcc_find_request(DCC_CHAT_TYPE, nick, NULL); + if (dcc != NULL) dcc_close(dcc); + signal_stop(); + } +} + +static void event_nick(IRC_SERVER_REC *server, const char *data, + const char *orignick) +{ + QUERY_REC *query; + CHAT_DCC_REC *dcc; + char *params, *nick, *tag; + + g_return_if_fail(data != NULL); + g_return_if_fail(orignick != NULL); + + params = event_get_params(data, 1, &nick); + if (g_ascii_strcasecmp(nick, orignick) == 0) { + /* shouldn't happen, but just to be sure irssi doesn't + get into infinite loop */ + g_free(params); + return; + } + + while ((dcc = dcc_chat_find_nick(server, orignick)) != NULL) { + g_free(dcc->nick); + dcc->nick = g_strdup(nick); + + tag = g_strconcat("=", dcc->id, NULL); + query = irc_query_find(server, tag); + g_free(tag); + + /* change the id too */ + g_free(dcc->id); + dcc->id = NULL; + dcc->id = dcc_chat_get_new_id(nick); + + if (query != NULL) { + tag = g_strconcat("=", dcc->id, NULL); + query_change_nick(query, tag); + g_free(tag); + } + } + + g_free(params); +} + +void dcc_chat_init(void) +{ + dcc_register_type("CHAT"); + settings_add_bool("dcc", "dcc_mirc_ctcp", FALSE); + settings_add_str("dcc", "dcc_autochat_masks", ""); + + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + command_bind("me", NULL, (SIGNAL_FUNC) cmd_me); + command_bind("action", NULL, (SIGNAL_FUNC) cmd_action); + command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp); + command_bind("dcc chat", NULL, (SIGNAL_FUNC) cmd_dcc_chat); + command_set_options("dcc chat", "passive"); + command_bind("mircdcc", NULL, (SIGNAL_FUNC) cmd_mircdcc); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + command_bind("whois", NULL, (SIGNAL_FUNC) cmd_whois); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("ctcp msg dcc chat", (SIGNAL_FUNC) ctcp_msg_dcc_chat); + signal_add_first("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_add("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); + signal_add("dcc reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply_redirect); + signal_add("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_add("event nick", (SIGNAL_FUNC) event_nick); +} + +void dcc_chat_deinit(void) +{ + dcc_unregister_type("CHAT"); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); + command_unbind("me", (SIGNAL_FUNC) cmd_me); + command_unbind("action", (SIGNAL_FUNC) cmd_action); + command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp); + command_unbind("dcc chat", (SIGNAL_FUNC) cmd_dcc_chat); + command_unbind("mircdcc", (SIGNAL_FUNC) cmd_mircdcc); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + command_unbind("whois", (SIGNAL_FUNC) cmd_whois); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("ctcp msg dcc chat", (SIGNAL_FUNC) ctcp_msg_dcc_chat); + signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg); + signal_remove("dcc ctcp dcc", (SIGNAL_FUNC) dcc_ctcp_redirect); + signal_remove("dcc reply dcc", (SIGNAL_FUNC) dcc_ctcp_reply_redirect); + signal_remove("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_remove("event nick", (SIGNAL_FUNC) event_nick); +} diff --git a/src/irc/dcc/dcc-chat.h b/src/irc/dcc/dcc-chat.h new file mode 100644 index 0000000..ef992bb --- /dev/null +++ b/src/irc/dcc/dcc-chat.h @@ -0,0 +1,40 @@ +#ifndef IRSSI_IRC_DCC_DCC_CHAT_H +#define IRSSI_IRC_DCC_DCC_CHAT_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_CHAT(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, CHAT_DCC_REC, type, "DCC", "CHAT") + +#define IS_DCC_CHAT(dcc) \ + (DCC_CHAT(dcc) ? TRUE : FALSE) + +struct CHAT_DCC_REC { +#include <irssi/src/irc/dcc/dcc-rec.h> + + char *id; /* unique identifier - usually same as nick. */ + NET_SENDBUF_REC *sendbuf; + + unsigned int mirc_ctcp:1; /* Send CTCPs without the CTCP_MESSAGE prefix */ + unsigned int connection_lost:1; /* other side closed connection */ +}; + +#define DCC_CHAT_TYPE module_get_uniq_id_str("DCC", "CHAT") + +CHAT_DCC_REC *dcc_chat_find_id(const char *id); + +/* Send `data' to dcc chat. */ +void dcc_chat_send(CHAT_DCC_REC *dcc, const char *data); + +/* Send a CTCP message/notify to target. + Send the CTCP via DCC chat if `chat' is specified. */ +void dcc_ctcp_message(IRC_SERVER_REC *server, const char *target, + CHAT_DCC_REC *chat, int notice, const char *msg); + +/* If `item' is a query of a =nick, return DCC chat record of nick */ +CHAT_DCC_REC *item_get_dcc(WI_ITEM_REC *item); + +void dcc_chat_init(void); +void dcc_chat_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-file-rec.h b/src/irc/dcc/dcc-file-rec.h new file mode 100644 index 0000000..28a488e --- /dev/null +++ b/src/irc/dcc/dcc-file-rec.h @@ -0,0 +1,9 @@ +#include <irssi/src/irc/dcc/dcc-rec.h> + +uoff_t size, skipped; /* file size / skipped at start */ +int fhandle; /* file handle */ +int queue; /* queue number */ + +/* counter buffer */ +char count_buf[4]; +int count_pos; diff --git a/src/irc/dcc/dcc-file.h b/src/irc/dcc/dcc-file.h new file mode 100644 index 0000000..3898635 --- /dev/null +++ b/src/irc/dcc/dcc-file.h @@ -0,0 +1,10 @@ +#ifndef IRSSI_IRC_DCC_DCC_FILE_H +#define IRSSI_IRC_DCC_DCC_FILE_H + +#include <irssi/src/irc/dcc/dcc.h> + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> +} FILE_DCC_REC; + +#endif diff --git a/src/irc/dcc/dcc-get.c b/src/irc/dcc/dcc-get.c new file mode 100644 index 0000000..0214387 --- /dev/null +++ b/src/irc/dcc/dcc-get.c @@ -0,0 +1,627 @@ +/* + dcc-get.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> + +static char *dcc_get_recv_buffer; + +GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + GET_DCC_REC *dcc; + + dcc = g_new0(GET_DCC_REC, 1); + dcc->orig_type = module_get_uniq_id_str("DCC", "SEND"); + dcc->type = module_get_uniq_id_str("DCC", "GET"); + dcc->fhandle = -1; + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void sig_dcc_destroyed(GET_DCC_REC *dcc) +{ + if (!IS_DCC_GET(dcc)) return; + + g_free_not_null(dcc->file); + if (dcc->fhandle != -1) close(dcc->fhandle); +} + +char *dcc_get_download_path(const char *fname) +{ + char *str, *downpath; + char *base; + + base = g_path_get_basename(fname); + downpath = convert_home(settings_get_str("dcc_download_path")); + str = g_strconcat(downpath, G_DIR_SEPARATOR_S, base, NULL); + g_free(downpath); + g_free(base); + + return str; +} + +static char *dcc_get_rename_file(const char *fname) +{ + GString *newname; + struct stat statbuf; + char *ret; + int num; + + newname = g_string_new(NULL); + num = 1; + do { + g_string_printf(newname, "%s.%d", fname, num); + num++; + } while (stat(newname->str, &statbuf) == 0); + + ret = newname->str; + g_string_free(newname, FALSE); + return ret; +} + +static void sig_dccget_send(GET_DCC_REC *dcc); + +void dcc_get_send_received(GET_DCC_REC *dcc) +{ + guint32 recd; + + recd = (guint32) htonl(dcc->transfd & 0xffffffff); + memcpy(dcc->count_buf, &recd, 4); + + dcc->count_pos = + net_transmit(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + if (dcc->count_pos == 4) dcc->count_pos = 0; + + /* count_pos might be -1 here. if this happens, the + count_buf should be re-sent.. also, if it's 1, 2 or 3, the + last 1-3 bytes should be sent later. these happen probably + never, but I just want to do it right.. :) */ + if (dcc->tagwrite == -1) { + dcc->tagwrite = + i_input_add(dcc->handle, I_INPUT_WRITE, (GInputFunction) sig_dccget_send, dcc); + } +} + +/* input function: DCC GET is free to send data */ +static void sig_dccget_send(GET_DCC_REC *dcc) +{ + guint32 recd; + int ret; + + if (dcc->count_pos != 0) { + ret = net_transmit(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + + if (dcc->count_pos <= 0) + dcc->count_pos = ret; + else if (ret > 0) + dcc->count_pos += ret; + + if (dcc->count_pos == 4) dcc->count_pos = 0; + + } + + if (dcc->count_pos == 0) { + g_source_remove(dcc->tagwrite); + dcc->tagwrite = -1; + } + + memcpy(&recd, dcc->count_buf, 4); + if (recd != (guint32) htonl(dcc->transfd & 0xffffffff)) + dcc_get_send_received(dcc); +} + +#define DCC_GET_RECV_BUFFER_SIZE 32768 + +/* input function: DCC GET received data */ +static void sig_dccget_receive(GET_DCC_REC *dcc) +{ + int ret; + + if (dcc_get_recv_buffer == NULL) { + dcc_get_recv_buffer = g_malloc(DCC_GET_RECV_BUFFER_SIZE); + } + + for (;;) { + ret = net_receive(dcc->handle, dcc_get_recv_buffer, + DCC_GET_RECV_BUFFER_SIZE); + if (ret == 0) break; + + if (ret < 0) { + /* socket closed - transmit complete, + or other side died.. */ + dcc_close(DCC(dcc)); + return; + } + + if (write(dcc->fhandle, dcc_get_recv_buffer, ret) != ret) { + /* most probably out of disk space */ + signal_emit("dcc error write", 2, + dcc, g_strerror(errno)); + dcc_close(DCC(dcc)); + return; + } + dcc->transfd += ret; + break; + } + + /* send number of total bytes received */ + if (dcc->count_pos <= 0) + dcc_get_send_received(dcc); + + signal_emit("dcc transfer update", 1, dcc); +} + +/* callback: net_connect() finished for DCC GET */ +void sig_dccget_connected(GET_DCC_REC *dcc) +{ + struct stat statbuf; + char *fname, *tempfname, *str; + int ret, ret_errno, temphandle, old_umask; + + if (!dcc->from_dccserver) { + if (net_geterror(dcc->handle) != 0) { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + return; + } + + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + } + + g_free_not_null(dcc->file); + dcc->file = dcc_get_download_path(dcc->arg); + + /* if some plugin wants to change the file name/path here.. */ + signal_emit("dcc get receive", 1, dcc); + + if (stat(dcc->file, &statbuf) == 0 && + dcc->get_type == DCC_GET_RENAME) { + /* file exists, rename.. */ + fname = dcc_get_rename_file(dcc->file); + g_free(dcc->file); + dcc->file = fname; + } + + if (dcc->get_type != DCC_GET_RESUME) { + int dcc_file_create_mode = octal2dec(settings_get_int("dcc_file_create_mode")); + + /* we want to overwrite the file, remove it here. + if it gets created after this, we'll fail. */ + unlink(dcc->file); + + /* just to make sure we won't run into race conditions + if download_path is in some global temp directory */ + tempfname = g_strconcat(dcc->file, ".XXXXXX", NULL); + + old_umask = umask(0077); + temphandle = mkstemp(tempfname); + umask(old_umask); + + if (temphandle == -1) + ret = -1; + else { + if (fchmod(temphandle, dcc_file_create_mode) != 0) + g_warning("fchmod(3) failed: %s", strerror(errno)); + /* proceed even if chmod fails */ + ret = 0; + } + + close(temphandle); + + if (ret != -1) { + ret = link(tempfname, dcc->file); + + if (ret == -1 && + /* Linux */ + (errno == EPERM || + /* FUSE */ + errno == ENOSYS || errno == EACCES || + /* BSD */ + errno == EOPNOTSUPP)) { + /* hard links aren't supported - some people + want to download stuff to FAT/NTFS/etc + partitions, so fallback to rename() */ + ret = rename(tempfname, dcc->file); + } + } + + /* if ret = 0, we're the file owner now */ + dcc->fhandle = ret == -1 ? -1 : + open(dcc->file, O_WRONLY | O_TRUNC); + + /* close/remove the temp file */ + ret_errno = errno; + unlink(tempfname); + g_free(tempfname); + + if (dcc->fhandle == -1) { + signal_emit("dcc error file create", 3, + dcc, dcc->file, g_strerror(ret_errno)); + dcc_destroy(DCC(dcc)); + return; + } + } + + dcc->starttime = time(NULL); + if (dcc->size == 0) { + dcc_close(DCC(dcc)); + return; + } + dcc->tagread = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) sig_dccget_receive, dcc); + signal_emit("dcc connected", 1, dcc); + + if (dcc->from_dccserver) { + str = g_strdup_printf("121 %s %d\n", + dcc->server ? dcc->server->nick : "??", 0); + net_transmit(dcc->handle, str, strlen(str)); + } +} + +void dcc_get_connect(GET_DCC_REC *dcc) +{ + if (dcc->get_type == DCC_GET_DEFAULT) { + dcc->get_type = settings_get_bool("dcc_autorename") ? + DCC_GET_RENAME : DCC_GET_OVERWRITE; + } + + if (dcc->from_dccserver) { + sig_dccget_connected(dcc); + return; + } + + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + + if (dcc->handle != NULL) { + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_WRITE | I_INPUT_READ, + (GInputFunction) sig_dccget_connected, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static void dcc_get_listen(GET_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR addr; + int port; + + /* accept connection */ + handle = net_accept(dcc->handle, &addr, &port); + if (handle == NULL) + return; + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + memcpy(&dcc->addr, &addr, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + + dcc->tagconn = i_input_add(handle, I_INPUT_READ | I_INPUT_WRITE, + (GInputFunction) sig_dccget_connected, dcc); +} + +void dcc_get_passive(GET_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR own_ip; + int port; + char host[MAX_IP_LEN]; + + handle = dcc_listen(net_sendbuffer_handle(dcc->server->handle), + &own_ip, &port); + if (handle == NULL) + cmd_return_error(CMDERR_ERRNO); + + dcc->handle = handle; + dcc->tagconn = i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_get_listen, dcc); + + /* Let's send the reply to the other client! */ + dcc_ip2str(&own_ip, host); + irc_send_cmdv(dcc->server, + "PRIVMSG %s :\001DCC SEND %s %s %d %"PRIuUOFF_T" %d\001", + dcc->nick, dcc->arg, host, port, dcc->size, dcc->pasv_id); +} + +#define get_params_match(params, pos) \ + ((is_numeric(params[pos], '\0') || is_ipv6_address(params[pos])) && \ + is_numeric(params[(pos)+1], '\0') && atol(params[(pos)+1]) < 65536 && \ + is_numeric(params[(pos)+2], '\0')) + +/* Return number of parameters in `params' that belong to file name. + Normally it's paramcount-3, but I don't think anything forbids of + adding some extension where there could be more parameters after + file size. + + MIRC sends filenames with spaces quoted ("file name"), but I'd rather + not trust that entirely either. At least some clients that don't really + understand the problem with spaces in file names sends the file name + without any quotes. */ +int get_file_params_count(char **params, int paramcount) +{ + int pos, best; + + if (*params[0] == '"') { + /* quoted file name? */ + for (pos = 0; pos < paramcount-3; pos++) { + if (strlen(params[pos]) == 0) + continue; + if (params[pos][strlen(params[pos])-1] == '"' && + get_params_match(params, pos+1)) + return pos+1; + } + } + + best = paramcount-3; + for (pos = paramcount-3; pos > 0; pos--) { + if (get_params_match(params, pos)) + best = pos; + } + + return best; +} + +char *get_file_name(char **params, int fileparams) +{ + GString *out = g_string_new(params[0]); + char *ret; + int pos; + + for (pos = 1; pos < fileparams; pos++) { + out = g_string_append(out, " "); + out = g_string_append(out, params[pos]); + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +/* CTCP: DCC SEND */ +static void ctcp_msg_dcc_send(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, CHAT_DCC_REC *chat) +{ + GET_DCC_REC *dcc; + SEND_DCC_REC *temp_dcc; + IPADDR ip; + char *address, **params, *fname; + int paramcount, fileparams; + int port, len, quoted = FALSE; + uoff_t size; + int p_id = -1; + int passive = FALSE; + + if (addr == NULL) + addr = ""; + if (nick == NULL) + nick = ""; + + /* SEND <file name> <address> <port> <size> [...] */ + /* SEND <file name> <address> 0 <size> <id> (DCC SEND passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 4) { + signal_emit("dcc error ctcp", 5, "SEND", data, + nick, addr, target); + g_strfreev(params); + return; + } + + fileparams = get_file_params_count(params, paramcount); + + address = g_strdup(params[fileparams]); + dcc_str2ip(address, &ip); + port = atoi(params[fileparams+1]); + size = str_to_uofft(params[fileparams+2]); + + /* If this DCC uses passive protocol then store the id for later use. */ + if (paramcount == fileparams + 4) { + p_id = atoi(params[fileparams+3]); + passive = TRUE; + } + + fname = get_file_name(params, fileparams); + g_strfreev(params); + + len = strlen(fname); + if (len > 1 && *fname == '"' && fname[len-1] == '"') { + /* "file name" - MIRC sends filenames with spaces like this */ + fname[len-1] = '\0'; + memmove(fname, fname+1, len); + quoted = TRUE; + } + + if (passive && port != 0) { + /* This is NOT a DCC SEND request! This is a reply to our + passive request. We MUST check the IDs and then connect to + the remote host. */ + + temp_dcc = DCC_SEND(dcc_find_request(DCC_SEND_TYPE, nick, fname)); + if (temp_dcc != NULL && p_id == temp_dcc->pasv_id) { + temp_dcc->target = g_strdup(target); + temp_dcc->port = port; + temp_dcc->size = size; + temp_dcc->file_quoted = quoted; + + memcpy(&temp_dcc->addr, &ip, sizeof(IPADDR)); + if (temp_dcc->addr.family == AF_INET) + net_ip2host(&temp_dcc->addr, temp_dcc->addrstr); + else { + /* with IPv6, show it to us as it was sent */ + g_strlcpy(temp_dcc->addrstr, address, + sizeof(temp_dcc->addrstr)); + } + + /* This new signal is added to let us invoke + dcc_send_connect() which is found in dcc-send.c */ + signal_emit("dcc reply send pasv", 1, temp_dcc); + g_free(address); + g_free(fname); + return; + } else if (temp_dcc != NULL && p_id != temp_dcc->pasv_id) { + /* IDs don't match... remove the old DCC SEND and + return */ + dcc_destroy(DCC(temp_dcc)); + g_free(address); + g_free(fname); + return; + } + } + + dcc = DCC_GET(dcc_find_request(DCC_GET_TYPE, nick, fname)); + if (dcc != NULL) + dcc_destroy(DCC(dcc)); /* remove the old DCC */ + + dcc = dcc_get_create(server, chat, nick, fname); + if (dcc == NULL) { + g_free(address); + g_free(fname); + g_warn_if_reached(); + return; + } + dcc->target = g_strdup(target); + + if (passive && port == 0) + dcc->pasv_id = p_id; /* Assign the ID to the DCC */ + + memcpy(&dcc->addr, &ip, sizeof(ip)); + if (dcc->addr.family == AF_INET) + net_ip2host(&dcc->addr, dcc->addrstr); + else { + /* with IPv6, show it to us as it was sent */ + g_strlcpy(dcc->addrstr, address, sizeof(dcc->addrstr)); + } + dcc->port = port; + dcc->size = size; + dcc->file_quoted = quoted; + + signal_emit("dcc request", 2, dcc, addr); + + g_free(address); + g_free(fname); +} + +/* handle receiving DCC - GET/RESUME. */ +void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, + DCC_GET_FUNC pasv_accept_func) +{ + GET_DCC_REC *dcc; + GSList *tmp, *next; + char *nick, *arg, *fname; + void *free_arg; + int found; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_STRIP_TRAILING_WS, &nick, &arg)) + return; + + if (*nick == '\0') { + dcc = DCC_GET(dcc_find_request_latest(DCC_GET_TYPE)); + if (dcc != NULL) { + if (!dcc_is_passive(dcc)) + accept_func(dcc); + else + pasv_accept_func(dcc); + } + cmd_params_free(free_arg); + return; + } + + fname = cmd_get_quoted_param(&arg); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + GET_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_GET(dcc) && g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (dcc_is_waiting_user(dcc) || dcc->from_dccserver) && + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { + found = TRUE; + if (!dcc_is_passive(dcc)) + accept_func(dcc); + else + pasv_accept_func(dcc); + } + } + + if (!found) + signal_emit("dcc error get not found", 1, nick); + + cmd_params_free(free_arg); +} + +/* SYNTAX: DCC GET [<nick> [<file>]] */ +static void cmd_dcc_get(const char *data) +{ + cmd_dcc_receive(data, dcc_get_connect, dcc_get_passive); +} + +void dcc_get_init(void) +{ + dcc_register_type("GET"); + settings_add_bool("dcc", "dcc_autorename", FALSE); + settings_add_str("dcc", "dcc_download_path", "~"); + settings_add_int("dcc", "dcc_file_create_mode", 644); + + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("ctcp msg dcc send", (SIGNAL_FUNC) ctcp_msg_dcc_send); + command_bind("dcc get", NULL, (SIGNAL_FUNC) cmd_dcc_get); +} + +void dcc_get_deinit(void) +{ + dcc_unregister_type("GET"); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("ctcp msg dcc send", (SIGNAL_FUNC) ctcp_msg_dcc_send); + command_unbind("dcc get", (SIGNAL_FUNC) cmd_dcc_get); + g_free_and_null(dcc_get_recv_buffer); +} diff --git a/src/irc/dcc/dcc-get.h b/src/irc/dcc/dcc-get.h new file mode 100644 index 0000000..3624f2c --- /dev/null +++ b/src/irc/dcc/dcc-get.h @@ -0,0 +1,45 @@ +#ifndef IRSSI_IRC_DCC_DCC_GET_H +#define IRSSI_IRC_DCC_DCC_GET_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_GET(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, GET_DCC_REC, type, "DCC", "GET") + +#define IS_DCC_GET(dcc) \ + (DCC_GET(dcc) ? TRUE : FALSE) + +enum { + DCC_GET_DEFAULT, + + DCC_GET_RENAME, + DCC_GET_OVERWRITE, + DCC_GET_RESUME +}; + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> + + int get_type; /* what to do if file exists? */ + char *file; /* file name we're really moving, arg is just the reference */ + + unsigned int file_quoted:1; /* file name was received quoted ("file name") */ + unsigned int from_dccserver:1; /* get is using dccserver method */ +} GET_DCC_REC; + +#define DCC_GET_TYPE module_get_uniq_id_str("DCC", "GET") + +typedef void (*DCC_GET_FUNC) (GET_DCC_REC *); + +/* handle receiving DCC - GET/RESUME. */ +void cmd_dcc_receive(const char *data, DCC_GET_FUNC accept_func, + DCC_GET_FUNC pasv_accept_func); + +void dcc_get_passive(GET_DCC_REC *dcc); +void dcc_get_connect(GET_DCC_REC *dcc); +char *dcc_get_download_path(const char *fname); + +void dcc_get_init(void); +void dcc_get_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-queue.c b/src/irc/dcc/dcc-queue.c new file mode 100644 index 0000000..0253be4 --- /dev/null +++ b/src/irc/dcc/dcc-queue.c @@ -0,0 +1,227 @@ +/* + dcc-queue.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + DCC queue by Heikki Orsila <heikki.orsila@tut.fi> (no copyrights claimed) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-queue.h> + +static GPtrArray *queuelist; + +/* dcc_queue_old finds an old queue (if it exists) */ +int dcc_queue_old(const char *nick, const char *servertag) +{ + int i; + + for (i = 0; i < queuelist->len; i++) { + GSList *qlist = g_ptr_array_index(queuelist, i); + + for (; qlist != NULL; qlist = qlist->next) { + DCC_QUEUE_REC *rec = qlist->data; + + if (rec == NULL) + continue; + + if (*nick != '\0' && + g_ascii_strcasecmp(nick, rec->nick) != 0) + continue; + + if (*servertag != '\0' && + g_ascii_strcasecmp(servertag, rec->servertag) != 0) + continue; + + /* found a queue matching nick/server! */ + return i; + } + } + + return -1; +} + + +int dcc_queue_new(void) +{ + int i; + + for (i = 0; i < queuelist->len; i++) { + if (g_ptr_array_index(queuelist, i) == NULL) + break; + } + + if (i == queuelist->len) + g_ptr_array_set_size(queuelist, (i + 1) * 2); + + /* create stub */ + g_ptr_array_index(queuelist, i) = g_slist_append(NULL, NULL); + return i; +} + +static void dcc_queue_free_rec(DCC_QUEUE_REC *rec) +{ + if (rec != NULL) { + g_free(rec->servertag); + g_free(rec->nick); + g_free(rec->file); + g_free(rec); + } +} + +void dcc_queue_free(int queue) +{ + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + while (*qlist != NULL) { + DCC_QUEUE_REC *rec = (*qlist)->data; + + *qlist = (*qlist)->next; + dcc_queue_free_rec(rec); + } +} + +/* add an element to queue. element will have nick/servertag/fname/chat as data. + mode specifies how the element should be added (append or prepend) +*/ + +void dcc_queue_add(int queue, int mode, const char *nick, const char *fname, + const char *servertag, CHAT_DCC_REC *chat, int passive) +{ + DCC_QUEUE_REC *rec; + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + rec = g_new0(DCC_QUEUE_REC, 1); + rec->chat = chat; + rec->servertag = g_strdup(servertag); + rec->nick = g_strdup(nick); + rec->file = g_strdup(fname); + rec->passive = passive; + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + if (mode == DCC_QUEUE_PREPEND) + *qlist = g_slist_insert(*qlist, rec, 1); + else + *qlist = g_slist_append(*qlist, rec); +} + +/* removes the head or the tail from the queue. returns the number of + elements removed from the queue (0 or 1). if remove_head is non-zero, + the head is removed (or actually stub is removed and the current head + becomes the stub), otherwise the tail is removed. */ +static int dcc_queue_remove_entry(int queue, int remove_head) +{ + DCC_QUEUE_REC *rec; + GSList **qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = (GSList **) &g_ptr_array_index(queuelist, queue); + if (*qlist == NULL || (*qlist)->next == NULL) + return 0; + + rec = remove_head ? (*qlist)->data : g_slist_last(*qlist)->data; + *qlist = g_slist_remove(*qlist, rec); + + dcc_queue_free_rec(rec); + return 1; +} + +/* removes the head, but not stub from the queue. returns number of elements + removed from the queue (0 or 1) */ +int dcc_queue_remove_head(int queue) +{ + return dcc_queue_remove_entry(queue, 1); +} + +/* removes the tail, but not stub from the queue. returns number of elements + removed from the queue (0 or 1) */ +int dcc_queue_remove_tail(int queue) +{ + return dcc_queue_remove_entry(queue, 0); +} + +DCC_QUEUE_REC *dcc_queue_get_next(int queue) +{ + GSList *qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = g_ptr_array_index(queuelist, queue); + return qlist == NULL || qlist->next == NULL ? NULL : qlist->next->data; +} + +GSList *dcc_queue_get_queue(int queue) +{ + GSList *qlist; + + g_assert(queue >= 0 && queue < queuelist->len); + + qlist = g_ptr_array_index(queuelist, queue); + return qlist == NULL ? NULL : qlist->next; +} + +static void sig_dcc_destroyed(CHAT_DCC_REC *dcc) +{ + int i; + + if (!IS_DCC_CHAT(dcc)) + return; + + for (i = 0; i < queuelist->len; i++) { + GSList *qlist = g_ptr_array_index(queuelist, i); + + for (; qlist != NULL; qlist = qlist->next) { + DCC_QUEUE_REC *rec = qlist->data; + + if (rec != NULL && rec->chat == dcc) + rec->chat = NULL; + } + } +} + +void dcc_queue_init(void) +{ + queuelist = g_ptr_array_new(); + + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); +} + +void dcc_queue_deinit(void) +{ + int i; + + for (i = 0; i < queuelist->len; i++) + dcc_queue_free(i); + + g_ptr_array_free(queuelist, TRUE); + + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); +} diff --git a/src/irc/dcc/dcc-queue.h b/src/irc/dcc/dcc-queue.h new file mode 100644 index 0000000..1ab4ecb --- /dev/null +++ b/src/irc/dcc/dcc-queue.h @@ -0,0 +1,45 @@ +#ifndef IRSSI_IRC_DCC_DCC_QUEUE_H_ +#define IRSSI_IRC_DCC_DCC_QUEUE_H_ + +#include <irssi/src/irc/dcc/dcc-chat.h> + +enum { + DCC_QUEUE_NORMAL, + DCC_QUEUE_PREPEND, + DCC_QUEUE_APPEND +}; + +typedef struct { + CHAT_DCC_REC *chat; + char *servertag; + char *nick; + char *file; + int passive; /* for passive DCCs */ +} DCC_QUEUE_REC; + +/* create a new queue. returns it's designation number (int) */ +int dcc_queue_new(void); + +void dcc_queue_free(int queue); + +/* finds an old queue and returns it's designation number (int). if not + found return -1 */ +int dcc_queue_old(const char *nick, const char *servertag); + +/* adds nick/fname/servertag triplet into queue */ +void dcc_queue_add(int queue, int mode, const char *nick, const char *fname, + const char *servertag, CHAT_DCC_REC *chat, int passive); + +int dcc_queue_remove_head(int queue); + +int dcc_queue_remove_tail(int queue); + +/* return the first entry from queue */ +DCC_QUEUE_REC *dcc_queue_get_next(int queue); + +GSList *dcc_queue_get_queue(int queue); + +void dcc_queue_init(void); +void dcc_queue_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-rec.h b/src/irc/dcc/dcc-rec.h new file mode 100644 index 0000000..afb029d --- /dev/null +++ b/src/irc/dcc/dcc-rec.h @@ -0,0 +1,27 @@ +int type; +int orig_type; /* original DCC type that was sent to us - same as type except GET and SEND are swapped */ +time_t created; + +IRC_SERVER_REC *server; +char *servertag; /* for resetting server later if we get disconnected */ +char *mynick; /* my current nick */ +char *nick; + +CHAT_DCC_REC *chat; /* if the request came through DCC chat */ +char *target; /* who the request was sent to - your nick, channel or NULL if you sent the request */ +char *arg; + +IPADDR addr; /* address we're connected in */ +char addrstr[MAX_IP_LEN]; /* in readable form */ +int port; /* port we're connected in */ + +GIOChannel *handle; /* socket handle */ +int tagconn, tagread, tagwrite; +time_t starttime; /* transfer start time */ +uoff_t transfd; /* bytes transferred */ + +int pasv_id; /* DCC Id for passive DCCs. <0 means a passive DCC, >=0 means a standard DCC */ + +unsigned int destroyed:1; /* We're about to destroy this DCC recond */ + +GHashTable *module_data; diff --git a/src/irc/dcc/dcc-resume.c b/src/irc/dcc/dcc-resume.c new file mode 100644 index 0000000..dd62ff9 --- /dev/null +++ b/src/irc/dcc/dcc-resume.c @@ -0,0 +1,248 @@ +/* + dcc-resume.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/dcc/dcc-file.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-chat.h> + +static FILE_DCC_REC *dcc_resume_find(int type, const char *nick, int port) +{ + GSList *tmp; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + FILE_DCC_REC *dcc = tmp->data; + + if (dcc->type == type && !dcc_is_connected(dcc) && + dcc->port == port && + g_ascii_strcasecmp(dcc->nick, nick) == 0) + return dcc; + } + + return NULL; +} + +#define get_params_match_resume(params, pos) \ + (is_numeric(params[pos], '\0') && atol(params[pos]) < 65536 && \ + is_numeric(params[(pos)+1], '\0')) + +/* Based on get_file_params_count() found in dcc-get.c. The main difference + is represented by the number of params expected after the filename (2 at + least). I've added this new routine to avoid possible troubles connected + to relaxing the old checks done on DCC GET params to suite the ACCEPT/RESUME + needs. + */ +int get_file_params_count_resume(char **params, int paramcount) +{ + int pos, best; + + if (*params[0] == '"') { + /* quoted file name? */ + for (pos = 0; pos < paramcount-2; pos++) { + if (strlen(params[pos]) == 0) + continue; + if (params[pos][strlen(params[pos])-1] == '"' && + get_params_match_resume(params, pos+1)) + return pos+1; + } + } + + best = paramcount-2; + for (pos = paramcount-2; pos > 0; pos--) { + if (get_params_match_resume(params, pos)) + best = pos; + } + + return best; +} + + +static int dcc_ctcp_resume_parse(int type, const char *data, const char *nick, + FILE_DCC_REC **dcc, uoff_t *size, int *pasv_id) +{ + char **params; + int paramcount, fileparams; + int port; + + /* RESUME|ACCEPT <file name> <port> <size> */ + /* RESUME|ACCEPT <file name> 0 <size> <id> (passive protocol) */ + params = g_strsplit(data, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + return 0; + } + + fileparams = get_file_params_count_resume(params, paramcount); + + if (paramcount >= fileparams + 2) { + port = atoi(params[fileparams]); + *size = str_to_uofft(params[fileparams+1]); + *pasv_id = ((port == 0) && (paramcount == fileparams + 3)) ? atoi(params[fileparams+2]) : -1; + *dcc = dcc_resume_find(type, nick, port); + g_strfreev(params); + + /* If the ID is different then the DCC cannot be resumed */ + return ((*dcc != NULL) && ((*dcc)->pasv_id == *pasv_id)); + } + g_strfreev(params); + return FALSE; +} + +static int dcc_resume_file_check(FILE_DCC_REC *dcc, IRC_SERVER_REC *server, + uoff_t size) +{ + if (size >= dcc->size) { + /* whole file sent */ + dcc->starttime = time(NULL); + dcc_reject(DCC(dcc), server); + } else if (lseek(dcc->fhandle, (off_t)size, SEEK_SET) != (off_t)size) { + /* error */ + dcc_reject(DCC(dcc), server); + } else { + dcc->transfd = dcc->skipped = size; + return TRUE; + } + + return FALSE; +} + +/* CTCP: DCC RESUME - requesting to resume DCC SEND */ +static void ctcp_msg_dcc_resume(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + FILE_DCC_REC *dcc; + char *str; + uoff_t size; + int pasv_id = -1; + + if (!dcc_ctcp_resume_parse(DCC_SEND_TYPE, data, nick, &dcc, &size, &pasv_id)) { + signal_emit("dcc error ctcp", 5, "RESUME", data, + nick, addr, target); + } else if (dcc != NULL && dcc_resume_file_check(dcc, server, size)) { + if (!dcc_is_passive(dcc)) { + str = g_strdup_printf(DCC_SEND(dcc)->file_quoted ? + "DCC ACCEPT \"%s\" %d %"PRIuUOFF_T : + "DCC ACCEPT %s %d %"PRIuUOFF_T, + dcc->arg, dcc->port, dcc->transfd); + } else { + str = g_strdup_printf(DCC_SEND(dcc)->file_quoted ? + "DCC ACCEPT \"%s\" 0 %"PRIuUOFF_T" %d" : + "DCC ACCEPT %s 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->transfd, dcc->pasv_id); + } + dcc_ctcp_message(dcc->server, dcc->nick, + dcc->chat, FALSE, str); + g_free(str); + } +} + +/* CTCP: DCC ACCEPT - accept resuming DCC GET */ +static void ctcp_msg_dcc_accept(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + FILE_DCC_REC *dcc; + uoff_t size; + int pasv_id; + + if (!dcc_ctcp_resume_parse(DCC_GET_TYPE, data, nick, &dcc, &size, &pasv_id) || + (dcc != NULL && DCC_GET(dcc)->get_type != DCC_GET_RESUME)) { + signal_emit("dcc error ctcp", 5, "ACCEPT", data, + nick, addr, target); + } else if (dcc != NULL && dcc_resume_file_check(dcc, server, size)) { + if (!dcc_is_passive(dcc)) + dcc_get_connect(DCC_GET(dcc)); + else + dcc_get_passive(DCC_GET(dcc)); + } +} + +/* Resume a DCC GET */ +static void dcc_send_resume(GET_DCC_REC *dcc) +{ + off_t pos; + char *str; + + g_return_if_fail(dcc != NULL); + + dcc->file = dcc_get_download_path(dcc->arg); + dcc->fhandle = open(dcc->file, O_WRONLY); + if (dcc->fhandle == -1) { + signal_emit("dcc error file open", 3, dcc->nick, dcc->file, + GINT_TO_POINTER(errno)); + return; + } + + dcc->get_type = DCC_GET_RESUME; + + pos = lseek(dcc->fhandle, 0, SEEK_END); + dcc->transfd = pos < 0 ? 0 : (uoff_t)pos; + dcc->skipped = dcc->transfd; + + if (dcc->skipped == dcc->size) { + /* already received whole file */ + dcc->starttime = time(NULL); + dcc_reject(DCC(dcc), NULL); + } else { + if (!dcc_is_passive(dcc)) { + str = g_strdup_printf(dcc->file_quoted ? + "DCC RESUME \"%s\" %d %"PRIuUOFF_T : + "DCC RESUME %s %d %"PRIuUOFF_T, + dcc->arg, dcc->port, dcc->transfd); + } else { + str = g_strdup_printf(dcc->file_quoted ? + "DCC RESUME \"%s\" 0 %"PRIuUOFF_T" %d" : + "DCC RESUME %s 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->transfd, dcc->pasv_id); + } + dcc_ctcp_message(dcc->server, dcc->nick, + dcc->chat, FALSE, str); + g_free(str); + } +} + +/* SYNTAX: DCC RESUME [<nick> [<file>]] */ +static void cmd_dcc_resume(const char *data) +{ + cmd_dcc_receive(data, dcc_send_resume, dcc_send_resume); +} + +void dcc_resume_init(void) +{ + signal_add("ctcp msg dcc resume", (SIGNAL_FUNC) ctcp_msg_dcc_resume); + signal_add("ctcp msg dcc accept", (SIGNAL_FUNC) ctcp_msg_dcc_accept); + command_bind("dcc resume", NULL, (SIGNAL_FUNC) cmd_dcc_resume); +} + +void dcc_resume_deinit(void) +{ + signal_remove("ctcp msg dcc resume", (SIGNAL_FUNC) ctcp_msg_dcc_resume); + signal_remove("ctcp msg dcc accept", (SIGNAL_FUNC) ctcp_msg_dcc_accept); + command_unbind("dcc resume", (SIGNAL_FUNC) cmd_dcc_resume); +} diff --git a/src/irc/dcc/dcc-send.c b/src/irc/dcc/dcc-send.c new file mode 100644 index 0000000..eccb8d3 --- /dev/null +++ b/src/irc/dcc/dcc-send.c @@ -0,0 +1,487 @@ +/* + dcc-send.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> + +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-queue.h> + +#include <glob.h> + +#ifndef GLOB_TILDE +# define GLOB_TILDE 0 /* unsupported */ +#endif + +static int dcc_send_one_file(int queue, const char *target, const char *fname, + IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + int passive); + +static void dcc_queue_send_next(int queue) +{ + IRC_SERVER_REC *server; + DCC_QUEUE_REC *qrec; + int send_started = FALSE; + + while ((qrec = dcc_queue_get_next(queue)) != NULL && !send_started) { + server = qrec->servertag == NULL ? NULL : + IRC_SERVER(server_find_tag(qrec->servertag)); + + if (server == NULL && qrec->chat == NULL) { + /* no way to send this request */ + signal_emit("dcc error send no route", 2, + qrec->nick, qrec->file); + } else { + send_started = dcc_send_one_file(queue, qrec->nick, + qrec->file, server, + qrec->chat, + qrec->passive); + } + dcc_queue_remove_head(queue); + } + + if (!send_started) { + /* no files in queue anymore, remove it */ + dcc_queue_free(queue); + } +} + +static char *dcc_send_get_file(const char *fname) +{ + char *str, *path; + + str = convert_home(fname); + if (!g_path_is_absolute(str)) { + /* full path not given to file, use dcc_upload_path */ + g_free(str); + + path = convert_home(settings_get_str("dcc_upload_path")); + str = *path == '\0' ? g_strdup(fname) : + g_strconcat(path, G_DIR_SEPARATOR_S, fname, NULL); + g_free(path); + } + + return str; +} + +static void dcc_send_add(const char *servertag, CHAT_DCC_REC *chat, + const char *nick, char *fileargs, int add_mode, + int passive) +{ + struct stat st; + glob_t globbuf; + char *fname; + int i, ret, files, flags, queue, start_new_transfer; + + memset(&globbuf, 0, sizeof(globbuf)); + flags = GLOB_NOCHECK | GLOB_TILDE; + + /* this loop parses all <file> parameters and adds them to glubbuf */ + for (;;) { + fname = cmd_get_quoted_param(&fileargs); + if (*fname == '\0') + break; + + if (glob(fname, flags, 0, &globbuf) < 0) + break; + + /* this flag must not be set before first call to glob! + (man glob) */ + flags |= GLOB_APPEND; + } + + files = 0; queue = -1; start_new_transfer = 0; + + /* add all globbed files to a proper queue */ + for (i = 0; i < globbuf.gl_pathc; i++) { + char *fname = dcc_send_get_file(globbuf.gl_pathv[i]); + + ret = stat(fname, &st); + if (ret == 0 && S_ISDIR(st.st_mode)) { + /* we don't want directories */ + errno = EISDIR; + ret = -1; + } + + if (ret < 0) { + signal_emit("dcc error file open", 3, + nick, fname, errno); + g_free(fname); + continue; + } + + if (queue < 0) { + /* in append and prepend mode try to find an + old queue. if an old queue is not found + create a new queue. if not in append or + prepend mode, create a new queue */ + if (add_mode != DCC_QUEUE_NORMAL) + queue = dcc_queue_old(nick, servertag); + start_new_transfer = 0; + if (queue < 0) { + queue = dcc_queue_new(); + start_new_transfer = 1; + } + } + + dcc_queue_add(queue, add_mode, nick, + fname, servertag, chat, passive); + files++; + g_free(fname); + } + + if (files > 0 && start_new_transfer) + dcc_queue_send_next(queue); + + globfree(&globbuf); +} + +/* DCC SEND [-append | -prepend | -flush | -rmtail | -rmhead | -passive] + <nick> <file> [<file> ...] */ +static void cmd_dcc_send(const char *data, IRC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + const char *servertag; + char *nick, *fileargs; + void *free_arg; + CHAT_DCC_REC *chat; + GHashTable *optlist; + int queue, mode, passive; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS, + "dcc send", &optlist, &nick, &fileargs)) + return; + + chat = item_get_dcc(item); + if (chat != NULL && + (chat->mirc_ctcp || g_ascii_strcasecmp(nick, chat->nick) != 0)) + chat = NULL; + + if (IS_IRC_SERVER(server) && server->connected) + servertag = server->tag; + else if (chat != NULL) + servertag = chat->servertag; + else + servertag = NULL; + + if (servertag == NULL && chat == NULL) + cmd_param_error(CMDERR_NOT_CONNECTED); + + passive = g_hash_table_lookup(optlist, "passive") != NULL; + + if (g_hash_table_lookup(optlist, "rmhead") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_remove_head(queue); + } else if (g_hash_table_lookup(optlist, "rmtail") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_remove_tail(queue); + } else if (g_hash_table_lookup(optlist, "flush") != NULL) { + queue = dcc_queue_old(nick, servertag); + if (queue != -1) + dcc_queue_free(queue); + } else { + if (g_hash_table_lookup(optlist, "append") != NULL) + mode = DCC_QUEUE_APPEND; + else if (g_hash_table_lookup(optlist, "prepend") != NULL) + mode = DCC_QUEUE_PREPEND; + else + mode = DCC_QUEUE_NORMAL; + + if (*fileargs == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + dcc_send_add(servertag, chat, nick, fileargs, mode, passive); + } + + cmd_params_free(free_arg); +} + +static SEND_DCC_REC *dcc_send_create(IRC_SERVER_REC *server, + CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + SEND_DCC_REC *dcc; + + dcc = g_new0(SEND_DCC_REC, 1); + dcc->orig_type = module_get_uniq_id_str("DCC", "GET"); + dcc->type = module_get_uniq_id_str("DCC", "SEND"); + dcc->fhandle = -1; + dcc->queue = -1; + + dcc_init_rec(DCC(dcc), server, chat, nick, arg); + if (dcc->module_data == NULL) { + /* failed to successfully init; TODO: change API */ + g_free(dcc); + return NULL; + } + + return dcc; +} + +static void sig_dcc_destroyed(SEND_DCC_REC *dcc) +{ + if (!IS_DCC_SEND(dcc)) return; + + if (dcc->fhandle != -1) + close(dcc->fhandle); + + dcc_queue_send_next(dcc->queue); +} + +/* input function: DCC SEND - we're ready to send more data */ +static void dcc_send_data(SEND_DCC_REC *dcc) +{ + char buffer[512]; + int ret; + + ret = read(dcc->fhandle, buffer, sizeof(buffer)); + if (ret <= 0) { + /* no need to call this function anymore.. + in fact it just eats all the cpu.. */ + dcc->waitforend = TRUE; + g_source_remove(dcc->tagwrite); + dcc->tagwrite = -1; + return; + } + + ret = net_transmit(dcc->handle, buffer, ret); + if (ret > 0) dcc->transfd += ret; + dcc->gotalldata = FALSE; + + lseek(dcc->fhandle, dcc->transfd, SEEK_SET); + + signal_emit("dcc transfer update", 1, dcc); +} + +/* input function: DCC SEND - received some data */ +static void dcc_send_read_size(SEND_DCC_REC *dcc) +{ + guint32 bytes; + int ret; + + ret = net_receive(dcc->handle, dcc->count_buf+dcc->count_pos, + 4-dcc->count_pos); + if (ret == -1) { + dcc_close(DCC(dcc)); + return; + } + + dcc->count_pos += ret; + + if (dcc->count_pos != 4) + return; + + memcpy(&bytes, dcc->count_buf, sizeof(bytes)); + bytes = ntohl(bytes); + dcc->count_pos = 0; + + if (dcc->waitforend && bytes == (dcc->transfd & 0xffffffff)) { + /* file is sent */ + dcc->gotalldata = TRUE; + dcc_close(DCC(dcc)); + } +} + +/* input function: DCC SEND - someone tried to connect to our socket */ +static void dcc_send_connected(SEND_DCC_REC *dcc) +{ + GIOChannel *handle; + IPADDR addr; + int port; + + /* accept connection */ + handle = net_accept(dcc->handle, &addr, &port); + if (handle == NULL) + return; + + /* TODO: some kind of paranoia check would be nice. it would check + that the host of the nick who we sent the request matches the + address who connected us. */ + + net_disconnect(dcc->handle); + g_source_remove(dcc->tagconn); + dcc->tagconn = -1; + + dcc->starttime = time(NULL); + dcc->handle = handle; + memcpy(&dcc->addr, &addr, sizeof(IPADDR)); + net_ip2host(&dcc->addr, dcc->addrstr); + dcc->port = port; + + dcc->tagread = i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_send_read_size, dcc); + dcc->tagwrite = i_input_add(handle, I_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc); + + signal_emit("dcc connected", 1, dcc); +} + +/* input function: DCC SEND - connect to the receiver (passive protocol) */ +static void dcc_send_connect(SEND_DCC_REC *dcc) +{ + dcc->handle = dcc_connect_ip(&dcc->addr, dcc->port); + + if (dcc->handle != NULL) { + dcc->starttime = time(NULL); + + dcc->tagread = i_input_add(dcc->handle, I_INPUT_READ, + (GInputFunction) dcc_send_read_size, dcc); + dcc->tagwrite = + i_input_add(dcc->handle, I_INPUT_WRITE, (GInputFunction) dcc_send_data, dcc); + signal_emit("dcc connected", 1, dcc); + } else { + /* error connecting */ + signal_emit("dcc error connect", 1, dcc); + dcc_destroy(DCC(dcc)); + } +} + +static int dcc_send_one_file(int queue, const char *target, const char *fname, + IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + int passive) +{ + struct stat st; + char *str; + char host[MAX_IP_LEN]; + int hfile, port = 0; + SEND_DCC_REC *dcc; + IPADDR own_ip; + GIOChannel *handle; + + if (dcc_find_request(DCC_SEND_TYPE, target, fname)) { + signal_emit("dcc error send exists", 2, target, fname); + return FALSE; + } + + str = dcc_send_get_file(fname); + hfile = open(str, O_RDONLY); + g_free(str); + + if (hfile == -1) { + signal_emit("dcc error file open", 3, target, fname, + GINT_TO_POINTER(errno)); + return FALSE; + } + + if (fstat(hfile, &st) < 0) { + g_warning("fstat() failed: %s", strerror(errno)); + close(hfile); + return FALSE; + } + + /* start listening (only if passive == FALSE )*/ + + if (passive == FALSE) { + handle = dcc_listen(chat != NULL ? chat->handle : + net_sendbuffer_handle(server->handle), + &own_ip, &port); + if (handle == NULL) { + close(hfile); + g_warning("dcc_listen() failed: %s", strerror(errno)); + return FALSE; + } + } else { + handle = NULL; + } + + str = g_path_get_basename(fname); + + /* Replace all the spaces with underscore so that lesser + intelligent clients can communicate.. */ + if (settings_get_bool("dcc_send_replace_space_with_underscore")) + g_strdelimit(str, " ", '_'); + + dcc = dcc_send_create(server, chat, target, str); + g_free(str); + if (dcc == NULL) { + g_warn_if_reached(); + close(hfile); + return FALSE; + } + + dcc->handle = handle; + dcc->port = port; + dcc->size = st.st_size; + dcc->fhandle = hfile; + dcc->queue = queue; + dcc->file_quoted = strchr(fname, ' ') != NULL; + if (!passive) { + dcc->tagconn = + i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_send_connected, dcc); + } + + /* Generate an ID for this send if using passive protocol */ + if (passive) { + dcc->pasv_id = rand() % 64; + } + + /* send DCC request */ + signal_emit("dcc request send", 1, dcc); + + + dcc_ip2str(&own_ip, host); + if (passive == FALSE) { + str = g_strdup_printf(dcc->file_quoted ? + "DCC SEND \"%s\" %s %d %"PRIuUOFF_T : + "DCC SEND %s %s %d %"PRIuUOFF_T, + dcc->arg, host, port, dcc->size); + } else { + str = g_strdup_printf(dcc->file_quoted ? + "DCC SEND \"%s\" 16843009 0 %"PRIuUOFF_T" %d" : + "DCC SEND %s 16843009 0 %"PRIuUOFF_T" %d", + dcc->arg, dcc->size, dcc->pasv_id); + } + dcc_ctcp_message(server, target, chat, FALSE, str); + + g_free(str); + return TRUE; +} + +void dcc_send_init(void) +{ + dcc_register_type("SEND"); + settings_add_str("dcc", "dcc_upload_path", "~"); + settings_add_bool("dcc", "dcc_send_replace_space_with_underscore", FALSE); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add("dcc reply send pasv", (SIGNAL_FUNC) dcc_send_connect); + command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send); + command_set_options("dcc send", "append flush prepend rmhead rmtail passive"); + + dcc_queue_init(); +} + +void dcc_send_deinit(void) +{ + dcc_queue_deinit(); + + dcc_unregister_type("SEND"); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("dcc reply send pasv", (SIGNAL_FUNC) dcc_send_connect); + command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send); +} diff --git a/src/irc/dcc/dcc-send.h b/src/irc/dcc/dcc-send.h new file mode 100644 index 0000000..881ef64 --- /dev/null +++ b/src/irc/dcc/dcc-send.h @@ -0,0 +1,27 @@ +#ifndef IRSSI_IRC_DCC_DCC_SEND_H +#define IRSSI_IRC_DCC_DCC_SEND_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_SEND(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, SEND_DCC_REC, type, "DCC", "SEND") + +#define IS_DCC_SEND(dcc) \ + (DCC_SEND(dcc) ? TRUE : FALSE) + +typedef struct { +#include <irssi/src/irc/dcc/dcc-file-rec.h> + + unsigned int file_quoted:1; /* file name was received quoted ("file name") */ + + /* fastsending: */ + unsigned int waitforend:1; /* file is sent, just wait for the replies from the other side */ + unsigned int gotalldata:1; /* got all acks from the other end (needed to make sure the end of transfer works right) */ +} SEND_DCC_REC; + +#define DCC_SEND_TYPE module_get_uniq_id_str("DCC", "SEND") + +void dcc_send_init(void); +void dcc_send_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc-server.c b/src/irc/dcc/dcc-server.c new file mode 100644 index 0000000..4d1e94a --- /dev/null +++ b/src/irc/dcc/dcc-server.c @@ -0,0 +1,413 @@ +/* + dcc-server.c : irssi + + Copyright (C) 2003 Mark Trumbull + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/net-sendbuffer.h> +#include <irssi/src/core/misc.h> + +#include <irssi/src/irc/core/irc-servers.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-server.h> + +void sig_dccget_connected(GET_DCC_REC *dcc); +GET_DCC_REC *dcc_get_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); + +void dcc_chat_input(CHAT_DCC_REC *dcc); +CHAT_DCC_REC *dcc_chat_create(IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); + +static void sig_dcc_destroyed(SERVER_DCC_REC *dcc) +{ + if (!IS_DCC_SERVER(dcc)) + return; + + if (dcc->sendbuf != NULL) + net_sendbuffer_destroy(dcc->sendbuf, FALSE); +} + +/* Start listening for incoming connections */ +static GIOChannel *dcc_listen_port(GIOChannel *iface, IPADDR *ip, int port) +{ + if (net_getsockname(iface, ip, NULL) == -1) + return NULL; + + if (IPADDR_IS_V6(ip)) + return net_listen(NULL, &port); + else + return net_listen(&ip4_any, &port); +} + +/* input function: DCC SERVER received some data.. */ +static void dcc_server_input(SERVER_DCC_REC *dcc) +{ + char *str; + int ret; + + g_return_if_fail(IS_DCC_SERVER(dcc)); + + do { + ret = net_sendbuffer_receive_line(dcc->sendbuf, &str, 1); + + if (ret == -1) { + /* connection lost */ + dcc_close(DCC(dcc)); + break; + } + + if (ret > 0) { + dcc->transfd += ret; + signal_emit("dcc server message", 2, dcc, str); + } + + if (dcc->connection_established) { + /* We set handle to NULL first because the new (chat/get) is using the same */ + /* handle and we don't want dcc_close to disconnect it.*/ + dcc->handle = NULL; + dcc_close(DCC(dcc)); + break; + } + } while (ret > 0); +} + +static void dcc_server_update_flags(SERVER_DCC_REC *dcc, const char *flags) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(IS_DCC_SERVER(dcc)); + + if (*flags == '+' || *flags == '-') { + const char *ptr = flags + 1; + unsigned int value = (*flags == '+') ? 1 : 0; + + while (*ptr) { + if (*ptr == 's' || *ptr == 'S') { dcc->accept_send = value; } + else if (*ptr == 'c' || *ptr == 'C') { dcc->accept_chat = value; } + else if (*ptr == 'f' || *ptr == 'F') { dcc->accept_fserve = value; } + ptr++; + } + } +} + +/* Initialize DCC record */ +static void dcc_init_server_rec(SERVER_DCC_REC *dcc, IRC_SERVER_REC *server, + const char *mynick, const char *servertag) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(IS_DCC_SERVER(dcc)); + + MODULE_DATA_INIT(dcc); + dcc->created = time(NULL); + dcc->chat = NULL; + dcc->arg = NULL; + dcc->nick = NULL; + dcc->tagconn = dcc->tagread = dcc->tagwrite = -1; + dcc->server = server; + dcc->mynick = g_strdup(mynick); + dcc->servertag = g_strdup(servertag); + + dcc_conns = g_slist_append(dcc_conns, dcc); + signal_emit("dcc created", 1, dcc); +} + +static SERVER_DCC_REC *dcc_server_create(IRC_SERVER_REC *server, const char *flags) +{ + SERVER_DCC_REC *dcc; + + dcc = g_new0(SERVER_DCC_REC, 1); + dcc->orig_type = dcc->type = DCC_SERVER_TYPE; + dcc_server_update_flags(dcc, flags); + + dcc_init_server_rec(dcc, server, dcc->mynick, dcc->servertag); + return dcc; +} + +static SERVER_DCC_REC *dcc_server_clone(SERVER_DCC_REC *dcc) +{ + SERVER_DCC_REC *newdcc; + + g_return_val_if_fail(IS_DCC_SERVER(dcc), NULL); + + newdcc = g_new0(SERVER_DCC_REC, 1); + newdcc->orig_type = newdcc->type = DCC_SERVER_TYPE; + newdcc->accept_send = dcc->accept_send; + newdcc->accept_chat = dcc->accept_chat; + newdcc->accept_fserve = dcc->accept_fserve; + + dcc_init_server_rec(newdcc, dcc->server, dcc->mynick, dcc->servertag); + return newdcc; +} + +/* input function: DCC SERVER - someone tried to connect to our socket */ +static void dcc_server_listen(SERVER_DCC_REC *dcc) +{ + SERVER_DCC_REC *newdcc; + IPADDR ip; + GIOChannel *handle; + int port; + + g_return_if_fail(IS_DCC_SERVER(dcc)); + + /* accept connection */ + handle = net_accept(dcc->handle, &ip, &port); + if (handle == NULL) + return; + + /* Create a new DCC SERVER to handle this connection */ + newdcc = dcc_server_clone(dcc); + + newdcc->starttime = time(NULL); + newdcc->handle = handle; + newdcc->sendbuf = net_sendbuffer_create(handle, 0); + memcpy(&newdcc->addr, &ip, sizeof(IPADDR)); + net_ip2host(&newdcc->addr, newdcc->addrstr); + newdcc->port = port; + newdcc->tagread = + i_input_add(handle, I_INPUT_READ, (GInputFunction) dcc_server_input, newdcc); + + signal_emit("dcc connected", 1, newdcc); +} + +/* DCC SERVER: text received */ +static void dcc_server_msg(SERVER_DCC_REC *dcc, const char *msg) +{ + g_return_if_fail(IS_DCC_SERVER(dcc)); + g_return_if_fail(msg != NULL); + + /* Check for CHAT protocol */ + if (g_ascii_strncasecmp(msg, "100 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting chat requests.*/ + if (dcc->accept_chat) { + /* Connect and start DCC Chat */ + char *str; + CHAT_DCC_REC *dccchat = dcc_chat_create(dcc->server, NULL, msg, "chat"); + + dccchat->starttime = time(NULL); + dccchat->handle = dcc->handle; + dccchat->sendbuf = net_sendbuffer_create(dccchat->handle, 0); + memcpy(&dccchat->addr, &dcc->addr, sizeof(IPADDR)); + net_ip2host(&dccchat->addr, dccchat->addrstr); + dccchat->port = dcc->port; + dccchat->tagread = i_input_add(dccchat->handle, I_INPUT_READ, + (GInputFunction) dcc_chat_input, dccchat); + + dcc->connection_established = 1; + signal_emit("dcc connected", 1, dccchat); + + str = g_strdup_printf("101 %s\n", + (dccchat->server) ? dccchat->server->nick : "??"); + net_sendbuffer_send(dccchat->sendbuf, str, strlen(str)); + g_free(str); + } + } + + /* Check for FSERVE protocol */ + if (g_ascii_strncasecmp(msg, "110 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting fserve requests.*/ + if (dcc->accept_fserve) { + /* TODO - Connect and start DCC Fserve */ + } + } + + /* Check for SEND protocol */ + if (g_ascii_strncasecmp(msg, "120 ", 4) == 0) { + msg += 4; + /* Check if this server is accepting send requests.*/ + if (dcc->accept_send) { + /* Connect and start DCC Send */ + GET_DCC_REC *dccget; + char **params, *fname, *nick; + int paramcount, len, quoted = FALSE; + uoff_t size; + + /* 120 clientnickname filesize filename */ + params = g_strsplit(msg, " ", -1); + paramcount = g_strv_length(params); + + if (paramcount < 3) { + g_strfreev(params); + signal_stop(); + return; + } + + nick = params[0]; + size = str_to_uofft(params[1]); + fname = g_strjoinv(" ", ¶ms[2]); + + len = strlen(fname); + if (len > 1 && *fname == '"' && fname[len-1] == '"') { + /* "file name" - MIRC sends filenames with spaces like this */ + fname[len-1] = '\0'; + memmove(fname, fname+1, len); + quoted = TRUE; + } + + dccget = dcc_get_create(dcc->server, NULL, nick, fname); + dccget->handle = dcc->handle; + dccget->target = g_strdup(dcc->server ? dcc->server->nick : "??"); + memcpy(&dccget->addr, &dcc->addr, sizeof(dcc->addr)); + if (dccget->addr.family == AF_INET) { + net_ip2host(&dccget->addr, dccget->addrstr); + } else { + /* with IPv6, show it to us as it was sent */ + memcpy(dccget->addrstr, dcc->addrstr, sizeof(dccget->addrstr)); + } + dccget->port = dcc->port; + dccget->size = size; + dccget->file_quoted = quoted; + dccget->from_dccserver = 1; + + dcc->connection_established = 1; + signal_emit("dcc request", 2, dccget, dccget->addrstr); + + g_strfreev(params); + g_free(fname); + } + } + + signal_stop(); +} + +SERVER_DCC_REC *dcc_server_find_port(const char *port_str) +{ + GSList *tmp; + unsigned int port = 0; + + g_return_val_if_fail(port_str != NULL, NULL); + + port = atoi(port_str); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + SERVER_DCC_REC *dcc = tmp->data; + + if (IS_DCC_SERVER(dcc) && dcc->port == port) + return dcc; + } + + return NULL; +} + +/* SYNTAX: DCC SERVER [+|-scf] [port] */ +static void cmd_dcc_server(const char *data, IRC_SERVER_REC *server) +{ + void *free_arg; + GIOChannel *handle; + SERVER_DCC_REC *dcc; + IPADDR own_ip; + char *flags, *port; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2, &flags, &port)) + return; + + dcc = dcc_server_find_port(port); + if (dcc != NULL) { + /* Server is already running, update it */ + dcc_server_update_flags(dcc, flags); + cmd_params_free(free_arg); + return; + } + + /* start listening */ + if (!IS_IRC_SERVER(server) || !server->connected) { + cmd_param_error(CMDERR_NOT_CONNECTED); + } + + handle = dcc_listen_port(net_sendbuffer_handle(server->handle), + &own_ip, atoi(port)); + + if (handle == NULL) { + cmd_param_error(CMDERR_ERRNO); + } + + dcc = dcc_server_create(server, flags); + dcc->handle = handle; + dcc->port = atoi(port); + dcc->tagconn = + i_input_add(dcc->handle, I_INPUT_READ, (GInputFunction) dcc_server_listen, dcc); + + signal_emit("dcc server started", 1, dcc); + + cmd_params_free(free_arg); +} + +/* DCC CLOSE SERVER <port> */ +static void cmd_dcc_close(char *data, SERVER_REC *server) +{ + GSList *tmp, *next; + char *port_str; + void *free_arg; + int found, port; + + g_return_if_fail(data != NULL); + + if (g_ascii_strncasecmp(data, "SERVER ", 7) != 0 || + !cmd_get_params(data, &free_arg, 2, NULL, &port_str)) { + return; + } + + if (*port_str == '\0') { + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + } + + port = atoi(port_str); + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + SERVER_DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (IS_DCC_SERVER(dcc) && dcc->port == port) { + found = TRUE; + dcc_close(DCC(dcc)); + } + } + + if (found) { + signal_stop(); + } + + cmd_params_free(free_arg); +} + +void dcc_server_init(void) +{ + dcc_register_type("SERVER"); + command_bind("dcc server", NULL, (SIGNAL_FUNC) cmd_dcc_server); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_add_first("dcc server message", (SIGNAL_FUNC) dcc_server_msg); +} + +void dcc_server_deinit(void) +{ + dcc_unregister_type("SERVER"); + command_unbind("dcc server", (SIGNAL_FUNC) cmd_dcc_server); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed); + signal_remove("dcc server message", (SIGNAL_FUNC) dcc_server_msg); +} + diff --git a/src/irc/dcc/dcc-server.h b/src/irc/dcc/dcc-server.h new file mode 100644 index 0000000..1a4449d --- /dev/null +++ b/src/irc/dcc/dcc-server.h @@ -0,0 +1,29 @@ +#ifndef IRSSI_IRC_DCC_DCC_SERVER_H +#define IRSSI_IRC_DCC_DCC_SERVER_H + +#include <irssi/src/irc/dcc/dcc.h> + +#define DCC_SERVER(dcc) \ + MODULE_CHECK_CAST_MODULE(dcc, SERVER_DCC_REC, type, "DCC", "SERVER") + +#define IS_DCC_SERVER(dcc) \ + (DCC_SERVER(dcc) ? TRUE : FALSE) + +struct SERVER_DCC_REC { +#include <irssi/src/irc/dcc/dcc-rec.h> + NET_SENDBUF_REC *sendbuf; + + unsigned int accept_send:1; /* Accept SEND connections */ + unsigned int accept_chat:1; /* Accept CHAT connections */ + unsigned int accept_fserve:1; /* Accept FSERVE connections */ + unsigned int connection_established:1; /* We have made a connection */ +}; + +#define DCC_SERVER_TYPE module_get_uniq_id_str("DCC", "SERVER") + +typedef struct SERVER_DCC_REC SERVER_DCC_REC; + +void dcc_server_init(void); +void dcc_server_deinit(void); + +#endif diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c new file mode 100644 index 0000000..bb1c6c2 --- /dev/null +++ b/src/irc/dcc/dcc.c @@ -0,0 +1,600 @@ +/* + dcc.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "module.h" +#include <irssi/src/core/signals.h> +#include <irssi/src/core/commands.h> +#include <irssi/src/core/network.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/ignore.h> +#include <irssi/src/core/levels.h> + +#include <irssi/src/irc/core/irc-servers.h> +#include <irssi/src/core/servers-setup.h> + +#include <irssi/src/irc/dcc/dcc-chat.h> +#include <irssi/src/irc/dcc/dcc-get.h> +#include <irssi/src/irc/dcc/dcc-send.h> +#include <irssi/src/irc/dcc/dcc-server.h> + +void dcc_resume_init(void); +void dcc_resume_deinit(void); + +void dcc_autoget_init(void); +void dcc_autoget_deinit(void); + +GSList *dcc_conns; + +static GSList *dcc_types; +static int dcc_timeouttag; + +void dcc_register_type(const char *type) +{ + dcc_types = g_slist_append(dcc_types, g_strdup(type)); +} + +void dcc_unregister_type(const char *type) +{ + GSList *pos; + + pos = i_slist_find_string(dcc_types, type); + if (pos != NULL) { + void *tmp = pos->data; + dcc_types = g_slist_remove(dcc_types, pos->data); + g_free(tmp); + } +} + +int dcc_str2type(const char *str) +{ + if (i_slist_find_string(dcc_types, str) == NULL) + return -1; + + return module_get_uniq_id_str("DCC", str); +} + +/* Initialize DCC record */ +void dcc_init_rec(DCC_REC *dcc, IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg) +{ + g_return_if_fail(dcc != NULL); + g_return_if_fail(nick != NULL); + g_return_if_fail(arg != NULL); + + MODULE_DATA_INIT(dcc); + dcc->created = time(NULL); + dcc->chat = chat; + dcc->arg = g_strdup(arg); + dcc->nick = g_strdup(nick); + dcc->tagconn = dcc->tagread = dcc->tagwrite = -1; + dcc->server = server; + dcc->mynick = g_strdup(server != NULL ? server->nick : + chat != NULL ? chat->nick : "??"); + + dcc->servertag = server != NULL ? g_strdup(server->tag) : + (chat == NULL ? NULL : g_strdup(chat->servertag)); + + dcc->pasv_id = -1; /* Not a passive DCC */ + + dcc_conns = g_slist_append(dcc_conns, dcc); + signal_emit("dcc created", 1, dcc); +} + +/* Destroy DCC record */ +void dcc_destroy(DCC_REC *dcc) +{ + g_return_if_fail(dcc != NULL); + if (dcc->destroyed) return; + + dcc_conns = g_slist_remove(dcc_conns, dcc); + + dcc->destroyed = TRUE; + signal_emit("dcc destroyed", 1, dcc); + + if (dcc->handle != NULL) net_disconnect(dcc->handle); + if (dcc->tagconn != -1) g_source_remove(dcc->tagconn); + if (dcc->tagread != -1) g_source_remove(dcc->tagread); + if (dcc->tagwrite != -1) g_source_remove(dcc->tagwrite); + + MODULE_DATA_DEINIT(dcc); + g_free_not_null(dcc->servertag); + g_free_not_null(dcc->target); + g_free(dcc->mynick); + g_free(dcc->nick); + g_free(dcc->arg); + g_free(dcc); +} + +DCC_REC *dcc_find_request_latest(int type) +{ + DCC_REC *latest; + GSList *tmp; + + latest = NULL; + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->type == type && dcc_is_waiting_user(dcc)) + latest = dcc; + } + + return latest; +} + +DCC_REC *dcc_find_request(int type, const char *nick, const char *arg) +{ + GSList *tmp; + + g_return_val_if_fail(nick != NULL, NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->type == type && !dcc_is_connected(dcc) && + g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (arg == NULL || g_strcmp0(dcc->arg, arg) == 0)) + return dcc; + } + + return NULL; +} + +void dcc_ip2str(IPADDR *ip, char *host) +{ + IPADDR temp_ip; + guint32 addr; + + if (*settings_get_str("dcc_own_ip") != '\0') { + /* overridden IP address */ + net_host2ip(settings_get_str("dcc_own_ip"), &temp_ip); + ip = &temp_ip; + } + + if (IPADDR_IS_V6(ip)) { + /* IPv6 */ + net_ip2host(ip, host); + } else { + memcpy(&addr, &ip->ip, sizeof(addr)); + g_snprintf(host, MAX_IP_LEN, "%lu", + (unsigned long) htonl(addr)); + } +} + +void dcc_str2ip(const char *str, IPADDR *ip) +{ + guint32 addr; + + if (strchr(str, ':') == NULL) { + /* normal IPv4 address in 32bit number form */ + addr = strtoul(str, NULL, 10); + ip->family = AF_INET; + addr = (guint32) ntohl(addr); + memcpy(&ip->ip, &addr, sizeof(addr)); + } else { + /* IPv6 - in standard form */ + net_host2ip(str, ip); + } +} + +/* Start listening for incoming connections */ +GIOChannel *dcc_listen(GIOChannel *iface, IPADDR *ip, int *port) +{ + GIOChannel *handle; + IPADDR *listen_ip = NULL; + const char *dcc_port, *p, *own_ip; + int first, last; + + if (net_getsockname(iface, ip, NULL) == -1) + return NULL; + + /* figure out if we want to listen in IPv4 address or in "any" address, + which may mean IPv4+IPv6 or just IPv6 depending on OS. */ + own_ip = settings_get_str("dcc_own_ip"); + if (*own_ip != '\0') { + if (is_ipv4_address(own_ip)) + listen_ip = &ip4_any; + } else { + if (!IPADDR_IS_V6(ip)) + listen_ip = &ip4_any; + } + + /* get first port */ + dcc_port = settings_get_str("dcc_port"); + first = atoi(dcc_port); + if (first == 0) { + /* random port */ + *port = 0; + return net_listen(listen_ip, port); + } + + /* get last port */ + p = strchr(dcc_port, ' '); + if (p == NULL) p = strchr(dcc_port, '-'); + + dcc_port = p; + if (dcc_port == NULL) + last = first; + else { + last = atoi(dcc_port+1); + if (last == 0) + last = first; + } + + /* use the first available port */ + for (*port = first; *port <= last; (*port)++) { + handle = net_listen(listen_ip, port); + if (handle != NULL) + return handle; + } + + return NULL; +} + +/* Connect to specified IP address using the correct own_ip. */ +GIOChannel *dcc_connect_ip(IPADDR *ip, int port) +{ + IPADDR *own_ip, temp_ip; + const char *own_ip_str; + GIOChannel *handle; + + own_ip_str = settings_get_str("dcc_own_ip"); + own_ip = NULL; + if (*own_ip_str != '\0') { + /* use the specified interface for connecting */ + net_host2ip(own_ip_str, &temp_ip); + if (IPADDR_IS_V6(ip) == IPADDR_IS_V6(&temp_ip)) + own_ip = &temp_ip; + } + + if (own_ip == NULL) + own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4; + + handle = net_connect_ip(ip, port, own_ip); + if (handle == NULL && errno == EADDRNOTAVAIL && own_ip != NULL) { + /* dcc_own_ip is external address */ + own_ip = IPADDR_IS_V6(ip) ? source_host_ip6 : source_host_ip4; + handle = net_connect_ip(ip, port, own_ip); + } + return handle; +} + +/* Server connected - update server for DCC records that have + the same server tag */ +static void sig_connected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == NULL && dcc->servertag != NULL && + g_ascii_strcasecmp(dcc->servertag, server->tag) == 0) { + dcc->server = server; + g_free(dcc->mynick); + dcc->mynick = g_strdup(server->nick); + } + } +} + +/* Server disconnected, remove it from all DCC records */ +static void sig_server_disconnected(IRC_SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == server) + dcc->server = NULL; + } +} + +/* Your nick changed, change nick in all DCC records */ +static void sig_server_nick_changed(IRC_SERVER_REC *server) +{ + GSList *tmp; + + if (!IS_IRC_SERVER(server)) return; + + for (tmp = dcc_conns; tmp != NULL; tmp = tmp->next) { + DCC_REC *dcc = tmp->data; + + if (dcc->server == server) { + g_free(dcc->mynick); + dcc->mynick = g_strdup(server->nick); + } + } +} + +/* handle emitting "ctcp msg dcc" signal - don't use it directly because + with /IGNORE * CTCPS we'd be ignored */ +static void ctcp_msg(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + if (g_ascii_strncasecmp(data, "dcc ", 4) != 0) + return; + data += 4; + + signal_emit("ctcp msg dcc", 5, server, data, nick, addr, target); + signal_stop(); +} + +/* handle emitting "ctcp reply dcc" signal - don't use it directly because + with /IGNORE * CTCPS we'd be ignored */ +static void ctcp_reply(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, const char *target) +{ + if (g_ascii_strncasecmp(data, "dcc ", 4) != 0) + return; + data += 4; + + signal_emit("ctcp reply dcc", 5, server, data, nick, addr, target); + signal_stop(); +} + +/* Handle incoming DCC CTCP messages - either from IRC server or DCC chat */ +static void ctcp_msg_dcc(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target, DCC_REC *chat) +{ + char *args, *str; + + if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC)) + return; + + str = g_strconcat("ctcp msg dcc ", data, NULL); + args = strchr(str+13, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+13); + if (!signal_emit(str, 6, server, args, nick, addr, target, chat)) { + signal_emit("default ctcp msg dcc", 6, + server, data, nick, addr, target, chat); + } + g_free(str); +} + +/* Handle incoming DCC CTCP replies - either from IRC server or DCC chat */ +static void ctcp_reply_dcc(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + const char *target) +{ + char *args, *str; + + if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC)) + return; + + str = g_strconcat("ctcp reply dcc ", data, NULL); + args = strchr(str+15, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + ascii_strdown(str+15); + if (!signal_emit(str, 5, server, args, nick, addr, target)) { + signal_emit("default ctcp reply dcc", 5, + server, data, nick, addr, target); + } + g_free(str); +} + +/* CTCP REPLY: REJECT */ +static void ctcp_reply_dcc_reject(IRC_SERVER_REC *server, const char *data, + const char *nick, const char *addr, + DCC_REC *chat) +{ + DCC_REC *dcc; + char *type, *args; + + type = g_strdup(data); + args = strchr(type, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + dcc = dcc_find_request(dcc_str2type(type), nick, args); + if (dcc != NULL) dcc_close(dcc); + + g_free(type); +} + +void dcc_close(DCC_REC *dcc) +{ + signal_emit("dcc closed", 1, dcc); + dcc_destroy(dcc); +} + +/* Reject a DCC request */ +void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server) +{ + g_return_if_fail(dcc != NULL); + + signal_emit("dcc rejected", 1, dcc); + + if (dcc->server != NULL) + server = dcc->server; + + if (server != NULL && !dcc_is_connected(dcc)) { + irc_send_cmdv(server, "NOTICE %s :\001DCC REJECT %s %s\001", + dcc->nick, dcc_type2str(dcc->orig_type), + dcc->arg); + } + + dcc_close(dcc); +} + +static int dcc_timeout_func(void) +{ + GSList *tmp, *next; + time_t now; + + now = time(NULL)-settings_get_time("dcc_timeout")/1000; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (dcc->tagread == -1 && now > dcc->created && !IS_DCC_SERVER(dcc)) { + /* Timed out - don't send DCC REJECT CTCP so CTCP + flooders won't affect us and it really doesn't + matter that much anyway if the other side doen't + get it.. + + We don't want dcc servers to time out. */ + dcc_close(dcc); + } + } + + return 1; +} + +static void event_no_such_nick(IRC_SERVER_REC *server, char *data) +{ + char *params, *nick; + GSList *tmp, *next; + + g_return_if_fail(data != NULL); + + params = event_get_params(data, 2, NULL, &nick); + + /* check if we've send any dcc requests to this nick.. */ + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (!dcc_is_connected(dcc) && dcc->server == server && + dcc->nick != NULL && g_ascii_strcasecmp(dcc->nick, nick) == 0) + dcc_close(dcc); + } + + g_free(params); +} + +/* SYNTAX: DCC CLOSE <type> <nick> [<file>] */ +static void cmd_dcc_close(char *data, IRC_SERVER_REC *server) +{ + GSList *tmp, *next; + char *typestr, *nick, *arg, *fname; + void *free_arg; + int found, type; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, + &typestr, &nick, &arg)) + return; + + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + ascii_strup(typestr); + type = dcc_str2type(typestr); + if (type == -1) { + signal_emit("dcc error unknown type", 1, typestr); + cmd_params_free(free_arg); + return; + } + + fname = cmd_get_quoted_param(&arg); + + found = FALSE; + for (tmp = dcc_conns; tmp != NULL; tmp = next) { + DCC_REC *dcc = tmp->data; + + next = tmp->next; + if (dcc->type == type && g_ascii_strcasecmp(dcc->nick, nick) == 0 && + (*fname == '\0' || g_strcmp0(dcc->arg, fname) == 0)) { + dcc_reject(dcc, server); + found = TRUE; + } + } + + if (!found) { + signal_emit("dcc error close not found", 3, + typestr, nick, arg); + } + + cmd_params_free(free_arg); +} + +static void cmd_dcc(const char *data, IRC_SERVER_REC *server, void *item) +{ + command_runsub("dcc", data, server, item); +} + +void irc_dcc_init(void) +{ + dcc_conns = NULL; + dcc_timeouttag = g_timeout_add(1000, (GSourceFunc) dcc_timeout_func, NULL); + + settings_add_str("dcc", "dcc_port", "0"); + settings_add_time("dcc", "dcc_timeout", "5min"); + settings_add_str("dcc", "dcc_own_ip", ""); + + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed); + signal_add("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_add("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_add("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc); + signal_add("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc); + signal_add("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_add("event 401", (SIGNAL_FUNC) event_no_such_nick); + command_bind("dcc", NULL, (SIGNAL_FUNC) cmd_dcc); + command_bind("dcc close", NULL, (SIGNAL_FUNC) cmd_dcc_close); + + dcc_chat_init(); + dcc_get_init(); + dcc_send_init(); + dcc_resume_init(); + dcc_autoget_init(); + dcc_server_init(); + + settings_check(); + module_register("dcc", "irc"); +} + +void irc_dcc_deinit(void) +{ + while (dcc_conns != NULL) + dcc_destroy(dcc_conns->data); + + dcc_chat_deinit(); + dcc_get_deinit(); + dcc_send_deinit(); + dcc_resume_deinit(); + dcc_autoget_deinit(); + dcc_server_deinit(); + + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("server nick changed", (SIGNAL_FUNC) sig_server_nick_changed); + signal_remove("ctcp msg", (SIGNAL_FUNC) ctcp_msg); + signal_remove("ctcp reply", (SIGNAL_FUNC) ctcp_reply); + signal_remove("ctcp msg dcc", (SIGNAL_FUNC) ctcp_msg_dcc); + signal_remove("ctcp reply dcc", (SIGNAL_FUNC) ctcp_reply_dcc); + signal_remove("ctcp reply dcc reject", (SIGNAL_FUNC) ctcp_reply_dcc_reject); + signal_remove("event 401", (SIGNAL_FUNC) event_no_such_nick); + command_unbind("dcc", (SIGNAL_FUNC) cmd_dcc); + command_unbind("dcc close", (SIGNAL_FUNC) cmd_dcc_close); + + g_source_remove(dcc_timeouttag); +} + +MODULE_ABICHECK(irc_dcc) diff --git a/src/irc/dcc/dcc.h b/src/irc/dcc/dcc.h new file mode 100644 index 0000000..258d339 --- /dev/null +++ b/src/irc/dcc/dcc.h @@ -0,0 +1,67 @@ +#ifndef IRSSI_IRC_DCC_DCC_H +#define IRSSI_IRC_DCC_DCC_H + +#include <irssi/src/core/modules.h> +#include <irssi/src/core/network.h> + +#define DCC(dcc) ((DCC_REC *) (dcc)) + +typedef struct CHAT_DCC_REC CHAT_DCC_REC; + +typedef struct { +#include <irssi/src/irc/dcc/dcc-rec.h> +} DCC_REC; + +/* fully connected? */ +#define dcc_is_connected(dcc) \ + ((dcc)->starttime != 0) + +/* not connected, we're waiting for other side to connect */ +#define dcc_is_listening(dcc) \ + ((dcc)->handle != NULL && (dcc)->starttime == 0) + +/* not connected, waiting for user to accept it */ +#define dcc_is_waiting_user(dcc) \ + ((dcc)->handle == NULL) + +/* passive DCC */ +#define dcc_is_passive(dcc) \ + ((dcc)->pasv_id >= 0) + +extern GSList *dcc_conns; + +void dcc_register_type(const char *type); +void dcc_unregister_type(const char *type); + +int dcc_str2type(const char *str); +#define dcc_type2str(type) (module_find_id_str("DCC", type)) + +/* Initialize DCC record */ +void dcc_init_rec(DCC_REC *dcc, IRC_SERVER_REC *server, CHAT_DCC_REC *chat, + const char *nick, const char *arg); +void dcc_destroy(DCC_REC *dcc); + +/* Find waiting DCC requests (non-connected) */ +DCC_REC *dcc_find_request_latest(int type); +DCC_REC *dcc_find_request(int type, const char *nick, const char *arg); + +/* IP <-> string for DCC CTCP messages. + `str' must be at least MAX_IP_LEN bytes. + If /SET dcc_own_ip is set, dcc_ip2str() always returns it. */ +void dcc_ip2str(IPADDR *ip, char *str); +void dcc_str2ip(const char *str, IPADDR *ip); + +/* Start listening for incoming connections */ +GIOChannel *dcc_listen(GIOChannel *iface, IPADDR *ip, int *port); +/* Connect to specified IP address using the correct own_ip. */ +GIOChannel *dcc_connect_ip(IPADDR *ip, int port); + +/* Close DCC - sends "dcc closed" signal and calls dcc_destroy() */ +void dcc_close(DCC_REC *dcc); +/* Reject a DCC request */ +void dcc_reject(DCC_REC *dcc, IRC_SERVER_REC *server); + +void dcc_init(void); +void dcc_deinit(void); + +#endif diff --git a/src/irc/dcc/meson.build b/src/irc/dcc/meson.build new file mode 100644 index 0000000..0c3d706 --- /dev/null +++ b/src/irc/dcc/meson.build @@ -0,0 +1,31 @@ +# this file is part of irssi + +libirc_dcc_a = static_library('irc_dcc', + files( + 'dcc-autoget.c', + 'dcc-chat.c', + 'dcc-get.c', + 'dcc-queue.c', + 'dcc-resume.c', + 'dcc-send.c', + 'dcc-server.c', + 'dcc.c', + ), + include_directories : rootinc, + implicit_include_directories : false, + dependencies : dep) + +install_headers( + files( + 'dcc-chat.h', + 'dcc-file-rec.h', + 'dcc-file.h', + 'dcc-get.h', + 'dcc-queue.h', + 'dcc-rec.h', + 'dcc-send.h', + 'dcc-server.h', + 'dcc.h', + 'module.h', + ), + subdir : incdir / 'src' / 'irc' / 'dcc') diff --git a/src/irc/dcc/module.h b/src/irc/dcc/module.h new file mode 100644 index 0000000..4330d0a --- /dev/null +++ b/src/irc/dcc/module.h @@ -0,0 +1,4 @@ +#include <irssi/src/common.h> +#include <irssi/src/irc/core/irc.h> + +#define MODULE_NAME "irc/dcc" |