summaryrefslogtreecommitdiffstats
path: root/tdeioslave/fish
diff options
context:
space:
mode:
Diffstat (limited to 'tdeioslave/fish')
-rw-r--r--tdeioslave/fish/AUTHORS1
-rw-r--r--tdeioslave/fish/CMakeLists.txt45
-rw-r--r--tdeioslave/fish/COPYING340
-rw-r--r--tdeioslave/fish/ChangeLog71
-rw-r--r--tdeioslave/fish/ConfigureChecks.cmake32
-rw-r--r--tdeioslave/fish/FAQ37
-rw-r--r--tdeioslave/fish/INSTALL167
-rw-r--r--tdeioslave/fish/Makefile.am33
-rw-r--r--tdeioslave/fish/README258
-rw-r--r--tdeioslave/fish/TODO10
-rw-r--r--tdeioslave/fish/configure.in.in9
-rw-r--r--tdeioslave/fish/fish.cpp1661
-rw-r--r--tdeioslave/fish/fish.h211
-rwxr-xr-xtdeioslave/fish/fish.pl369
-rw-r--r--tdeioslave/fish/fish.protocol81
-rw-r--r--tdeioslave/fish/genfishcode.cmake8
-rwxr-xr-xtdeioslave/fish/genfishcode.pl43
-rw-r--r--tdeioslave/fish/nxfish.protocol74
18 files changed, 3450 insertions, 0 deletions
diff --git a/tdeioslave/fish/AUTHORS b/tdeioslave/fish/AUTHORS
new file mode 100644
index 000000000..fc4f1567e
--- /dev/null
+++ b/tdeioslave/fish/AUTHORS
@@ -0,0 +1 @@
+J�rg Walter <[email protected]>
diff --git a/tdeioslave/fish/CMakeLists.txt b/tdeioslave/fish/CMakeLists.txt
new file mode 100644
index 000000000..b0498131a
--- /dev/null
+++ b/tdeioslave/fish/CMakeLists.txt
@@ -0,0 +1,45 @@
+#################################################
+#
+# (C) 2010-2011 Serghei Amelian
+# serghei (DOT) amelian (AT) gmail.com
+#
+# Improvements and feedback are welcome
+#
+# This file is released under GPL >= 2
+#
+#################################################
+
+include( ConfigureChecks.cmake )
+
+include_directories(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}
+ ${TDE_INCLUDE_DIR}
+ ${TQT_INCLUDE_DIRS}
+)
+
+link_directories(
+ ${TQT_LIBRARY_DIRS}
+)
+
+
+##### other data ################################
+
+install( FILES fish.protocol nxfish.protocol DESTINATION ${SERVICES_INSTALL_DIR} )
+
+
+##### tdeio_fish (module) #########################
+
+add_custom_command( OUTPUT fishcode.h
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/genfishcode.pl ${CMAKE_CURRENT_SOURCE_DIR}/fish.pl > fishcode.h
+ DEPENDS fish.pl )
+
+set_property( SOURCE fish.cpp APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fishcode.h )
+
+set( target tdeio_fish )
+
+tde_add_kpart( ${target} AUTOMOC
+ SOURCES fish.cpp
+ LINK tdeio-shared util
+ DESTINATION ${PLUGIN_INSTALL_DIR}
+)
diff --git a/tdeioslave/fish/COPYING b/tdeioslave/fish/COPYING
new file mode 100644
index 000000000..2d08eab44
--- /dev/null
+++ b/tdeioslave/fish/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/tdeioslave/fish/ChangeLog b/tdeioslave/fish/ChangeLog
new file mode 100644
index 000000000..7f621f3d4
--- /dev/null
+++ b/tdeioslave/fish/ChangeLog
@@ -0,0 +1,71 @@
+1.2.3 by Willy De la Court <[email protected]>
+ Changes in the EXEC code as J�rg Walter proposed.
+ fixed 2 bugs when executing in Shell mode
+
+1.2.2 by Willy De la Court <[email protected]>
+ Security fix tempfile should not be world readable
+ bugfix write to the file not the command
+
+1.2.1 by Willy De la Court <[email protected]>
+ implemented su for fish://localhost/
+ fish://root@localhost/ will su to root
+ fish://someuser@localhost/ will su to someuser
+ fish://localhost:22/ will still use ssh
+ strange problem with su when sending password need to wait a while (1 sec)
+ after reception of the password prompt.
+ some indentations fixed
+ i18n all messages
+
+1.2 by Willy De la Court <[email protected]>
+ implementation of the EXEC function
+ made sure all the VER lines where the same
+ used eval and system() for executing the command
+ Send the VER command directly after the FISH command.
+ After using kill to close the child process used wait to really make sure the child
+ has died. If the child took some time to die the select()
+ returns "Interrupted system call" error. This should solve some hanging problems.
+ added hasExec so it can be tested.
+ backport to BRANCH sendCommand(FISH_VER); and wait(NULL) to fix potential bugs.
+
+1.1.4 by Willy De la Court <[email protected]>
+ fixes Bug 49881: file time differs by 1 hour
+ and backported to BRANCH
+
+1.1.3
+ removed compression option, which fixes 2 bugs: #45448 and an
+ untracked user report about ssh version misdetect; also, is
+ more consistent with overall design policy: leave connection
+ details to the ssh client
+
+ fixed a bug which made lots of ssh zombie processes hang around
+
+1.1.2
+ fixed a bug which made inserting shell args via fish:-URL possible
+
+1.1.1
+ fixed shell mode symlink display
+
+ made perl server compatible with 5.005
+
+1.1
+ added a perl server implementation which is transferred
+ and started automatically if perl is present
+
+ added KDE3 support
+
+ added support for commercial SSH
+
+ modifed shell commands to use file(1)'s -i option (version
+ 3.37 and up) instead of local hack
+
+ fixed an annoying bug with copying/moving dir trees
+
+ fixed bug which made creating new files fail sometimes
+
+ added support for changing the user name in the pass dialog
+
+1.0.1
+ added #include <sys/resource.h> (needed on some platforms)
+
+1.0
+ initial release
diff --git a/tdeioslave/fish/ConfigureChecks.cmake b/tdeioslave/fish/ConfigureChecks.cmake
new file mode 100644
index 000000000..46ea1c181
--- /dev/null
+++ b/tdeioslave/fish/ConfigureChecks.cmake
@@ -0,0 +1,32 @@
+#################################################
+#
+# (C) 2010-2011 Serghei Amelian
+# serghei (DOT) amelian (AT) gmail.com
+#
+# Improvements and feedback are welcome
+#
+# This file is released under GPL >= 2
+#
+#################################################
+
+check_include_file( stropts.h HAVE_STROPTS )
+check_include_file( libutil.h HAVE_LIBUTIL_H )
+check_include_file( util.h HAVE_UTIL_H )
+check_include_file( pty.h HAVE_PTY_H )
+
+
+tde_save( CMAKE_REQUIRED_LIBRARIES )
+set( CMAKE_REQUIRED_LIBRARIES util )
+
+check_c_source_runs("
+ #include <pty.h>
+ int main(int argc, char* argv) {
+ int master_fd, slave_fd;
+ int result;
+ result = openpty(&master_fd, &slave_fd, 0, 0, 0);
+ return 0;
+ }"
+ HAVE_OPENPTY
+)
+
+tde_restore( CMAKE_REQUIRED_LIBRARIES )
diff --git a/tdeioslave/fish/FAQ b/tdeioslave/fish/FAQ
new file mode 100644
index 000000000..6c96494e7
--- /dev/null
+++ b/tdeioslave/fish/FAQ
@@ -0,0 +1,37 @@
+Freqeuently Asked Questions, last updated for tdeio_fish 1.1
+
+Q: Typing fish:/some.host.com does not work
+A: It is fish://some.host.com (double slash)
+
+Q: How can I use a different port?
+A: Use regular URL syntax: fish://some.host.com:2222
+
+Q: Something isn't working. I get strange/no displays
+A: Could be a bug, could be a problem with the remote
+ tools. Try having perl somewhere in the PATH on the
+ remote machine, that should work reliably. Shell-
+ only mode is prone to different tool's opinion about
+ parameters and what they mean. Shell-only mode is
+ thouroughly tested only on GNU tools, and has
+ superficial testing on BSD and Digital Unix. Solaris
+ seems to have problems. Any reports for shell mode on
+ non-GNU machines welcome (BTW, if you see a file
+ .fishsrv.pl in your remote home directory, fish did
+ use perl mode)
+
+Q: The connection stays open. How do I disconnect?
+A: Just wait. The system default idle timeout is used.
+ (about a minute or so)
+
+Q: Why are there no icons?
+A: With this release, you should have icons almost always,
+ but best results are obtained if you install a recent
+ version of the 'file' utility that supports the '-i'
+ option.
+
+Q: How do I specify which program to use for SSH?
+A: Not at all, sorry. After evaluating other programs (rsh,
+ rlogin, telnet) I came to the conclusion it was way too
+ complex to support these, as only ssh supports both,
+ password authentication and automatic execution.
+
diff --git a/tdeioslave/fish/INSTALL b/tdeioslave/fish/INSTALL
new file mode 100644
index 000000000..02a4a0740
--- /dev/null
+++ b/tdeioslave/fish/INSTALL
@@ -0,0 +1,167 @@
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes a while. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 4. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. You can give `configure'
+initial values for variables by setting them in the environment. Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+ CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+ env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory. After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on. Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+ CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+ If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+ Use and save the results of the tests in FILE instead of
+ `./config.cache'. Set FILE to `/dev/null' to disable caching, for
+ debugging `configure'.
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made.
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
diff --git a/tdeioslave/fish/Makefile.am b/tdeioslave/fish/Makefile.am
new file mode 100644
index 000000000..3c79a3d3e
--- /dev/null
+++ b/tdeioslave/fish/Makefile.am
@@ -0,0 +1,33 @@
+kde_module_LTLIBRARIES = tdeio_fish.la
+
+INCLUDES = $(all_includes)
+AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_TDEIO) -ltdetexteditor
+
+tdeio_fish_la_SOURCES = fish.cpp
+tdeio_fish_la_LIBADD = $(LIB_TDESYCOCA) $(LIBUTIL)
+tdeio_fish_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -lutil
+noinst_HEADERS = fishcode.h fish.h
+
+EXTRA_DIST = AUTHORS COPYING ChangeLog INSTALL README TODO FAQ fish.pl
+
+DISTCLEANFILES = fishcode.h
+
+kdelnk_DATA = fish.protocol nxfish.protocol
+kdelnkdir = $(kde_servicesdir)
+
+METASOURCES = AUTO
+
+fish.lo: fishcode.h
+
+fishcode.h: fish.pl
+ SUM=`$(MD5SUM) $(srcdir)/fish.pl | cut -d ' ' $(MD5SUM_CUT)`; \
+ echo '#define CHECKSUM "'$$SUM'"' > $@; \
+ echo 'static const char *fishCode(' >> $@; \
+ sed -e 's/\\/\\\\/g;s/"/\\"/g;s/^[ ]*/"/;/^"# /d;s/[ ]*$$/\\n"/;/^"\\n"$$/d;s/{CHECKSUM}/'$$SUM'/;' $(srcdir)/fish.pl >> $@; \
+ echo ');' >> $@;
+
+messages:
+ $(XGETTEXT) *.cpp -o $(podir)/tdeio_fish.pot
+
+
+
diff --git a/tdeioslave/fish/README b/tdeioslave/fish/README
new file mode 100644
index 000000000..d71098256
--- /dev/null
+++ b/tdeioslave/fish/README
@@ -0,0 +1,258 @@
+Overview of tdeio_fish
+====================
+
+ ------------------------------------------------------------------------
+ NOTE FOR KDE2 USERS: This is the last release supporting KDE2. However,
+ you might need to modify Makefiles to get things installed into the
+ right directories.
+ ------------------------------------------------------------------------
+
+ FISH is a protocol to get filesystem access without special server
+ software, only using a remote shell. (Hence the name: FIles transferred
+ over SHell protocol).
+ It was first devised by Pavel Machek <[email protected]> and implemented
+ as a Midnight Commander vfs module in 1998.
+
+ This is a complete client implementation using his original version
+ 0.0.2 protocol, extending it with 2 commands (which are only optional -
+ should a real FISH server exist on server side that doesn't understand
+ them, this ioslave still works, only slower). Moreover, this client does
+ complete shell metacharacter quoting on all arguments, a fact that is
+ neccessary but missing from the specs.
+ Extensions used are: append (APPEND command), copy (COPY command),
+ lscount (LIST first prints number of files to be listed), lslinks (LIST
+ shows symlink info instead of info about link targets), lsmime (LIST
+ determines the MIME type on the server side)
+ Password and host key queries are handled via dialog boxes.
+ The goal of this client is to make a remote directory look and feel exactly
+ like a local directory, with all comfort, only slower.
+
+ NOTE: From version 1.1.3 on, compression is no longer turned on auto-
+ matically. You have to specify it via ~/.ssh/config or wherever
+ your local ssh client reads its settings. The same goes for all other
+ connection parameters. OpenSSH for example has a powerful configuration
+ file syntax which lets you configure access differently for each host,
+ something I do not intend to duplicate. Read the ssh_config(5) man page
+ for details. If someone knows the docs to read for commercial ssh please
+ tell me so I can include that here as well.
+
+ Included below is the original posting from the mc mailing list archives.
+
+ If perl is installed on the remote machine and in the default PATH, it will
+ be used to transfer a custom server script which is much faster than
+ shell-only mode and more predictable as well. The script is stored in a
+ file called .fishsrv.pl in the working directory directly after login and
+ will be reused on subsequent connections.
+
+ 2001/10/07 J�rg Walter <[email protected]>
+
+
+
+From: Pavel Machek <[email protected]>
+Subject: New virtual filesystem - fish
+Date: Tue, 15 Sep 1998 22:30:07 +0200
+
+Hi!
+
+New virtual filesystem has been created, which allows you to access
+files on remote computer over rsh/ssh connection, with _no_ server
+needed on the other side. To use it from mc or any program using
+libvfs.so, do
+
+
+Note that password authentication will not work, so you must be
+authenticated using [rs]hosts or RSA key.
+
+For protocol, see mc/vfs/README.fish. If someone wants to write
+server, it would be good idea, since it works without server but
+performance is not optimal.
+
+ Pavel
+
+PS: Protocol looks like this. If you have any comments, it is time to
+speak.
+
+
+ FIles transferred over SHell protocol (V 0.0.2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This protocol was designed for transferring files over secureshell
+(ssh) connection. It can be as well used for transfers over rsh, and
+there may be other uses.
+
+Client sends requests of following form:
+
+#FISH_COMMAND
+equivalent shell commands,
+which may be multiline
+
+Only fish commands are defined here, shell equivalents are for your
+information only and will probably vary from implementation to
+implementation. Fish commands always have priority: server is
+expected to execute fish command if it understands it. If it does not,
+however, it can try the luck and execute shell command.
+
+Server's reply is multiline, but alwyas ends with
+
+### 000<optional text>
+
+line. ### is prefix to mark this line, 000 is return code. Return
+codes are superset to those used in ftp.
+
+There are few new exit codes defined:
+
+000 don't know; if there were no previous lines, this marks COMPLETE
+success, if they were, it marks failure.
+
+001 don't know; if there were no previous lines, this marks
+PRELIMinary success, if they were, it marks failure
+
+ Connecting
+ ~~~~~~~~~~
+Client uses "echo FISH:;/bin/sh" as command executed on remote
+machine. This should make it possible for server to distinguish FISH
+connections from normal rsh/ssh.
+
+ Commands
+ ~~~~~~~~
+#FISH
+echo; start_fish_server; echo '### 200'
+
+This command is sent at the begining. It marks that client wishes to
+talk via FISH protocol. #VER command must follow. If server
+understands FISH protocol, it has option to put FISH server somewhere
+on system path and name it start_fish_server.
+
+#VER 0.0.2 <feature1> <feature2> <...>
+echo '### 000'
+
+This command is the second one. It sends client version and extensions
+to the server. Server should reply with protocol version to be used,
+and list of extensions accepted.
+
+VER 0.0.0 <feature2>
+### 200
+
+#PWD
+pwd; echo '### 200'
+
+Server should reply with current directory (in form /abc/def/ghi)
+followed by line indicating success.
+
+#LIST /directory
+ls -lLa $1 | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo "P$p $u.$g
+S$s
+d$m $d $y
+:$n
+"; done )
+ls -lLa $1 | grep '^[cb]' | ( while read p x u g a i m d y n; do echo "P$p $u.$g
+E$a$i
+dD$m $d $y
+:$n
+"; done )
+echo '### 200'
+
+This allows client to list directory or get status information about
+single file. Output is in following form (any line except :<filename>
+may be ommited):
+
+P<unix permissions> <owner>.<group>
+S<size>
+d<3-letters month name> <day> <year or HH:MM>
+D<year> <month> <day> <hour> <minute> <second>[.1234]
+E<major-of-device>,<minor>
+:<filename>
+L<filename symlink points to>
+<blank line to separate items>
+
+Unix permissions are of form X--------- where X is type of
+file. Currently, '-' means regular file, 'd' means directory, 'c', 'b'
+means character and block device, 'l' means symbolic link, 'p' means
+FIFO and 's' means socket.
+
+'d' has three fields: month (one of strings Jan Feb Mar Apr May Jun
+Jul Aug Sep Oct Nov Dec), day of month, and third is either single
+number indicating year, or HH:MM field (assume current year in such
+case). As you've probably noticed, this is pretty broken; it is for
+compatibility with ls listing.
+
+#RETR /some/name
+ls -l /some/name | ( read a b c d x e; echo $x ); echo '### 100'; cat /some/name; echo '### 200'
+
+Server sends line with filesize on it, followed by line with ### 100
+indicating partial success, then it sends binary data (exactly
+filesize bytes) and follows them with (with no preceeding newline) ###
+200.
+
+Note that there's no way to abort running RETR command - except
+closing the connection.
+
+#STOR <size> /file/name
+<i><font color="#008000">> /file/name; echo '### 001'; ( dd bs=4096 count=<size/4096>; dd bs=<size%4096> count=1 ) 2>/dev/null | ( cat > %s; cat > /dev/null ); echo '### 200'
+</font></i>
+This command is for storing /file/name, which is exactly size bytes
+big. You probably think I went crazy. Well, I did not: that strange
+cat > /dev/null has purpose to discard any extra data which was not
+written to disk (due to for example out of space condition).
+
+[Why? Imagine uploading file with "rm -rf /" line in it.]
+
+#CWD /somewhere
+cd /somewhere; echo '### 000'
+
+It is specified here, but I'm not sure how wise idea is to use this
+one: it breaks stateless-ness of the protocol.
+
+Following commands should be rather self-explanatory:
+
+#CHMOD 1234 file
+chmod 1234 file; echo '### 000'
+
+#DELE /some/path
+rm -f /some/path; echo '### 000'
+
+#MKD /some/path
+mkdir /some/path; echo '### 000'
+
+#RMD /some/path
+rmdir /some/path; echo '### 000'
+
+#RENAME /path/a /path/b
+mv /path/a /path/b; echo '### 000'
+
+#LINK /path/a /path/b
+ln /path/a /path/b; echo '### 000'
+
+#SYMLINK /path/a /path/b
+ln -s /path/a /path/b; echo '### 000'
+
+#CHOWN user /file/name
+chown user /file/name; echo '### 000'
+
+#CHGRP group /file/name
+chgrp group /file/name; echo '### 000'
+
+#READ <offset> <size> /path/and/filename
+cat /path/and/filename | ( dd bs=4096 count=<offset/4096> > /dev/null;
+dd bs=<offset%4096> count=1 > /dev/null;
+dd bs=4096 count=<offset/4096>;
+dd bs=<offset%4096> count=1; )
+
+Returns ### 200 on successfull exit, ### 291 on successfull exit when
+reading ended at eof, ### 292 on successfull exit when reading did not
+end at eof.
+
+#WRITE <offset> <size> /path/and/filename
+
+Hmm, shall we define these ones if we know our client is not going to
+use them?
+
+
+That's all, folks!
+
+
+--
+I'm really [email protected]. Pavel
+Look at http://atrey.karlin.mff.cuni.cz/~pavel/ ;-).
diff --git a/tdeioslave/fish/TODO b/tdeioslave/fish/TODO
new file mode 100644
index 000000000..ba3bf69bb
--- /dev/null
+++ b/tdeioslave/fish/TODO
@@ -0,0 +1,10 @@
+L resume (could be very slow in shell mode due to WRITE being slow)
+L other remote shells (rlogin, telnet) - difficult, would need a shell prompt detector which is impossible to get 100% accurate. Contributions welcome.
+L show host list when called as fish://
+L plug into sidebar, show directory tree there
+M employ locking to only show one password dialog, so that loading many files at once from the same host would use a cahced password instead of opening tons of dialog boxes
+M more meaningful error messages (need perl server first)
+H use rsync or a similar technique (if possible at all)
+M proxying via intermediate ssh account
+H make it work with charsets other than latin1
+
diff --git a/tdeioslave/fish/configure.in.in b/tdeioslave/fish/configure.in.in
new file mode 100644
index 000000000..086dc0dae
--- /dev/null
+++ b/tdeioslave/fish/configure.in.in
@@ -0,0 +1,9 @@
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE"
+
+AC_CHECK_HEADERS(termios.h pty.h libutil.h util.h sys/types.h sys/ioctl.h stropts.h)
+
+kde_save_LIBS="$LIBS"
+LIBS="$LIBS $LIBUTIL"
+AC_CHECK_FUNCS(getpt openpty isastream)
+LIBS="$kde_save_LIBS"
diff --git a/tdeioslave/fish/fish.cpp b/tdeioslave/fish/fish.cpp
new file mode 100644
index 000000000..98c11a712
--- /dev/null
+++ b/tdeioslave/fish/fish.cpp
@@ -0,0 +1,1661 @@
+/***************************************************************************
+ fish.cpp - a FISH tdeioslave
+ -------------------
+ begin : Thu Oct 4 17:09:14 CEST 2001
+ copyright : (C) 2001-2003 by J�rg Walter
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License *
+ * *
+ ***************************************************************************/
+
+/*
+ This code contains fragments and ideas from the ftp tdeioslave
+ done by David Faure <[email protected]>.
+
+ Structure is a bit complicated, since I made the mistake to use
+ TDEProcess... now there is a lightweight homebrew async IO system
+ inside, but if signals/slots become available for ioslaves, switching
+ back to TDEProcess should be easy.
+*/
+
+#include "config.h"
+
+#include <tqcstring.h>
+#include <tqfile.h>
+#include <tqsocket.h>
+#include <tqdatetime.h>
+#include <tqbitarray.h>
+#include <tqregexp.h>
+
+#include <stdlib.h>
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#include <math.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/types.h>
+#ifdef HAVE_STROPTS
+#include <stropts.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#include <kdebug.h>
+#include <tdemessagebox.h>
+#include <kinstance.h>
+#include <tdeglobal.h>
+#include <kstandarddirs.h>
+#include <tdelocale.h>
+#include <kremoteencoding.h>
+#include <kurl.h>
+#include <ksock.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <kmimetype.h>
+#include <kmimemagic.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "fish.h"
+#include "fishcode.h"
+
+#ifndef NDEBUG
+#define myDebug(x) kdDebug(7127) << __LINE__ << ": " x
+#define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0)
+#define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0)
+#define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0)
+#define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0)
+#define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.size()<<")" << endl); statEntry(x);}while(0)
+#define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0)
+#define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0)
+#define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0)
+#define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0)
+#define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0)
+#define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0)
+#define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0)
+#define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0)
+#define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0)
+#define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0)
+#else
+#define myDebug(x)
+#define sendmimeType(x) mimeType(x)
+#endif
+
+static char *sshPath = NULL;
+static char *suPath = NULL;
+// disabled: currently not needed. Didn't work reliably.
+// static int isOpenSSH = 0;
+
+static int isNXFish = 0;
+
+#define E(x) ((const char*)remoteEncoding()->encode(x).data())
+
+using namespace TDEIO;
+extern "C" {
+
+static void ripper(int)
+{
+ while (waitpid(-1,0,WNOHANG) > 0) {
+ // do nothing, go on
+ }
+}
+
+int KDE_EXPORT kdemain( int argc, char **argv )
+{
+ TDELocale::setMainCatalogue("tdeio_fish");
+ TDEInstance instance("fish");
+
+ myDebug( << "*** Starting fish " << endl);
+ if (argc != 4) {
+ myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl);
+ exit(-1);
+ }
+
+ setenv("TZ", "UTC", true);
+
+ struct sigaction act;
+ memset(&act,0,sizeof(act));
+ act.sa_handler = ripper;
+ act.sa_flags = 0
+#ifdef SA_NOCLDSTOP
+ | SA_NOCLDSTOP
+#endif
+#ifdef SA_RESTART
+ | SA_RESTART
+#endif
+ ;
+ sigaction(SIGCHLD,&act,NULL);
+
+ if (qstrcmp(argv[1],"nxfish")==0) {
+ // Set NXFish - Mode
+ isNXFish=1;
+ }
+
+ fishProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ myDebug( << "*** fish Done" << endl);
+ return 0;
+}
+
+}
+
+const struct fishProtocol::fish_info fishProtocol::fishInfo[] = {
+ { ("FISH"), 0,
+ ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"),
+ 1 },
+ { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0,
+ ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"),
+ 1 },
+ { ("PWD"), 0,
+ ("pwd"),
+ 1 },
+ { ("LIST"), 1,
+ ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
+ "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
+ 0 },
+ { ("STAT"), 1,
+ ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
+ "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
+ 0 },
+ { ("RETR"), 1,
+ ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"),
+ 1 },
+ { ("STOR"), 2,
+ ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;"
+ "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"),
+ 0 },
+ { ("CWD"), 1,
+ ("cd %1"),
+ 0 },
+ { ("CHMOD"), 2,
+ ("chmod %1 %2"),
+ 0 },
+ { ("DELE"), 1,
+ ("rm -f %1"),
+ 0 },
+ { ("MKD"), 1,
+ ("mkdir %1"),
+ 0 },
+ { ("RMD"), 1,
+ ("rmdir %1"),
+ 0 },
+ { ("RENAME"), 2,
+ ("mv -f %1 %2"),
+ 0 },
+ { ("LINK"), 2,
+ ("ln -f %1 %2"),
+ 0 },
+ { ("SYMLINK"), 2,
+ ("ln -sf %1 %2"),
+ 0 },
+ { ("CHOWN"), 2,
+ ("chown %1 %2"),
+ 0 },
+ { ("CHGRP"), 2,
+ ("chgrp %1 %2"),
+ 0 },
+ { ("READ"), 3,
+ ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;"
+ "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;"
+ "dd bs=%2 count=1; ) 2>/dev/null;"),
+ 0 },
+ // Yes, this is "ibs=1", since dd "count" is input blocks.
+ // On network connections, read() may not fill the buffer
+ // completely (no more data immediately available), but dd
+ // does ignore that fact by design. Sorry, writes are slow.
+ // OTOH, WRITE is not used by the current ioslave methods,
+ // we use APPEND.
+ { ("WRITE"), 3,
+ (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | "
+ "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"),
+ 0 },
+ { ("COPY"), 2,
+ ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"),
+ 0 },
+ { ("APPEND"), 2,
+ (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"),
+ 0 },
+ { ("EXEC"), 2,
+ ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"),
+ 0 }
+};
+
+fishProtocol::fishProtocol(const TQCString &pool_socket, const TQCString &app_socket)
+ : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024),
+ mimeTypeSent(false)
+{
+ myDebug( << "fishProtocol::fishProtocol()" << endl);
+ if (sshPath == NULL) {
+ // disabled: currently not needed. Didn't work reliably.
+ // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null");
+ if (isNXFish)
+ sshPath = strdup(TQFile::encodeName(TDEStandardDirs::findExe("nxfish")));
+ else
+ sshPath = strdup(TQFile::encodeName(TDEStandardDirs::findExe("ssh")));
+ }
+ if (suPath == NULL) {
+ suPath = strdup(TQFile::encodeName(TDEStandardDirs::findExe("su")));
+ }
+ childPid = 0;
+ connectionPort = 0;
+ isLoggedIn = false;
+ writeReady = true;
+ isRunning = false;
+ firstLogin = true;
+ errorCount = 0;
+ rawRead = 0;
+ rawWrite = -1;
+ recvLen = -1;
+ sendLen = -1;
+ setMultipleAuthCaching( true );
+ connectionAuth.keepPassword = true;
+ connectionAuth.url.setProtocol("fish");
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ typeAtom.m_uds = UDS_FILE_TYPE;
+ typeAtom.m_long = 0;
+ mimeAtom.m_uds = UDS_MIME_TYPE;
+ mimeAtom.m_long = 0;
+ mimeAtom.m_str = TQString::null;
+
+ hasAppend = false;
+
+ isStat = false; // FIXME: just a workaround for konq deficiencies
+ redirectUser = ""; // FIXME: just a workaround for konq deficiencies
+ redirectPass = ""; // FIXME: just a workaround for konq deficiencies
+ fishCodeLen = strlen(fishCode);
+}
+/* ---------------------------------------------------------------------------------- */
+
+
+fishProtocol::~fishProtocol()
+{
+ myDebug( << "fishProtocol::~fishProtocol()" << endl);
+ shutdownConnection(true);
+}
+
+/* --------------------------------------------------------------------------- */
+
+/**
+Connects to a server and logs us in via SSH. Then starts FISH protocol.
+*/
+void fishProtocol::openConnection() {
+ if (childPid) return;
+
+ if (connectionHost.isEmpty() && !isNXFish)
+ {
+ error( TDEIO::ERR_UNKNOWN_HOST, TQString::null );
+ return;
+ }
+
+ infoMessage(i18n("Connecting..."));
+
+ myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort << endl);
+ sendCommand(FISH_FISH);
+ sendCommand(FISH_VER);
+ if (connectionStart()) {
+ error(ERR_COULD_NOT_CONNECT,connectionHost);
+ shutdownConnection();
+ return;
+ };
+ myDebug( << "subprocess is running" << endl);
+}
+
+static int open_pty_pair(int fd[2])
+{
+#if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY)
+/** with kind regards to The GNU C Library
+Reference Manual for Version 2.2.x of the GNU C Library */
+ int master, slave;
+ char *name;
+ struct ::termios ti;
+ memset(&ti,0,sizeof(ti));
+
+ ti.c_cflag = CLOCAL|CREAD|CS8;
+ ti.c_cc[VMIN] = 1;
+
+#ifdef HAVE_GETPT
+ master = getpt();
+#else
+ master = open("/dev/ptmx", O_RDWR);
+#endif
+ if (master < 0) return 0;
+
+ if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master;
+
+ name = ptsname(master);
+ if (name == NULL) goto close_master;
+
+ slave = open(name, O_RDWR);
+ if (slave == -1) goto close_master;
+
+#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
+ if (isastream(slave) &&
+ (ioctl(slave, I_PUSH, "ptem") < 0 ||
+ ioctl(slave, I_PUSH, "ldterm") < 0))
+ goto close_slave;
+#endif
+
+ tcsetattr(slave, TCSANOW, &ti);
+ fd[0] = master;
+ fd[1] = slave;
+ return 0;
+
+#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
+close_slave:
+#endif
+ close(slave);
+
+close_master:
+ close(master);
+ return -1;
+#else
+#ifdef HAVE_OPENPTY
+ struct ::termios ti;
+ memset(&ti,0,sizeof(ti));
+
+ ti.c_cflag = CLOCAL|CREAD|CS8;
+ ti.c_cc[VMIN] = 1;
+
+ return openpty(fd,fd+1,NULL,&ti,NULL);
+#else
+#ifdef __GNUC__
+#warning "No tty support available. Password dialog won't work."
+#endif
+ return socketpair(PF_UNIX,SOCK_STREAM,0,fd);
+#endif
+#endif
+}
+/**
+creates the subprocess
+*/
+bool fishProtocol::connectionStart() {
+ int fd[2];
+ int rc, flags;
+ thisFn = TQString::null;
+
+ rc = open_pty_pair(fd);
+ if (rc == -1) {
+ myDebug( << "socketpair failed, error: " << strerror(errno) << endl);
+ return true;
+ }
+
+ if (!requestNetwork()) return true;
+ myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl);
+ childPid = fork();
+ if (childPid == -1) {
+ myDebug( << "fork failed, error: " << strerror(errno) << endl);
+ close(fd[0]);
+ close(fd[1]);
+ childPid = 0;
+ dropNetwork();
+ return true;
+ }
+ if (childPid == 0) {
+ // taken from konsole, see TEPty.C for details
+ // note: if we're running on socket pairs,
+ // this will fail, but thats what we expect
+
+ for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL);
+
+ struct rlimit rlp;
+ getrlimit(RLIMIT_NOFILE, &rlp);
+ for (int i = 0; i < (int)rlp.rlim_cur; i++)
+ if (i != fd[1]) close(i);
+
+ dup2(fd[1],0);
+ dup2(fd[1],1);
+ dup2(fd[1],2);
+ if (fd[1] > 2) close(fd[1]);
+
+ setsid();
+
+#if defined(TIOCSCTTY)
+ ioctl(0, TIOCSCTTY, 0);
+#endif
+
+ int pgrp = getpid();
+#if defined( _AIX) || defined( __hpux)
+ tcsetpgrp(0, pgrp);
+#else
+ ioctl(0, TIOCSPGRP, (char *)&pgrp);
+#endif
+
+ const char *dev = ttyname(0);
+ setpgid(0,0);
+ if (dev) close(open(dev, O_WRONLY, 0));
+ setpgid(0,0);
+
+ if (local) {
+ execl(suPath, "su", "-", connectionUser.latin1(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0);
+ } else {
+ #define common_args "-l", connectionUser.latin1(), "-x", "-e", "none", \
+ "-q", connectionHost.latin1(), \
+ "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0
+ // disabled: leave compression up to the client.
+ // (isOpenSSH?"-C":"+C"),
+
+ if (connectionPort)
+ execl(sshPath, "ssh", "-p", TQString::number(connectionPort).latin1(), common_args);
+ else
+ execl(sshPath, "ssh", common_args);
+ #undef common_args
+ }
+ myDebug( << "could not exec! " << strerror(errno) << endl);
+ ::exit(-1);
+ }
+ close(fd[1]);
+ rc = fcntl(fd[0],F_GETFL,&flags);
+ rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK);
+ childFd = fd[0];
+
+ fd_set rfds, wfds;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ char buf[32768];
+ int offset = 0;
+ while (!isLoggedIn) {
+ FD_SET(childFd,&rfds);
+ FD_ZERO(&wfds);
+ if (outBufPos >= 0) FD_SET(childFd,&wfds);
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000;
+ rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ return true;
+ }
+ if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
+ if (outBuf) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
+ else rc = 0;
+ if (rc >= 0) outBufPos += rc;
+ else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ outBufPos = -1;
+ //return true;
+ }
+ if (outBufPos >= outBufLen) {
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ }
+ }
+ if (FD_ISSET(childFd,&rfds)) {
+ rc = read(childFd,buf+offset,32768-offset);
+ if (rc > 0) {
+ int noff = establishConnection(buf,rc+offset);
+ if (noff < 0) return false;
+ if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
+ offset = noff;
+ } else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+writes one chunk of data to stdin of child process
+*/
+void fishProtocol::writeChild(const char *buf, TDEIO::fileoffset_t len) {
+ if (outBufPos >= 0 && outBuf) {
+#if 0
+ TQString debug;
+ debug.setLatin1(outBuf,outBufLen);
+ if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)" << endl);
+#endif
+ return;
+ }
+ outBuf = buf;
+ outBufPos = 0;
+ outBufLen = len;
+}
+
+/**
+manages initial communication setup including password queries
+*/
+int fishProtocol::establishConnection(char *buffer, TDEIO::fileoffset_t len) {
+ TQString buf;
+ buf.setLatin1(buffer,len);
+ int pos;
+ // Strip trailing whitespace
+ while (buf.length() && (buf[buf.length()-1] == ' '))
+ buf.truncate(buf.length()-1);
+
+ myDebug( << "establishing: got " << buf << endl);
+ while (childPid && ((pos = buf.find('\n')) >= 0 ||
+ buf.endsWith(":") || buf.endsWith("?"))) {
+ pos++;
+ TQString str = buf.left(pos);
+ buf = buf.mid(pos);
+ if (str == "\n")
+ continue;
+ if (str == "FISH:\n") {
+ thisFn = TQString::null;
+ infoMessage(i18n("Initiating protocol..."));
+ if (!connectionAuth.password.isEmpty()) {
+ connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1);
+ cacheAuthentication(connectionAuth);
+ }
+ isLoggedIn = true;
+ return 0;
+ } else if (!str.isEmpty()) {
+ thisFn += str;
+ } else if (buf.endsWith(":")) {
+ if (!redirectUser.isEmpty() && connectionUser != redirectUser) {
+ KURL dest = url;
+ dest.setUser(redirectUser);
+ dest.setPass(redirectPass);
+ redirection(dest);
+ commandList.clear();
+ commandCodes.clear();
+ finished();
+ redirectUser = "";
+ redirectPass = "";
+ return -1;
+ } else if (!connectionPassword.isEmpty()) {
+ myDebug( << "sending cpass" << endl);
+ connectionAuth.password = connectionPassword+"\n";
+ connectionPassword = TQString::null;
+ // su does not like receiving a password directly after sending
+ // the password prompt so we wait a while.
+ if (local)
+ sleep(1);
+ writeChild(connectionAuth.password.latin1(),connectionAuth.password.length());
+ } else {
+ myDebug( << "sending mpass" << endl);
+ connectionAuth.prompt = thisFn+buf;
+ if (local)
+ connectionAuth.caption = i18n("Local Login") + " - " + url.user() + "@" + url.host();
+ else
+ connectionAuth.caption = i18n("SSH Authorization") + " - " + url.user() + "@" + url.host();
+ if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) {
+ connectionAuth.password = TQString::null; // don't prefill
+ if ( !openPassDlg(connectionAuth)) {
+ error(ERR_USER_CANCELED,connectionHost);
+ shutdownConnection();
+ return -1;
+ }
+ }
+ firstLogin = false;
+ connectionAuth.password += "\n";
+ if (connectionAuth.username != connectionUser) {
+ KURL dest = url;
+ dest.setUser(connectionAuth.username);
+ dest.setPass(connectionAuth.password);
+ redirection(dest);
+ if (isStat) { // FIXME: just a workaround for konq deficiencies
+ redirectUser = connectionAuth.username;
+ redirectPass = connectionAuth.password;
+ }
+ commandList.clear();
+ commandCodes.clear();
+ finished();
+ return -1;
+ }
+ myDebug( << "sending pass" << endl);
+ if (local)
+ sleep(1);
+ writeChild(connectionAuth.password.latin1(),connectionAuth.password.length());
+ }
+ thisFn = TQString::null;
+ return 0;
+ } else if (buf.endsWith("?")) {
+ int rc = messageBox(QuestionYesNo,thisFn+buf);
+ if (rc == KMessageBox::Yes) {
+ writeChild("yes\n",4);
+ } else {
+ writeChild("no\n",3);
+ }
+ thisFn = TQString::null;
+ return 0;
+ } else {
+ myDebug( << "unmatched case in initial handling! shouldn't happen!" << endl);
+ }
+ }
+ return buf.length();
+}
+/**
+sets connection information for subsequent commands
+*/
+void fishProtocol::setHost(const TQString & host, int port, const TQString & u, const TQString & pass){
+ TQString user(u);
+
+ if (isNXFish)
+ local = 0;
+ else
+ local = (host == "localhost" && port == 0);
+
+ if (port <= 0) port = 0;
+ if (user.isEmpty()) user = getenv("LOGNAME");
+
+ if (host == connectionHost && port == connectionPort && user == connectionUser)
+ return;
+ myDebug( << "setHost " << u << "@" << host << endl);
+
+ if (childPid) shutdownConnection();
+
+ connectionHost = host;
+ connectionAuth.url.setHost(host);
+
+ connectionUser = user;
+ connectionAuth.username = user;
+ connectionAuth.url.setUser(user);
+
+ connectionPort = port;
+ connectionPassword = pass;
+ firstLogin = true;
+}
+
+/**
+Forced close of the connection
+
+This function gets called from the application side of the universe,
+it shouldn't send any response.
+ */
+void fishProtocol::closeConnection(){
+ myDebug( << "closeConnection()" << endl);
+ shutdownConnection(true);
+}
+
+/**
+Closes the connection
+ */
+void fishProtocol::shutdownConnection(bool forced){
+ if (childPid) {
+ kill(childPid,SIGTERM); // We may not have permission...
+ childPid = 0;
+ close(childFd); // ...in which case this should do the trick
+ childFd = -1;
+ if (!forced)
+ {
+ dropNetwork();
+ infoMessage(i18n("Disconnected."));
+ }
+ }
+ outBufPos = -1;
+ outBuf = NULL;
+ outBufLen = 0;
+ qlist.clear();
+ commandList.clear();
+ commandCodes.clear();
+ isLoggedIn = false;
+ writeReady = true;
+ isRunning = false;
+ rawRead = 0;
+ rawWrite = -1;
+ recvLen = -1;
+ sendLen = -1;
+}
+/**
+builds each FISH request and sets the error counter
+*/
+bool fishProtocol::sendCommand(fish_command_type cmd, ...) {
+ const fish_info &info = fishInfo[cmd];
+ myDebug( << "queueing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines << endl);
+
+ va_list list;
+ va_start(list, cmd);
+ TQString realCmd = info.command;
+ TQString realAlt = info.alt;
+ static TQRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
+ for (int i = 0; i < info.params; i++) {
+ TQString arg(va_arg(list, const char *));
+ int pos = -2;
+ while ((pos = rx.search(arg,pos+2)) >= 0) {
+ arg.replace(pos,0,TQString("\\"));
+ }
+ //myDebug( << "arg " << i << ": " << arg << endl);
+ realCmd.append(" ").append(arg);
+ realAlt.replace(TQRegExp("%"+TQString::number(i+1)),arg);
+ }
+ TQString s("#");
+ s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n");
+ if (realCmd == "FISH")
+ s.prepend(" ");
+ commandList.append(s);
+ commandCodes.append(cmd);
+ return true;
+}
+
+/**
+checks response string for result code, converting 000 and 001 appropriately
+*/
+int fishProtocol::handleResponse(const TQString &str){
+ myDebug( << "handling: " << str << endl);
+ if (str.startsWith("### ")) {
+ bool isOk = false;
+ int result = str.mid(4,3).toInt(&isOk);
+ if (!isOk) result = 500;
+ if (result == 0) result = (errorCount != 0?500:200);
+ if (result == 1) result = (errorCount != 0?500:100);
+ myDebug( << "result: " << result << ", errorCount: " << errorCount << endl);
+ return result;
+ } else {
+ errorCount++;
+ return 0;
+ }
+}
+
+int fishProtocol::makeTimeFromLs(const TQString &monthStr, const TQString &dayStr, const TQString &timeyearStr)
+{
+ TQDateTime dt(TQDate::currentDate(Qt::UTC));
+ int year = dt.date().year();
+ int month = dt.date().month();
+ int currentMonth = month;
+ int day = dayStr.toInt();
+
+ static const char * const monthNames[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) {
+ month = i+1;
+ break;
+ }
+
+ int pos = timeyearStr.find(':');
+ if (timeyearStr.length() == 4 && pos == -1) {
+ year = timeyearStr.toInt();
+ } else if (pos == -1) {
+ return 0;
+ } else {
+ if (month > currentMonth + 1) year--;
+ dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0);
+ }
+ dt.date().setYMD(year,month,day);
+
+ return dt.toTime_t();
+}
+
+/**
+parses response from server and acts accordingly
+*/
+void fishProtocol::manageConnection(const TQString &l) {
+ TQString line(l);
+ int rc = handleResponse(line);
+ UDSAtom atom;
+ TQDateTime dt;
+ TDEIO::filesize_t fsize;
+ int pos, pos2, pos3;
+ bool isOk = false;
+ if (!rc) {
+ switch (fishCommand) {
+ case FISH_VER:
+ if (line.startsWith("VER 0.0.3")) {
+ line.append(" ");
+ hasAppend = line.contains(" append ");
+ } else {
+ error(ERR_UNSUPPORTED_PROTOCOL,line);
+ shutdownConnection();
+ }
+ break;
+ case FISH_PWD:
+ url.setPath(line);
+ redirection(url);
+ break;
+ case FISH_LIST:
+ myDebug( << "listReason: " << listReason << endl);
+ /* Fall through */
+ case FISH_STAT:
+ if (line.length() > 0) {
+ switch (line[0].cell()) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ fsize = line.toULongLong(&isOk);
+ if (fsize > 0 && isOk) errorCount--;
+ if ((fishCommand == FISH_LIST) && (listReason == LIST))
+ totalSize(fsize);
+ break;
+
+ case 'P':
+ errorCount--;
+ if (line[1] == 'd') {
+ mimeAtom.m_str = "inode/directory";
+ typeAtom.m_long = S_IFDIR;
+ } else {
+ if (line[1] == '-') {
+ typeAtom.m_long = S_IFREG;
+ } else if (line[1] == 'l') {
+ typeAtom.m_long = S_IFLNK;
+ } else if (line[1] == 'c') {
+ typeAtom.m_long = S_IFCHR;
+ } else if (line[1] == 'b') {
+ typeAtom.m_long = S_IFBLK;
+ } else if (line[1] == 's') {
+ typeAtom.m_long = S_IFSOCK;
+ } else if (line[1] == 'p') {
+ typeAtom.m_long = S_IFIFO;
+ } else {
+ myDebug( << "unknown file type: " << line[1].cell() << endl);
+ errorCount++;
+ break;
+ }
+ }
+ //myDebug( << "file type: " << atom.m_long << endl);
+ //udsEntry.append(atom);
+
+ atom.m_uds = UDS_ACCESS;
+ atom.m_long = 0;
+ if (line[2] == 'r') atom.m_long |= S_IRUSR;
+ if (line[3] == 'w') atom.m_long |= S_IWUSR;
+ if (line[4] == 'x' || line[4] == 's') atom.m_long |= S_IXUSR;
+ if (line[4] == 'S' || line[4] == 's') atom.m_long |= S_ISUID;
+ if (line[5] == 'r') atom.m_long |= S_IRGRP;
+ if (line[6] == 'w') atom.m_long |= S_IWGRP;
+ if (line[7] == 'x' || line[7] == 's') atom.m_long |= S_IXGRP;
+ if (line[7] == 'S' || line[7] == 's') atom.m_long |= S_ISGID;
+ if (line[8] == 'r') atom.m_long |= S_IROTH;
+ if (line[9] == 'w') atom.m_long |= S_IWOTH;
+ if (line[10] == 'x' || line[10] == 't') atom.m_long |= S_IXOTH;
+ if (line[10] == 'T' || line[10] == 't') atom.m_long |= S_ISVTX;
+ udsEntry.append(atom);
+
+ atom.m_uds = UDS_USER;
+ atom.m_long = 0;
+ pos = line.find('.',12);
+ if (pos < 0) {
+ errorCount++;
+ break;
+ }
+ atom.m_str = line.mid(12,pos-12);
+ udsEntry.append(atom);
+
+ atom.m_uds = UDS_GROUP;
+ atom.m_long = 0;
+ atom.m_str = line.mid(pos+1);
+ udsEntry.append(atom);
+ break;
+
+ case 'd':
+ atom.m_uds = UDS_MODIFICATION_TIME;
+ pos = line.find(' ');
+ pos2 = line.find(' ',pos+1);
+ if (pos < 0 || pos2 < 0) break;
+ errorCount--;
+ atom.m_long = makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1));
+ udsEntry.append(atom);
+ break;
+
+ case 'D':
+ atom.m_uds = UDS_MODIFICATION_TIME;
+ pos = line.find(' ');
+ pos2 = line.find(' ',pos+1);
+ pos3 = line.find(' ',pos2+1);
+ if (pos < 0 || pos2 < 0 || pos3 < 0) break;
+ dt.setDate(TQDate(line.mid(1,pos-1).toInt(),line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt()));
+ pos = pos3;
+ pos2 = line.find(' ',pos+1);
+ pos3 = line.find(' ',pos2+1);
+ if (pos < 0 || pos2 < 0 || pos3 < 0) break;
+ dt.setTime(TQTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt()));
+ errorCount--;
+ atom.m_long = dt.toTime_t();
+ udsEntry.append(atom);
+ break;
+
+ case 'S':
+ atom.m_uds = UDS_SIZE;
+ atom.m_long = line.mid(1).toULongLong(&isOk);
+ if (!isOk) break;
+ errorCount--;
+ udsEntry.append(atom);
+ break;
+
+ case 'E':
+ errorCount--;
+ break;
+
+ case ':':
+ atom.m_uds = UDS_NAME;
+ atom.m_long = 0;
+ pos = line.findRev('/');
+ atom.m_str = thisFn = line.mid(pos < 0?1:pos+1);
+ if (fishCommand == FISH_LIST)
+ udsEntry.append(atom);
+ // By default, the mimetype comes from the extension
+ // We'll use the file(1) result only as fallback [like the rest of KDE does]
+ {
+ KURL kurl("fish://host/");
+ kurl.setFileName(thisFn); // properly encode special chars
+ KMimeType::Ptr mime = KMimeType::findByURL(kurl);
+ if ( mime->name() != KMimeType::defaultMimeType() )
+ mimeAtom.m_str = mime->name();
+ }
+ errorCount--;
+ break;
+
+ case 'M': {
+ TQString type = line.mid(1);
+
+ // First thing's first. If remote says this is a directory, throw out any
+ // name-based file type guesses.
+ if (type == "inode/directory" && mimeAtom.m_str != type) {
+ mimeAtom.m_str = type;
+ typeAtom.m_long = S_IFDIR;
+ }
+ // This is getting ugly. file(1) makes some uneducated
+ // guesses, so we must try to ignore them (#51274)
+ else if (mimeAtom.m_str.isEmpty() && line.right(8) != "/unknown" &&
+ (thisFn.find('.') < 0 || (line.left(8) != "Mtext/x-"
+ && line != "Mtext/plain"))) {
+ mimeAtom.m_str = type;
+ }
+ errorCount--;
+ break;
+ }
+
+ case 'L':
+ atom.m_uds = UDS_LINK_DEST;
+ atom.m_long = 0;
+ atom.m_str = line.mid(1);
+ udsEntry.append(atom);
+ if (!typeAtom.m_long) typeAtom.m_long = S_IFLNK;
+ errorCount--;
+ break;
+ }
+ } else {
+ if (!mimeAtom.m_str.isNull())
+ udsEntry.append(mimeAtom);
+ mimeAtom.m_str = TQString::null;
+
+ udsEntry.append(typeAtom);
+ typeAtom.m_long = 0;
+
+ if (fishCommand == FISH_STAT)
+ udsStatEntry = udsEntry;
+ else if (listReason == LIST) {
+ listEntry(udsEntry, false); //1
+ } else if (listReason == CHECK) checkExist = true; //0
+ errorCount--;
+ udsEntry.clear();
+ }
+ break;
+
+ case FISH_RETR:
+ if (line.length() == 0) {
+ error(ERR_IS_DIRECTORY,url.prettyURL());
+ recvLen = 0;
+ break;
+ }
+ recvLen = line.toLongLong(&isOk);
+ if (!isOk) {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ break;
+ }
+ break;
+ default : break;
+ }
+
+ } else if (rc == 100) {
+ switch (fishCommand) {
+ case FISH_FISH:
+ writeChild(fishCode, fishCodeLen);
+ break;
+ case FISH_READ:
+ recvLen = 1024;
+ /* fall through */
+ case FISH_RETR:
+ myDebug( << "reading " << recvLen << endl);
+ if (recvLen == -1) {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ } else {
+ rawRead = recvLen;
+ dataRead = 0;
+ mimeTypeSent = false;
+ if (recvLen == 0)
+ {
+ mimeType("application/x-zerosize");
+ mimeTypeSent = true;
+ }
+ }
+ break;
+ case FISH_STOR:
+ case FISH_WRITE:
+ case FISH_APPEND:
+ rawWrite = sendLen;
+ //myDebug( << "sending " << sendLen << endl);
+ writeChild(NULL,0);
+ break;
+ default : break;
+ }
+ } else if (rc/100 != 2) {
+ switch (fishCommand) {
+ case FISH_STOR:
+ case FISH_WRITE:
+ case FISH_APPEND:
+ error(ERR_COULD_NOT_WRITE,url.prettyURL());
+ shutdownConnection();
+ break;
+ case FISH_RETR:
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ break;
+ case FISH_READ:
+ if ( rc == 501 )
+ {
+ mimeType("inode/directory");
+ mimeTypeSent = true;
+ recvLen = 0;
+ finished();
+ }
+ else
+ {
+ error(ERR_COULD_NOT_READ,url.prettyURL());
+ shutdownConnection();
+ }
+ break;
+ case FISH_FISH:
+ case FISH_VER:
+ error(ERR_SLAVE_DEFINED,line);
+ shutdownConnection();
+ break;
+ case FISH_PWD:
+ case FISH_CWD:
+ error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL());
+ break;
+ case FISH_LIST:
+ myDebug( << "list error. reason: " << listReason << endl);
+ if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL());
+ else if (listReason == CHECK) {
+ checkExist = false;
+ finished();
+ }
+ break;
+ case FISH_STAT:
+ error(ERR_DOES_NOT_EXIST,url.prettyURL());
+ udsStatEntry.clear();
+ break;
+ case FISH_CHMOD:
+ error(ERR_CANNOT_CHMOD,url.prettyURL());
+ break;
+ case FISH_CHOWN:
+ case FISH_CHGRP:
+ error(ERR_ACCESS_DENIED,url.prettyURL());
+ break;
+ case FISH_MKD:
+ if ( rc == 501 )
+ error(ERR_DIR_ALREADY_EXIST,url.prettyURL());
+ else
+ error(ERR_COULD_NOT_MKDIR,url.prettyURL());
+ break;
+ case FISH_RMD:
+ error(ERR_COULD_NOT_RMDIR,url.prettyURL());
+ break;
+ case FISH_DELE:
+ error(ERR_CANNOT_DELETE,url.prettyURL());
+ break;
+ case FISH_RENAME:
+ error(ERR_CANNOT_RENAME,url.prettyURL());
+ break;
+ case FISH_COPY:
+ case FISH_LINK:
+ case FISH_SYMLINK:
+ error(ERR_COULD_NOT_WRITE,url.prettyURL());
+ break;
+ default : break;
+ }
+ } else {
+ if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE);
+ if (fishCommand == FISH_FISH) {
+ connected();
+ } else if (fishCommand == FISH_LIST) {
+ if (listReason == LIST) {
+ listEntry(UDSEntry(),true);
+ } else if (listReason == CHECK) {
+ if (!checkOverwrite && checkExist)
+ {
+ error(ERR_FILE_ALREADY_EXIST,url.prettyURL());
+ return; // Don't call finished!
+ }
+ }
+ } else if (fishCommand == FISH_STAT) {
+ UDSAtom atom;
+
+ atom.m_uds = TDEIO::UDS_NAME;
+ atom.m_str = url.fileName();
+ udsStatEntry.append( atom );
+ statEntry(udsStatEntry);
+ } else if (fishCommand == FISH_APPEND) {
+ dataReq();
+ if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(TQString::number(rawData.size())),E(url.path()));
+ else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path()));
+ sendLen = rawData.size();
+ } else if (fishCommand == FISH_WRITE) {
+ dataReq();
+ if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(TQString::number(putPos)),E(TQString::number(rawData.size())),E(url.path()));
+ else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path()));
+ putPos += rawData.size();
+ sendLen = rawData.size();
+ } else if (fishCommand == FISH_RETR) {
+ data(TQByteArray());
+ }
+ finished();
+ }
+}
+
+void fishProtocol::writeStdin(const TQString &line)
+{
+ qlist.append(line);
+
+ if (writeReady) {
+ writeReady = false;
+ //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl);
+ myDebug( << "Writing: " << qlist.first() << endl);
+ myDebug( << "---------" << endl);
+ writeChild((const char *)qlist.first().latin1(), qlist.first().length());
+ }
+}
+
+void fishProtocol::sent()
+{
+ if (rawWrite > 0) {
+ myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl);
+ writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite));
+ rawWrite -= rawData.size();
+ if (rawWrite > 0) {
+ dataReq();
+ if (readData(rawData) <= 0) {
+ shutdownConnection();
+ }
+ }
+ return;
+ } else if (rawWrite == 0) {
+ // workaround: some dd's insist in reading multiples of
+ // 8 bytes, swallowing up to seven bytes. Sending
+ // newlines is safe even when a sane dd is used
+ writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15);
+ rawWrite = -1;
+ return;
+ }
+ if (qlist.count() > 0) qlist.remove(qlist.begin());
+ if (qlist.count() == 0) {
+ writeReady = true;
+ } else {
+ //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl);
+ myDebug( << "Writing: " << qlist.first() << endl);
+ myDebug( << "---------" << endl);
+ writeChild((const char *)qlist.first().latin1(),qlist.first().length());
+ }
+}
+
+int fishProtocol::received(const char *buffer, TDEIO::fileoffset_t buflen)
+{
+ int pos = 0;
+ do {
+ if (buflen <= 0) break;
+
+ if (rawRead > 0) {
+ //myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl);
+ int dataSize = (rawRead > buflen?buflen:rawRead);
+ if (!mimeTypeSent)
+ {
+ int mimeSize = TQMIN(dataSize, (int)mimeBuffer.size()-dataRead);
+ memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize);
+ dataRead += mimeSize;
+ rawRead -= mimeSize;
+ buffer += mimeSize;
+ buflen -= mimeSize;
+ if (rawRead == 0) // End of data
+ mimeBuffer.resize(dataRead);
+ if (dataRead < (int)mimeBuffer.size())
+ {
+ myDebug( << "wait for more" << endl);
+ break;
+ }
+
+ // We need a KMimeType::findByNameAndContent(filename,data)
+ // For now we do: find by extension, and if not found (or extension not reliable)
+ // then find by content.
+ bool accurate = false;
+ KMimeType::Ptr mime = KMimeType::findByURL( url, 0, false, true, &accurate );
+ if ( !mime || mime->name() == KMimeType::defaultMimeType()
+ || !accurate )
+ {
+ KMimeType::Ptr p_mimeType = KMimeType::findByContent(mimeBuffer);
+ if ( p_mimeType && p_mimeType->name() != KMimeType::defaultMimeType() )
+ mime = p_mimeType;
+ }
+
+ sendmimeType(mime->name());
+
+
+ mimeTypeSent = true;
+ if (fishCommand != FISH_READ) {
+ totalSize(dataRead + rawRead);
+ data(mimeBuffer);
+ processedSize(dataRead);
+ }
+ mimeBuffer.resize(1024);
+ pos = 0;
+ continue; // Process rest of buffer/buflen
+ }
+
+ TQByteArray bdata;
+ bdata.duplicate(buffer,dataSize);
+ data(bdata);
+
+ dataRead += dataSize;
+ rawRead -= dataSize;
+ processedSize(dataRead);
+ if (rawRead <= 0) {
+ buffer += dataSize;
+ buflen -= dataSize;
+ } else {
+ return 0;
+ }
+ }
+
+ if (buflen <= 0) break;
+
+ pos = 0;
+ // Find newline
+ while((pos < buflen) && (buffer[pos] != '\n'))
+ ++pos;
+
+ if (pos < buflen)
+ {
+ TQString s = remoteEncoding()->decode(TQCString(buffer,pos+1));
+
+ buffer += pos+1;
+ buflen -= pos+1;
+
+ manageConnection(s);
+
+ pos = 0;
+ // Find next newline
+ while((pos < buflen) && (buffer[pos] != '\n'))
+ ++pos;
+ }
+ } while (childPid && buflen && (rawRead > 0 || pos < buflen));
+ return buflen;
+}
+/** get a file */
+void fishProtocol::get(const KURL& u){
+ myDebug( << "@@@@@@@@@ get " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ recvLen = -1;
+ sendCommand(FISH_RETR,E(url.path()));
+ }
+ run();
+}
+
+/** put a file */
+void fishProtocol::put(const KURL& u, int permissions, bool overwrite, bool /*resume*/){
+ myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << overwrite << " " /* << resume */ << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ putPerm = permissions;
+ checkOverwrite = overwrite;
+ checkExist = false;
+ putPos = 0;
+ listReason = CHECK;
+ sendCommand(FISH_LIST,E(url.path()));
+ sendCommand(FISH_STOR,"0",E(url.path()));
+ }
+ run();
+}
+/** executes next command in sequence or calls finished() if all is done */
+void fishProtocol::finished() {
+ if (commandList.count() > 0) {
+ fishCommand = (fish_command_type)commandCodes.first();
+ errorCount = -fishInfo[fishCommand].lines;
+ rawRead = 0;
+ rawWrite = -1;
+ udsEntry.clear();
+ udsStatEntry.clear();
+ writeStdin(commandList.first());
+ //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().find("\n")-1))+"...");
+ commandList.remove(commandList.begin());
+ commandCodes.remove(commandCodes.begin());
+ } else {
+ myDebug( << "_______ emitting finished()" << endl);
+ SlaveBase::finished();
+ isRunning = false;
+ }
+}
+/** aborts command sequence and calls error() */
+void fishProtocol::error(int type, const TQString &detail) {
+ commandList.clear();
+ commandCodes.clear();
+ myDebug( << "ERROR: " << type << " - " << detail << endl);
+ SlaveBase::error(type,detail);
+ isRunning = false;
+}
+/** executes a chain of commands */
+void fishProtocol::run() {
+ if (!isRunning) {
+ int rc;
+ isRunning = true;
+ finished();
+ fd_set rfds, wfds;
+ FD_ZERO(&rfds);
+ char buf[32768];
+ int offset = 0;
+ while (isRunning) {
+ FD_SET(childFd,&rfds);
+ FD_ZERO(&wfds);
+ if (outBufPos >= 0) FD_SET(childFd,&wfds);
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 1000;
+ rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
+ if (rc < 0) {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
+#if 0
+ TQString debug;
+ debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos);
+ myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl);
+#endif
+ if (outBufLen-outBufPos > 0) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
+ else rc = 0;
+ if (rc >= 0) outBufPos += rc;
+ else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ if (outBufPos >= outBufLen) {
+ outBufPos = -1;
+ outBuf = NULL;
+ sent();
+ }
+ }
+ else if (FD_ISSET(childFd,&rfds)) {
+ rc = read(childFd,buf+offset,32768-offset);
+ //myDebug( << "read " << rc << " bytes" << endl);
+ if (rc > 0) {
+ int noff = received(buf,rc+offset);
+ if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
+ //myDebug( << "left " << noff << " bytes: " << TQString::fromLatin1(buf,offset) << endl);
+ offset = noff;
+ } else {
+ if (errno == EINTR)
+ continue;
+ myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
+ error(ERR_CONNECTION_BROKEN,connectionHost);
+ shutdownConnection();
+ return;
+ }
+ }
+ if (wasKilled())
+ return;
+ }
+ }
+}
+/** stat a file */
+void fishProtocol::stat(const KURL& u){
+ myDebug( << "@@@@@@@@@ stat " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ isStat = true; // FIXME: just a workaround for konq deficiencies
+ openConnection();
+ isStat = false; // FIXME: just a workaround for konq deficiencies
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand(FISH_STAT,E(url.path(-1)));
+ }
+ run();
+}
+/** find mimetype for a file */
+void fishProtocol::mimetype(const KURL& u){
+ myDebug( << "@@@@@@@@@ mimetype " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ recvLen = 1024;
+ sendCommand(FISH_READ,"0","1024",E(url.path()));
+ }
+ run();
+}
+/** list a directory */
+void fishProtocol::listDir(const KURL& u){
+ myDebug( << "@@@@@@@@@ listDir " << u << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ listReason = LIST;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ run();
+}
+/** create a directory */
+void fishProtocol::mkdir(const KURL& u, int permissions) {
+ myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand(FISH_MKD,E(url.path()));
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(TQString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** rename a file */
+void fishProtocol::rename(const KURL& s, const KURL& d, bool overwrite) {
+ myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << overwrite << endl);
+ if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
+ error(ERR_UNSUPPORTED_ACTION,s.prettyURL());
+ return;
+ }
+ setHost(s.host(),s.port(),s.user(),s.pass());
+ url = d;
+ openConnection();
+ if (!isLoggedIn) return;
+ KURL src = s;
+ url.cleanPath();
+ src.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_RENAME,E(src.path()),E(url.path()));
+ }
+ run();
+}
+/** create a symlink */
+void fishProtocol::symlink(const TQString& target, const KURL& u, bool overwrite) {
+ myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << overwrite << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_SYMLINK,E(target),E(url.path()));
+ }
+ run();
+}
+/** change file permissions */
+void fishProtocol::chmod(const KURL& u, int permissions){
+ myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(TQString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** copies a file */
+void fishProtocol::copy(const KURL &s, const KURL &d, int permissions, bool overwrite) {
+ myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << overwrite << endl);
+ if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
+ error(ERR_UNSUPPORTED_ACTION,s.prettyURL());
+ return;
+ }
+ //myDebug( << s << endl << d << endl);
+ setHost(s.host(),s.port(),s.user(),s.pass());
+ url = d;
+ openConnection();
+ if (!isLoggedIn) return;
+ KURL src = s;
+ url.cleanPath();
+ src.cleanPath();
+ if (!src.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ if (!overwrite) {
+ listReason = CHECK;
+ checkOverwrite = false;
+ sendCommand(FISH_LIST,E(url.path()));
+ }
+ sendCommand(FISH_COPY,E(src.path()),E(url.path()));
+ if (permissions > -1) sendCommand(FISH_CHMOD,E(TQString::number(permissions,8)),E(url.path()));
+ }
+ run();
+}
+/** removes a file or directory */
+void fishProtocol::del(const KURL &u, bool isFile){
+ myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ url.cleanPath();
+ if (!url.hasPath()) {
+ sendCommand(FISH_PWD);
+ } else {
+ sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path()));
+ }
+ run();
+}
+/** special like background execute */
+void fishProtocol::special( const TQByteArray &data ){
+ int tmp;
+
+ TQDataStream stream(data, IO_ReadOnly);
+
+ stream >> tmp;
+ switch (tmp) {
+ case FISH_EXEC_CMD: // SSH EXEC
+ {
+ KURL u;
+ TQString command;
+ TQString tempfile;
+ stream >> u;
+ stream >> command;
+ myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl);
+ setHost(u.host(),u.port(),u.user(),u.pass());
+ url = u;
+ openConnection();
+ if (!isLoggedIn) return;
+ sendCommand(FISH_EXEC,E(command),E(url.path()));
+ run();
+ break;
+ }
+ default:
+ // Some command we don't understand.
+ error(ERR_UNSUPPORTED_ACTION,TQString().setNum(tmp));
+ break;
+ }
+}
+/** report status */
+void fishProtocol::slave_status() {
+ myDebug( << "@@@@@@@@@ slave_status" << endl);
+ if (childPid > 0)
+ slaveStatus(connectionHost,isLoggedIn);
+ else
+ slaveStatus(TQString::null,false);
+}
diff --git a/tdeioslave/fish/fish.h b/tdeioslave/fish/fish.h
new file mode 100644
index 000000000..43dd5477d
--- /dev/null
+++ b/tdeioslave/fish/fish.h
@@ -0,0 +1,211 @@
+/***************************************************************************
+ fish.h - a FISH tdeioslave
+ -------------------
+ begin : Thu Oct 4 17:09:14 CEST 2001
+ copyright : (C) 2001 by J�rg Walter
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License *
+ * *
+ ***************************************************************************/
+#ifndef __fish_h__
+#define __fish_h__
+
+#include <tqstring.h>
+#include <tqcstring.h>
+
+
+#include <kurl.h>
+#include <tdeio/global.h>
+#include <tdeio/slavebase.h>
+#include <kprocess.h>
+#include <tdeio/authinfo.h>
+#include <time.h>
+
+#define FISH_EXEC_CMD 'X'
+
+class fishProtocol : public TDEIO::SlaveBase
+{
+public:
+ fishProtocol(const TQCString &pool_socket, const TQCString &app_socket);
+ virtual ~fishProtocol();
+
+ /**
+Connects to a server and logs us in via SSH. Then starts FISH protocol.
+@ref isConnected is set to true if logging on was successful.
+It is set to false if the connection becomes closed.
+
+ */
+ void openConnection();
+
+ /**
+ Clean up connection
+ */
+ void shutdownConnection(bool forced=false);
+ /** sets connection information for subsequent commands */
+ void setHost(const TQString & host, int port, const TQString & user, const TQString & pass);
+ /** Forced close of the connection */
+ void closeConnection();
+ /** get a file */
+ void get(const KURL& url);
+ /** put a file */
+ void put(const KURL& url, int permissions, bool overwrite, bool resume);
+ /** aborts command sequence and calls error() */
+ void error(int type, const TQString &detail);
+ /** executes next command in sequence or calls finished() if all is done */
+ void finished();
+ /** stat a file */
+ void stat(const KURL& url);
+ /** find mimetype for a file */
+ void mimetype(const KURL& url);
+ /** list a directory */
+ void listDir(const KURL& url);
+ /** create a directory */
+ void mkdir(const KURL&url, int permissions);
+ /** rename a file */
+ void rename(const KURL& src, const KURL& dest, bool overwrite);
+ /** create a symlink */
+ void symlink(const TQString& target, const KURL& dest, bool overwrite);
+ /** change file permissions */
+ void chmod(const KURL& url, int permissions);
+ /** copies a file */
+ void copy(const KURL &src, const KURL &dest, int permissions, bool overwrite);
+ /** report status */
+ void slave_status();
+ /** removes a file or directory */
+ void del(const KURL &u, bool isfile);
+ /** special like background execute */
+ void special( const TQByteArray &data );
+
+private: // Private attributes
+ /** the SSH process used to communicate with the remote end */
+ pid_t childPid;
+ /** fd for reading and writing to the process */
+ int childFd;
+ /** buffer for data to be written */
+ const char *outBuf;
+ /** current write position in buffer */
+ TDEIO::fileoffset_t outBufPos;
+ /** length of buffer */
+ TDEIO::fileoffset_t outBufLen;
+ /** use su if true else use ssh */
+ bool local;
+ /** // FIXME: just a workaround for konq deficiencies */
+ bool isStat;
+ /** // FIXME: just a workaround for konq deficiencies */
+ TQString redirectUser, redirectPass;
+
+protected: // Protected attributes
+ /** for LIST/STAT */
+ TDEIO::UDSEntry udsEntry;
+ /** for LIST/STAT */
+ TDEIO::UDSEntry udsStatEntry;
+ /** for LIST/STAT */
+ TDEIO::UDSAtom typeAtom;
+ /** for LIST/STAT */
+ TDEIO::UDSAtom mimeAtom;
+ /** for LIST/STAT */
+ TQString thisFn;
+ /** for STAT */
+ TQString wantedFn;
+ TQString statPath;
+ /** url of current request */
+ KURL url;
+ /** true if connection is logged in successfully */
+ bool isLoggedIn;
+ /** host name of current connection */
+ TQString connectionHost;
+ /** user name of current connection */
+ TQString connectionUser;
+ /** port of current connection */
+ int connectionPort;
+ /** password of current connection */
+ TQString connectionPassword;
+ /** AuthInfo object used for logging in */
+ TDEIO::AuthInfo connectionAuth;
+ /** number of lines received, == 0 -> everything went ok */
+ int errorCount;
+ /** queue for lines to be sent */
+ TQStringList qlist;
+ /** queue for commands to be sent */
+ TQStringList commandList;
+ /** queue for commands to be sent */
+ TQValueList<int> commandCodes;
+ /** bytes still to be read in raw mode */
+ TDEIO::fileoffset_t rawRead;
+ /** bytes still to be written in raw mode */
+ TDEIO::fileoffset_t rawWrite;
+ /** data bytes to read in next read command */
+ TDEIO::fileoffset_t recvLen;
+ /** data bytes to write in next write command */
+ TDEIO::fileoffset_t sendLen;
+ /** true if the last write operation was finished */
+ bool writeReady;
+ /** true if a command stack is currently executing */
+ bool isRunning;
+ /** reason of LIST command */
+ enum { CHECK, LIST } listReason;
+ /** true if FISH server understands APPEND command */
+ bool hasAppend;
+ /** permission of created file */
+ int putPerm;
+ /** true if file may be overwritten */
+ bool checkOverwrite;
+ /** current position of write */
+ TDEIO::fileoffset_t putPos;
+ /** true if file already existed */
+ bool checkExist;
+ /** true if this is the first login attempt (== use cached password) */
+ bool firstLogin;
+ /** write buffer */
+ TQByteArray rawData;
+ /** buffer for storing bytes used for MimeMagic */
+ TQByteArray mimeBuffer;
+ /** whther the mimetype has been sent already */
+ bool mimeTypeSent;
+ /** number of bytes read so far */
+ TDEIO::fileoffset_t dataRead;
+ /** details about each fishCommand */
+ static const struct fish_info {
+ const char *command;
+ int params;
+ const char *alt;
+ int lines;
+ } fishInfo[];
+ /** last FISH command sent to server */
+ enum fish_command_type { FISH_FISH, FISH_VER, FISH_PWD, FISH_LIST, FISH_STAT,
+ FISH_RETR, FISH_STOR,
+ FISH_CWD, FISH_CHMOD, FISH_DELE, FISH_MKD, FISH_RMD,
+ FISH_RENAME, FISH_LINK, FISH_SYMLINK, FISH_CHOWN,
+ FISH_CHGRP, FISH_READ, FISH_WRITE, FISH_COPY, FISH_APPEND, FISH_EXEC } fishCommand;
+ int fishCodeLen;
+protected: // Protected methods
+ /** manages initial communication setup including password queries */
+ int establishConnection(char *buffer, TDEIO::fileoffset_t buflen);
+ int received(const char *buffer, TDEIO::fileoffset_t buflen);
+ void sent();
+ /** builds each FISH request and sets the error counter */
+ bool sendCommand(fish_command_type cmd, ...);
+ /** checks response string for result code, converting 000 and 001 appropriately */
+ int handleResponse(const TQString &str);
+ /** parses a ls -l time spec */
+ int makeTimeFromLs(const TQString &dayStr, const TQString &monthStr, const TQString &timeyearStr);
+ /** executes a chain of commands */
+ void run();
+ /** creates the subprocess */
+ bool connectionStart();
+ /** writes one chunk of data to stdin of child process */
+ void writeChild(const char *buf, TDEIO::fileoffset_t len);
+ /** parses response from server and acts accordingly */
+ void manageConnection(const TQString &line);
+ /** writes to process */
+ void writeStdin(const TQString &line);
+};
+
+
+#endif
diff --git a/tdeioslave/fish/fish.pl b/tdeioslave/fish/fish.pl
new file mode 100755
index 000000000..ace813bb9
--- /dev/null
+++ b/tdeioslave/fish/fish.pl
@@ -0,0 +1,369 @@
+#!/usr/bin/perl
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 2 of the License
+=pod
+This file was transferred by tdeio_fish, a network client part of the
+KDE project. You may safely delete it, it will be transferred again
+when needed. It's only purpose is to make tdeio_fish access faster and
+more reliable.
+=cut
+
+use Fcntl;
+
+$|++;
+#open(DEBUG,">/tmp/tdeio_fish.debug.$$.log");
+# save code in initial directory if just transferred
+if (defined $code) {
+ unlink('.fishsrv.pl');
+ sysopen(FH,'.fishsrv.pl',O_WRONLY|O_CREAT|O_EXCL);
+ print FH $code;
+ close(FH);
+ chmod(0444,'.fishsrv.pl');
+# request new code if it changed (checksum mismatch)
+# for automatic upgrades
+} elsif ($ARGV[0] ne "{CHECKSUM}") {
+ $|=1;
+ print "### 100 transfer fish server\n";
+ while(<STDIN>) {
+ last if /^__END__/;
+ $code.=$_;
+ }
+ exit(eval($code));
+}
+
+# we are up and running.
+print "### 200\n";
+use strict;
+use POSIX qw(getcwd dup2 strftime);
+$SIG{'CHLD'} = 'IGNORE';
+$| = 1;
+MAIN: while (<STDIN>) {
+ chomp;
+ chomp;
+ next if !length($_) || substr($_,0,1) ne '#';
+#print DEBUG "$_\n";
+ s/^#//;
+ /^VER / && do {
+ # We do not advertise "append" capability anymore, as "write" is
+ # as fast in perl mode and more reliable (overlapping writes)
+ print "VER 0.0.3 copy lscount lslinks lsmime exec stat\n### 200\n";
+ next;
+ };
+ /^PWD$/ && do {
+ print getcwd(),"\n### 200\n";
+ next;
+ };
+ /^SYMLINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $ofn = unquote($1);
+ my $fn = unquote($2);
+ print (symlink($ofn,$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^COPY\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $ofn = unquote($1);
+ my $fn = unquote($2);
+ my ($size) = (stat($ofn))[7];
+ my $read = 1;
+ if (-l $ofn) {
+ my $dest = readlink($ofn);
+ unlink($fn);
+ symlink($dest,$fn) || ($read = 0);
+ } else {
+ sysopen(FH,$ofn,O_RDONLY) || do { print "### 500 $!\n"; next; };
+ sysopen(OFH,$fn,O_WRONLY|O_CREAT|O_TRUNC) || do { close(FH); print "### 500 $!\n"; next; };
+ local $/ = undef;
+ my $buffer = '';
+ while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) {
+ $size -= $read;
+ if (syswrite(OFH,$buffer,$read) != $read) {
+ close(FH); close(OFH);
+ print "### 500 $!\n";
+ next MAIN;
+ }
+
+ }
+ while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) {
+ $size -= $read;
+ if (syswrite(OFH,$buffer,$read) != $read) {
+ close(FH); close(OFH);
+ print "### 500 $!\n";
+ next MAIN;
+ }
+ }
+ close(FH);
+ close(OFH);
+ }
+ if ($read > 0) {
+ print "### 200\n";
+ } else {
+ print "### 500 $!\n";
+ }
+ next;
+ };
+ /^LINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $ofn = unquote($1);
+ my $fn = unquote($2);
+ print (link($ofn,$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^RENAME\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $ofn = unquote($1);
+ my $fn = unquote($2);
+ print (rename($ofn,$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^CHGRP\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $fn = unquote($2);
+ print (chown(-1,int($1),$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^CHOWN\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $fn = unquote($2);
+ print (chown(int($1),-1,$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^CHMOD\s+([0-7]+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $fn = unquote($2);
+ print (chmod(oct($1),$fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^DELE\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $fn = unquote($1);
+ print (unlink($fn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^RMD\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $dn = unquote($1);
+ print (rmdir($dn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^MKD\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $dn = unquote($1);
+ if (mkdir($dn,0777)) {
+ print "### 200\n";
+ } else {
+ my $err = $!;
+ print (chdir($dn)?"### 501 $err\n":"### 500 $err\n");
+ }
+ next;
+ };
+ /^CWD\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $dn = unquote($1);
+ print (chdir($dn)?"### 200\n":"### 500 $!\n");
+ next;
+ };
+ /^LIST\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ list($1, 1);
+ next;
+ };
+ /^STAT\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ list($1, 0);
+ next;
+ };
+ /^WRITE\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ write_loop($2,$3,O_WRONLY|O_CREAT,$1);
+ next;
+ };
+ /^APPEND\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ write_loop($1,$2,O_WRONLY|O_APPEND);
+ next;
+ };
+ /^STOR\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ write_loop($1,$2,O_WRONLY|O_CREAT|O_TRUNC);
+ next;
+ };
+ /^RETR\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ read_loop($1);
+ next;
+ };
+ /^READ\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ read_loop($3,$2,$1);
+ next;
+ };
+ /^EXEC\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do {
+ my $tempfile = unquote($2);
+ my $command = unquote($1);
+ $command = $command . ";echo \"###RESULT: \$?\"";
+ print("### 500 $!\n"), next
+ if (!sysopen(FH,$tempfile,O_CREAT|O_EXCL|O_WRONLY,0600));
+ my $pid = fork();
+ print("### 500 $!\n"), next
+ if (!defined $pid);
+ if ($pid == 0) {
+ open(STDOUT,'>>&FH');
+ open(STDERR,'>>&FH');
+ open(STDIN,'</dev/null'); # not sure here, ms windows anyone?
+ exec('/bin/sh','-c',$command);
+ print STDERR "Couldn't exec /bin/sh: $!\n";
+ exit(255);
+ }
+ waitpid($pid,0);
+ close(FH);
+ print "### 200\n";
+ next;
+ };
+}
+exit(0);
+
+sub list {
+ my $dn = unquote($_[0]);
+ my @entries;
+ if (!-e $dn) {
+ print "### 404 File does not exist\n";
+ return;
+ } elsif ($_[1] && -d _) {
+ opendir(DIR,$dn) || do { print "### 500 $!\n"; return; };
+ @entries = readdir(DIR);
+ closedir(DIR);
+ } else {
+ ($dn, @entries) = $dn =~ m{(.*)/(.*)};
+ $dn = '/' if (!length($dn));
+ }
+ print scalar(@entries),"\n### 100\n";
+ my $cwd = getcwd();
+ chdir($dn) || do { print "### 500 $!\n"; return; };
+ foreach (@entries) {
+ my $link = readlink;
+ my ($mode,$uid,$gid,$size,$mtime) = (lstat)[2,4,5,7,9];
+ print filetype($mode,$link,$uid,$gid);
+ print "S$size\n";
+ print strftime("D%Y %m %d %H %M %S\n",localtime($mtime));
+ print ":$_\n";
+ print "L$link\n" if defined $link;
+ print mimetype($_);
+ print "\n";
+ }
+ chdir($cwd);
+ print "### 200\n";
+}
+
+sub read_loop {
+ my $fn = unquote($_[0]);
+ my ($size) = ($_[1]?int($_[1]):(stat($fn))[7]);
+ my $error = '';
+ print "### 501 Is directory\n" and return if -d $fn;
+ sysopen(FH,$fn,O_RDONLY) || ($error = $!);
+ if ($_[2]) {
+ sysseek(FH,int($_[2]),0) || do { close(FH); $error ||= $!; };
+ }
+ print "### 500 $error\n" and return if $error;
+ if (@_ < 2) {
+ print "$size\n";
+ }
+ print "### 100\n";
+ my $buffer = '';
+ my $read = 1;
+ while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) {
+#print DEBUG "$size left, $read read\n";
+ $size -= $read;
+ print $buffer;
+ }
+ while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) {
+#print DEBUG "$size left, $read read\n";
+ $size -= $read;
+ print $buffer;
+ }
+ while ($size > 0) {
+ print ' ';
+ $size--;
+ }
+ $error ||= $! if $read <= 0;
+ close(FH);
+ if (!$error) {
+ print "### 200\n";
+ } else {
+ print "### 500 $error\n";
+ }
+}
+
+sub write_loop {
+ my $size = int($_[0]);
+ my $fn = unquote($_[1]);
+#print DEBUG "write_loop called $size size, $fn fn, $_[2]\n";
+ my $error = '';
+ sysopen(FH,$fn,$_[2]) || do { print "### 400 $!\n"; return; };
+ eval { flock(FH,2); };
+ if ($_[3]) {
+ sysseek(FH,int($_[3]),0) || do { close(FH);print "### 400 $!\n"; return; };
+ }
+ <STDIN>;
+ print "### 100\n";
+ my $buffer = '';
+ my $read = 1;
+ while ($size > 32768 && ($read = read(STDIN,$buffer,32768)) > 0) {
+#print DEBUG "$size left, $read read\n";
+ $size -= $read;
+ $error ||= $! if (syswrite(FH,$buffer,$read) != $read);
+ }
+ while ($size > 0 && ($read = read(STDIN,$buffer,$size)) > 0) {
+#print DEBUG "$size left, $read read\n";
+ $size -= $read;
+ $error ||= $! if (syswrite(FH,$buffer,$read) != $read);
+ }
+ close(FH);
+ if (!$error) {
+ print "### 200\n";
+ } else {
+ print "### 500 $error\n";
+ }
+}
+
+sub unquote { $_ = shift; s/\\(.)/$1/g; return $_; }
+
+sub filetype {
+ my ($mode,$link,$uid,$gid) = @_;
+ my $result = 'P';
+ while (1) {
+ -f _ && do { $result .= '-'; last; };
+ -d _ && do { $result .= 'd'; last; };
+ defined($link) && do { $result .= 'l'; last; };
+ -c _ && do { $result .= 'c'; last; };
+ -b _ && do { $result .= 'b'; last; };
+ -S _ && do { $result .= 's'; last; };
+ -p _ && do { $result .= 'p'; last; };
+ $result .= '?'; last;
+ }
+ $result .= ($mode & 0400?'r':'-');
+ $result .= ($mode & 0200?'w':'-');
+ $result .= ($mode & 0100?($mode&04000?'s':'x'):($mode&04000?'S':'-'));
+ $result .= ($mode & 0040?'r':'-');
+ $result .= ($mode & 0020?'w':'-');
+ $result .= ($mode & 0010?($mode&02000?'s':'x'):($mode&02000?'S':'-'));
+ $result .= ($mode & 0004?'r':'-');
+ $result .= ($mode & 0002?'w':'-');
+ $result .= ($mode & 0001?($mode&01000?'t':'x'):($mode&01000?'T':'-'));
+
+ $result .= ' ';
+ $result .= (getpwuid($uid)||$uid);
+ $result .= '.';
+ $result .= (getgrgid($gid)||$gid);
+ $result .= "\n";
+ return $result;
+}
+
+sub mimetype {
+ my $fn = shift;
+ return "Minode/directory\n" if -d $fn;
+ pipe(IN,OUT);
+ my $pid = fork();
+ return '' if (!defined $pid);
+ if ($pid) {
+ close(OUT);
+ my $type = <IN>;
+ close(IN);
+ chomp $type;
+ chomp $type;
+ $type =~ s/[,; ].*//;
+ return '' if ($type !~ m/\//);
+ return "M$type\n"
+ }
+ close(IN);
+ sysopen(NULL,'/dev/null',O_RDWR);
+ dup2(fileno(NULL),fileno(STDIN));
+ dup2(fileno(OUT),fileno(STDOUT));
+ dup2(fileno(NULL),fileno(STDERR));
+ exec('/usr/bin/file','-i','-b','-L',$fn);
+ exit(0);
+}
+__END__
diff --git a/tdeioslave/fish/fish.protocol b/tdeioslave/fish/fish.protocol
new file mode 100644
index 000000000..466429c17
--- /dev/null
+++ b/tdeioslave/fish/fish.protocol
@@ -0,0 +1,81 @@
+[Protocol]
+exec=tdeio_fish
+protocol=fish
+input=none
+output=filesystem
+listing=Name,Type,Size,Date,Access,Owner,Group,Link,
+reading=true
+writing=true
+makedir=true
+deleting=true
+linking=true
+moving=true
+Icon=remote
+Description=A tdeioslave for the FISH protocol
+Description[af]='n Kioslave vir die FISH protokol
+Description[be]=Kioslave для пратакола FISH
+Description[bn]=ফিশ (FISH) প্রোটোকল-এর জন্য একটি tdeioslave
+Description[br]=Ur c'hioslave evit ar c'homenad FISH
+Description[bs]=tdeioslave za FISH protokol
+Description[ca]=Un tdeioslave pel protocol FISH
+Description[cs]=Pomocný protokol pro FISH
+Description[csb]=Plugins protokòłu FISH
+Description[da]=En tdeioslave for FISH-protokollen
+Description[de]=Ein-/Ausgabemodul für das FISH-Protokoll
+Description[el]=Ένας tdeioslave για το πρωτόκολλο FISH
+Description[eo]=K-enel-sklavo por la FISH protokolo
+Description[es]=Un tdeioslave para el protocolo FISH
+Description[et]=FISH protokolli IO-moodul
+Description[eu]=FISH protokolorako tdeioslavea
+Description[fa]=یک tdeioslave برای قرارداد FISH
+Description[fi]=Liitäntä FISH-yhteyskäytäntö
+Description[fr]=Un module d'entrées / sorties pour le protocole FISH
+Description[fy]=In tdeioslave foar it FISH protokol
+Description[ga]=tdeioslave le haghaidh an phrótacail FISH
+Description[gl]=Un tdeioslave para o protocolo FISH
+Description[he]=ממשק tdeioslave עבור פרוטוקול FISH
+Description[hi]=फिश प्रोटोकॉल हेतु के-आई-ओ-स्लेव
+Description[hr]=Kioslave za FISH protokol
+Description[hu]=TDE-protokoll a FISH protokollhoz
+Description[is]=tdeioslave fyrir FISH samskiptaregluna
+Description[it]=Un tdeioslave per il protocollo FISH
+Description[ja]=FISH プロトコルのための tdeioslave
+Description[ka]=tdeioslave FISH პროტოკოლისთვის
+Description[kk]=FISH протоколға арналған файл жүйесінің модулі
+Description[km]=tdeioslave សម្រាប់​ពិធីការ FISH
+Description[ko]=FISH 프로토콜을 위한 TDEIO 슬레이브
+Description[lt]=Kiovergas FISH protokolui
+Description[lv]=TDEIO vergs FISH protokolam
+Description[mk]=КИО-служител за протоколот FISH
+Description[ms]=Hamba tdeio untuk protokol FISH
+Description[nb]=En IU-slave for FISH-protokollen
+Description[nds]=En In-/Utgaavdeenst för dat FISH-Protokoll
+Description[ne]=FISH प्रोटोकलका लागि एउटा किओस्लेभ
+Description[nl]=Een tdeioslave voor het protocol FISH
+Description[nn]=Ein IU-slave for FISH-protokollen
+Description[pa]=FISH ਪਰੋਟੋਕਾਲ ਲਈ tdeioslave
+Description[pl]=Wtyczka protokołu FISH
+Description[pt]=Um 'tdeioslave' para o protocolo FISH
+Description[pt_BR]=Uma implementação para o protocolo FISH
+Description[ro]=Un dispozitiv de I/E pentru protocolul FISH
+Description[ru]=Модуль файловой системы для протокола FISH
+Description[rw]=tdeio-umugaragu ya Porotokole FISH
+Description[se]=SO-šláva FISH-protokolla várás
+Description[sk]=tdeioslave pre protokol FISH
+Description[sl]=tdeioslave za protokol FISH
+Description[sr]=Kioslave за протокол FISH
+Description[sr@Latn]=Kioslave za protokol FISH
+Description[sv]=En I/O-slav för protokollet FISH
+Description[ta]=FISH நெறிமுறைக்கான ஒரு க்யோஸ்லேவ்
+Description[te]=ఫిష్ ప్రొటొకాల్ కొరకు ఐఒ బానిస
+Description[th]=ตัวนำข้อมูลเข้า-ออกสำหรับโปรโตคอล FISH
+Description[tr]=FISH protokolü için tdeioslave
+Description[tt]=FISH protokolı öçen birem sistemeneñ modulı
+Description[uk]=Підлеглий B/В для протоколу FISH
+Description[uz]=FISH protokoli uchun KCH-sleyv
+Description[uz@cyrillic]=FISH протоколи учун КЧ-слейв
+Description[vi]=A tdeioslave (đày tớ vào ra TDE) cho giao thức FISH
+Description[wa]=On tdeioslave pol protocole FISH
+Description[zh_CN]=FISH 协议的 TDEIO 仆人
+Description[zh_TW]=用於 FISH 通訊協定的 tdeioslave
+DocPath=tdeioslave/fish/index.html
diff --git a/tdeioslave/fish/genfishcode.cmake b/tdeioslave/fish/genfishcode.cmake
new file mode 100644
index 000000000..9b35a51ec
--- /dev/null
+++ b/tdeioslave/fish/genfishcode.cmake
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+SUM=$( @MD5SUM@ @CMAKE_CURRENT_SOURCE_DIR@/fish.pl | cut -d ' ' @MD5SUM_CUT@ )
+
+#echo "#define CHECKSUM "\"$SUM\"" > fishcode.h
+#echo 'static const char *fishCode(' >> fishcode.h
+#sed -e 's/\\/\\\\/g;s/"/\\"/g;s/^[ ]*/"/;/^"# /d;s/[ ]*$$/\\n"/;/^"\\n"$$/d;s/{CHECKSUM}/'$$SUM'/;' @CMAKE_CURRENT_SOURCE_DIR@/fish.pl >> fishcode.h
+#echo ');' >> fishcode.h
diff --git a/tdeioslave/fish/genfishcode.pl b/tdeioslave/fish/genfishcode.pl
new file mode 100755
index 000000000..60dfff8de
--- /dev/null
+++ b/tdeioslave/fish/genfishcode.pl
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Digest::MD5;
+
+sub md5sum {
+ my $filename = shift;
+ my $digest;
+ eval {
+ open( my $FILE, '<', $filename )
+ or die "Can't find file $filename\n";
+ my $ctx = Digest::MD5->new;
+ $ctx->addfile($FILE);
+ $digest = $ctx->hexdigest;
+ close($FILE);
+ };
+ if ($@) {
+ warn $@;
+ }
+ return $digest;
+}
+
+my $file = $ARGV[0] or die "Missing filename argument";
+
+my $fish_md5 = md5sum($file)
+ or die "Couldn't compute MD5 for some reason\n";
+print qq{#define CHECKSUM "$fish_md5"\n};
+print qq{static const char *fishCode(\n};
+
+open( my $FISH, "<", "$file" ) or die "Can't open $file\n";
+while (<$FISH>) {
+ chomp;
+ s|\\|\\\\|g;
+ s|"|\\"|g;
+ s/^\s*/"/;
+ next if /^"# /;
+ s/\s*$/\\n"/;
+ next if /^"\\n"$/;
+ print "$_\n";
+}
+close($FISH);
+print qq{);\n};
diff --git a/tdeioslave/fish/nxfish.protocol b/tdeioslave/fish/nxfish.protocol
new file mode 100644
index 000000000..ce7129caa
--- /dev/null
+++ b/tdeioslave/fish/nxfish.protocol
@@ -0,0 +1,74 @@
+[Protocol]
+exec=tdeio_fish
+protocol=nxfish
+input=none
+output=filesystem
+listing=Name,Type,Size,Date,Access,Owner,Group,Link,
+reading=true
+writing=true
+makedir=true
+deleting=true
+linking=true
+moving=true
+Icon=remote
+Description=A tdeioslave for the NXFISH protocol
+Description[af]='n Kioslave vir die NXFISH protokol
+Description[be]=Kioslave для пратакола NXFISH
+Description[br]=Ur c'hioslave evit ar c'homenad NXFISH
+Description[bs]=tdeioslave za NXFISH protokol
+Description[ca]=Un tdeioslave pel protocol NXFISH
+Description[cs]=Pomocný protokol pro NXFISH
+Description[csb]=Plugins protokòłu NXFISH
+Description[da]=En tdeioslave for NXFISH-protokollen
+Description[de]=Ein-/Ausgabemodul für das NXFISH-Protokoll
+Description[el]=Ένας tdeioslave για το πρωτόκολλο NXFISH
+Description[eo]=K-enel-sklavo por la NXFISH protokolo
+Description[es]=Un tdeioslave para el protocolo NXFISH
+Description[et]=NXFISH protokolli IO-moodul
+Description[eu]=NXFISH protokolorako tdeioslavea
+Description[fa]=یک tdeioslave برای قرارداد NXFISH
+Description[fi]=Liitäntä NXFISH-yhteyskäytäntö
+Description[fr]=Un module d'entrées / sorties pour le protocole NXFISH
+Description[fy]=In tdeioslave foar it protokol NXFISH
+Description[ga]=tdeioslave le haghaidh an phrótacail NXFISH
+Description[gl]=Un tdeioslave para o protocolo NXFISH
+Description[he]=ממשק tdeioslave עבור פרוטוקול NXFISH
+Description[hr]=Kioslave za NXFISH protokol
+Description[hu]=TDE-protokoll az NXFISH protokollhoz
+Description[is]=tdeioslave fyrir NXFISH samskiptaregluna
+Description[it]=Un tdeioslave per il protocollo NXFISH
+Description[ja]=NXFISH プロトコルのための tdeioslave
+Description[ka]=tdeioslave NXFISH ოქმისთვის
+Description[kk]=NXFISH протоколы үшін tdeioslave
+Description[km]=tdeioslave សម្រាប់​ពិធីការ NXFISH
+Description[ko]=FISH 프로토콜을 위한 TDEIO 슬레이브
+Description[lt]=Pagalbinė tdeio programa NXFISH protokolui
+Description[mk]=КИО-служител за протоколот NXFISH
+Description[nb]=En kioskslave for NXFISH-protokollen
+Description[nds]=En In-/Utgaavdeenst för dat NXFISH-Protokoll
+Description[ne]=NXFISH प्रोटोकलका लागि किओस्लेभ
+Description[nl]=Een tdeioslave voor het protocol NXFISH
+Description[nn]=Ein IU-slave for NXFISH-protokollen
+Description[pa]=NXFISH ਪਰੋਟੋਕਾਲ ਲਈ tdeioslave
+Description[pl]=Wtyczka protokołu NXFISH
+Description[pt]=Um 'tdeioslave' para o protocolo NXFISH
+Description[pt_BR]=Uma implementação para o protocolo NXFISH
+Description[ro]=Un tdeioslave pentru protocolul NXFISH
+Description[ru]=Модуль файловой системы для протокола NXFISH
+Description[se]=SO-šláva NXFISH-protokolla várás
+Description[sk]=tdeioslave pre protokol NXFISH
+Description[sl]=tdeioslave za protokol NXFISH
+Description[sr]=Kioslave за протокол NXFISH
+Description[sr@Latn]=Kioslave za protokol NXFISH
+Description[sv]=En I/O-slav för protokollet NXFISH
+Description[te]=ఎన్ ఎక్స్ ఫిష్ ప్రొటొకాల్ కొరకు ఐఒ బానిస
+Description[th]=ตัวนำข้อมูลเข้า-ออกสำหรับโปรโตคอล NXFISH
+Description[tr]=NXFISH protokolü için tdeioslave
+Description[uk]=Підлеглий B/В для протоколу NXFISH
+Description[uz]=NXFISH protokoli uchun KCH-sleyvi
+Description[uz@cyrillic]=NXFISH протоколи учун КЧ-слейви
+Description[vi]=A tdeioslave (đày tớ vào ra TDE) cho giao thức NXFISH
+Description[wa]=On tdeioslave pol protocole NXFISH
+Description[zh_CN]=NXFISH 协议的 TDEIO 仆人
+Description[zh_TW]=用於 NXFISH 通訊協定的 tdeioslave
+DocPath=tdeioslave/nxfish/index.html