diff options
Diffstat (limited to 'kioslave/fish')
-rw-r--r-- | kioslave/fish/AUTHORS | 1 | ||||
-rw-r--r-- | kioslave/fish/COPYING | 340 | ||||
-rw-r--r-- | kioslave/fish/ChangeLog | 71 | ||||
-rw-r--r-- | kioslave/fish/FAQ | 37 | ||||
-rw-r--r-- | kioslave/fish/INSTALL | 167 | ||||
-rw-r--r-- | kioslave/fish/Makefile.am | 33 | ||||
-rw-r--r-- | kioslave/fish/README | 258 | ||||
-rw-r--r-- | kioslave/fish/TODO | 10 | ||||
-rw-r--r-- | kioslave/fish/configure.in.in | 9 | ||||
-rw-r--r-- | kioslave/fish/fish.cpp | 1661 | ||||
-rw-r--r-- | kioslave/fish/fish.h | 211 | ||||
-rwxr-xr-x | kioslave/fish/fish.pl | 369 | ||||
-rw-r--r-- | kioslave/fish/fish.protocol | 81 | ||||
-rw-r--r-- | kioslave/fish/nxfish.protocol | 74 |
14 files changed, 3322 insertions, 0 deletions
diff --git a/kioslave/fish/AUTHORS b/kioslave/fish/AUTHORS new file mode 100644 index 000000000..fc4f1567e --- /dev/null +++ b/kioslave/fish/AUTHORS @@ -0,0 +1 @@ +Jrg Walter <trouble@garni.ch> diff --git a/kioslave/fish/COPYING b/kioslave/fish/COPYING new file mode 100644 index 000000000..2d08eab44 --- /dev/null +++ b/kioslave/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/kioslave/fish/ChangeLog b/kioslave/fish/ChangeLog new file mode 100644 index 000000000..7f621f3d4 --- /dev/null +++ b/kioslave/fish/ChangeLog @@ -0,0 +1,71 @@ +1.2.3 by Willy De la Court <willy.delacourt@pandora.be> + Changes in the EXEC code as Jrg Walter proposed. + fixed 2 bugs when executing in Shell mode + +1.2.2 by Willy De la Court <willy.delacourt@pandora.be> + Security fix tempfile should not be world readable + bugfix write to the file not the command + +1.2.1 by Willy De la Court <willy.delacourt@pandora.be> + 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 <willy.delacourt@pandora.be> + 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 <willy.delacourt@pandora.be> + 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/kioslave/fish/FAQ b/kioslave/fish/FAQ new file mode 100644 index 000000000..dce0aef41 --- /dev/null +++ b/kioslave/fish/FAQ @@ -0,0 +1,37 @@ +Freqeuently Asked Questions, last updated for kio_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/kioslave/fish/INSTALL b/kioslave/fish/INSTALL new file mode 100644 index 000000000..02a4a0740 --- /dev/null +++ b/kioslave/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/kioslave/fish/Makefile.am b/kioslave/fish/Makefile.am new file mode 100644 index 000000000..27308245b --- /dev/null +++ b/kioslave/fish/Makefile.am @@ -0,0 +1,33 @@ +kde_module_LTLIBRARIES = kio_fish.la + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +kio_fish_la_SOURCES = fish.cpp +kio_fish_la_LIBADD = $(LIB_KSYCOCA) #$(LIBUTIL) +kio_fish_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +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)/kio_fish.pot + + + diff --git a/kioslave/fish/README b/kioslave/fish/README new file mode 100644 index 000000000..d1afdc3d1 --- /dev/null +++ b/kioslave/fish/README @@ -0,0 +1,258 @@ +Overview of kio_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 <pavel@bug.ucw.cz> 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 Jrg Walter <trouble@garni.ch> + + + +From: Pavel Machek <pavel@bug.ucw.cz> +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 + +cd /#sh:user@host.to.connect.to/ + +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! + pavel@ucw.cz + + +-- +I'm really pavel@atrey.karlin.mff.cuni.cz. Pavel +Look at http://atrey.karlin.mff.cuni.cz/~pavel/ ;-). diff --git a/kioslave/fish/TODO b/kioslave/fish/TODO new file mode 100644 index 000000000..ba3bf69bb --- /dev/null +++ b/kioslave/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/kioslave/fish/configure.in.in b/kioslave/fish/configure.in.in new file mode 100644 index 000000000..086dc0dae --- /dev/null +++ b/kioslave/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/kioslave/fish/fish.cpp b/kioslave/fish/fish.cpp new file mode 100644 index 000000000..3967bcd6b --- /dev/null +++ b/kioslave/fish/fish.cpp @@ -0,0 +1,1661 @@ +/*************************************************************************** + fish.cpp - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001-2003 by J�rg Walter + email : jwalt-kde@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 kioslave + done by David Faure <faure@kde.org>. + + Structure is a bit complicated, since I made the mistake to use + KProcess... now there is a lightweight homebrew async IO system + inside, but if signals/slots become available for ioslaves, switching + back to KProcess should be easy. +*/ + +#include "config.h" + +#include <qcstring.h> +#include <qfile.h> +#include <qsocket.h> +#include <qdatetime.h> +#include <qbitarray.h> +#include <qregexp.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 <kmessagebox.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.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 KIO; +extern "C" { + +static void ripper(int) +{ + while (waitpid(-1,0,WNOHANG) > 0) { + // do nothing, go on + } +} + +int KDE_EXPORT kdemain( int argc, char **argv ) +{ + KLocale::setMainCatalogue("kio_fish"); + KInstance 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 QCString &pool_socket, const QCString &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(QFile::encodeName(KStandardDirs::findExe("nxfish"))); + else + sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("ssh"))); + } + if (suPath == NULL) { + suPath = strdup(QFile::encodeName(KStandardDirs::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 = QString::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( KIO::ERR_UNKNOWN_HOST, QString::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 = QString::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", QString::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, KIO::fileoffset_t len) { + if (outBufPos >= 0 && outBuf) { +#if 0 + QString 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, KIO::fileoffset_t len) { + QString 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++; + QString str = buf.left(pos); + buf = buf.mid(pos); + if (str == "\n") + continue; + if (str == "FISH:\n") { + thisFn = QString::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 = QString::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 = QString::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 = QString::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 = QString::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 QString & host, int port, const QString & u, const QString & pass){ + QString 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); + QString realCmd = info.command; + QString realAlt = info.alt; + static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); + for (int i = 0; i < info.params; i++) { + QString arg(va_arg(list, const char *)); + int pos = -2; + while ((pos = rx.search(arg,pos+2)) >= 0) { + arg.replace(pos,0,QString("\\")); + } + //myDebug( << "arg " << i << ": " << arg << endl); + realCmd.append(" ").append(arg); + realAlt.replace(QRegExp("%"+QString::number(i+1)),arg); + } + QString 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 QString &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 QString &monthStr, const QString &dayStr, const QString &timeyearStr) +{ + QDateTime dt(QDate::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 QString &l) { + QString line(l); + int rc = handleResponse(line); + UDSAtom atom; + QDateTime dt; + KIO::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(QDate(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(QTime(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': { + QString 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 = QString::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 = KIO::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(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + sendLen = rawData.size(); + } else if (fishCommand == FISH_WRITE) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + putPos += rawData.size(); + sendLen = rawData.size(); + } else if (fishCommand == FISH_RETR) { + data(QByteArray()); + } + finished(); + } +} + +void fishProtocol::writeStdin(const QString &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, KIO::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 = QMIN(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 + } + + QByteArray 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) + { + QString s = remoteEncoding()->decode(QCString(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 QString &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 + QString 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: " << QString::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(QString::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 QString& 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(QString::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(QString::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 QByteArray &data ){ + int tmp; + + QDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case FISH_EXEC_CMD: // SSH EXEC + { + KURL u; + QString command; + QString 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,QString().setNum(tmp)); + break; + } +} +/** report status */ +void fishProtocol::slave_status() { + myDebug( << "@@@@@@@@@ slave_status" << endl); + if (childPid > 0) + slaveStatus(connectionHost,isLoggedIn); + else + slaveStatus(QString::null,false); +} diff --git a/kioslave/fish/fish.h b/kioslave/fish/fish.h new file mode 100644 index 000000000..e2665a320 --- /dev/null +++ b/kioslave/fish/fish.h @@ -0,0 +1,211 @@ +/*************************************************************************** + fish.h - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001 by Jrg Walter + email : trouble@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qstring.h> +#include <qcstring.h> + + +#include <kurl.h> +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kprocess.h> +#include <kio/authinfo.h> +#include <time.h> + +#define FISH_EXEC_CMD 'X' + +class fishProtocol : public KIO::SlaveBase +{ +public: + fishProtocol(const QCString &pool_socket, const QCString &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 QString & host, int port, const QString & user, const QString & 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 QString &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 QString& 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 QByteArray &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 */ + KIO::fileoffset_t outBufPos; + /** length of buffer */ + KIO::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 */ + QString redirectUser, redirectPass; + +protected: // Protected attributes + /** for LIST/STAT */ + KIO::UDSEntry udsEntry; + /** for LIST/STAT */ + KIO::UDSEntry udsStatEntry; + /** for LIST/STAT */ + KIO::UDSAtom typeAtom; + /** for LIST/STAT */ + KIO::UDSAtom mimeAtom; + /** for LIST/STAT */ + QString thisFn; + /** for STAT */ + QString wantedFn; + QString statPath; + /** url of current request */ + KURL url; + /** true if connection is logged in successfully */ + bool isLoggedIn; + /** host name of current connection */ + QString connectionHost; + /** user name of current connection */ + QString connectionUser; + /** port of current connection */ + int connectionPort; + /** password of current connection */ + QString connectionPassword; + /** AuthInfo object used for logging in */ + KIO::AuthInfo connectionAuth; + /** number of lines received, == 0 -> everything went ok */ + int errorCount; + /** queue for lines to be sent */ + QStringList qlist; + /** queue for commands to be sent */ + QStringList commandList; + /** queue for commands to be sent */ + QValueList<int> commandCodes; + /** bytes still to be read in raw mode */ + KIO::fileoffset_t rawRead; + /** bytes still to be written in raw mode */ + KIO::fileoffset_t rawWrite; + /** data bytes to read in next read command */ + KIO::fileoffset_t recvLen; + /** data bytes to write in next write command */ + KIO::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 */ + KIO::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 */ + QByteArray rawData; + /** buffer for storing bytes used for MimeMagic */ + QByteArray mimeBuffer; + /** whther the mimetype has been sent already */ + bool mimeTypeSent; + /** number of bytes read so far */ + KIO::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, KIO::fileoffset_t buflen); + int received(const char *buffer, KIO::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 QString &str); + /** parses a ls -l time spec */ + int makeTimeFromLs(const QString &dayStr, const QString &monthStr, const QString &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, KIO::fileoffset_t len); + /** parses response from server and acts accordingly */ + void manageConnection(const QString &line); + /** writes to process */ + void writeStdin(const QString &line); +}; + + +#endif diff --git a/kioslave/fish/fish.pl b/kioslave/fish/fish.pl new file mode 100755 index 000000000..1ba539f9f --- /dev/null +++ b/kioslave/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 kio_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 kio_fish access faster and +more reliable. +=cut + +use Fcntl; + +$|++; +#open(DEBUG,">/tmp/kio_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/kioslave/fish/fish.protocol b/kioslave/fish/fish.protocol new file mode 100644 index 000000000..c14599d50 --- /dev/null +++ b/kioslave/fish/fish.protocol @@ -0,0 +1,81 @@ +[Protocol] +exec=kio_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 kioslave for the FISH protocol +Description[af]='n Kioslave vir die FISH protokol +Description[be]=Kioslave для пратакола FISH +Description[bn]=ফিশ (FISH) প্রোটোকল-এর জন্য একটি kioslave +Description[br]=Ur c'hioslave evit ar c'homenad FISH +Description[bs]=kioslave za FISH protokol +Description[ca]=Un kioslave pel protocol FISH +Description[cs]=Pomocný protokol pro FISH +Description[csb]=Plugins protokòłu FISH +Description[da]=En kioslave for FISH-protokollen +Description[de]=Ein-/Ausgabemodul für das FISH-Protokoll +Description[el]=Ένας kioslave για το πρωτόκολλο FISH +Description[eo]=K-enel-sklavo por la FISH protokolo +Description[es]=Un kioslave para el protocolo FISH +Description[et]=FISH protokolli IO-moodul +Description[eu]=FISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد FISH +Description[fi]=Liitäntä FISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole FISH +Description[fy]=In kioslave foar it FISH protokol +Description[ga]=kioslave le haghaidh an phrótacail FISH +Description[gl]=Un kioslave para o protocolo FISH +Description[he]=ממשק kioslave עבור פרוטוקול FISH +Description[hi]=फिश प्रोटोकॉल हेतु के-आई-ओ-स्लेव +Description[hr]=Kioslave za FISH protokol +Description[hu]=KDE-protokoll a FISH protokollhoz +Description[is]=kioslave fyrir FISH samskiptaregluna +Description[it]=Un kioslave per il protocollo FISH +Description[ja]=FISH プロトコルのための kioslave +Description[ka]=kioslave FISH პროტოკოლისთვის +Description[kk]=FISH протоколға арналған файл жүйесінің модулі +Description[km]=kioslave សម្រាប់ពិធីការ FISH +Description[ko]=FISH 프로토콜을 위한 KIO 슬레이브 +Description[lt]=Kiovergas FISH protokolui +Description[lv]=KIO vergs FISH protokolam +Description[mk]=КИО-служител за протоколот FISH +Description[ms]=Hamba kio 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 kioslave voor het protocol FISH +Description[nn]=Ein IU-slave for FISH-protokollen +Description[pa]=FISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokołu FISH +Description[pt]=Um 'kioslave' 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]=kio-umugaragu ya Porotokole FISH +Description[se]=SO-šláva FISH-protokolla várás +Description[sk]=kioslave pre protokol FISH +Description[sl]=kioslave 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 kioslave +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 kioslave (đày tớ vào ra KDE) cho giao thức FISH +Description[wa]=On kioslave pol protocole FISH +Description[zh_CN]=FISH 协议的 KIO 仆人 +Description[zh_TW]=用於 FISH 通訊協定的 kioslave +DocPath=kioslave/fish.html diff --git a/kioslave/fish/nxfish.protocol b/kioslave/fish/nxfish.protocol new file mode 100644 index 000000000..f050282af --- /dev/null +++ b/kioslave/fish/nxfish.protocol @@ -0,0 +1,74 @@ +[Protocol] +exec=kio_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 kioslave 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]=kioslave za NXFISH protokol +Description[ca]=Un kioslave pel protocol NXFISH +Description[cs]=Pomocný protokol pro NXFISH +Description[csb]=Plugins protokòłu NXFISH +Description[da]=En kioslave for NXFISH-protokollen +Description[de]=Ein-/Ausgabemodul für das NXFISH-Protokoll +Description[el]=Ένας kioslave για το πρωτόκολλο NXFISH +Description[eo]=K-enel-sklavo por la NXFISH protokolo +Description[es]=Un kioslave para el protocolo NXFISH +Description[et]=NXFISH protokolli IO-moodul +Description[eu]=NXFISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد NXFISH +Description[fi]=Liitäntä NXFISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole NXFISH +Description[fy]=In kioslave foar it protokol NXFISH +Description[ga]=kioslave le haghaidh an phrótacail NXFISH +Description[gl]=Un kioslave para o protocolo NXFISH +Description[he]=ממשק kioslave עבור פרוטוקול NXFISH +Description[hr]=Kioslave za NXFISH protokol +Description[hu]=KDE-protokoll az NXFISH protokollhoz +Description[is]=kioslave fyrir NXFISH samskiptaregluna +Description[it]=Un kioslave per il protocollo NXFISH +Description[ja]=NXFISH プロトコルのための kioslave +Description[ka]=kioslave NXFISH ოქმისთვის +Description[kk]=NXFISH протоколы үшін kioslave +Description[km]=kioslave សម្រាប់ពិធីការ NXFISH +Description[ko]=FISH 프로토콜을 위한 KIO 슬레이브 +Description[lt]=Pagalbinė kio 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 kioslave voor het protocol NXFISH +Description[nn]=Ein IU-slave for NXFISH-protokollen +Description[pa]=NXFISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokołu NXFISH +Description[pt]=Um 'kioslave' para o protocolo NXFISH +Description[pt_BR]=Uma implementação para o protocolo NXFISH +Description[ro]=Un kioslave pentru protocolul NXFISH +Description[ru]=Модуль файловой системы для протокола NXFISH +Description[se]=SO-šláva NXFISH-protokolla várás +Description[sk]=kioslave pre protokol NXFISH +Description[sl]=kioslave 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 kioslave +Description[uk]=Підлеглий B/В для протоколу NXFISH +Description[uz]=NXFISH protokoli uchun KCH-sleyvi +Description[uz@cyrillic]=NXFISH протоколи учун КЧ-слейви +Description[vi]=A kioslave (đày tớ vào ra KDE) cho giao thức NXFISH +Description[wa]=On kioslave pol protocole NXFISH +Description[zh_CN]=NXFISH 协议的 KIO 仆人 +Description[zh_TW]=用於 NXFISH 通訊協定的 kioslave +DocPath=kioslave/fish.html |