diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:22:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:22:03 +0000 |
commit | ffccd5b2b05243e7976db80f90f453dccfae9886 (patch) | |
tree | 39a43152d27f7390d8f7a6fb276fa6887f87c6e8 /src/vfs/ftpfs | |
parent | Initial commit. (diff) | |
download | mc-ffccd5b2b05243e7976db80f90f453dccfae9886.tar.xz mc-ffccd5b2b05243e7976db80f90f453dccfae9886.zip |
Adding upstream version 3:4.8.30.upstream/3%4.8.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/vfs/ftpfs')
-rw-r--r-- | src/vfs/ftpfs/Makefile.am | 8 | ||||
-rw-r--r-- | src/vfs/ftpfs/Makefile.in | 740 | ||||
-rw-r--r-- | src/vfs/ftpfs/ftpfs.c | 2784 | ||||
-rw-r--r-- | src/vfs/ftpfs/ftpfs.h | 46 | ||||
-rw-r--r-- | src/vfs/ftpfs/ftpfs_parse_ls.c | 1236 |
5 files changed, 4814 insertions, 0 deletions
diff --git a/src/vfs/ftpfs/Makefile.am b/src/vfs/ftpfs/Makefile.am new file mode 100644 index 0000000..b581563 --- /dev/null +++ b/src/vfs/ftpfs/Makefile.am @@ -0,0 +1,8 @@ + +AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) + +noinst_LTLIBRARIES = libvfs-ftpfs.la + +libvfs_ftpfs_la_SOURCES = \ + ftpfs.c ftpfs.h \ + ftpfs_parse_ls.c
\ No newline at end of file diff --git a/src/vfs/ftpfs/Makefile.in b/src/vfs/ftpfs/Makefile.in new file mode 100644 index 0000000..e6e561f --- /dev/null +++ b/src/vfs/ftpfs/Makefile.in @@ -0,0 +1,740 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/vfs/ftpfs +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/longlong.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/nls.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \ + $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/m4.include/gnulib/mode_t.m4 \ + $(top_srcdir)/m4.include/gnulib/stat-size.m4 \ + $(top_srcdir)/m4.include/gnulib/fstypename.m4 \ + $(top_srcdir)/m4.include/gnulib/fsusage.m4 \ + $(top_srcdir)/m4.include/gnulib/mountlist.m4 \ + $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \ + $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \ + $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \ + $(top_srcdir)/m4.include/ax_check_pcre2.m4 \ + $(top_srcdir)/m4.include/dx_doxygen.m4 \ + $(top_srcdir)/m4.include/ax_require_defined.m4 \ + $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4.include/ax_append_flag.m4 \ + $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \ + $(top_srcdir)/m4.include/mc-cflags.m4 \ + $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4.include/mc-check-search-type.m4 \ + $(top_srcdir)/m4.include/mc-get-fs-info.m4 \ + $(top_srcdir)/m4.include/mc-with-x.m4 \ + $(top_srcdir)/m4.include/mc-use-termcap.m4 \ + $(top_srcdir)/m4.include/mc-with-screen.m4 \ + $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \ + $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \ + $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \ + $(top_srcdir)/m4.include/mc-subshell.m4 \ + $(top_srcdir)/m4.include/mc-background.m4 \ + $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \ + $(top_srcdir)/m4.include/mc-glib.m4 \ + $(top_srcdir)/m4.include/mc-vfs.m4 \ + $(top_srcdir)/m4.include/vfs/rpc.m4 \ + $(top_srcdir)/m4.include/vfs/socket.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \ + $(top_srcdir)/m4.include/mc-version.m4 \ + $(top_srcdir)/m4.include/mc-tests.m4 \ + $(top_srcdir)/m4.include/mc-i18n.m4 \ + $(top_srcdir)/m4.include/mc-assert.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libvfs_ftpfs_la_LIBADD = +am_libvfs_ftpfs_la_OBJECTS = ftpfs.lo ftpfs_parse_ls.lo +libvfs_ftpfs_la_OBJECTS = $(am_libvfs_ftpfs_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +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 = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/config/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/ftpfs.Plo \ + ./$(DEPDIR)/ftpfs_parse_ls.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libvfs_ftpfs_la_SOURCES) +DIST_SOURCES = $(libvfs_ftpfs_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +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)/config/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@ +CHECK_CFLAGS = @CHECK_CFLAGS@ +CHECK_LIBS = @CHECK_LIBS@ +COM_ERR_CFLAGS = @COM_ERR_CFLAGS@ +COM_ERR_LIBS = @COM_ERR_LIBS@ +CP1251 = @CP1251@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DOC_LINGUAS = @DOC_LINGUAS@ +DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DX_CONFIG = @DX_CONFIG@ +DX_DOCDIR = @DX_DOCDIR@ +DX_DOT = @DX_DOT@ +DX_DOXYGEN = @DX_DOXYGEN@ +DX_DVIPS = @DX_DVIPS@ +DX_EGREP = @DX_EGREP@ +DX_ENV = @DX_ENV@ +DX_FLAG_chi = @DX_FLAG_chi@ +DX_FLAG_chm = @DX_FLAG_chm@ +DX_FLAG_doc = @DX_FLAG_doc@ +DX_FLAG_dot = @DX_FLAG_dot@ +DX_FLAG_html = @DX_FLAG_html@ +DX_FLAG_man = @DX_FLAG_man@ +DX_FLAG_pdf = @DX_FLAG_pdf@ +DX_FLAG_ps = @DX_FLAG_ps@ +DX_FLAG_rtf = @DX_FLAG_rtf@ +DX_FLAG_xml = @DX_FLAG_xml@ +DX_HHC = @DX_HHC@ +DX_LATEX = @DX_LATEX@ +DX_MAKEINDEX = @DX_MAKEINDEX@ +DX_PDFLATEX = @DX_PDFLATEX@ +DX_PERL = @DX_PERL@ +DX_PROJECT = @DX_PROJECT@ +E2P_CFLAGS = @E2P_CFLAGS@ +E2P_LIBS = @E2P_LIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXT2FS_CFLAGS = @EXT2FS_CFLAGS@ +EXT2FS_LIBS = @EXT2FS_LIBS@ +EXTHELPERSDIR = @EXTHELPERSDIR@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GMODULE_CFLAGS = @GMODULE_CFLAGS@ +GMODULE_LIBS = @GMODULE_LIBS@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +HAVE_FILECMD = @HAVE_FILECMD@ +HAVE_ZIPINFO = @HAVE_ZIPINFO@ +HAVE_nroff = @HAVE_nroff@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBMC_RELEASE = @LIBMC_RELEASE@ +LIBMC_VERSION = @LIBMC_VERSION@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSSH_CFLAGS = @LIBSSH_CFLAGS@ +LIBSSH_LIBS = @LIBSSH_LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANDOC = @MANDOC@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MAN_DATE = @MAN_DATE@ +MAN_FLAGS = @MAN_FLAGS@ +MAN_VERSION = @MAN_VERSION@ +MCLIBS = @MCLIBS@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +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@ +PCRE_CFLAGS = @PCRE_CFLAGS@ +PCRE_LIBS = @PCRE_LIBS@ +PERL = @PERL@ +PERL_FOR_BUILD = @PERL_FOR_BUILD@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +PYTHON = @PYTHON@ +RANLIB = @RANLIB@ +RUBY = @RUBY@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SLANG_CFLAGS = @SLANG_CFLAGS@ +SLANG_LIBS = @SLANG_LIBS@ +STRIP = @STRIP@ +TESTS_LDFLAGS = @TESTS_LDFLAGS@ +UNZIP = @UNZIP@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +X11_WWW = @X11_WWW@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XMKMF = @XMKMF@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +ZIP = @ZIP@ +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@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) +noinst_LTLIBRARIES = libvfs-ftpfs.la +libvfs_ftpfs_la_SOURCES = \ + ftpfs.c ftpfs.h \ + ftpfs_parse_ls.c + +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) --gnu src/vfs/ftpfs/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/vfs/ftpfs/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-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libvfs-ftpfs.la: $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_DEPENDENCIES) $(EXTRA_libvfs_ftpfs_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs_parse_ls.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.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)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.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)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +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-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/ftpfs.Plo + -rm -f ./$(DEPDIR)/ftpfs_parse_ls.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-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)/ftpfs.Plo + -rm -f ./$(DEPDIR)/ftpfs_parse_ls.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-noinstLTLIBRARIES \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/vfs/ftpfs/ftpfs.c b/src/vfs/ftpfs/ftpfs.c new file mode 100644 index 0000000..549ba32 --- /dev/null +++ b/src/vfs/ftpfs/ftpfs.c @@ -0,0 +1,2784 @@ +/* + Virtual File System: FTP file system. + + Copyright (C) 1995-2023 + Free Software Foundation, Inc. + + Written by: + Ching Hui, 1995 + Jakub Jelinek, 1995 + Miguel de Icaza, 1995, 1996, 1997 + Norbert Warmuth, 1997 + Pavel Machek, 1998 + Yury V. Zaytsev, 2010 + Slava Zanko <slavazanko@gmail.com>, 2010, 2013 + Andrew Borodin <aborodin@vmail.ru>, 2010-2022 + + This file is part of the Midnight Commander. + + The Midnight Commander 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 3 of the License, + or (at your option) any later version. + + The Midnight Commander 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, see <http://www.gnu.org/licenses/>. + */ + +/** + * \file + * \brief Source: Virtual File System: FTP file system + * \author Ching Hui + * \author Jakub Jelinek + * \author Miguel de Icaza + * \author Norbert Warmuth + * \author Pavel Machek + * \date 1995, 1997, 1998 + * + * \todo +- make it more robust - all the connects etc. should handle EADDRINUSE and + ERETRY (have I spelled these names correctly?) +- make the user able to flush a connection - all the caches will get empty + etc., (tarfs as well), we should give there a user selectable timeout + and assign a key sequence. +- use hash table instead of linklist to cache ftpfs directory. + +What to do with this? + + + * NOTE: Usage of tildes is deprecated, consider: + * \verbatim + cd ftp//:pavel@hobit + cd ~ + \endverbatim + * And now: what do I want to do? Do I want to go to /home/pavel or to + * ftp://hobit/home/pavel? I think first has better sense... + * + \verbatim + { + int f = !strcmp( remote_path, "/~" ); + if (f || !strncmp( remote_path, "/~/", 3 )) { + char *s; + s = mc_build_filename ( qhome (*bucket), remote_path +3-f, (char *) NULL ); + g_free (remote_path); + remote_path = s; + } + } + \endverbatim + */ + +/* \todo Fix: Namespace pollution: horrible */ + +#include <config.h> +#include <stdio.h> /* sscanf() */ +#include <stdlib.h> /* atoi() */ +#include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */ +#include <netdb.h> /* struct hostent */ +#include <sys/socket.h> /* AF_INET */ +#include <netinet/in.h> /* struct in_addr */ +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#include <arpa/ftp.h> +#include <arpa/telnet.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <inttypes.h> /* uintmax_t */ + +#include "lib/global.h" +#include "lib/file-entry.h" +#include "lib/util.h" +#include "lib/strutil.h" /* str_move() */ +#include "lib/mcconfig.h" + +#include "lib/tty/tty.h" /* enable/disable interrupt key */ +#include "lib/widget.h" /* message() */ + +#include "src/history.h" +#include "src/setup.h" /* for load_anon_passwd */ + +#include "lib/vfs/vfs.h" +#include "lib/vfs/utilvfs.h" +#include "lib/vfs/netutil.h" +#include "lib/vfs/xdirentry.h" +#include "lib/vfs/gc.h" /* vfs_stamp_create */ + +#include "ftpfs.h" + +/*** global variables ****************************************************************************/ + +/* Delay to retry a connection */ +int ftpfs_retry_seconds = 30; + +/* Method to use to connect to ftp sites */ +gboolean ftpfs_use_passive_connections = TRUE; +gboolean ftpfs_use_passive_connections_over_proxy = FALSE; + +/* Method used to get directory listings: + * 1: try 'LIST -la <path>', if it fails + * fall back to CWD <path>; LIST + * 0: always use CWD <path>; LIST + */ +gboolean ftpfs_use_unix_list_options = TRUE; + +/* First "CWD <path>", then "LIST -la ." */ +gboolean ftpfs_first_cd_then_ls = TRUE; + +/* Use the ~/.netrc */ +gboolean ftpfs_use_netrc = TRUE; + +/* Anonymous setup */ +char *ftpfs_anonymous_passwd = NULL; +int ftpfs_directory_timeout = 900; + +/* Proxy host */ +char *ftpfs_proxy_host = NULL; + +/* whether we have to use proxy by default? */ +gboolean ftpfs_always_use_proxy = FALSE; + +gboolean ftpfs_ignore_chattr_errors = TRUE; + +/*** file scope macro definitions ****************************************************************/ + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define FTP_SUPER(super) ((ftp_super_t *) (super)) +#define FTP_FILE_HANDLER(fh) ((ftp_file_handler_t *) (fh)) +#define FH_SOCK FTP_FILE_HANDLER(fh)->sock + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#define RFC_AUTODETECT 0 +#define RFC_DARING 1 +#define RFC_STRICT 2 + +/* ftpfs_command wait_flag: */ +#define NONE 0x00 +#define WAIT_REPLY 0x01 +#define WANT_STRING 0x02 + +#define FTP_COMMAND_PORT 21 + +/* some defines only used by ftpfs_changetype */ +/* These two are valid values for the second parameter */ +#define TYPE_ASCII 0 +#define TYPE_BINARY 1 + +/* This one is only used to initialize bucket->isbinary, don't use it as + second parameter to ftpfs_changetype. */ +#define TYPE_UNKNOWN -1 + +#define ABORT_TIMEOUT (5 * G_USEC_PER_SEC) +/*** file scope type declarations ****************************************************************/ + +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +/* This should match the keywords[] array below */ +typedef enum +{ + NETRC_NONE = 0, + NETRC_DEFAULT, + NETRC_MACHINE, + NETRC_LOGIN, + NETRC_PASSWORD, + NETRC_PASSWD, + NETRC_ACCOUNT, + NETRC_MACDEF, + NETRC_UNKNOWN +} keyword_t; + +typedef struct +{ + struct vfs_s_super base; /* base class */ + + int sock; + + char *proxy; /* proxy server, NULL if no proxy */ + gboolean failed_on_login; /* used to pass the failure reason to upper levels */ + gboolean use_passive_connection; + gboolean remote_is_amiga; /* No leading slash allowed for AmiTCP (Amiga) */ + int isbinary; + gboolean cwd_deferred; /* current_directory was changed but CWD command hasn't + been sent yet */ + int strict; /* ftp server doesn't understand + * "LIST -la <path>"; use "CWD <path>"/ + * "LIST" instead + */ + gboolean ctl_connection_busy; + char *current_dir; +} ftp_super_t; + +typedef struct +{ + vfs_file_handler_t base; /* base class */ + + int sock; + gboolean append; +} ftp_file_handler_t; + +/*** forward declarations (file scope functions) *************************************************/ + +static char *ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super); +static int ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, + const char *remote_path); +static int ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super); +static gboolean ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super, + const char *netrcpass); +static gboolean ftpfs_netrc_lookup (const char *host, char **login, char **pass); + +/*** file scope variables ************************************************************************/ + +static int code; + +static char reply_str[80]; + +static struct vfs_s_subclass ftpfs_subclass; +static struct vfs_class *vfs_ftpfs_ops = VFS_CLASS (&ftpfs_subclass); + +static GSList *no_proxy = NULL; + +static char buffer[BUF_MEDIUM]; +static char *netrc = NULL; +static const char *netrcp; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_set_blksize (struct stat *s) +{ +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + /* redefine block size */ + s->st_blksize = 64 * 1024; /* FIXME */ +#endif +} + +/* --------------------------------------------------------------------------------------------- */ + +static struct stat * +ftpfs_default_stat (struct vfs_class *me) +{ + struct stat *s; + + s = vfs_s_default_stat (me, S_IFDIR | 0755); + ftpfs_set_blksize (s); + vfs_adjust_stat (s); + + return s; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Translate a Unix path, i.e. MC's internal path representation (e.g. + /somedir/somefile) to a path valid for the remote server. Every path + transferred to the remote server has to be mangled by this function + right prior to sending it. + Currently only Amiga ftp servers are handled in a special manner. + + When the remote server is an amiga: + a) strip leading slash if necessary + b) replace first occurrence of ":/" with ":" + c) strip trailing "/." + */ +static char * +ftpfs_translate_path (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path) +{ + char *ret, *p; + + if (!FTP_SUPER (super)->remote_is_amiga) + return g_strdup (remote_path); + + if (me->logfile != NULL) + { + fprintf (me->logfile, "MC -- ftpfs_translate_path: %s\n", remote_path); + fflush (me->logfile); + } + + /* strip leading slash(es) */ + while (IS_PATH_SEP (*remote_path)) + remote_path++; + + /* Don't change "/" into "", e.g. "CWD " would be invalid. */ + if (*remote_path == '\0') + return g_strdup ("."); + + ret = g_strdup (remote_path); + + /* replace first occurrence of ":/" with ":" */ + p = strchr (ret, ':'); + if (p != NULL && IS_PATH_SEP (p[1])) + str_move (p + 1, p + 2); + + /* strip trailing "/." */ + p = strrchr (ret, PATH_SEP); + if ((p != NULL) && (*(p + 1) == '.') && (*(p + 2) == '\0')) + *p = '\0'; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/** Extract the hostname and username from the path */ +/* + * path is in the form: [user@]hostname:port/remote-dir, e.g.: + * ftp://sunsite.unc.edu/pub/linux + * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc + * ftp://tsx-11.mit.edu:8192/ + * ftp://joe@foo.edu:11321/private + * If the user is empty, e.g. ftp://@roxanne/private, then your login name + * is supplied. + */ + +static vfs_path_element_t * +ftpfs_correct_url_parameters (const vfs_path_element_t * velement) +{ + vfs_path_element_t *path_element = vfs_path_element_clone (velement); + + if (path_element->port == 0) + path_element->port = FTP_COMMAND_PORT; + + if (path_element->user == NULL) + { + /* Look up user and password in netrc */ + if (ftpfs_use_netrc) + ftpfs_netrc_lookup (path_element->host, &path_element->user, &path_element->password); + } + if (path_element->user == NULL) + path_element->user = g_strdup ("anonymous"); + + /* Look up password in netrc for known user */ + if (ftpfs_use_netrc && path_element->password == NULL) + { + char *new_user = NULL; + char *new_passwd = NULL; + + ftpfs_netrc_lookup (path_element->host, &new_user, &new_passwd); + + /* If user is different, remove password */ + if (new_user != NULL && strcmp (path_element->user, new_user) != 0) + MC_PTR_FREE (path_element->password); + + g_free (new_user); + g_free (new_passwd); + } + + return path_element; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */ + +static int +ftpfs_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len) +{ + while (TRUE) + { + char answer[BUF_1K]; + + if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0) + { + if (string_buf != NULL) + *string_buf = '\0'; + code = 421; + return 4; + } + + /* cppcheck-suppress invalidscanf */ + switch (sscanf (answer, "%d", &code)) + { + case 0: + if (string_buf != NULL) + g_strlcpy (string_buf, answer, string_len); + code = 500; + return 5; + case 1: + if (answer[3] == '-') + { + while (TRUE) + { + int i; + + if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0) + { + if (string_buf != NULL) + *string_buf = '\0'; + code = 421; + return 4; + } + /* cppcheck-suppress invalidscanf */ + if ((sscanf (answer, "%d", &i) > 0) && (code == i) && (answer[3] == ' ')) + break; + } + } + if (string_buf != NULL) + g_strlcpy (string_buf, answer, string_len); + return code / 100; + default: + break; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +ftpfs_reconnect (struct vfs_class *me, struct vfs_s_super *super) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + int sock; + + sock = ftpfs_open_socket (me, super); + if (sock != -1) + { + char *cwdir = ftp_super->current_dir; + + close (ftp_super->sock); + ftp_super->sock = sock; + ftp_super->current_dir = NULL; + + if (ftpfs_login_server (me, super, super->path_element->password)) + { + if (cwdir == NULL) + return TRUE; + + sock = ftpfs_chdir_internal (me, super, cwdir); + g_free (cwdir); + return (sock == COMPLETE); + } + + ftp_super->current_dir = cwdir; + } + + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +G_GNUC_PRINTF (4, 5) +ftpfs_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *fmt, + ...) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + va_list ap; + GString *cmdstr; + int status; + static gboolean retry = FALSE; + static int level = 0; /* ftpfs_login_server() use ftpfs_command() */ + + cmdstr = g_string_sized_new (32); + va_start (ap, fmt); + g_string_vprintf (cmdstr, fmt, ap); + va_end (ap); + g_string_append (cmdstr, "\r\n"); + + if (me->logfile != NULL) + { + if (strncmp (cmdstr->str, "PASS ", 5) == 0) + fputs ("PASS <Password not logged>\r\n", me->logfile); + else + { + size_t ret; + + ret = fwrite (cmdstr->str, cmdstr->len, 1, me->logfile); + (void) ret; + } + + fflush (me->logfile); + } + + got_sigpipe = 0; + tty_enable_interrupt_key (); + status = write (ftp_super->sock, cmdstr->str, cmdstr->len); + + if (status < 0) + { + code = 421; + + if (errno == EPIPE) + { /* Remote server has closed connection */ + if (level == 0) + { + level = 1; + status = ftpfs_reconnect (me, super) ? 1 : 0; + level = 0; + if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0)) + goto ok; + + } + got_sigpipe = 1; + } + g_string_free (cmdstr, TRUE); + tty_disable_interrupt_key (); + return TRANSIENT; + } + + retry = FALSE; + + ok: + tty_disable_interrupt_key (); + + if (wait_reply != NONE) + { + status = ftpfs_get_reply (me, ftp_super->sock, + (wait_reply & WANT_STRING) != 0 ? reply_str : NULL, + sizeof (reply_str) - 1); + if ((wait_reply & WANT_STRING) != 0 && !retry && level == 0 && code == 421) + { + retry = TRUE; + level = 1; + status = ftpfs_reconnect (me, super) ? 1 : 0; + level = 0; + if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0)) + goto ok; + } + retry = FALSE; + g_string_free (cmdstr, TRUE); + return status; + } + + g_string_free (cmdstr, TRUE); + return COMPLETE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static struct vfs_s_super * +ftpfs_new_archive (struct vfs_class *me) +{ + ftp_super_t *arch; + + arch = g_new0 (ftp_super_t, 1); + arch->base.me = me; + arch->base.name = g_strdup (PATH_SEP_STR); + arch->sock = -1; + arch->use_passive_connection = ftpfs_use_passive_connections; + arch->strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT; + arch->isbinary = TYPE_UNKNOWN; + + return VFS_SUPER (arch); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + + if (ftp_super->sock != -1) + { + vfs_print_message (_("ftpfs: Disconnecting from %s"), super->path_element->host); + ftpfs_command (me, super, NONE, "%s", "QUIT"); + close (ftp_super->sock); + } + g_free (ftp_super->current_dir); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_changetype (struct vfs_class *me, struct vfs_s_super *super, int binary) +{ + if (binary != FTP_SUPER (super)->isbinary) + { + if (ftpfs_command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE) + ERRNOR (EIO, -1); + FTP_SUPER (super)->isbinary = binary; + } + return binary; +} + +/* --------------------------------------------------------------------------------------------- */ +/* This routine logs the user in */ + +static int +ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super, const char *netrcpass) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + char *pass; + char *op; + char *name; /* login user name */ + gboolean anon = FALSE; + char reply_string[BUF_MEDIUM]; + + ftp_super->isbinary = TYPE_UNKNOWN; + + if (super->path_element->password != NULL) /* explicit password */ + op = g_strdup (super->path_element->password); + else if (netrcpass != NULL) /* password from netrc */ + op = g_strdup (netrcpass); + else if (strcmp (super->path_element->user, "anonymous") == 0 + || strcmp (super->path_element->user, "ftp") == 0) + { + if (ftpfs_anonymous_passwd == NULL) /* default anonymous password */ + ftpfs_init_passwd (); + op = g_strdup (ftpfs_anonymous_passwd); + anon = TRUE; + } + else + { /* ask user */ + char *p; + + p = g_strdup_printf (_("FTP: Password required for %s"), super->path_element->user); + op = vfs_get_password (p); + g_free (p); + if (op == NULL) + ERRNOR (EPERM, 0); + super->path_element->password = g_strdup (op); + } + + if (!anon || me->logfile != NULL) + pass = op; + else + { + pass = g_strconcat ("-", op, (char *) NULL); + wipe_password (op); + } + + /* Proxy server accepts: username@host-we-want-to-connect */ + if (ftp_super->proxy != NULL) + name = + g_strconcat (super->path_element->user, "@", + super->path_element->host[0] == + '!' ? super->path_element->host + 1 : super->path_element->host, + (char *) NULL); + else + name = g_strdup (super->path_element->user); + + if (ftpfs_get_reply (me, ftp_super->sock, reply_string, sizeof (reply_string) - 1) == COMPLETE) + { + char *reply_up; + + reply_up = g_ascii_strup (reply_string, -1); + ftp_super->remote_is_amiga = strstr (reply_up, "AMIGA") != NULL; + if (strstr (reply_up, " SPFTP/1.0.0000 SERVER ") != NULL) /* handles `LIST -la` in a weird way */ + ftp_super->strict = RFC_STRICT; + g_free (reply_up); + + if (me->logfile != NULL) + { + fprintf (me->logfile, "MC -- remote_is_amiga = %s\n", + ftp_super->remote_is_amiga ? "yes" : "no"); + fflush (me->logfile); + } + + vfs_print_message ("%s", _("ftpfs: sending login name")); + + switch (ftpfs_command (me, super, WAIT_REPLY, "USER %s", name)) + { + case CONTINUE: + vfs_print_message ("%s", _("ftpfs: sending user password")); + code = ftpfs_command (me, super, WAIT_REPLY, "PASS %s", pass); + if (code == CONTINUE) + { + char *p; + + p = g_strdup_printf (_("FTP: Account required for user %s"), + super->path_element->user); + op = input_dialog (p, _("Account:"), MC_HISTORY_FTPFS_ACCOUNT, "", + INPUT_COMPLETE_USERNAMES); + g_free (p); + if (op == NULL) + ERRNOR (EPERM, 0); + vfs_print_message ("%s", _("ftpfs: sending user account")); + code = ftpfs_command (me, super, WAIT_REPLY, "ACCT %s", op); + g_free (op); + } + if (code != COMPLETE) + break; + + MC_FALLTHROUGH; + + case COMPLETE: + vfs_print_message ("%s", _("ftpfs: logged in")); + wipe_password (pass); + g_free (name); + return TRUE; + + default: + ftp_super->failed_on_login = TRUE; + wipe_password (super->path_element->password); + super->path_element->password = NULL; + goto login_fail; + } + } + + message (D_ERROR, MSG_ERROR, _("ftpfs: Login incorrect for user %s "), + super->path_element->user); + + login_fail: + wipe_password (pass); + g_free (name); + ERRNOR (EPERM, FALSE); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_load_no_proxy_list (void) +{ + /* FixMe: shouldn't be hardcoded!!! */ + char *mc_file; + + mc_file = g_build_filename (mc_global.sysconfig_dir, "mc.no_proxy", (char *) NULL); + if (exist_file (mc_file)) + { + FILE *npf; + + npf = fopen (mc_file, "r"); + if (npf != NULL) + { + char s[BUF_LARGE]; /* provide for BUF_LARGE characters */ + + while (fgets (s, sizeof (s), npf) != NULL) + { + char *p; + + p = strchr (s, '\n'); + if (p == NULL) /* skip bogus entries */ + { + int c; + + while ((c = fgetc (npf)) != EOF && c != '\n') + ; + } + else if (p != s) + { + *p = '\0'; + no_proxy = g_slist_prepend (no_proxy, g_strdup (s)); + } + } + + fclose (npf); + } + } + + g_free (mc_file); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Return TRUE if FTP proxy should be used for this host, FALSE otherwise */ + +static gboolean +ftpfs_check_proxy (const char *host) +{ + + if (ftpfs_proxy_host == NULL || *ftpfs_proxy_host == '\0' || host == NULL || *host == '\0') + return FALSE; /* sanity check */ + + if (*host == '!') + return TRUE; + + if (!ftpfs_always_use_proxy) + return FALSE; + + if (strchr (host, '.') == NULL) + return FALSE; + + if (no_proxy == NULL) + { + GSList *npe; + + ftpfs_load_no_proxy_list (); + + for (npe = no_proxy; npe != NULL; npe = g_slist_next (npe)) + { + const char *domain = (const char *) npe->data; + + if (domain[0] == '.') + { + size_t ld, lh; + + ld = strlen (domain); + lh = strlen (host); + + while (ld != 0 && lh != 0 && host[lh - 1] == domain[ld - 1]) + { + ld--; + lh--; + } + + if (ld == 0) + return FALSE; + } + else if (g_ascii_strcasecmp (host, domain) == 0) + return FALSE; + } + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_get_proxy_host_and_port (const char *proxy, char **host, int *port) +{ + vfs_path_element_t *path_element; + + path_element = vfs_url_split (proxy, FTP_COMMAND_PORT, URL_USE_ANONYMOUS); + *host = path_element->host; + path_element->host = NULL; + *port = path_element->port; + vfs_path_element_free (path_element); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super) +{ + struct addrinfo hints, *res, *curr_res; + int my_socket = 0; + char *host = NULL; + char port[8]; + int tmp_port = 0; + int e; + + (void) me; + + if (super->path_element->host == NULL || *super->path_element->host == '\0') + { + vfs_print_message ("%s", _("ftpfs: Invalid host name.")); + me->verrno = EINVAL; + return (-1); + } + + /* Use a proxy host? */ + /* Hosts to connect to that start with a ! should use proxy */ + if (FTP_SUPER (super)->proxy != NULL) + ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &tmp_port); + else + { + host = g_strdup (super->path_element->host); + tmp_port = super->path_element->port; + } + + g_snprintf (port, sizeof (port), "%hu", (unsigned short) tmp_port); + + tty_enable_interrupt_key (); /* clear the interrupt flag */ + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + +#ifdef AI_ADDRCONFIG + /* By default, only look up addresses using address types for + * which a local interface is configured (i.e. no IPv6 if no IPv6 + * interfaces, likewise for IPv4 (see RFC 3493 for details). */ + hints.ai_flags = AI_ADDRCONFIG; +#endif + + e = getaddrinfo (host, port, &hints, &res); + +#ifdef AI_ADDRCONFIG + if (e == EAI_BADFLAGS) + { + /* Retry with no flags if AI_ADDRCONFIG was rejected. */ + hints.ai_flags = 0; + e = getaddrinfo (host, port, &hints, &res); + } +#endif + + *port = '\0'; + + if (e != 0) + { + tty_disable_interrupt_key (); + vfs_print_message (_("ftpfs: %s"), gai_strerror (e)); + g_free (host); + me->verrno = EINVAL; + return (-1); + } + + for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next) + { + my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol); + + if (my_socket < 0) + { + if (curr_res->ai_next != NULL) + continue; + + tty_disable_interrupt_key (); + vfs_print_message (_("ftpfs: %s"), unix_error_string (errno)); + g_free (host); + freeaddrinfo (res); + me->verrno = errno; + return (-1); + } + + vfs_print_message (_("ftpfs: making connection to %s"), host); + MC_PTR_FREE (host); + + if (connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0) + break; + + me->verrno = errno; + close (my_socket); + + if (errno == EINTR && tty_got_interrupt ()) + vfs_print_message ("%s", _("ftpfs: connection interrupted by user")); + else if (res->ai_next == NULL) + vfs_print_message (_("ftpfs: connection to server failed: %s"), + unix_error_string (errno)); + else + continue; + + freeaddrinfo (res); + tty_disable_interrupt_key (); + return (-1); + } + + freeaddrinfo (res); + tty_disable_interrupt_key (); + return my_socket; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_open_archive_int (struct vfs_class *me, struct vfs_s_super *super) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + int retry_seconds = 0; + + /* We do not want to use the passive if we are using proxies */ + if (ftp_super->proxy != NULL) + ftp_super->use_passive_connection = ftpfs_use_passive_connections_over_proxy; + + do + { + ftp_super->failed_on_login = FALSE; + + ftp_super->sock = ftpfs_open_socket (me, super); + if (ftp_super->sock == -1) + return (-1); + + if (ftpfs_login_server (me, super, NULL)) + { + /* Logged in, no need to retry the connection */ + break; + } + + if (!ftp_super->failed_on_login) + return (-1); + + /* Close only the socket descriptor */ + close (ftp_super->sock); + + if (ftpfs_retry_seconds != 0) + { + int count_down; + + retry_seconds = ftpfs_retry_seconds; + tty_enable_interrupt_key (); + for (count_down = retry_seconds; count_down != 0; count_down--) + { + vfs_print_message (_("Waiting to retry... %d (Control-G to cancel)"), count_down); + sleep (1); + if (tty_got_interrupt ()) + { + /* me->verrno = E; */ + tty_disable_interrupt_key (); + return 0; + } + } + tty_disable_interrupt_key (); + } + } + while (retry_seconds != 0); + + ftp_super->current_dir = ftpfs_get_current_directory (me, super); + if (ftp_super->current_dir == NULL) + ftp_super->current_dir = g_strdup (PATH_SEP_STR); + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_open_archive (struct vfs_s_super *super, + const vfs_path_t * vpath, const vfs_path_element_t * vpath_element) +{ + (void) vpath; + + super->path_element = ftpfs_correct_url_parameters (vpath_element); + if (ftpfs_check_proxy (super->path_element->host)) + FTP_SUPER (super)->proxy = ftpfs_proxy_host; + super->root = + vfs_s_new_inode (vpath_element->class, super, ftpfs_default_stat (vpath_element->class)); + + return ftpfs_open_archive_int (vpath_element->class, super); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super, + const vfs_path_t * vpath, void *cookie) +{ + vfs_path_element_t *path_element; + int result; + + (void) vpath; + (void) cookie; + + path_element = ftpfs_correct_url_parameters (vpath_element); + + result = ((strcmp (path_element->host, super->path_element->host) == 0) + && (strcmp (path_element->user, super->path_element->user) == 0) + && (path_element->port == super->path_element->port)) ? 1 : 0; + + vfs_path_element_free (path_element); + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/* The returned directory should always contain a trailing slash */ + +static char * +ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super) +{ + char buf[MC_MAXPATHLEN + 1]; + + if (ftpfs_command (me, super, NONE, "%s", "PWD") == COMPLETE && + ftpfs_get_reply (me, FTP_SUPER (super)->sock, buf, sizeof (buf)) == COMPLETE) + { + char *bufp = NULL; + char *bufq; + + for (bufq = buf; *bufq != '\0'; bufq++) + if (*bufq == '"') + { + if (bufp == NULL) + bufp = bufq + 1; + else + { + *bufq = '\0'; + + if (*bufp != '\0') + { + if (!IS_PATH_SEP (bufq[-1])) + { + *bufq++ = PATH_SEP; + *bufq = '\0'; + } + + if (IS_PATH_SEP (*bufp)) + return g_strdup (bufp); + + /* If the remote server is an Amiga a leading slash + might be missing. MC needs it because it is used + as separator between hostname and path internally. */ + return g_strconcat (PATH_SEP_STR, bufp, (char *) NULL); + } + + break; + } + } + } + + me->verrno = EIO; + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Setup Passive PASV FTP connection */ + +static gboolean +ftpfs_setup_passive_pasv (struct vfs_class *me, struct vfs_s_super *super, + int my_socket, struct sockaddr_storage *sa, socklen_t * salen) +{ + char *c; + char n[6]; + int xa, xb, xc, xd, xe, xf; + + if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "PASV") != COMPLETE) + return FALSE; + + /* Parse remote parameters */ + for (c = reply_str + 4; *c != '\0' && !isdigit ((unsigned char) *c); c++) + ; + + if (*c == '\0' || !isdigit ((unsigned char) *c)) + return FALSE; + + /* cppcheck-suppress invalidscanf */ + if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6) + return FALSE; + + n[0] = (unsigned char) xa; + n[1] = (unsigned char) xb; + n[2] = (unsigned char) xc; + n[3] = (unsigned char) xd; + n[4] = (unsigned char) xe; + n[5] = (unsigned char) xf; + + memcpy (&(((struct sockaddr_in *) sa)->sin_addr.s_addr), (void *) n, 4); + memcpy (&(((struct sockaddr_in *) sa)->sin_port), (void *) &n[4], 2); + + return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Setup Passive EPSV FTP connection */ + +static gboolean +ftpfs_setup_passive_epsv (struct vfs_class *me, struct vfs_s_super *super, + int my_socket, struct sockaddr_storage *sa, socklen_t * salen) +{ + char *c; + int port; + + if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "EPSV") != COMPLETE) + return FALSE; + + /* (|||<port>|) */ + c = strchr (reply_str, '|'); + if (c == NULL || strlen (c) <= 3) + return FALSE; + + c += 3; + port = atoi (c); + if (port < 0 || port > 65535) + return FALSE; + + port = htons (port); + + switch (sa->ss_family) + { + case AF_INET: + ((struct sockaddr_in *) sa)->sin_port = port; + break; + case AF_INET6: + ((struct sockaddr_in6 *) sa)->sin6_port = port; + break; + default: + break; + } + + return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Setup Passive ftp connection, we use it for source routed connections */ + +static gboolean +ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super, + int my_socket, struct sockaddr_storage *sa, socklen_t * salen) +{ + /* It's IPV4, so try PASV first, some servers and ALGs get confused by EPSV */ + if (sa->ss_family == AF_INET) + { + if (!ftpfs_setup_passive_pasv (me, super, my_socket, sa, salen)) + /* An IPV4 FTP server might support EPSV, so if PASV fails we can try EPSV anyway */ + if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen)) + return FALSE; + } + /* It's IPV6, so EPSV is our only hope */ + else if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen)) + return FALSE; + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Setup Active PORT or EPRT FTP connection */ + +static int +ftpfs_setup_active (struct vfs_class *me, struct vfs_s_super *super, + struct sockaddr_storage data_addr, socklen_t data_addrlen) +{ + unsigned short int port; + char *addr; + unsigned int af; + int res; + + switch (data_addr.ss_family) + { + case AF_INET: + af = FTP_INET; + port = ((struct sockaddr_in *) &data_addr)->sin_port; + break; + case AF_INET6: + af = FTP_INET6; + port = ((struct sockaddr_in6 *) &data_addr)->sin6_port; + break; + default: + /* Not implemented */ + return 0; + } + + addr = g_try_malloc (NI_MAXHOST); + if (addr == NULL) + ERRNOR (ENOMEM, -1); + + res = + getnameinfo ((struct sockaddr *) &data_addr, data_addrlen, addr, NI_MAXHOST, NULL, 0, + NI_NUMERICHOST); + if (res != 0) + { + const char *err_str; + + g_free (addr); + + if (res == EAI_SYSTEM) + { + me->verrno = errno; + err_str = unix_error_string (me->verrno); + } + else + { + me->verrno = EIO; + err_str = gai_strerror (res); + } + + vfs_print_message (_("ftpfs: could not make address-to-name translation: %s"), err_str); + + return (-1); + } + + /* If we are talking to an IPV4 server, try PORT, and, only if it fails, go for EPRT */ + if (af == FTP_INET) + { + unsigned char *a = (unsigned char *) &((struct sockaddr_in *) &data_addr)->sin_addr; + unsigned char *p = (unsigned char *) &port; + + if (ftpfs_command (me, super, WAIT_REPLY, + "PORT %u,%u,%u,%u,%u,%u", a[0], a[1], a[2], a[3], + p[0], p[1]) == COMPLETE) + { + g_free (addr); + return 1; + } + } + + /* + * Converts network MSB first order to host byte order (LSB + * first on i386). If we do it earlier, we will run into an + * endianness issue, because the server actually expects to see + * "PORT A,D,D,R,MSB,LSB" in the PORT command. + */ + port = ntohs (port); + + /* We are talking to an IPV6 server or PORT failed, so we can try EPRT anyway */ + res = + (ftpfs_command (me, super, WAIT_REPLY, "EPRT |%u|%s|%hu|", af, addr, port) == + COMPLETE) ? 1 : 0; + g_free (addr); + return res; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Initialize a socket for FTP DATA connection */ + +static int +ftpfs_init_data_socket (struct vfs_class *me, struct vfs_s_super *super, + struct sockaddr_storage *data_addr, socklen_t * data_addrlen) +{ + const unsigned int attempts = 10; + unsigned int i; + ftp_super_t *ftp_super = FTP_SUPER (super); + int result; + + for (i = 0; i < attempts; i++) + { + memset (data_addr, 0, sizeof (*data_addr)); + *data_addrlen = sizeof (*data_addr); + + if (ftp_super->use_passive_connection) + { + result = getpeername (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen); + if (result == 0) + break; + + me->verrno = errno; + + if (me->verrno == ENOTCONN) + { + vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i); + if (ftpfs_reconnect (me, super)) + continue; /* get name of new socket */ + } + else + { + /* error -- stop loop */ + vfs_print_message (_("ftpfs: could not get socket name: %s"), + unix_error_string (me->verrno)); + } + } + else + { + result = getsockname (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen); + if (result == 0) + break; + + me->verrno = errno; + + vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i); + if (ftpfs_reconnect (me, super)) + continue; /* get name of new socket */ + + /* error -- stop loop */ + vfs_print_message ("%s", _("ftpfs: could not reconnect to server")); + } + + i = attempts; + } + + if (i >= attempts) + return (-1); + + switch (data_addr->ss_family) + { + case AF_INET: + ((struct sockaddr_in *) data_addr)->sin_port = 0; + break; + case AF_INET6: + ((struct sockaddr_in6 *) data_addr)->sin6_port = 0; + break; + default: + vfs_print_message ("%s", _("ftpfs: invalid address family")); + ERRNOR (EINVAL, -1); + } + + result = socket (data_addr->ss_family, SOCK_STREAM, IPPROTO_TCP); + if (result < 0) + { + me->verrno = errno; + vfs_print_message (_("ftpfs: could not create socket: %s"), unix_error_string (me->verrno)); + result = -1; + } + + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Initialize FTP DATA connection */ + +static int +ftpfs_initconn (struct vfs_class *me, struct vfs_s_super *super) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + struct sockaddr_storage data_addr; + socklen_t data_addrlen; + + /* + * Don't factor socket initialization out of these conditionals, + * because ftpfs_init_data_socket initializes it in different way + * depending on use_passive_connection flag. + */ + + /* Try to establish a passive connection first (if requested) */ + if (ftp_super->use_passive_connection) + { + int data_sock; + + data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen); + if (data_sock < 0) + return (-1); + + if (ftpfs_setup_passive (me, super, data_sock, &data_addr, &data_addrlen)) + return data_sock; + + vfs_print_message ("%s", _("ftpfs: could not setup passive mode")); + ftp_super->use_passive_connection = FALSE; + + close (data_sock); + } + + /* If passive setup is disabled or failed, fallback to active connections */ + if (!ftp_super->use_passive_connection) + { + int data_sock; + + data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen); + if (data_sock < 0) + return (-1); + + if ((bind (data_sock, (struct sockaddr *) &data_addr, data_addrlen) != 0) || + (getsockname (data_sock, (struct sockaddr *) &data_addr, &data_addrlen) != 0) || + (listen (data_sock, 1) != 0)) + { + close (data_sock); + ERRNOR (errno, -1); + } + + if (ftpfs_setup_active (me, super, data_addr, data_addrlen) != 0) + return data_sock; + + close (data_sock); + } + + /* Restore the initial value of use_passive_connection (for subsequent retries) */ + ftp_super->use_passive_connection = + ftp_super->proxy != + NULL ? ftpfs_use_passive_connections_over_proxy : ftpfs_use_passive_connections; + + me->verrno = EIO; + return (-1); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_open_data_connection (struct vfs_class *me, struct vfs_s_super *super, const char *cmd, + const char *remote, int isbinary, int reget) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + int s, j, data; + + /* FTP doesn't allow to open more than one file at a time */ + if (ftp_super->ctl_connection_busy) + return (-1); + + s = ftpfs_initconn (me, super); + if (s == -1) + return (-1); + + if (ftpfs_changetype (me, super, isbinary) == -1) + { + close (s); + return (-1); + } + + if (reget > 0) + { + j = ftpfs_command (me, super, WAIT_REPLY, "REST %d", reget); + if (j != CONTINUE) + { + close (s); + ERRNOR (EIO, -1); + } + } + + if (remote == NULL) + j = ftpfs_command (me, super, WAIT_REPLY, "%s", cmd); + else + { + char *remote_path; + + remote_path = ftpfs_translate_path (me, super, remote); + j = ftpfs_command (me, super, WAIT_REPLY, "%s /%s", cmd, + /* WarFtpD can't STORE //filename */ + IS_PATH_SEP (*remote_path) ? remote_path + 1 : remote_path); + g_free (remote_path); + } + + if (j != PRELIM) + { + close (s); + ERRNOR (EPERM, -1); + } + + if (ftp_super->use_passive_connection) + data = s; + else + { + struct sockaddr_storage from; + socklen_t fromlen = sizeof (from); + + tty_enable_interrupt_key (); + data = accept (s, (struct sockaddr *) &from, &fromlen); + if (data < 0) + me->verrno = errno; + tty_disable_interrupt_key (); + close (s); + if (data < 0) + return (-1); + } + + ftp_super->ctl_connection_busy = TRUE; + return data; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_linear_abort (struct vfs_class *me, vfs_file_handler_t * fh) +{ + struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh); + ftp_super_t *ftp_super = FTP_SUPER (super); + static unsigned char const ipbuf[3] = { IAC, IP, IAC }; + fd_set mask; + int dsock = FH_SOCK; + + FH_SOCK = -1; + ftp_super->ctl_connection_busy = FALSE; + + vfs_print_message ("%s", _("ftpfs: aborting transfer.")); + + if (send (ftp_super->sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf)) + { + vfs_print_message (_("ftpfs: abort error: %s"), unix_error_string (errno)); + if (dsock != -1) + close (dsock); + return; + } + + if (ftpfs_command (me, super, NONE, "%cABOR", DM) != COMPLETE) + { + vfs_print_message ("%s", _("ftpfs: abort failed")); + if (dsock != -1) + close (dsock); + return; + } + + if (dsock != -1) + { + FD_ZERO (&mask); + FD_SET (dsock, &mask); + + if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0) + { + gint64 start_tim; + char buf[BUF_8K]; + + start_tim = g_get_monotonic_time (); + + /* flush the remaining data */ + while (read (dsock, buf, sizeof (buf)) > 0) + { + gint64 tim; + + tim = g_get_monotonic_time (); + + if (tim > start_tim + ABORT_TIMEOUT) + { + /* server keeps sending, drop the connection and ftpfs_reconnect */ + close (dsock); + ftpfs_reconnect (me, super); + return; + } + } + } + close (dsock); + } + + if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) == TRANSIENT) && (code == 426)) + ftpfs_get_reply (me, ftp_super->sock, NULL, 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +#if 0 +static void +resolve_symlink_without_ls_options (struct vfs_class *me, struct vfs_s_super *super, + struct vfs_s_inode *dir) +{ + struct linklist *flist; + struct direntry *fe, *fel; + char tmp[MC_MAXPATHLEN]; + + dir->symlink_status = FTPFS_RESOLVING_SYMLINKS; + for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next) + { + /* flist->data->l_stat is already initialized with 0 */ + fel = flist->data; + if (S_ISLNK (fel->s.st_mode) && fel->linkname != NULL) + { + int depth; + + if (IS_PATH_SEP (fel->linkname[0])) + { + if (strlen (fel->linkname) >= MC_MAXPATHLEN) + continue; + strcpy (tmp, fel->linkname); + } + else + { + if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN) + continue; + strcpy (tmp, dir->remote_path); + if (tmp[1] != '\0') + strcat (tmp, PATH_SEP_STR); + strcat (tmp + 1, fel->linkname); + } + + for (depth = 0; depth < 100; depth++) + { /* depth protects against recursive symbolic links */ + canonicalize_pathname (tmp); + fe = _get_file_entry_t (bucket, tmp, 0, 0); + if (fe != NULL) + { + if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0) + { + /* Symlink points to link which isn't resolved, yet. */ + if (IS_PATH_SEP (fe->linkname[0])) + { + if (strlen (fe->linkname) >= MC_MAXPATHLEN) + break; + strcpy (tmp, fe->linkname); + } + else + { + /* at this point tmp looks always like this + /directory/filename, i.e. no need to check + strrchr's return value */ + *(strrchr (tmp, PATH_SEP) + 1) = '\0'; /* dirname */ + if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN) + break; + strcat (tmp, fe->linkname); + } + continue; + } + else + { + fel->l_stat = g_new (struct stat, 1); + if (S_ISLNK (fe->s.st_mode)) + *fel->l_stat = *fe->l_stat; + else + *fel->l_stat = fe->s; + (*fel->l_stat).st_ino = bucket->__inode_counter++; + } + } + break; + } + } + } + + dir->symlink_status = FTPFS_RESOLVED_SYMLINKS; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +resolve_symlink_with_ls_options (struct vfs_class *me, struct vfs_s_super *super, + struct vfs_s_inode *dir) +{ + char buffer[2048] = "", *filename; + int sock; + FILE *fp; + struct stat s; + struct linklist *flist; + struct direntry *fe; + int switch_method = 0; + + dir->symlink_status = FTPFS_RESOLVED_SYMLINKS; + if (strchr (dir->remote_path, ' ') == NULL) + sock = ftpfs_open_data_connection (bucket, "LIST -lLa", dir->remote_path, TYPE_ASCII, 0); + else + { + if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE) + { + vfs_print_message ("%s", _("ftpfs: CWD failed.")); + return; + } + + sock = ftpfs_open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0); + } + + if (sock == -1) + { + vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink")); + return; + } + + fp = fdopen (sock, "r"); + if (fp == NULL) + { + close (sock); + vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink")); + return; + } + tty_enable_interrupt_key (); + flist = dir->file_list->next; + + while (TRUE) + { + do + { + if (flist == dir->file_list) + goto done; + + fe = flist->data; + flist = flist->next; + } + while (!S_ISLNK (fe->s.st_mode)); + + while (TRUE) + { + if (fgets (buffer, sizeof (buffer), fp) == NULL) + goto done; + + if (me->logfile != NULL) + { + fputs (buffer, me->logfile); + fflush (me->logfile); + } + + vfs_die ("This code should be commented out\n"); + + if (vfs_parse_ls_lga (buffer, &s, &filename, NULL)) + { + int r; + + r = strcmp (fe->name, filename); + g_free (filename); + if (r == 0) + { + if (S_ISLNK (s.st_mode)) + { + /* This server doesn't understand LIST -lLa */ + switch_method = 1; + goto done; + } + + fe->l_stat = g_try_new (struct stat, 1); + if (fe->l_stat == NULL) + goto done; + + *fe->l_stat = s; + (*fe->l_stat).st_ino = bucket->__inode_counter++; + break; + } + + if (r < 0) + break; + } + } + } + + done: + while (fgets (buffer, sizeof (buffer), fp) != NULL) + ; + tty_disable_interrupt_key (); + fclose (fp); + ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +resolve_symlink (struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir) +{ + vfs_print_message ("%s", _("Resolving symlink...")); + + if (FTP_SUPER (super)->strict_rfc959_list_cmd) + resolve_symlink_without_ls_options (me, super, dir); + else + resolve_symlink_with_ls_options (me, super, dir); +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path) +{ + struct vfs_s_super *super = dir->super; + ftp_super_t *ftp_super = FTP_SUPER (super); + int sock; + char lc_buffer[BUF_8K]; + int res; + gboolean cd_first; + GSList *dirlist = NULL; + GSList *entlist; + GSList *iter; + int err_count = 0; + + cd_first = ftpfs_first_cd_then_ls || (ftp_super->strict == RFC_STRICT) + || (strchr (remote_path, ' ') != NULL); + + again: + vfs_print_message (_("ftpfs: Reading FTP directory %s... %s%s"), + remote_path, + ftp_super->strict == + RFC_STRICT ? _("(strict rfc959)") : "", cd_first ? _("(chdir first)") : ""); + + if (cd_first && ftpfs_chdir_internal (me, super, remote_path) != COMPLETE) + { + me->verrno = ENOENT; + vfs_print_message ("%s", _("ftpfs: CWD failed.")); + return (-1); + } + + dir->timestamp = g_get_monotonic_time () + ftpfs_directory_timeout * G_USEC_PER_SEC; + + if (ftp_super->strict == RFC_STRICT) + sock = ftpfs_open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0); + else if (cd_first) + /* Dirty hack to avoid autoprepending / to . */ + /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */ + sock = ftpfs_open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0); + else + { + char *path; + + /* Trailing "/." is necessary if remote_path is a symlink */ + path = g_strconcat (remote_path, PATH_SEP_STR ".", (char *) NULL); + sock = ftpfs_open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0); + g_free (path); + } + + if (sock == -1) + { + fallback: + if (ftp_super->strict == RFC_AUTODETECT) + { + /* It's our first attempt to get a directory listing from this + server (UNIX style LIST command) */ + ftp_super->strict = RFC_STRICT; + /* I hate goto, but recursive call needs another 8K on stack */ + /* return ftpfs_dir_load (me, dir, remote_path); */ + cd_first = TRUE; + goto again; + } + + vfs_print_message ("%s", _("ftpfs: failed; nowhere to fallback to")); + ERRNOR (EACCES, -1); + } + + /* read full directory list, then parse it */ + while ((res = vfs_s_get_line_interruptible (me, lc_buffer, sizeof (lc_buffer), sock)) != 0) + { + if (res == EINTR) + { + me->verrno = ECONNRESET; + close (sock); + ftp_super->ctl_connection_busy = FALSE; + ftpfs_get_reply (me, ftp_super->sock, NULL, 0); + g_slist_free_full (dirlist, g_free); + vfs_print_message (_("%s: failure"), me->name); + return (-1); + } + + if (me->logfile != NULL) + { + fputs (lc_buffer, me->logfile); + fputs ("\n", me->logfile); + fflush (me->logfile); + } + + dirlist = g_slist_prepend (dirlist, g_strdup (lc_buffer)); + } + + close (sock); + ftp_super->ctl_connection_busy = FALSE; + me->verrno = E_REMOTE; + if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE)) + { + g_slist_free_full (dirlist, g_free); + goto fallback; + } + + if (dirlist == NULL && !cd_first) + { + /* The LIST command may produce an empty output. In such scenario + it is not clear whether this is caused by 'remote_path' being + a non-existent path or for some other reason (listing empty + directory without the -a option, non-readable directory, etc.). + + Since 'dir_load' is a crucial method, when it comes to determine + whether a given path is a _directory_, the code must try its best + to determine the type of 'remote_path'. The only reliable way to + achieve this is through issuing a CWD command. */ + + cd_first = TRUE; + goto again; + } + + /* parse server's reply */ + dirlist = g_slist_reverse (dirlist); /* restore order */ + entlist = ftpfs_parse_long_list (me, dir, dirlist, &err_count); + g_slist_free_full (dirlist, g_free); + + for (iter = entlist; iter != NULL; iter = g_slist_next (iter)) + vfs_s_insert_entry (me, dir, VFS_ENTRY (iter->data)); + + g_slist_free (entlist); + + if (ftp_super->strict == RFC_AUTODETECT) + ftp_super->strict = RFC_DARING; + + vfs_print_message (_("%s: done."), me->name); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_file_store (struct vfs_class *me, vfs_file_handler_t * fh, char *name, char *localname) +{ + struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh); + ftp_super_t *ftp_super = FTP_SUPER (super); + ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh); + + int h, sock; + off_t n_stored = 0; +#ifdef HAVE_STRUCT_LINGER_L_LINGER + struct linger li; +#else + int flag_one = 1; +#endif + char lc_buffer[BUF_8K]; + struct stat s; + char *w_buf; + + h = open (localname, O_RDONLY); + if (h == -1) + ERRNOR (EIO, -1); + + if (fstat (h, &s) == -1) + { + me->verrno = errno; + close (h); + return (-1); + } + + sock = + ftpfs_open_data_connection (me, super, ftp->append ? "APPE" : "STOR", name, TYPE_BINARY, 0); + if (sock < 0) + { + close (h); + return (-1); + } +#ifdef HAVE_STRUCT_LINGER_L_LINGER + li.l_onoff = 1; + li.l_linger = 120; + setsockopt (sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof (li)); +#else + setsockopt (sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one)); +#endif + + tty_enable_interrupt_key (); + while (TRUE) + { + ssize_t n_read, n_written; + + while ((n_read = read (h, lc_buffer, sizeof (lc_buffer))) == -1) + { + if (errno != EINTR) + { + me->verrno = errno; + goto error_return; + } + if (tty_got_interrupt ()) + { + me->verrno = EINTR; + goto error_return; + } + } + if (n_read == 0) + break; + + n_stored += n_read; + w_buf = lc_buffer; + + while ((n_written = write (sock, w_buf, n_read)) != n_read) + { + if (n_written == -1) + { + if (errno == EINTR && !tty_got_interrupt ()) + continue; + + me->verrno = errno; + goto error_return; + } + + w_buf += n_written; + n_read -= n_written; + } + + vfs_print_message ("%s: %" PRIuMAX "/%" PRIuMAX, + _("ftpfs: storing file"), (uintmax_t) n_stored, (uintmax_t) s.st_size); + } + tty_disable_interrupt_key (); + + close (sock); + ftp_super->ctl_connection_busy = FALSE; + close (h); + + if (ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE) + ERRNOR (EIO, -1); + return 0; + + error_return: + tty_disable_interrupt_key (); + close (sock); + ftp_super->ctl_connection_busy = FALSE; + close (h); + + ftpfs_get_reply (me, ftp_super->sock, NULL, 0); + return (-1); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_linear_start (struct vfs_class *me, vfs_file_handler_t * fh, off_t offset) +{ + char *name; + + name = vfs_s_fullpath (me, fh->ino); + if (name == NULL) + return 0; + + FH_SOCK = + ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh), "RETR", name, TYPE_BINARY, + offset); + g_free (name); + if (FH_SOCK == -1) + ERRNOR (EACCES, 0); + + fh->linear = LS_LINEAR_OPEN; + FTP_FILE_HANDLER (fh)->append = FALSE; + return 1; +} + +/* --------------------------------------------------------------------------------------------- */ + +static ssize_t +ftpfs_linear_read (struct vfs_class *me, vfs_file_handler_t * fh, void *buf, size_t len) +{ + ssize_t n; + struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh); + + while ((n = read (FH_SOCK, buf, len)) < 0) + { + if ((errno == EINTR) && !tty_got_interrupt ()) + continue; + break; + } + + if (n < 0) + ftpfs_linear_abort (me, fh); + else if (n == 0) + { + FTP_SUPER (super)->ctl_connection_busy = FALSE; + close (FH_SOCK); + FH_SOCK = -1; + if ((ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0) != COMPLETE)) + ERRNOR (E_REMOTE, -1); + return 0; + } + + ERRNOR (errno, n); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_linear_close (struct vfs_class *me, vfs_file_handler_t * fh) +{ + if (FH_SOCK != -1) + ftpfs_linear_abort (me, fh); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_ctl (void *fh, int ctlop, void *arg) +{ + (void) arg; + + switch (ctlop) + { + case VFS_CTL_IS_NOTREADY: + { + vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); + int v; + + if (file->linear == LS_NOT_LINEAR) + vfs_die ("You may not do this"); + if (file->linear == LS_LINEAR_CLOSED || file->linear == LS_LINEAR_PREOPEN) + return 0; + + v = vfs_s_select_on_two (FH_SOCK, 0); + return (((v < 0) && (errno == EINTR)) || v == 0) ? 1 : 0; + } + default: + return 0; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_send_command (const vfs_path_t * vpath, const char *cmd, int flags) +{ + const char *rpath; + char *p; + struct vfs_s_super *super; + int r; + struct vfs_class *me; + gboolean flush_directory_cache = (flags & OPT_FLUSH) != 0; + + me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath)); + + rpath = vfs_s_get_path (vpath, &super, 0); + if (rpath == NULL) + return (-1); + + p = ftpfs_translate_path (me, super, rpath); + r = ftpfs_command (me, super, WAIT_REPLY, cmd, p); + g_free (p); + vfs_stamp_create (vfs_ftpfs_ops, super); + if ((flags & OPT_IGNORE_ERROR) != 0) + r = COMPLETE; + if (r != COMPLETE) + { + me->verrno = EPERM; + return (-1); + } + if (flush_directory_cache) + vfs_s_invalidate (me, super); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_stat (const vfs_path_t * vpath, struct stat *buf) +{ + int ret; + + ret = vfs_s_stat (vpath, buf); + ftpfs_set_blksize (buf); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_lstat (const vfs_path_t * vpath, struct stat *buf) +{ + int ret; + + ret = vfs_s_lstat (vpath, buf); + ftpfs_set_blksize (buf); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_fstat (void *vfs_info, struct stat *buf) +{ + int ret; + + ret = vfs_s_fstat (vfs_info, buf); + ftpfs_set_blksize (buf); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_chmod (const vfs_path_t * vpath, mode_t mode) +{ + char buf[BUF_SMALL]; + int ret; + + g_snprintf (buf, sizeof (buf), "SITE CHMOD %4.4o /%%s", (unsigned int) (mode & 07777)); + ret = ftpfs_send_command (vpath, buf, OPT_FLUSH); + return ftpfs_ignore_chattr_errors ? 0 : ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group) +{ +#if 0 + (void) vpath; + (void) owner; + (void) group; + + me->verrno = EPERM; + return (-1); +#else + /* Everyone knows it is not possible to chown remotely, so why bother them. + If someone's root, then copy/move will always try to chown it... */ + (void) vpath; + (void) owner; + (void) group; + return 0; +#endif +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_unlink (const vfs_path_t * vpath) +{ + return ftpfs_send_command (vpath, "DELE /%s", OPT_FLUSH); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Return TRUE if path is the same directory as the one we are in now */ +static gboolean +ftpfs_is_same_dir (struct vfs_class *me, struct vfs_s_super *super, const char *path) +{ + (void) me; + + return (FTP_SUPER (super)->current_dir != NULL + && strcmp (path, FTP_SUPER (super)->current_dir) == 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path) +{ + ftp_super_t *ftp_super = FTP_SUPER (super); + int r; + char *p; + + if (!ftp_super->cwd_deferred && ftpfs_is_same_dir (me, super, remote_path)) + return COMPLETE; + + p = ftpfs_translate_path (me, super, remote_path); + r = ftpfs_command (me, super, WAIT_REPLY, "CWD /%s", p); + g_free (p); + + if (r != COMPLETE) + me->verrno = EIO; + else + { + g_free (ftp_super->current_dir); + ftp_super->current_dir = g_strdup (remote_path); + ftp_super->cwd_deferred = FALSE; + } + return r; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2) +{ + ftpfs_send_command (vpath1, "RNFR /%s", OPT_FLUSH); + return ftpfs_send_command (vpath2, "RNTO /%s", OPT_FLUSH); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_mkdir (const vfs_path_t * vpath, mode_t mode) +{ + (void) mode; /* FIXME: should be used */ + + return ftpfs_send_command (vpath, "MKD /%s", OPT_FLUSH); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_rmdir (const vfs_path_t * vpath) +{ + return ftpfs_send_command (vpath, "RMD /%s", OPT_FLUSH); +} + +/* --------------------------------------------------------------------------------------------- */ + +static vfs_file_handler_t * +ftpfs_fh_new (struct vfs_s_inode *ino, gboolean changed) +{ + ftp_file_handler_t *fh; + + fh = g_new0 (ftp_file_handler_t, 1); + vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed); + fh->sock = -1; + + return VFS_FILE_HANDLER (fh); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode) +{ + ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh); + + (void) mode; + + /* File will be written only, so no need to retrieve it from ftp server */ + if (((flags & O_WRONLY) == O_WRONLY) && ((flags & (O_RDONLY | O_RDWR)) == 0)) + { +#ifdef HAVE_STRUCT_LINGER_L_LINGER + struct linger li; +#else + int li = 1; +#endif + char *name; + + /* ftpfs_linear_start() called, so data will be written + * to local temporary file and stored to ftp server + * by vfs_s_close later + */ + if (FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh))->ctl_connection_busy) + { + if (fh->ino->localname == NULL) + { + vfs_path_t *vpath; + int handle; + + handle = vfs_mkstemps (&vpath, me->name, fh->ino->ent->name); + if (handle == -1) + return (-1); + + close (handle); + fh->ino->localname = vfs_path_free (vpath, FALSE); + ftp->append = (flags & O_APPEND) != 0; + } + return 0; + } + name = vfs_s_fullpath (me, fh->ino); + if (name == NULL) + return (-1); + + fh->handle = + ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh), + (flags & O_APPEND) != 0 ? "APPE" : "STOR", name, + TYPE_BINARY, 0); + g_free (name); + + if (fh->handle < 0) + return (-1); + +#ifdef HAVE_STRUCT_LINGER_L_LINGER + li.l_onoff = 1; + li.l_linger = 120; +#endif + setsockopt (fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof (li)); + + if (fh->ino->localname != NULL) + { + unlink (fh->ino->localname); + MC_PTR_FREE (fh->ino->localname); + } + return 0; + } + + if (fh->ino->localname == NULL && vfs_s_retrieve_file (me, fh->ino) == -1) + return (-1); + + if (fh->ino->localname == NULL) + vfs_die ("retrieve_file failed to fill in localname"); + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +ftpfs_fh_close (struct vfs_class *me, vfs_file_handler_t * fh) +{ + if (fh->handle != -1 && fh->ino->localname == NULL) + { + ftp_super_t *ftp = FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh)); + + close (fh->handle); + fh->handle = -1; + ftp->ctl_connection_busy = FALSE; + /* File is stored to destination already, so + * we prevent VFS_SUBCLASS (me)->ftpfs_file_store() call from vfs_s_close () + */ + fh->changed = FALSE; + if (ftpfs_get_reply (me, ftp->sock, NULL, 0) != COMPLETE) + ERRNOR (EIO, -1); + vfs_s_invalidate (me, VFS_FILE_HANDLER_SUPER (fh)); + } + + return 0; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_done (struct vfs_class *me) +{ + (void) me; + + g_slist_free_full (no_proxy, g_free); + + g_free (ftpfs_anonymous_passwd); + g_free (ftpfs_proxy_host); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_fill_names (struct vfs_class *me, fill_names_f func) +{ + GList *iter; + + for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter)) + { + const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data; + GString *name; + + name = vfs_path_element_build_pretty_path_str (super->path_element); + + func (name->str); + g_string_free (name, TRUE); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static keyword_t +ftpfs_netrc_next (void) +{ + char *p; + keyword_t i; + static const char *const keywords[] = { "default", "machine", + "login", "password", "passwd", "account", "macdef", NULL + }; + + while (TRUE) + { + netrcp = skip_separators (netrcp); + if (*netrcp != '\n') + break; + netrcp++; + } + if (*netrcp == '\0') + return NETRC_NONE; + + p = buffer; + if (*netrcp == '"') + { + for (netrcp++; *netrcp != '"' && *netrcp != '\0'; netrcp++) + { + if (*netrcp == '\\') + netrcp++; + *p++ = *netrcp; + } + } + else + { + for (; *netrcp != '\0' && !whiteness (*netrcp) && *netrcp != ','; netrcp++) + { + if (*netrcp == '\\') + netrcp++; + *p++ = *netrcp; + } + } + + *p = '\0'; + if (*buffer == '\0') + return NETRC_NONE; + + for (i = NETRC_DEFAULT; keywords[i - 1] != NULL; i++) + if (strcmp (keywords[i - 1], buffer) == 0) + return i; + + return NETRC_UNKNOWN; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +ftpfs_netrc_bad_mode (const char *netrcname) +{ + struct stat mystat; + + if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077) != 0) + { + static gboolean be_angry = TRUE; + + if (be_angry) + { + message (D_ERROR, MSG_ERROR, + _("~/.netrc file has incorrect mode\nRemove password or correct mode")); + be_angry = FALSE; + } + return TRUE; + } + + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Scan .netrc until we find matching "machine" or "default" + * domain is used for additional matching + * No search is done after "default" in compliance with "man netrc" + * Return TRUE if found, FALSE otherwise */ + +static gboolean +ftpfs_find_machine (const char *host, const char *domain) +{ + keyword_t keyword; + + if (host == NULL) + host = ""; + if (domain == NULL) + domain = ""; + + while ((keyword = ftpfs_netrc_next ()) != NETRC_NONE) + { + if (keyword == NETRC_DEFAULT) + return TRUE; + + if (keyword == NETRC_MACDEF) + { + /* Scan for an empty line, which concludes "macdef" */ + do + { + while (*netrcp != '\0' && *netrcp != '\n') + netrcp++; + if (*netrcp != '\n') + break; + netrcp++; + } + while (*netrcp != '\0' && *netrcp != '\n'); + + continue; + } + + if (keyword != NETRC_MACHINE) + continue; + + /* Take machine name */ + if (ftpfs_netrc_next () == NETRC_NONE) + break; + + if (g_ascii_strcasecmp (host, buffer) != 0) + { + const char *host_domain; + + /* Try adding our domain to short names in .netrc */ + host_domain = strchr (host, '.'); + if (host_domain == NULL) + continue; + + /* Compare domain part */ + if (g_ascii_strcasecmp (host_domain, domain) != 0) + continue; + + /* Compare local part */ + if (g_ascii_strncasecmp (host, buffer, host_domain - host) != 0) + continue; + } + + return TRUE; + } + + /* end of .netrc */ + return FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ +/* Extract login and password from .netrc for the host. + * pass may be NULL. + * Returns TRUE for success, FALSE for error */ + +static gboolean +ftpfs_netrc_lookup (const char *host, char **login, char **pass) +{ + char *netrcname; + char *tmp_pass = NULL; + char hostname[MAXHOSTNAMELEN]; + const char *domain; + static struct rupcache + { + struct rupcache *next; + char *host; + char *login; + char *pass; + } *rup_cache = NULL, *rupp; + + /* Initialize *login and *pass */ + MC_PTR_FREE (*login); + MC_PTR_FREE (*pass); + + /* Look up in the cache first */ + for (rupp = rup_cache; rupp != NULL; rupp = rupp->next) + if (strcmp (host, rupp->host) == 0) + { + *login = g_strdup (rupp->login); + *pass = g_strdup (rupp->pass); + return TRUE; + } + + /* Load current .netrc */ + netrcname = g_build_filename (mc_config_get_home_dir (), ".netrc", (char *) NULL); + if (!g_file_get_contents (netrcname, &netrc, NULL, NULL)) + { + g_free (netrcname); + return TRUE; + } + + netrcp = netrc; + + /* Find our own domain name */ + if (gethostname (hostname, sizeof (hostname)) < 0) + *hostname = '\0'; + + domain = strchr (hostname, '.'); + if (domain == NULL) + domain = ""; + + /* Scan for "default" and matching "machine" keywords */ + ftpfs_find_machine (host, domain); + + /* Scan for keywords following "default" and "machine" */ + while (TRUE) + { + keyword_t keyword; + + gboolean need_break = FALSE; + keyword = ftpfs_netrc_next (); + + switch (keyword) + { + case NETRC_LOGIN: + if (ftpfs_netrc_next () == NETRC_NONE) + { + need_break = TRUE; + break; + } + + /* We have another name already - should not happen */ + if (*login != NULL) + { + need_break = TRUE; + break; + } + + /* We have login name now */ + *login = g_strdup (buffer); + break; + + case NETRC_PASSWORD: + case NETRC_PASSWD: + if (ftpfs_netrc_next () == NETRC_NONE) + { + need_break = TRUE; + break; + } + + /* Ignore unsafe passwords */ + if (*login != NULL && + strcmp (*login, "anonymous") != 0 && strcmp (*login, "ftp") != 0 + && ftpfs_netrc_bad_mode (netrcname)) + { + need_break = TRUE; + break; + } + + /* Remember password. pass may be NULL, so use tmp_pass */ + if (tmp_pass == NULL) + tmp_pass = g_strdup (buffer); + break; + + case NETRC_ACCOUNT: + /* "account" is followed by a token which we ignore */ + if (ftpfs_netrc_next () == NETRC_NONE) + { + need_break = TRUE; + break; + } + + /* Ignore account, but warn user anyways */ + ftpfs_netrc_bad_mode (netrcname); + break; + + default: + /* Unexpected keyword or end of file */ + need_break = TRUE; + break; + } + + if (need_break) + break; + } + + MC_PTR_FREE (netrc); + g_free (netrcname); + + rupp = g_new (struct rupcache, 1); + rupp->host = g_strdup (host); + rupp->login = g_strdup (*login); + rupp->pass = g_strdup (tmp_pass); + + rupp->next = rup_cache; + rup_cache = rupp; + + *pass = tmp_pass; + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** This routine is called as the last step in load_setup */ +void +ftpfs_init_passwd (void) +{ + ftpfs_anonymous_passwd = load_anon_passwd (); + + if (ftpfs_anonymous_passwd == NULL) + { + /* If there is no anonymous ftp password specified + * then we'll just use anonymous@ + * We don't send any other thing because: + * - We want to remain anonymous + * - We want to stop SPAM + * - We don't want to let ftp sites to discriminate by the user, + * host or country. + */ + ftpfs_anonymous_passwd = g_strdup ("anonymous@"); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +vfs_init_ftpfs (void) +{ + tcp_init (); + + vfs_init_subclass (&ftpfs_subclass, "ftpfs", VFSF_NOLINKS | VFSF_REMOTE | VFSF_USETMP, "ftp"); + vfs_ftpfs_ops->done = ftpfs_done; + vfs_ftpfs_ops->fill_names = ftpfs_fill_names; + vfs_ftpfs_ops->stat = ftpfs_stat; + vfs_ftpfs_ops->lstat = ftpfs_lstat; + vfs_ftpfs_ops->fstat = ftpfs_fstat; + vfs_ftpfs_ops->chmod = ftpfs_chmod; + vfs_ftpfs_ops->chown = ftpfs_chown; + vfs_ftpfs_ops->unlink = ftpfs_unlink; + vfs_ftpfs_ops->rename = ftpfs_rename; + vfs_ftpfs_ops->mkdir = ftpfs_mkdir; + vfs_ftpfs_ops->rmdir = ftpfs_rmdir; + vfs_ftpfs_ops->ctl = ftpfs_ctl; + ftpfs_subclass.archive_same = ftpfs_archive_same; + ftpfs_subclass.new_archive = ftpfs_new_archive; + ftpfs_subclass.open_archive = ftpfs_open_archive; + ftpfs_subclass.free_archive = ftpfs_free_archive; + ftpfs_subclass.fh_new = ftpfs_fh_new; + ftpfs_subclass.fh_open = ftpfs_fh_open; + ftpfs_subclass.fh_close = ftpfs_fh_close; + ftpfs_subclass.dir_load = ftpfs_dir_load; + ftpfs_subclass.file_store = ftpfs_file_store; + ftpfs_subclass.linear_start = ftpfs_linear_start; + ftpfs_subclass.linear_read = ftpfs_linear_read; + ftpfs_subclass.linear_close = ftpfs_linear_close; + vfs_register_class (vfs_ftpfs_ops); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/src/vfs/ftpfs/ftpfs.h b/src/vfs/ftpfs/ftpfs.h new file mode 100644 index 0000000..d00c0b5 --- /dev/null +++ b/src/vfs/ftpfs/ftpfs.h @@ -0,0 +1,46 @@ +/** + * \file + * \brief Header: Virtual File System: FTP file system + */ + +#ifndef MC__VFS_FTPFS_H +#define MC__VFS_FTPFS_H + +#include "lib/vfs/xdirentry.h" + +/*** typedefs(not structures) and defined constants **********************************************/ + +#define FTP_INET 1 +#define FTP_INET6 2 + +#define OPT_FLUSH 1 +#define OPT_IGNORE_ERROR 2 + +/*** enums ***************************************************************************************/ + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/*** global variables defined in .c file *********************************************************/ + +extern gboolean ftpfs_use_netrc; +extern char *ftpfs_anonymous_passwd; +extern char *ftpfs_proxy_host; +extern int ftpfs_directory_timeout; +extern gboolean ftpfs_always_use_proxy; +extern gboolean ftpfs_ignore_chattr_errors; + +extern int ftpfs_retry_seconds; +extern gboolean ftpfs_use_passive_connections; +extern gboolean ftpfs_use_passive_connections_over_proxy; +extern gboolean ftpfs_use_unix_list_options; +extern gboolean ftpfs_first_cd_then_ls; + +/*** declarations of public functions ************************************************************/ + +void ftpfs_init_passwd (void); +void vfs_init_ftpfs (void); +GSList *ftpfs_parse_long_list (struct vfs_class *me, struct vfs_s_inode *dir, GSList * buf, + int *err_ret); + +/*** inline functions ****************************************************************************/ +#endif diff --git a/src/vfs/ftpfs/ftpfs_parse_ls.c b/src/vfs/ftpfs/ftpfs_parse_ls.c new file mode 100644 index 0000000..5db79e0 --- /dev/null +++ b/src/vfs/ftpfs/ftpfs_parse_ls.c @@ -0,0 +1,1236 @@ +/* + Virtual File System: FTP file system + + Copyright (C) 2015-2023 + The Free Software Foundation, Inc. + + Written by: Andrew Borodin <aborodin@vmail.ru>, 2013 + + This file is part of the Midnight Commander. + + The Midnight Commander 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 3 of the License, + or (at your option) any later version. + + The Midnight Commander 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, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * \brief Source: Virtual File System: FTP file system + * \author Andrew Borodin + * \date 2015 + * + * Parser of ftp long file list (reply to "LIST -la" command). + * Borrowed from lftp project (http://http://lftp.yar.ru/). + * Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net) + */ + +#include <config.h> + +#include <ctype.h> /* isdigit() */ +#include <stdio.h> /* sscanf() */ +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> /* mode_t */ +#include <time.h> +#include <unistd.h> +#include <sys/types.h> + +#include "lib/global.h" + +#include "lib/vfs/vfs.h" +#include "lib/vfs/utilvfs.h" + +#include "ftpfs.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +#define number_of_parsers 7 + +#define NO_SIZE ((off_t) (-1L)) +#define NO_DATE ((time_t) (-1L)) + +#define FIRST_TOKEN strtok (line, " \t") +#define NEXT_TOKEN strtok (NULL, " \t") +#define FIRST_TOKEN_R strtok_r (line, " \t", &next) +#define NEXT_TOKEN_R strtok_r (NULL, " \t", &next) + +#define ERR2 do { (*err)++; return FALSE; } while (FALSE) + +/*** file scope type declarations ****************************************************************/ + +typedef enum +{ + UNKNOWN = 0, + DIRECTORY, + SYMLINK, + NORMAL +} filetype; + +typedef gboolean (*ftpfs_line_parser) (char *line, struct stat * s, char **filename, + char **linkname, int *err); + +/*** forward declarations (file scope functions) *************************************************/ + +static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, + char **linkname, int *err); +static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename, + char **linkname, int *err); + +/*** file scope variables ************************************************************************/ + +static time_t rawnow; +static struct tm now; + +static ftpfs_line_parser line_parsers[number_of_parsers] = { + ftpfs_parse_long_list_UNIX, + ftpfs_parse_long_list_NT, + ftpfs_parse_long_list_EPLF, + ftpfs_parse_long_list_MLSD, + ftpfs_parse_long_list_AS400, + ftpfs_parse_long_list_OS2, + ftpfs_parse_long_list_MacWebStar +}; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static inline uid_t +ftpfs_get_uid (const char *s) +{ + uid_t u; + + if (*s < '0' || *s > '9') + u = vfs_finduid (s); + else + u = (uid_t) atol (s); + + return u; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline gid_t +ftpfs_get_gid (const char *s) +{ + gid_t g; + + if (*s < '0' || *s > '9') + g = vfs_findgid (s); + else + g = (gid_t) atol (s); + + return g; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +ftpfs_init_time (void) +{ + time (&rawnow); + now = *localtime (&rawnow); +} + +/* --------------------------------------------------------------------------------------------- */ + +static int +guess_year (int month, int day, int hour, int minute) +{ + int year; + + (void) hour; + (void) minute; + + year = now.tm_year + 1900; + + if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6) + year--; + + return year; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute) +{ + if (year_or_time[2] == ':') + { + if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2) + return FALSE; + + *year = -1; + } + else + { + if (sscanf (year_or_time, "%d", year) != 1) + return FALSE; + + *hour = *minute = 0; + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Converts struct tm to time_t, assuming the data in tm is UTC rather + than local timezone (mktime assumes the latter). + + Contributed by Roger Beeman <beeman@cisco.com>, with the help of + Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */ +static time_t +mktime_from_utc (const struct tm *t) +{ + struct tm tc; + time_t tl, tb; + + memcpy (&tc, t, sizeof (struct tm)); + + /* UTC times are never DST; if we say -1, we'll introduce odd localtime- + * dependent errors. */ + + tc.tm_isdst = 0; + + tl = mktime (&tc); + if (tl == -1) + return (-1); + + tb = mktime (gmtime (&tl)); + + return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl))); +} + +/* --------------------------------------------------------------------------------------------- */ + +static time_t +ftpfs_convert_date (const char *s) +{ + struct tm tm; + int year, month, day, hour, minute, second; + int skip = 0; + int n; + + memset (&tm, 0, sizeof (tm)); + + n = sscanf (s, "%4d%n", &year, &skip); + + /* try to workaround server's y2k bug * + * I hope in the next 300 years the y2k bug will be finally fixed :) */ + if (n == 1 && year >= 1910 && year <= 1930) + { + n = sscanf (s, "%5d%n", &year, &skip); + year = year - 19100 + 2000; + } + + if (n != 1) + return NO_DATE; + + n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second); + + if (n != 5) + return NO_DATE; + + tm.tm_year = year - 1900; + tm.tm_mon = month - 1; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + + return mktime_from_utc (&tm); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + -rwxr-xr-x 1 lav root 4771 Sep 12 1996 install-sh + -rw-r--r-- 1 lav root 1349 Feb 2 14:10 lftp.lsm + drwxr-xr-x 4 lav root 1024 Feb 22 15:32 lib + lrwxrwxrwx 1 lav root 33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig + + NOTE: group may be missing. + */ + +static gboolean +parse_ls_line (char *line, struct stat *s, char **filename, char **linkname) +{ + char *next = NULL; + char *t; + mode_t type, mode = 0; + char *group_or_size; + struct tm date; + const char *day_of_month; + gboolean year_anomaly = FALSE; + char *name; + + /* parse perms */ + t = FIRST_TOKEN_R; + if (t == NULL) + return FALSE; + + if (!vfs_parse_filetype (t, NULL, &type)) + return FALSE; + + if (vfs_parse_fileperms (t + 1, NULL, &mode)) + mode |= type; + + s->st_mode = mode; + + /* link count */ + t = NEXT_TOKEN_R; + if (t == NULL) + return FALSE; + s->st_nlink = atol (t); + + /* user */ + t = NEXT_TOKEN_R; + if (t == NULL) + return FALSE; + + s->st_uid = ftpfs_get_uid (t); + + /* group or size */ + group_or_size = NEXT_TOKEN_R; + + /* size or month */ + t = NEXT_TOKEN_R; + if (t == NULL) + return FALSE; + if (isdigit ((unsigned char) *t)) + { + /* it's size, so the previous was group: */ + long long size; + int n; + + s->st_gid = ftpfs_get_gid (group_or_size); + + if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0') + s->st_size = (off_t) size; + t = NEXT_TOKEN_R; + if (t == NULL) + return FALSE; + } + else + { + /* it was month, so the previous was size: */ + long long size; + int n; + + if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0') + s->st_size = (off_t) size; + } + +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + s->st_blksize = 512; +#endif +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + s->st_blocks = (s->st_size + 511) / 512; +#endif + + memset (&date, 0, sizeof (date)); + + if (!vfs_parse_month (t, &date)) + date.tm_mon = 0; + + day_of_month = NEXT_TOKEN_R; + if (day_of_month == NULL) + return FALSE; + date.tm_mday = atoi (day_of_month); + + /* time or year */ + t = NEXT_TOKEN_R; + if (t == NULL) + return FALSE; + date.tm_isdst = -1; + date.tm_hour = date.tm_min = 0; + date.tm_sec = 30; + + if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2) + date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900; + else + { + if (day_of_month + strlen (day_of_month) + 1 == t) + year_anomaly = TRUE; + date.tm_year = atoi (t) - 1900; + /* We don't know the hour. Set it to something other than 0, or + * DST -1 will end up changing the date. */ + date.tm_hour = 12; + date.tm_min = 0; + date.tm_sec = 0; + } + + s->st_mtime = mktime (&date); + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + + name = strtok_r (NULL, "", &next); + if (name == NULL) + return FALSE; + + /* there are ls which output extra space after year. */ + if (year_anomaly && *name == ' ') + name++; + + if (!S_ISLNK (s->st_mode)) + *linkname = NULL; + else + { + char *arrow; + + for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++) + if (arrow != name && arrow[4] != '\0') + { + *arrow = '\0'; + *linkname = g_strdup (arrow + 4); + break; + } + } + + *filename = g_strdup (name); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + int tmp; + gboolean ret; + + if (sscanf (line, "total %d", &tmp) == 1) + return FALSE; + + if (strncasecmp (line, "Status of ", 10) == 0) + return FALSE; /* STAT output. */ + + ret = parse_ls_line (line, s, filename, linkname); + if (!ret) + (*err)++; + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + 07-13-98 09:06PM <DIR> aix + 07-13-98 09:06PM <DIR> hpux + 07-13-98 09:06PM <DIR> linux + 07-13-98 09:06PM <DIR> ncr + 07-13-98 09:06PM <DIR> solaris + 03-18-98 06:01AM 2109440 nlxb318e.tar + 07-02-98 11:17AM 13844 Whatsnew.txt + */ + +static gboolean +ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + char *t; + int month, day, year, hour, minute; + char am; + struct tm tms; + long long size; + + t = FIRST_TOKEN; + if (t == NULL) + ERR2; + if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3) + ERR2; + if (year >= 70) + year += 1900; + else + year += 2000; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + am = 'A'; /* AM/PM is optional */ + if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2) + ERR2; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + + if (am == 'P') /* PM - after noon */ + { + hour += 12; + if (hour == 24) + hour = 0; + } + + tms.tm_sec = 30; /* seconds after the minute [0, 61] */ + tms.tm_min = minute; /* minutes after the hour [0, 59] */ + tms.tm_hour = hour; /* hour since midnight [0, 23] */ + tms.tm_mday = day; /* day of the month [1, 31] */ + tms.tm_mon = month - 1; /* months since January [0, 11] */ + tms.tm_year = year - 1900; /* years since 1900 */ + tms.tm_isdst = -1; + + + s->st_mtime = mktime (&tms); + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + + if (strcmp (t, "<DIR>") == 0) + s->st_mode = S_IFDIR; + else + { + s->st_mode = S_IFREG; + if (sscanf (t, "%lld", &size) != 1) + ERR2; + s->st_size = (off_t) size; + } + + t = strtok (NULL, ""); + if (t == NULL) + ERR2; + while (*t == ' ') + t++; + if (*t == '\0') + ERR2; + + *filename = g_strdup (t); + *linkname = NULL; + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + +i774.71425,m951188401,/, users + +i774.49602,m917883130,r,s79126, jgr_www2.exe + + starts with + + comma separated + first character of field is type: + i - ? + m - modification time + / - means directory + r - means plain file + s - size + up - permissions in octal + \t - file name follows. + */ + +static gboolean +ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + size_t len; + const char *b; + const char *name = NULL; + size_t name_len = 0; + off_t size = NO_SIZE; + time_t date = NO_DATE; + long date_l; + long long size_ll; + gboolean dir = FALSE; + gboolean type_known = FALSE; + int perms = -1; + const char *scan; + ssize_t scan_len; + + len = strlen (line); + b = line; + + if (len < 2 || b[0] != '+') + ERR2; + + scan = b + 1; + scan_len = len - 1; + + while (scan != NULL && scan_len > 0) + { + const char *comma; + + switch (*scan) + { + case '\t': /* the rest is file name. */ + name = scan + 1; + name_len = scan_len - 1; + scan = NULL; + break; + case 's': + if (sscanf (scan + 1, "%lld", &size_ll) != 1) + break; + size = size_ll; + break; + case 'm': + if (sscanf (scan + 1, "%ld", &date_l) != 1) + break; + date = date_l; + break; + case '/': + dir = TRUE; + type_known = TRUE; + break; + case 'r': + dir = FALSE; + type_known = TRUE; + break; + case 'i': + break; + case 'u': + if (scan[1] == 'p') /* permissions. */ + if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1) + perms = -1; + break; + default: + name = NULL; + scan = NULL; + break; + } + if (scan == NULL || scan_len == 0) + break; + + comma = (const char *) memchr (scan, ',', scan_len); + if (comma == NULL) + break; + + scan_len -= comma + 1 - scan; + scan = comma + 1; + } + + if (name == NULL || !type_known) + ERR2; + + *filename = g_strndup (name, name_len); + *linkname = NULL; + + if (size != NO_SIZE) + s->st_size = size; + if (date != NO_DATE) + { + s->st_mtime = date; + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + } + if (type_known) + s->st_mode = dir ? S_IFDIR : S_IFREG; + if (perms != -1) + s->st_mode |= perms; /* FIXME */ + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/* + Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; / + Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; .. + Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin + Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub + Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ + modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name + modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name + modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www + */ + +static gboolean +ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + const char *name = NULL; + off_t size = NO_SIZE; + time_t date = NO_DATE; + const char *owner = NULL; + const char *group = NULL; + filetype type = UNKNOWN; + int perms = -1; + char *space; + char *tok; + + space = strstr (line, "; "); + if (space != NULL) + { + name = space + 2; + *space = '\0'; + } + else + { + /* NcFTPd does not put a semicolon after last fact, workaround it. */ + space = strchr (line, ' '); + if (space == NULL) + ERR2; + name = space + 1; + *space = '\0'; + } + + for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";")) + { + if (strcasecmp (tok, "Type=cdir") == 0 + || strcasecmp (tok, "Type=pdir") == 0 || strcasecmp (tok, "Type=dir") == 0) + { + type = DIRECTORY; + continue; + } + if (strcasecmp (tok, "Type=file") == 0) + { + type = NORMAL; + continue; + } + if (strcasecmp (tok, "Type=OS.unix=symlink") == 0) + { + type = SYMLINK; + continue; + } + if (strncasecmp (tok, "Modify=", 7) == 0) + { + date = ftpfs_convert_date (tok + 7); + continue; + } + if (strncasecmp (tok, "Size=", 5) == 0) + { + long long size_ll; + + if (sscanf (tok + 5, "%lld", &size_ll) == 1) + size = size_ll; + continue; + } + if (strncasecmp (tok, "Perm=", 5) == 0) + { + perms = 0; + for (tok += 5; *tok != '\0'; tok++) + { + switch (g_ascii_tolower (*tok)) + { + case 'e': + perms |= 0111; + break; + case 'l': + perms |= 0444; + break; + case 'r': + perms |= 0444; + break; + case 'c': + perms |= 0200; + break; + case 'w': + perms |= 0200; + break; + default: + break; + } + } + continue; + } + if (strncasecmp (tok, "UNIX.mode=", 10) == 0) + { + if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1) + perms = -1; + continue; + } + if (strncasecmp (tok, "UNIX.owner=", 11) == 0) + { + owner = tok + 11; + continue; + } + if (strncasecmp (tok, "UNIX.group=", 11) == 0) + { + group = tok + 11; + continue; + } + if (strncasecmp (tok, "UNIX.uid=", 9) == 0) + { + if (owner == NULL) + owner = tok + 9; + continue; + } + if (strncasecmp (tok, "UNIX.gid=", 9) == 0) + { + if (group == NULL) + group = tok + 9; + continue; + } + } + if (name == NULL || name[0] == '\0' || type == UNKNOWN) + ERR2; + + *filename = g_strdup (name); + *linkname = NULL; + + if (size != NO_SIZE) + s->st_size = size; + if (date != NO_DATE) + { + s->st_mtime = date; + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + } + switch (type) + { + case DIRECTORY: + s->st_mode = S_IFDIR; + break; + case SYMLINK: + s->st_mode = S_IFLNK; + break; + case NORMAL: + s->st_mode = S_IFREG; + break; + default: + g_assert_not_reached (); + } + if (perms != -1) + s->st_mode |= perms; /* FIXME */ + if (owner != NULL) + s->st_uid = ftpfs_get_uid (owner); + if (group != NULL) + s->st_gid = ftpfs_get_gid (group); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + ASUSER 8192 04/26/05 13:54:16 *DIR dir/ + ASUSER 8192 04/26/05 13:57:34 *DIR dir1/ + ASUSER 365255 02/28/01 15:41:40 *STMF readme.txt + ASUSER 8489625 03/18/03 09:37:00 *STMF saved.zip + ASUSER 365255 02/28/01 15:41:40 *STMF unist.old + */ + +static gboolean +ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + char *t; + char *user; + long long size; + int month, day, year, hour, minute, second; + struct tm tms; + time_t mtime; + mode_t type; + char *slash; + + t = FIRST_TOKEN; + if (t == NULL) + ERR2; + user = t; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (sscanf (t, "%lld", &size) != 1) + ERR2; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3) + ERR2; + if (year >= 70) + year += 1900; + else + year += 2000; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3) + ERR2; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + + tms.tm_sec = second; /* seconds after the minute [0, 61] */ + tms.tm_min = minute; /* minutes after the hour [0, 59] */ + tms.tm_hour = hour; /* hour since midnight [0, 23] */ + tms.tm_mday = day; /* day of the month [1, 31] */ + tms.tm_mon = month - 1; /* months since January [0, 11] */ + tms.tm_year = year - 1900; /* years since 1900 */ + tms.tm_isdst = -1; + mtime = mktime (&tms); + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (strcmp (t, "*DIR") == 0) + type = S_IFDIR; + else + type = S_IFREG; + + t = strtok (NULL, ""); + if (t == NULL) + ERR2; + while (*t == ' ') + t++; + if (*t == '\0') + ERR2; + + *linkname = NULL; + + slash = strchr (t, '/'); + if (slash != NULL) + { + if (slash == t) + return FALSE; + + *slash = '\0'; + type = S_IFDIR; + if (slash[1] != '\0') + { + *filename = g_strdup (t); + s->st_mode = type; /* FIXME */ + return TRUE; + } + } + + *filename = g_strdup (t); + s->st_mode = type; + s->st_size = (off_t) size; + s->st_mtime = mtime; + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + s->st_uid = ftpfs_get_uid (user); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + 0 DIR 06-27-96 11:57 PROTOCOL + 169 11-29-94 09:20 SYSLEVEL.MPT + */ + +static gboolean +ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err) +{ + char *t; + long long size; + int month, day, year, hour, minute; + struct tm tms; + + t = FIRST_TOKEN; + if (t == NULL) + ERR2; + + if (sscanf (t, "%lld", &size) != 1) + ERR2; + s->st_size = (off_t) size; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + s->st_mode = S_IFREG; + if (strcmp (t, "DIR") == 0) + { + s->st_mode = S_IFDIR; + t = NEXT_TOKEN; + + if (t == NULL) + ERR2; + } + + if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3) + ERR2; + if (year >= 70) + year += 1900; + else + year += 2000; + + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (sscanf (t, "%2d:%2d", &hour, &minute) != 3) + ERR2; + + tms.tm_sec = 30; /* seconds after the minute [0, 61] */ + tms.tm_min = minute; /* minutes after the hour [0, 59] */ + tms.tm_hour = hour; /* hour since midnight [0, 23] */ + tms.tm_mday = day; /* day of the month [1, 31] */ + tms.tm_mon = month - 1; /* months since January [0, 11] */ + tms.tm_year = year - 1900; /* years since 1900 */ + tms.tm_isdst = -1; + s->st_mtime = mktime (&tms); + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + + t = strtok (NULL, ""); + if (t == NULL) + ERR2; + while (*t == ' ') + t++; + if (*t == '\0') + ERR2; + *filename = g_strdup (t); + *linkname = NULL; + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename, + char **linkname, int *err) +{ + char *t; + mode_t type, mode; + struct tm date; + const char *day_of_month; + char *name; + + t = FIRST_TOKEN; + if (t == NULL) + ERR2; + + if (!vfs_parse_filetype (t, NULL, &type)) + ERR2; + + s->st_mode = type; + + if (!vfs_parse_fileperms (t + 1, NULL, &mode)) + ERR2; + /* permissions are meaningless here. */ + + /* "folder" or 0 */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + + if (strcmp (t, "folder") != 0) + { + long long size; + + /* size? */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + /* size */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (!isdigit ((unsigned char) *t)) + ERR2; + + if (sscanf (t, "%lld", &size) == 1) + s->st_size = (off_t) size; + } + else + { + /* ?? */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + } + + /* month */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + + memset (&date, 0, sizeof (date)); + + if (!vfs_parse_month (t, &date)) + ERR2; + + day_of_month = NEXT_TOKEN; + if (day_of_month == NULL) + ERR2; + + date.tm_mday = atoi (day_of_month); + + /* time or year */ + t = NEXT_TOKEN; + if (t == NULL) + ERR2; + if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min)) + ERR2; + + date.tm_isdst = -1; + date.tm_sec = 30; + if (date.tm_year == -1) + date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900; + else + date.tm_hour = 12; + + s->st_mtime = mktime (&date); + /* Use resulting time value */ + s->st_atime = s->st_ctime = s->st_mtime; + + name = strtok (NULL, ""); + if (name == NULL) + ERR2; + + /* no symlinks on Mac, but anyway. */ + if (!S_ISLNK (s->st_mode)) + *linkname = NULL; + else + { + char *arrow; + + for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++) + if (arrow != name && arrow[4] != '\0') + { + *arrow = '\0'; + *linkname = g_strdup (arrow + 4); + break; + } + } + + *filename = g_strdup (name); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +GSList * +ftpfs_parse_long_list (struct vfs_class * me, struct vfs_s_inode * dir, GSList * buf, int *err_ret) +{ + int err[number_of_parsers]; + GSList *set[number_of_parsers]; /* arrays of struct vfs_s_entry */ + size_t i; + GSList *bufp; + ftpfs_line_parser guessed_parser = NULL; + GSList **the_set = NULL; + int *the_err = NULL; + int *best_err1 = &err[0]; + int *best_err2 = &err[1]; + + ftpfs_init_time (); + + if (err_ret != NULL) + *err_ret = 0; + + memset (&err, 0, sizeof (err)); + memset (&set, 0, sizeof (set)); + + for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp)) + { + char *b = (char *) bufp->data; + size_t blen; + + blen = strlen (b); + + if (b[blen - 1] == '\r') + { + b[blen - 1] = '\0'; + blen--; + } + + if (blen == 0) + continue; + + if (guessed_parser == NULL) + { + for (i = 0; i < number_of_parsers; i++) + { + struct vfs_s_entry *info; + gboolean ok; + char *tmp_line; + int nlink; + + /* parser can clobber the line - work on a copy */ + tmp_line = g_strndup (b, blen); + + info = vfs_s_generate_entry (me, NULL, dir, 0); + nlink = info->ino->st.st_nlink; + ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name, + &info->ino->linkname, &err[i]); + if (ok && strchr (info->name, '/') == NULL) + { + info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */ + set[i] = g_slist_prepend (set[i], info); + } + else + vfs_s_free_entry (me, info); + + g_free (tmp_line); + + if (*best_err1 > err[i]) + best_err1 = &err[i]; + if (*best_err2 > err[i] && best_err1 != &err[i]) + best_err2 = &err[i]; + + if (*best_err1 > 16) + goto leave; /* too many errors with best parser. */ + } + + if (*best_err2 > (*best_err1 + 1) * 16) + { + i = (size_t) (best_err1 - err); + guessed_parser = line_parsers[i]; + the_set = &set[i]; + the_err = &err[i]; + } + } + else + { + struct vfs_s_entry *info; + gboolean ok; + char *tmp_line; + int nlink; + + /* parser can clobber the line - work on a copy */ + tmp_line = g_strndup (b, blen); + + info = vfs_s_generate_entry (me, NULL, dir, 0); + nlink = info->ino->st.st_nlink; + ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname, + the_err); + if (ok && strchr (info->name, '/') == NULL) + { + info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */ + *the_set = g_slist_prepend (*the_set, info); + } + else + vfs_s_free_entry (me, info); + + g_free (tmp_line); + } + } + + if (the_set == NULL) + { + i = best_err1 - err; + the_set = &set[i]; + the_err = &err[i]; + } + + leave: + for (i = 0; i < number_of_parsers; i++) + if (&set[i] != the_set) + { + for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp)) + vfs_s_free_entry (me, VFS_ENTRY (bufp->data)); + + g_slist_free (set[i]); + } + + if (err_ret != NULL && the_err != NULL) + *err_ret = *the_err; + + return the_set != NULL ? g_slist_reverse (*the_set) : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ |