File indexing completed on 2024-05-05 04:59:31
0001 /* 0002 fish.cpp - a FISH KIO worker 0003 SPDX-FileCopyrightText: 2001 Jörg Walter <trouble@garni.ch> 0004 SPDX-License-Identifier: GPL-2.0-only 0005 */ 0006 0007 /* 0008 This code contains fragments and ideas from the ftp KIO worker 0009 done by David Faure <faure@kde.org>. 0010 0011 Structure is a bit complicated, since I made the mistake to use 0012 KProcess... now there is a lightweight homebrew async IO system 0013 inside, but if signals/slots become available for KIO workers, switching 0014 back to KProcess should be easy. 0015 */ 0016 0017 #include "fish.h" 0018 0019 #include <config-fish.h> 0020 #include <config-runtime.h> 0021 0022 #include <QCoreApplication> 0023 #include <QDataStream> 0024 #include <QDateTime> 0025 #include <QDebug> 0026 #include <QFile> 0027 #include <QMimeDatabase> 0028 #include <QMimeType> 0029 #include <QRegularExpression> 0030 #include <QStandardPaths> 0031 0032 #include <stdlib.h> 0033 #include <sys/resource.h> 0034 #ifdef HAVE_PTY_H 0035 #include <pty.h> 0036 #endif 0037 0038 #ifdef HAVE_TERMIOS_H 0039 #include <termios.h> 0040 #endif 0041 0042 #ifdef HAVE_SYS_IOCTL_H 0043 #include <sys/ioctl.h> 0044 #endif 0045 0046 #ifdef HAVE_LIBUTIL_H 0047 #include <libutil.h> 0048 #endif 0049 0050 #ifdef HAVE_UTIL_H 0051 #include <util.h> 0052 #endif 0053 0054 #ifndef Q_OS_WIN 0055 #include <errno.h> 0056 #endif 0057 0058 #include <KLocalizedString> 0059 #include <KRemoteEncoding> 0060 0061 #include "fishcode.h" 0062 #include "loggingcategory.h" 0063 0064 #ifndef NDEBUG 0065 #define myDebug(x) qCDebug(KIO_FISH_DEBUG) << __LINE__ << ": " x 0066 #define dataReq() \ 0067 do { \ 0068 myDebug(<< "_______ emitting dataReq()"); \ 0069 dataReq(); \ 0070 } while (0) 0071 #define needSubURLData() \ 0072 do { \ 0073 myDebug(<< "_______ emitting needSubURLData()"); \ 0074 needSubURLData(); \ 0075 } while (0) 0076 #define workerStatus(x, y) \ 0077 do { \ 0078 myDebug(<< "_______ emitting workerStatus(" << x << ", " << y << ")"); \ 0079 workerStatus(x, y); \ 0080 } while (0) 0081 #define statEntry(x) \ 0082 do { \ 0083 myDebug(<< "_______ emitting statEntry(" << x.count() << ")"); \ 0084 statEntry(x); \ 0085 } while (0) 0086 #define listEntries(x) \ 0087 do { \ 0088 myDebug(<< "_______ emitting listEntries(...)"); \ 0089 listEntries(x); \ 0090 } while (0) 0091 #define canResume(x) \ 0092 do { \ 0093 myDebug(<< "_______ emitting canResume(" << (int)x << ")"); \ 0094 canResume(x); \ 0095 } while (0) 0096 #define totalSize(x) \ 0097 do { \ 0098 myDebug(<< "_______ emitting totalSize(" << (int)x << ")"); \ 0099 totalSize(x); \ 0100 } while (0) 0101 #define processedSize(x) \ 0102 do { \ 0103 myDebug(<< "_______ emitting processedSize(" << x << ")"); \ 0104 processedSize(x); \ 0105 } while (0) 0106 #define speed(x) \ 0107 do { \ 0108 myDebug(<< "_______ emitting speed(" << (int)x << ")"); \ 0109 speed(x); \ 0110 } while (0) 0111 #define redirection(x) \ 0112 do { \ 0113 myDebug(<< "_______ emitting redirection(" << x << ")"); \ 0114 redirection(x); \ 0115 } while (0) 0116 #define errorPage() \ 0117 do { \ 0118 myDebug(<< "_______ emitting errorPage()"); \ 0119 errorPage(); \ 0120 } while (0) 0121 #define sendmimeType(x) \ 0122 do { \ 0123 myDebug(<< "_______ emitting mimeType(" << x << ")"); \ 0124 mimeType(x); \ 0125 } while (0) 0126 #define warning(x) \ 0127 do { \ 0128 myDebug(<< "_______ emitting warning(" << x << ")"); \ 0129 warning(x); \ 0130 } while (0) 0131 #define infoMessage(x) \ 0132 do { \ 0133 myDebug(<< "_______ emitting infoMessage(" << x << ")"); \ 0134 infoMessage(x); \ 0135 } while (0) 0136 #else 0137 #define myDebug(x) 0138 #define sendmimeType(x) mimeType(x) 0139 #endif 0140 0141 #ifdef Q_OS_WIN 0142 #define ENDLINE "\r\n" 0143 #else 0144 #define ENDLINE '\n' 0145 #endif 0146 0147 static char *sshPath = nullptr; 0148 static char *suPath = nullptr; 0149 // disabled: currently not needed. Didn't work reliably. 0150 // static int isOpenSSH = 0; 0151 0152 /** the SSH process used to communicate with the remote end */ 0153 #ifndef Q_OS_WIN 0154 static pid_t childPid; 0155 #else 0156 static KProcess *childPid = 0; 0157 #endif 0158 0159 #define E(x) ((const char *)remoteEncoding()->encode(x).data()) 0160 0161 // Pseudo plugin class to embed meta data 0162 class KIOPluginForMetaData : public QObject 0163 { 0164 Q_OBJECT 0165 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.fish" FILE "fish.json") 0166 }; 0167 0168 using namespace KIO; 0169 extern "C" { 0170 0171 int Q_DECL_EXPORT kdemain(int argc, char **argv) 0172 { 0173 QCoreApplication app(argc, argv); 0174 app.setApplicationName("kio_fish"); 0175 0176 myDebug(<< "*** Starting fish "); 0177 if (argc != 4) { 0178 myDebug(<< "Usage: kio_fish protocol domain-socket1 domain-socket2"); 0179 exit(-1); 0180 } 0181 0182 setenv("TZ", "UTC", true); 0183 0184 fishProtocol worker(argv[2], argv[3]); 0185 worker.dispatchLoop(); 0186 0187 myDebug(<< "*** fish Done"); 0188 return 0; 0189 } 0190 } 0191 0192 const struct fishProtocol::fish_info fishProtocol::fishInfo[] = { 0193 {("FISH"), 0194 0, 0195 ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM 0196 " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' " 0197 "2>/dev/null;"), 0198 1}, 0199 {("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}, 0200 {("PWD"), 0, ("pwd"), 1}, 0201 {("LIST"), 0202 1, 0203 ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`;" 0204 "echo '### 100';" 0205 "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,/.*[ " 0206 "\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 " 0207 "echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" 0208 "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; )"), 0209 0}, 0210 {("STAT"), 0211 1, 0212 ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`;" 0213 "echo '### 100';" 0214 "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,/.*[ " 0215 "\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 " 0216 "echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" 0217 "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; )"), 0218 0}, 0219 {("RETR"), 1, ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"), 1}, 0220 {("STOR"), 0221 2, 0222 ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;" 0223 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"), 0224 0}, 0225 {("CWD"), 1, ("cd %1"), 0}, 0226 {("CHMOD"), 2, ("chmod %1 %2"), 0}, 0227 {("DELE"), 1, ("rm -f %1"), 0}, 0228 {("MKD"), 1, ("mkdir %1"), 0}, 0229 {("RMD"), 1, ("rmdir %1"), 0}, 0230 {("RENAME"), 2, ("mv -f %1 %2"), 0}, 0231 {("LINK"), 2, ("ln -f %1 %2"), 0}, 0232 {("SYMLINK"), 2, ("ln -sf %1 %2"), 0}, 0233 {("CHOWN"), 2, ("chown %1 %2"), 0}, 0234 {("CHGRP"), 2, ("chgrp %1 %2"), 0}, 0235 {("READ"), 0236 3, 0237 ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;" 0238 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;" 0239 "dd bs=%2 count=1; ) 2>/dev/null;"), 0240 0}, 0241 // Yes, this is "ibs=1", since dd "count" is input blocks. 0242 // On network connections, read() may not fill the buffer 0243 // completely (no more data immediately available), but dd 0244 // does ignore that fact by design. Sorry, writes are slow. 0245 // OTOH, WRITE is not used by the current KIO worker methods, 0246 // we use APPEND. 0247 {("WRITE"), 0248 3, 0249 (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | " 0250 "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"), 0251 0}, 0252 {("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}, 0253 {("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}, 0254 {("EXEC"), 2, ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"), 0}}; 0255 0256 fishProtocol::fishProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) 0257 : WorkerBase("fish", pool_socket, app_socket) 0258 , mimeBuffer(1024, '\0') 0259 , mimeTypeSent(false) 0260 { 0261 myDebug(<< "fishProtocol::fishProtocol()"); 0262 if (sshPath == nullptr) { 0263 // disabled: currently not needed. Didn't work reliably. 0264 // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null"); 0265 #ifdef Q_OS_WIN 0266 sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("plink")).constData()); 0267 #else 0268 sshPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("ssh")).constData()); 0269 #endif 0270 } 0271 if (suPath == nullptr) { 0272 suPath = strdup(QFile::encodeName(QStandardPaths::findExecutable("su")).constData()); 0273 } 0274 childPid = 0; 0275 connectionPort = 0; 0276 isLoggedIn = false; 0277 writeReady = true; 0278 isRunning = false; 0279 firstLogin = true; 0280 errorCount = 0; 0281 rawRead = 0; 0282 rawWrite = -1; 0283 recvLen = -1; 0284 sendLen = -1; 0285 connectionAuth.keepPassword = true; 0286 connectionAuth.url.setScheme("fish"); 0287 outBufPos = -1; 0288 outBuf = QByteArray(); 0289 0290 udsType = 0; 0291 0292 hasAppend = false; 0293 0294 isStat = false; // FIXME: just a workaround for konq deficiencies 0295 redirectUser = ""; // FIXME: just a workaround for konq deficiencies 0296 redirectPass = ""; // FIXME: just a workaround for konq deficiencies 0297 fishCodeLen = strlen(fishCode); 0298 } 0299 /* ---------------------------------------------------------------------------------- */ 0300 0301 fishProtocol::~fishProtocol() 0302 { 0303 myDebug(<< "fishProtocol::~fishProtocol()"); 0304 shutdownConnection(true); 0305 } 0306 0307 /* --------------------------------------------------------------------------- */ 0308 0309 /** 0310 Connects to a server and logs us in via SSH. Then starts FISH protocol. 0311 */ 0312 KIO::WorkerResult fishProtocol::openConnection() 0313 { 0314 if (childPid) 0315 return KIO::WorkerResult::pass(); 0316 0317 if (connectionHost.isEmpty()) { 0318 return error(KIO::ERR_UNKNOWN_HOST, QString()); 0319 } 0320 0321 infoMessage(i18n("Connecting...")); 0322 0323 myDebug(<< "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort); 0324 sendCommand(FISH_FISH); 0325 sendCommand(FISH_VER); 0326 const auto result = connectionStart(); 0327 if (!result.success()) { 0328 shutdownConnection(); 0329 return result; 0330 }; 0331 myDebug(<< "subprocess is running"); 0332 return result; 0333 } 0334 0335 // XXX Use KPty! XXX 0336 #ifndef Q_OS_WIN 0337 static int open_pty_pair(int fd[2]) 0338 { 0339 #if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY) 0340 /** with kind regards to The GNU C Library 0341 Reference Manual for Version 2.2.x of the GNU C Library */ 0342 int master, slave; 0343 char *name; 0344 struct ::termios ti; 0345 memset(&ti, 0, sizeof(ti)); 0346 0347 ti.c_cflag = CLOCAL | CREAD | CS8; 0348 ti.c_cc[VMIN] = 1; 0349 0350 #ifdef HAVE_GETPT 0351 master = getpt(); 0352 #else 0353 master = open("/dev/ptmx", O_RDWR); 0354 #endif 0355 if (master < 0) 0356 return 0; 0357 0358 if (grantpt(master) < 0 || unlockpt(master) < 0) 0359 goto close_master; 0360 0361 name = ptsname(master); 0362 if (name == NULL) 0363 goto close_master; 0364 0365 slave = open(name, O_RDWR); 0366 if (slave == -1) 0367 goto close_master; 0368 0369 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) 0370 if (isastream(slave) && (ioctl(slave, I_PUSH, "ptem") < 0 || ioctl(slave, I_PUSH, "ldterm") < 0)) 0371 goto close_slave; 0372 #endif 0373 0374 tcsetattr(slave, TCSANOW, &ti); 0375 fd[0] = master; 0376 fd[1] = slave; 0377 return 0; 0378 0379 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) 0380 close_slave: 0381 #endif 0382 close(slave); 0383 0384 close_master: 0385 close(master); 0386 return -1; 0387 #else 0388 #ifdef HAVE_OPENPTY 0389 struct ::termios ti; 0390 memset(&ti, 0, sizeof(ti)); 0391 0392 ti.c_cflag = CLOCAL | CREAD | CS8; 0393 ti.c_cc[VMIN] = 1; 0394 0395 return openpty(fd, fd + 1, nullptr, &ti, nullptr); 0396 #else 0397 #ifdef __GNUC__ 0398 #warning "No tty support available. Password dialog won't work." 0399 #endif 0400 return socketpair(PF_UNIX, SOCK_STREAM, 0, fd); 0401 #endif 0402 #endif 0403 } 0404 #endif 0405 /** 0406 creates the subprocess 0407 */ 0408 KIO::WorkerResult fishProtocol::connectionStart() 0409 { 0410 int fd[2]; 0411 int rc, flags; 0412 thisFn.clear(); 0413 0414 #ifndef Q_OS_WIN 0415 rc = open_pty_pair(fd); 0416 if (rc == -1) { 0417 myDebug(<< "socketpair failed, error: " << strerror(errno)); 0418 return error(ERR_CANNOT_CONNECT, connectionHost); 0419 ; 0420 } 0421 #endif 0422 0423 myDebug(<< "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser); 0424 #ifdef Q_OS_WIN 0425 childPid = new KProcess(); 0426 childPid->setOutputChannelMode(KProcess::MergedChannels); 0427 QStringList common_args; 0428 common_args << "-l" << connectionUser.toLatin1().constData() << "-x" << connectionHost.toLatin1().constData(); 0429 common_args << "echo;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= " 0430 "TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\""; 0431 0432 childPid->setProgram(sshPath, common_args); 0433 childPid->start(); 0434 0435 QByteArray buf; 0436 int offset = 0; 0437 while (!isLoggedIn) { 0438 if (outBuf.size()) { 0439 rc = childPid->write(outBuf); 0440 outBuf.clear(); 0441 } else 0442 rc = 0; 0443 0444 if (rc < 0) { 0445 myDebug(<< "write failed, rc: " << rc); 0446 outBufPos = -1; 0447 // return true; 0448 } 0449 0450 if (childPid->waitForReadyRead(1000)) { 0451 QByteArray buf2 = childPid->readAll(); 0452 buf += buf2; 0453 0454 const auto establishConnectionResult = establishConnection(buf, rc + offset); 0455 if (!establishConnectionResult.result.success()) { 0456 return establishConnectionResult.result; 0457 } 0458 const int noff = establishConnectionResult.remainingBufferSize; 0459 if (noff > 0) 0460 buf = buf.mid(/*offset+*/ noff); 0461 // offset = noff; 0462 } 0463 } 0464 #else 0465 childPid = fork(); 0466 if (childPid == -1) { 0467 myDebug(<< "fork failed, error: " << strerror(errno)); 0468 ::close(fd[0]); 0469 ::close(fd[1]); 0470 childPid = 0; 0471 return error(ERR_CANNOT_CONNECT, connectionHost); 0472 ; 0473 } 0474 if (childPid == 0) { 0475 // taken from konsole, see TEPty.C for details 0476 // note: if we're running on socket pairs, 0477 // this will fail, but thats what we expect 0478 0479 for (int sig = 1; sig < NSIG; sig++) 0480 signal(sig, SIG_DFL); 0481 0482 struct rlimit rlp; 0483 getrlimit(RLIMIT_NOFILE, &rlp); 0484 for (int i = 0; i < (int)rlp.rlim_cur; i++) 0485 if (i != fd[1]) 0486 ::close(i); 0487 0488 dup2(fd[1], 0); 0489 dup2(fd[1], 1); 0490 dup2(fd[1], 2); 0491 if (fd[1] > 2) 0492 ::close(fd[1]); 0493 0494 setsid(); 0495 0496 #if defined(TIOCSCTTY) 0497 ioctl(0, TIOCSCTTY, 0); 0498 #endif 0499 0500 int pgrp = getpid(); 0501 #if defined(_AIX) || defined(__hpux) 0502 tcsetpgrp(0, pgrp); 0503 #else 0504 ioctl(0, TIOCSPGRP, (char *)&pgrp); 0505 #endif 0506 0507 const char *dev = ttyname(0); 0508 setpgid(0, 0); 0509 if (dev) 0510 ::close(::open(dev, O_WRONLY, 0)); 0511 setpgid(0, 0); 0512 0513 if (local) { 0514 execl(suPath, 0515 "su", 0516 "-", 0517 connectionUser.toLatin1().constData(), 0518 "-c", 0519 "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= " 0520 "TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", 0521 (void *)nullptr); 0522 } else { 0523 #define common_args \ 0524 "-l", connectionUser.toLatin1().constData(), "-x", "-e", "none", "-q", connectionHost.toLatin1().constData(), \ 0525 "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 " \ 0526 "LC_ALL=C LOCALE=C /bin/sh; fi\"", \ 0527 (void *)nullptr 0528 // disabled: leave compression up to the client. 0529 // (isOpenSSH?"-C":"+C"), 0530 0531 if (connectionPort) 0532 execl(sshPath, "ssh", "-p", qPrintable(QString::number(connectionPort)), common_args); 0533 else 0534 execl(sshPath, "ssh", common_args); 0535 #undef common_args 0536 } 0537 myDebug(<< "could not exec! " << strerror(errno)); 0538 ::exit(-1); 0539 } 0540 ::close(fd[1]); 0541 rc = fcntl(fd[0], F_GETFL, &flags); 0542 rc = fcntl(fd[0], F_SETFL, flags | O_NONBLOCK); 0543 childFd = fd[0]; 0544 0545 fd_set rfds, wfds; 0546 FD_ZERO(&rfds); 0547 FD_ZERO(&wfds); 0548 char buf[32768]; 0549 int offset = 0; 0550 while (!isLoggedIn) { 0551 FD_SET(childFd, &rfds); 0552 FD_ZERO(&wfds); 0553 if (outBufPos >= 0) 0554 FD_SET(childFd, &wfds); 0555 struct timeval timeout; 0556 timeout.tv_sec = 0; 0557 timeout.tv_usec = 1000; 0558 rc = select(childFd + 1, &rfds, &wfds, nullptr, &timeout); 0559 if (rc < 0) { 0560 if (errno == EINTR) 0561 continue; 0562 myDebug(<< "select failed, rc: " << rc << ", error: " << strerror(errno)); 0563 return error(ERR_CANNOT_CONNECT, connectionHost); 0564 ; 0565 } 0566 if (FD_ISSET(childFd, &wfds) && outBufPos >= 0) { 0567 if (!outBuf.isEmpty()) 0568 rc = ::write(childFd, outBuf.constData() + outBufPos, outBuf.length() - outBufPos); 0569 else 0570 rc = 0; 0571 0572 if (rc >= 0) 0573 outBufPos += rc; 0574 else { 0575 if (errno == EINTR) 0576 continue; 0577 myDebug(<< "write failed, rc: " << rc << ", error: " << strerror(errno)); 0578 outBufPos = -1; 0579 // return true; 0580 } 0581 if (outBufPos >= outBuf.length()) { 0582 outBufPos = -1; 0583 outBuf = QByteArray(); 0584 } 0585 } else if (FD_ISSET(childFd, &rfds)) { 0586 rc = ::read(childFd, buf + offset, sizeof(buf) - offset); 0587 if (rc > 0) { 0588 const auto establishConnectionResult = establishConnection(buf, rc + offset); 0589 if (!establishConnectionResult.result.success()) { 0590 return establishConnectionResult.result; 0591 } 0592 const int noff = establishConnectionResult.remainingBufferSize; 0593 if (noff > 0) 0594 memmove(buf, buf + offset + rc - noff, noff); 0595 offset = noff; 0596 } else { 0597 if (errno == EINTR) 0598 continue; 0599 myDebug(<< "read failed, rc: " << rc << ", error: " << strerror(errno)); 0600 return error(ERR_CANNOT_CONNECT, connectionHost); 0601 ; 0602 } 0603 } 0604 } 0605 #endif 0606 return KIO::WorkerResult::pass(); 0607 } 0608 0609 /** 0610 writes one chunk of data to stdin of child process 0611 */ 0612 void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len) 0613 { 0614 if (outBufPos >= 0 && !outBuf.isEmpty()) { 0615 return; 0616 } 0617 outBuf = QByteArray(buf, len); 0618 outBufPos = 0; 0619 } 0620 0621 /** 0622 manages initial communication setup including password queries 0623 */ 0624 #ifndef Q_OS_WIN 0625 ConnectedResult fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len) 0626 { 0627 QString buf = QString::fromLatin1(buffer, len); 0628 #else 0629 ConnectedResult fishProtocol::establishConnection(const QByteArray &buffer) 0630 { 0631 QString buf = buffer; 0632 #endif 0633 int pos = 0; 0634 // Strip trailing whitespace 0635 while (buf.length() && (buf[buf.length() - 1] == ' ')) 0636 buf.truncate(buf.length() - 1); 0637 0638 myDebug(<< "establishing: got " << buf); 0639 while (childPid && ((pos = buf.indexOf('\n')) >= 0 || buf.endsWith(':') || buf.endsWith('?'))) { 0640 pos++; 0641 QString str = buf.left(pos); 0642 buf = buf.mid(pos); 0643 if (str == "\n") 0644 continue; 0645 if (str == "FISH:\n") { 0646 thisFn.clear(); 0647 infoMessage(i18n("Initiating protocol...")); 0648 if (!connectionAuth.password.isEmpty()) { 0649 connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length() - 1); 0650 if (connectionAuth.keepPassword) { 0651 cacheAuthentication(connectionAuth); 0652 } 0653 } 0654 isLoggedIn = true; 0655 return {0, KIO::WorkerResult::pass()}; 0656 } else if (!str.isEmpty()) { 0657 thisFn += str; 0658 } else if (buf.endsWith(':')) { 0659 if (!redirectUser.isEmpty() && connectionUser != redirectUser) { 0660 QUrl dest = url; 0661 dest.setUserName(redirectUser); 0662 dest.setPassword(redirectPass); 0663 redirection(dest); 0664 commandList.clear(); 0665 commandCodes.clear(); 0666 isRunning = false; 0667 redirectUser = ""; 0668 redirectPass = ""; 0669 return {-1, KIO::WorkerResult::fail()}; 0670 } 0671 if (!connectionPassword.isEmpty()) { 0672 myDebug(<< "sending cpass"); 0673 connectionAuth.password = connectionPassword + ENDLINE; 0674 connectionPassword.clear(); 0675 // su does not like receiving a password directly after sending 0676 // the password prompt so we wait a while. 0677 if (local) 0678 sleep(1); 0679 writeChild(connectionAuth.password.toLatin1().constData(), connectionAuth.password.length()); 0680 } else { 0681 myDebug(<< "sending mpass"); 0682 connectionAuth.prompt = thisFn + buf; 0683 if (local) 0684 connectionAuth.caption = i18n("Local Login"); 0685 else 0686 connectionAuth.caption = i18n("SSH Authentication"); 0687 if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) { 0688 connectionAuth.password.clear(); // don't prefill 0689 int errorCode = openPasswordDialog(connectionAuth); 0690 if (errorCode != 0) { 0691 shutdownConnection(); 0692 return {-1, error(errorCode, connectionHost)}; 0693 } 0694 } 0695 firstLogin = false; 0696 connectionAuth.password += ENDLINE; 0697 if (connectionAuth.username != connectionUser) { 0698 QUrl dest = url; 0699 dest.setUserName(connectionAuth.username); 0700 dest.setPassword(connectionAuth.password); 0701 redirection(dest); 0702 if (isStat) { // FIXME: just a workaround for konq deficiencies 0703 redirectUser = connectionAuth.username; 0704 redirectPass = connectionAuth.password; 0705 } 0706 commandList.clear(); 0707 commandCodes.clear(); 0708 isRunning = false; 0709 return {-1, KIO::WorkerResult::fail()}; 0710 } 0711 myDebug(<< "sending pass"); 0712 if (local) 0713 sleep(1); 0714 writeChild(connectionAuth.password.toLatin1().constData(), connectionAuth.password.length()); 0715 } 0716 thisFn.clear(); 0717 #ifdef Q_OS_WIN 0718 return {buf.length(), KIO::WorkerResult::pass()}; 0719 } 0720 #else 0721 return {0, KIO::WorkerResult::pass()}; 0722 } else if (buf.endsWith('?')) { 0723 int rc = messageBox(QuestionTwoActions, thisFn + buf, QString(), i18nc("@action:button", "Yes"), i18nc("@action:button", "No")); 0724 if (rc == KIO::WorkerBase::PrimaryAction) { 0725 writeChild("yes\n", 4); 0726 } else { 0727 writeChild("no\n", 3); 0728 } 0729 thisFn.clear(); 0730 return {0, KIO::WorkerResult::pass()}; 0731 } 0732 #endif 0733 else { 0734 myDebug(<< "unmatched case in initial handling! should not happen!"); 0735 } 0736 #ifdef Q_OS_WIN 0737 if (buf.endsWith(QLatin1String("(y/n)"))) { 0738 int rc = messageBox(QuestionTwoActions, thisFn + buf, QString(), i18nc("@action:button", "Yes"), i18nc("@action:button", "No")); 0739 if (rc == KIO::WorkerBase::PrimaryAction) { 0740 writeChild("y\n", 2); 0741 } else { 0742 writeChild("n\n", 2); 0743 } 0744 thisFn.clear(); 0745 return {0, KIO::WorkerResult::pass()}; 0746 } 0747 #endif 0748 } 0749 return {buf.length(), KIO::WorkerResult::pass()}; 0750 } 0751 0752 void fishProtocol::setHostInternal(const QUrl &u) 0753 { 0754 int port = u.port(); 0755 if (port <= 0) // no port is -1 in QUrl, but in kde3 we used 0 and the KIO worker assume that. 0756 port = 0; 0757 setHost(u.host(), port, u.userName(), u.password()); 0758 } 0759 0760 /** 0761 sets connection information for subsequent commands 0762 */ 0763 void fishProtocol::setHost(const QString &host, quint16 port, const QString &u, const QString &pass) 0764 { 0765 QString user(u); 0766 0767 local = (host == "localhost" && port == 0); 0768 if (user.isEmpty()) 0769 user = getenv("LOGNAME"); 0770 0771 if (host == connectionHost && port == connectionPort && user == connectionUser) 0772 return; 0773 myDebug(<< "setHost " << u << "@" << host); 0774 0775 if (childPid) 0776 shutdownConnection(); 0777 0778 connectionHost = host; 0779 connectionAuth.url.setHost(host); 0780 0781 connectionUser = user; 0782 connectionAuth.username = user; 0783 connectionAuth.url.setUserName(user); 0784 0785 connectionPort = port; 0786 connectionPassword = pass; 0787 firstLogin = true; 0788 } 0789 0790 /** 0791 Forced close of the connection 0792 0793 This function gets called from the application side of the universe, 0794 it shouldn't send any response. 0795 */ 0796 void fishProtocol::closeConnection() 0797 { 0798 myDebug(<< "closeConnection()"); 0799 shutdownConnection(true); 0800 } 0801 0802 /** 0803 Closes the connection 0804 */ 0805 void fishProtocol::shutdownConnection(bool forced) 0806 { 0807 if (childPid) { 0808 #ifdef Q_OS_WIN 0809 childPid->terminate(); 0810 #else 0811 int killStatus = kill(childPid, SIGTERM); // We may not have permission... 0812 if (killStatus == 0) 0813 waitpid(childPid, nullptr, 0); 0814 #endif 0815 childPid = 0; 0816 #ifndef Q_OS_WIN 0817 ::close(childFd); // ...in which case this should do the trick 0818 childFd = -1; 0819 #endif 0820 if (!forced) { 0821 infoMessage(i18n("Disconnected.")); 0822 } 0823 } 0824 outBufPos = -1; 0825 outBuf = QByteArray(); 0826 qlist.clear(); 0827 commandList.clear(); 0828 commandCodes.clear(); 0829 isLoggedIn = false; 0830 writeReady = true; 0831 isRunning = false; 0832 rawRead = 0; 0833 rawWrite = -1; 0834 recvLen = -1; 0835 sendLen = -1; 0836 } 0837 /** 0838 builds each FISH request and sets the error counter 0839 */ 0840 bool fishProtocol::sendCommand(fish_command_type cmd, ...) 0841 { 0842 const fish_info &info = fishInfo[cmd]; 0843 myDebug(<< "queuing: cmd=" << cmd << "['" << info.command << "'](" << info.params << "), alt=['" << info.alt << "'], lines=" << info.lines); 0844 0845 va_list list; 0846 va_start(list, cmd); 0847 QString realCmd = info.command; 0848 QString realAlt = info.alt; 0849 static const QRegularExpression rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); 0850 for (int i = 0; i < info.params; i++) { 0851 QString arg(va_arg(list, const char *)); 0852 int pos = -2; 0853 while ((pos = arg.indexOf(rx, pos + 2)) >= 0) { 0854 arg.replace(pos, 0, QString("\\")); 0855 } 0856 // myDebug( << "arg " << i << ": " << arg); 0857 realCmd.append(" ").append(arg); 0858 realAlt.replace(QRegularExpression(QLatin1Char('%') + QString::number(i + 1)), arg); 0859 } 0860 QString s("#"); 0861 s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 200'\n"); 0862 if (realCmd == "FISH") 0863 s.prepend(" "); 0864 commandList.append(s); 0865 commandCodes.append(cmd); 0866 va_end(list); 0867 return true; 0868 } 0869 0870 /** 0871 checks response string for result code, converting 000 and 001 appropriately 0872 */ 0873 int fishProtocol::handleResponse(const QString &str) 0874 { 0875 myDebug(<< "handling: " << str); 0876 if (str.startsWith(QLatin1String("### "))) { 0877 bool isOk = false; 0878 int result = str.mid(4, 3).toInt(&isOk); 0879 if (!isOk) 0880 result = 500; 0881 if (result == 0) 0882 result = (errorCount != 0 ? 500 : 200); 0883 if (result == 1) 0884 result = (errorCount != 0 ? 500 : 100); 0885 myDebug(<< "result: " << result << ", errorCount: " << errorCount); 0886 return result; 0887 } else { 0888 errorCount++; 0889 return 0; 0890 } 0891 } 0892 0893 int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr) 0894 { 0895 QDateTime dt(QDateTime::currentDateTime().toUTC()); 0896 int year = dt.date().year(); 0897 int month = dt.date().month(); 0898 int currentMonth = month; 0899 int day = dayStr.toInt(); 0900 0901 static const char *const monthNames[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 0902 0903 for (int i = 0; i < 12; i++) 0904 if (monthStr.startsWith(monthNames[i])) { 0905 month = i + 1; 0906 break; 0907 } 0908 0909 int pos = timeyearStr.indexOf(':'); 0910 if (timeyearStr.length() == 4 && pos == -1) { 0911 year = timeyearStr.toInt(); 0912 } else if (pos == -1) { 0913 return 0; 0914 } else { 0915 if (month > currentMonth + 1) 0916 year--; 0917 dt.setTime(QTime(timeyearStr.left(pos).toInt(), timeyearStr.mid(pos + 1).toInt(), 0)); 0918 } 0919 dt.setDate(QDate(year, month, day)); 0920 0921 return dt.toSecsSinceEpoch(); 0922 } 0923 0924 /** 0925 parses response from server and acts accordingly 0926 */ 0927 KIO::WorkerResult fishProtocol::manageConnection(const QString &l) 0928 { 0929 QString line(l); 0930 int rc = handleResponse(line); 0931 QDateTime dt; 0932 long pos, pos2, pos3; 0933 bool isOk = false; 0934 if (!rc) { 0935 switch (fishCommand) { 0936 case FISH_VER: 0937 if (line.startsWith(QLatin1String("VER 0.0.3"))) { 0938 line.append(" "); 0939 hasAppend = line.contains(" append "); 0940 } else { 0941 shutdownConnection(); 0942 return error(ERR_UNSUPPORTED_PROTOCOL, line); 0943 } 0944 break; 0945 case FISH_PWD: 0946 url.setPath(line); 0947 redirection(url); 0948 break; 0949 case FISH_LIST: 0950 myDebug(<< "listReason: " << static_cast<int>(listReason)); 0951 /* Fall through */ 0952 case FISH_STAT: 0953 if (line.length() > 0) { 0954 switch (line[0].cell()) { 0955 case '0': 0956 case '1': 0957 case '2': 0958 case '3': 0959 case '4': 0960 case '5': 0961 case '6': 0962 case '7': 0963 case '8': 0964 case '9': { 0965 long long val = line.toLongLong(&isOk); 0966 if (val > 0 && isOk) 0967 errorCount--; 0968 if ((fishCommand == FISH_LIST) && (listReason == LIST)) 0969 totalSize(val); 0970 } break; 0971 0972 case 'P': { 0973 errorCount--; 0974 if (line[1] == 'd') { 0975 udsMime = "inode/directory"; 0976 udsType = S_IFDIR; 0977 } else { 0978 if (line[1] == '-') { 0979 udsType = S_IFREG; 0980 } else if (line[1] == 'l') { 0981 udsType = S_IFLNK; 0982 } else if (line[1] == 'c') { 0983 udsType = S_IFCHR; 0984 } else if (line[1] == 'b') { 0985 udsType = S_IFBLK; 0986 } else if (line[1] == 's') { 0987 udsType = S_IFSOCK; 0988 } else if (line[1] == 'p') { 0989 udsType = S_IFIFO; 0990 } else { 0991 myDebug(<< "unknown file type: " << line[1].cell()); 0992 errorCount++; 0993 break; 0994 } 0995 } 0996 // myDebug( << "file type: " << udsType); 0997 0998 long long accessVal = 0; 0999 if (line[2] == 'r') 1000 accessVal |= S_IRUSR; 1001 if (line[3] == 'w') 1002 accessVal |= S_IWUSR; 1003 if (line[4] == 'x' || line[4] == 's') 1004 accessVal |= S_IXUSR; 1005 if (line[4] == 'S' || line[4] == 's') 1006 accessVal |= S_ISUID; 1007 if (line[5] == 'r') 1008 accessVal |= S_IRGRP; 1009 if (line[6] == 'w') 1010 accessVal |= S_IWGRP; 1011 if (line[7] == 'x' || line[7] == 's') 1012 accessVal |= S_IXGRP; 1013 if (line[7] == 'S' || line[7] == 's') 1014 accessVal |= S_ISGID; 1015 if (line[8] == 'r') 1016 accessVal |= S_IROTH; 1017 if (line[9] == 'w') 1018 accessVal |= S_IWOTH; 1019 if (line[10] == 'x' || line[10] == 't') 1020 accessVal |= S_IXOTH; 1021 if (line[10] == 'T' || line[10] == 't') 1022 accessVal |= S_ISVTX; 1023 udsEntry.replace(KIO::UDSEntry::UDS_ACCESS, accessVal); 1024 1025 pos = line.indexOf(':', 12); 1026 if (pos < 0) { 1027 errorCount++; 1028 break; 1029 } 1030 udsEntry.replace(KIO::UDSEntry::UDS_USER, line.mid(12, pos - 12)); 1031 udsEntry.replace(KIO::UDSEntry::UDS_GROUP, line.mid(pos + 1)); 1032 } break; 1033 1034 case 'd': 1035 pos = line.indexOf(' '); 1036 pos2 = line.indexOf(' ', pos + 1); 1037 if (pos < 0 || pos2 < 0) 1038 break; 1039 errorCount--; 1040 udsEntry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, 1041 makeTimeFromLs(line.mid(1, pos - 1), line.mid(pos + 1, pos2 - pos), line.mid(pos2 + 1))); 1042 break; 1043 1044 case 'D': 1045 pos = line.indexOf(' '); 1046 pos2 = line.indexOf(' ', pos + 1); 1047 pos3 = line.indexOf(' ', pos2 + 1); 1048 if (pos < 0 || pos2 < 0 || pos3 < 0) 1049 break; 1050 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())); 1051 pos = pos3; 1052 pos2 = line.indexOf(' ', pos + 1); 1053 pos3 = line.indexOf(' ', pos2 + 1); 1054 if (pos < 0 || pos2 < 0 || pos3 < 0) 1055 break; 1056 dt.setTime(QTime(line.mid(pos + 1, pos2 - pos - 1).toInt(), line.mid(pos2 + 1, pos3 - pos2 - 1).toInt(), line.mid(pos3 + 1).toInt())); 1057 errorCount--; 1058 udsEntry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toSecsSinceEpoch()); 1059 break; 1060 1061 case 'S': { 1062 long long sizeVal = line.mid(1).toLongLong(&isOk); 1063 if (!isOk) 1064 break; 1065 errorCount--; 1066 udsEntry.replace(KIO::UDSEntry::UDS_SIZE, sizeVal); 1067 } break; 1068 1069 case 'E': 1070 errorCount--; 1071 break; 1072 1073 case ':': 1074 pos = line.lastIndexOf('/'); 1075 thisFn = line.mid(pos < 0 ? 1 : pos + 1); 1076 if (fishCommand == FISH_LIST) { 1077 udsEntry.replace(KIO::UDSEntry::UDS_NAME, thisFn); 1078 } 1079 // By default, the mimetype comes from the extension 1080 // We'll use the file(1) result only as fallback [like the rest of KDE does] 1081 if (udsMime != "inode/directory") { 1082 QUrl kurl("fish://host/" + thisFn); 1083 QMimeDatabase db; 1084 QMimeType mime = db.mimeTypeForUrl(kurl); 1085 if (!mime.isDefault()) 1086 udsMime = mime.name(); 1087 } 1088 errorCount--; 1089 break; 1090 1091 case 'M': 1092 // This is getting ugly. file(1) makes some uneducated 1093 // guesses, so we must try to ignore them (#51274) 1094 if (udsMime.isEmpty() && line.right(8) != "/unknown" 1095 && (thisFn.indexOf('.') < 0 || (line.left(8) != "Mtext/x-" && line != "Mtext/plain"))) { 1096 udsMime = line.mid(1); 1097 if (udsMime == "inode/directory") // a symlink to a dir is a dir 1098 udsType = S_IFDIR; 1099 } 1100 errorCount--; 1101 break; 1102 1103 case 'L': 1104 udsEntry.replace(KIO::UDSEntry::UDS_LINK_DEST, line.mid(1)); 1105 if (!udsType) 1106 udsType = S_IFLNK; 1107 errorCount--; 1108 break; 1109 } 1110 } else { 1111 if (!udsMime.isNull()) 1112 udsEntry.replace(KIO::UDSEntry::UDS_MIME_TYPE, udsMime); 1113 udsMime.clear(); 1114 1115 udsEntry.replace(KIO::UDSEntry::UDS_FILE_TYPE, udsType); 1116 udsType = 0; 1117 1118 if (fishCommand == FISH_STAT) 1119 udsStatEntry = udsEntry; 1120 else if (listReason == LIST) { 1121 listEntry(udsEntry); // 1 1122 } else if (listReason == CHECK) 1123 checkExist = true; // 0 1124 errorCount--; 1125 udsEntry.clear(); 1126 } 1127 break; 1128 1129 case FISH_RETR: 1130 if (line.length() == 0) { 1131 recvLen = 0; 1132 return error(ERR_IS_DIRECTORY, url.toDisplayString()); 1133 } 1134 recvLen = line.toLongLong(&isOk); 1135 if (!isOk) { 1136 shutdownConnection(); 1137 return error(ERR_CANNOT_READ, url.toDisplayString()); 1138 } 1139 break; 1140 default: 1141 break; 1142 } 1143 1144 } else if (rc == 100) { 1145 switch (fishCommand) { 1146 case FISH_FISH: 1147 writeChild(fishCode, fishCodeLen); 1148 break; 1149 case FISH_READ: 1150 recvLen = 1024; 1151 /* fall through */ 1152 case FISH_RETR: 1153 myDebug(<< "reading " << recvLen); 1154 if (recvLen == -1) { 1155 shutdownConnection(); 1156 return error(ERR_CANNOT_READ, url.toDisplayString()); 1157 } 1158 1159 rawRead = recvLen; 1160 dataRead = 0; 1161 mimeTypeSent = false; 1162 if (recvLen == 0) { 1163 mimeType("application/x-zerosize"); 1164 mimeTypeSent = true; 1165 } 1166 break; 1167 case FISH_STOR: 1168 case FISH_WRITE: 1169 case FISH_APPEND: 1170 rawWrite = sendLen; 1171 // myDebug( << "sending " << sendLen); 1172 writeChild(nullptr, 0); 1173 break; 1174 default: 1175 break; 1176 } 1177 } else if (rc / 100 != 2) { 1178 switch (fishCommand) { 1179 case FISH_STOR: 1180 case FISH_WRITE: 1181 case FISH_APPEND: 1182 shutdownConnection(); 1183 return error(ERR_CANNOT_WRITE, url.toDisplayString()); 1184 case FISH_RETR: 1185 shutdownConnection(); 1186 return error(ERR_CANNOT_READ, url.toDisplayString()); 1187 case FISH_READ: 1188 if (rc == 501) { 1189 mimeType("inode/directory"); 1190 mimeTypeSent = true; 1191 recvLen = 0; 1192 finished(); 1193 } else { 1194 shutdownConnection(); 1195 return error(ERR_CANNOT_READ, url.toDisplayString()); 1196 } 1197 break; 1198 case FISH_FISH: 1199 case FISH_VER: 1200 shutdownConnection(); 1201 return error(ERR_WORKER_DEFINED, line); 1202 case FISH_PWD: 1203 case FISH_CWD: 1204 return error(ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()); 1205 case FISH_LIST: 1206 myDebug(<< "list error. reason: " << static_cast<int>(listReason)); 1207 if (listReason == LIST) 1208 return error(ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()); 1209 if (listReason == CHECK) { 1210 checkExist = false; 1211 finished(); 1212 } 1213 break; 1214 case FISH_STAT: 1215 udsStatEntry.clear(); 1216 return error(ERR_DOES_NOT_EXIST, url.toDisplayString()); 1217 case FISH_CHMOD: 1218 return error(ERR_CANNOT_CHMOD, url.toDisplayString()); 1219 case FISH_CHOWN: 1220 case FISH_CHGRP: 1221 return error(ERR_ACCESS_DENIED, url.toDisplayString()); 1222 case FISH_MKD: 1223 if (rc == 501) 1224 return error(ERR_DIR_ALREADY_EXIST, url.toDisplayString()); 1225 return error(ERR_CANNOT_MKDIR, url.toDisplayString()); 1226 case FISH_RMD: 1227 return error(ERR_CANNOT_RMDIR, url.toDisplayString()); 1228 case FISH_DELE: 1229 return error(ERR_CANNOT_DELETE, url.toDisplayString()); 1230 case FISH_RENAME: 1231 return error(ERR_CANNOT_RENAME, url.toDisplayString()); 1232 case FISH_COPY: 1233 case FISH_LINK: 1234 case FISH_SYMLINK: 1235 return error(ERR_CANNOT_WRITE, url.toDisplayString()); 1236 default: 1237 break; 1238 } 1239 } else { 1240 if (fishCommand == FISH_STOR) 1241 fishCommand = (hasAppend ? FISH_APPEND : FISH_WRITE); 1242 if (fishCommand == FISH_LIST) { 1243 if (listReason == CHECK && !checkOverwrite && checkExist) { 1244 return error(ERR_FILE_ALREADY_EXIST, url.toDisplayString()); 1245 } 1246 } else if (fishCommand == FISH_STAT) { 1247 udsStatEntry.replace(KIO::UDSEntry::UDS_NAME, url.fileName()); 1248 statEntry(udsStatEntry); 1249 } else if (fishCommand == FISH_APPEND) { 1250 dataReq(); 1251 if (readData(rawData) > 0) 1252 sendCommand(FISH_APPEND, E(QString::number(rawData.size())), E(url.path())); 1253 else if (!checkExist && putPerm > -1) 1254 sendCommand(FISH_CHMOD, E(QString::number(putPerm, 8)), E(url.path())); 1255 sendLen = rawData.size(); 1256 } else if (fishCommand == FISH_WRITE) { 1257 dataReq(); 1258 if (readData(rawData) > 0) 1259 sendCommand(FISH_WRITE, E(QString::number(putPos)), E(QString::number(rawData.size())), E(url.path())); 1260 else if (!checkExist && putPerm > -1) 1261 sendCommand(FISH_CHMOD, E(QString::number(putPerm, 8)), E(url.path())); 1262 putPos += rawData.size(); 1263 sendLen = rawData.size(); 1264 } else if (fishCommand == FISH_RETR) { 1265 data(QByteArray()); 1266 } 1267 finished(); 1268 } 1269 return KIO::WorkerResult::pass(); 1270 } 1271 1272 void fishProtocol::writeStdin(const QString &line) 1273 { 1274 qlist.append(E(line)); 1275 1276 if (writeReady) { 1277 writeReady = false; 1278 // myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n'))); 1279 myDebug(<< "Writing: " << qlist.first()); 1280 myDebug(<< "---------"); 1281 writeChild((const char *)qlist.first().constData(), qlist.first().length()); 1282 } 1283 } 1284 1285 void fishProtocol::sent() 1286 { 1287 if (rawWrite > 0) { 1288 myDebug(<< "writing raw: " << rawData.size() << "/" << rawWrite); 1289 writeChild(rawData.data(), (rawWrite > rawData.size() ? rawData.size() : rawWrite)); 1290 rawWrite -= rawData.size(); 1291 if (rawWrite > 0) { 1292 dataReq(); 1293 if (readData(rawData) <= 0) { 1294 shutdownConnection(); 1295 } 1296 } 1297 return; 1298 } else if (rawWrite == 0) { 1299 // workaround: some dd's insist in reading multiples of 1300 // 8 bytes, swallowing up to seven bytes. Sending 1301 // newlines is safe even when a sane dd is used 1302 writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", 15); 1303 rawWrite = -1; 1304 return; 1305 } 1306 if (qlist.count() > 0) 1307 qlist.erase(qlist.begin()); 1308 if (qlist.count() == 0) { 1309 writeReady = true; 1310 } else { 1311 // myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n'))); 1312 myDebug(<< "Writing: " << qlist.first()); 1313 myDebug(<< "---------"); 1314 writeChild((const char *)qlist.first().constData(), qlist.first().length()); 1315 } 1316 } 1317 1318 ReceivedResult fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen) 1319 { 1320 int pos = 0; 1321 do { 1322 if (buflen <= 0) 1323 break; 1324 1325 if (rawRead > 0) { 1326 myDebug(<< "processedSize " << dataRead << ", len " << buflen << "/" << rawRead); 1327 int dataSize = (rawRead > buflen ? buflen : rawRead); 1328 if (!mimeTypeSent) { 1329 int mimeSize = qMin(dataSize, (int)(mimeBuffer.size() - dataRead)); 1330 memcpy(mimeBuffer.data() + dataRead, buffer, mimeSize); 1331 dataRead += mimeSize; 1332 rawRead -= mimeSize; 1333 buffer += mimeSize; 1334 buflen -= mimeSize; 1335 if (rawRead == 0) // End of data 1336 mimeBuffer.resize(dataRead); 1337 if (dataRead < (int)mimeBuffer.size()) { 1338 myDebug(<< "wait for more"); 1339 break; 1340 } 1341 1342 QMimeDatabase db; 1343 sendmimeType(db.mimeTypeForFileNameAndData(url.path(), mimeBuffer).name()); 1344 mimeTypeSent = true; 1345 if (fishCommand != FISH_READ) { 1346 totalSize(dataRead + rawRead); 1347 data(mimeBuffer); 1348 processedSize(dataRead); 1349 } 1350 mimeBuffer.resize(1024); 1351 pos = 0; 1352 continue; // Process rest of buffer/buflen 1353 } 1354 1355 QByteArray bdata(buffer, dataSize); 1356 data(bdata); 1357 1358 dataRead += dataSize; 1359 rawRead -= dataSize; 1360 processedSize(dataRead); 1361 if (rawRead <= 0) { 1362 buffer += dataSize; 1363 buflen -= dataSize; 1364 } else { 1365 return {0, KIO::WorkerResult::pass()}; 1366 ; 1367 } 1368 } 1369 1370 if (buflen <= 0) 1371 break; 1372 1373 pos = 0; 1374 // Find newline 1375 while ((pos < buflen) && (buffer[pos] != '\n')) 1376 ++pos; 1377 1378 if (pos < buflen) { 1379 QString s = remoteEncoding()->decode(QByteArray(buffer, pos)); 1380 1381 buffer += pos + 1; 1382 buflen -= pos + 1; 1383 1384 const auto result = manageConnection(s); 1385 if (!result.success()) { 1386 return {buflen, result}; 1387 } 1388 1389 pos = 0; 1390 // Find next newline 1391 while ((pos < buflen) && (buffer[pos] != '\n')) 1392 ++pos; 1393 } 1394 } while (childPid && buflen && (rawRead > 0 || pos < buflen)); 1395 return {buflen, KIO::WorkerResult::pass()}; 1396 } 1397 /** get a file */ 1398 KIO::WorkerResult fishProtocol::get(const QUrl &u) 1399 { 1400 myDebug(<< "@@@@@@@@@ get " << u); 1401 setHostInternal(u); 1402 url = u; 1403 if (const auto result = openConnection(); !result.success()) { 1404 return result; 1405 } 1406 url = url.adjusted(QUrl::NormalizePathSegments); 1407 if (url.path().isEmpty()) { 1408 sendCommand(FISH_PWD); 1409 } else { 1410 recvLen = -1; 1411 sendCommand(FISH_RETR, E(url.path())); 1412 } 1413 return run(); 1414 } 1415 1416 /** put a file */ 1417 KIO::WorkerResult fishProtocol::put(const QUrl &u, int permissions, KIO::JobFlags flags) 1418 { 1419 myDebug(<< "@@@@@@@@@ put " << u << " " << permissions << " " << (flags & KIO::Overwrite) << " " /* << resume */); 1420 setHostInternal(u); 1421 1422 url = u; 1423 if (const auto result = openConnection(); !result.success()) { 1424 return result; 1425 } 1426 url = url.adjusted(QUrl::NormalizePathSegments); 1427 if (url.path().isEmpty()) { 1428 sendCommand(FISH_PWD); 1429 } else { 1430 putPerm = permissions; 1431 1432 checkOverwrite = flags & KIO::Overwrite; 1433 checkExist = false; 1434 putPos = 0; 1435 listReason = CHECK; 1436 sendCommand(FISH_LIST, E(url.path())); 1437 sendCommand(FISH_STOR, "0", E(url.path())); 1438 1439 const QString mtimeStr = metaData("modified"); 1440 if (!mtimeStr.isEmpty()) { 1441 QDateTime dt = QDateTime::fromString(mtimeStr, Qt::ISODate); 1442 // TODO set modification time on url.path() somehow 1443 // see FileProtocol::put if using utime() to do that. 1444 } 1445 } 1446 return run(); 1447 } 1448 /** executes next command in sequence or set isRunning false if all is done */ 1449 void fishProtocol::finished() 1450 { 1451 if (commandList.count() > 0) { 1452 fishCommand = (fish_command_type)commandCodes.first(); 1453 errorCount = -fishInfo[fishCommand].lines; 1454 rawRead = 0; 1455 rawWrite = -1; 1456 udsEntry.clear(); 1457 udsStatEntry.clear(); 1458 writeStdin(commandList.first()); 1459 // if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending 1460 // "+(commandList.first().mid(1,commandList.first().indexOf("\n")-1))+"..."); 1461 commandList.erase(commandList.begin()); 1462 commandCodes.erase(commandCodes.begin()); 1463 } else { 1464 isRunning = false; 1465 } 1466 } 1467 1468 /** aborts command sequence and calls error() */ 1469 KIO::WorkerResult fishProtocol::error(int type, const QString &detail) 1470 { 1471 commandList.clear(); 1472 commandCodes.clear(); 1473 myDebug(<< "ERROR: " << type << " - " << detail); 1474 isRunning = false; 1475 return KIO::WorkerResult::fail(type, detail); 1476 } 1477 1478 /** executes a chain of commands */ 1479 KIO::WorkerResult fishProtocol::run() 1480 /* This function writes to childFd fish commands (like #STOR 0 /tmp/test ...) that are stored in outBuf 1481 and reads from childFd the remote host's response. ChildFd is the fd to a process that communicates 1482 with .fishsrv.pl typically running on another computer. */ 1483 { 1484 if (isRunning) { 1485 // should have not been running, something is wrong 1486 return KIO::WorkerResult::fail(); 1487 } 1488 1489 int rc; 1490 isRunning = true; 1491 finished(); 1492 #ifndef Q_OS_WIN 1493 fd_set rfds, wfds; 1494 FD_ZERO(&rfds); 1495 #endif 1496 char buf[32768]; 1497 int offset = 0; 1498 while (isRunning) { 1499 #ifndef Q_OS_WIN 1500 FD_SET(childFd, &rfds); 1501 FD_ZERO(&wfds); 1502 if (outBufPos >= 0) 1503 FD_SET(childFd, &wfds); 1504 struct timeval timeout; 1505 timeout.tv_sec = 0; 1506 timeout.tv_usec = 1000; 1507 rc = select(childFd + 1, &rfds, &wfds, nullptr, &timeout); 1508 if (rc < 0) { 1509 if (errno == EINTR) 1510 continue; 1511 myDebug(<< "select failed, rc: " << rc << ", error: " << strerror(errno)); 1512 shutdownConnection(); 1513 return error(ERR_CONNECTION_BROKEN, connectionHost); 1514 } 1515 // We first write the complete buffer, including all newlines. 1516 // Do: send command and newlines, expect response then 1517 // Do not: send commands, expect response, send newlines, expect response on newlines 1518 // Newlines do not trigger a response. 1519 if (FD_ISSET(childFd, &wfds) && outBufPos >= 0) { 1520 if (outBuf.length() - outBufPos > 0) { 1521 rc = ::write(childFd, outBuf.constData() + outBufPos, outBuf.length() - outBufPos); 1522 } 1523 #else 1524 if (outBufPos >= 0) { 1525 if (outBuf.length() - outBufPos > 0) { 1526 rc = childPid->write(outBuf); 1527 } 1528 #endif 1529 else 1530 rc = 0; 1531 1532 if (rc >= 0) 1533 outBufPos += rc; 1534 else { 1535 #ifndef Q_OS_WIN 1536 if (errno == EINTR) 1537 continue; 1538 myDebug(<< "write failed, rc: " << rc << ", error: " << strerror(errno)); 1539 #else 1540 myDebug(<< "write failed, rc: " << rc); 1541 #endif 1542 shutdownConnection(); 1543 return error(ERR_CONNECTION_BROKEN, connectionHost); 1544 } 1545 if (outBufPos >= outBuf.length()) { 1546 outBufPos = -1; 1547 outBuf = QByteArray(); 1548 sent(); 1549 } 1550 } 1551 #ifndef Q_OS_WIN 1552 else if (FD_ISSET(childFd, &rfds)) { 1553 rc = ::read(childFd, buf + offset, sizeof(buf) - offset); 1554 #else 1555 else if (childPid->waitForReadyRead(1000)) { 1556 rc = childPid->read(buf + offset, sizeof(buf) - offset); 1557 #endif 1558 // myDebug( << "read " << rc << " bytes"); 1559 if (rc <= 0) { 1560 #ifndef Q_OS_WIN 1561 if (errno == EINTR) 1562 continue; 1563 myDebug(<< "read failed, rc: " << rc << ", error: " << strerror(errno)); 1564 #else 1565 myDebug(<< "read failed, rc: " << rc); 1566 #endif 1567 shutdownConnection(); 1568 return error(ERR_CONNECTION_BROKEN, connectionHost); 1569 } 1570 1571 const auto receivedResult = received(buf, rc + offset); 1572 if (!receivedResult.result.success()) { 1573 return receivedResult.result; 1574 } 1575 const int noff = receivedResult.remainingBufferSize; 1576 if (noff > 0) 1577 memmove(buf, buf + offset + rc - noff, noff); 1578 // myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset)); 1579 offset = noff; 1580 } 1581 if (wasKilled()) { 1582 return KIO::WorkerResult::fail(); 1583 } 1584 } 1585 1586 return KIO::WorkerResult::pass(); 1587 } 1588 1589 /** stat a file */ 1590 KIO::WorkerResult fishProtocol::stat(const QUrl &u) 1591 { 1592 myDebug(<< "@@@@@@@@@ stat " << u); 1593 setHostInternal(u); 1594 url = u; 1595 isStat = true; // FIXME: just a workaround for konq deficiencies 1596 const auto openConnectionResult = openConnection(); 1597 isStat = false; // FIXME: just a workaround for konq deficiencies 1598 if (!openConnectionResult.success()) { 1599 return openConnectionResult; 1600 } 1601 url = url.adjusted(QUrl::NormalizePathSegments); 1602 if (url.path().isEmpty()) { 1603 sendCommand(FISH_PWD); 1604 } else { 1605 sendCommand(FISH_STAT, E(url.adjusted(QUrl::StripTrailingSlash).path())); 1606 } 1607 return run(); 1608 } 1609 /** find mimetype for a file */ 1610 KIO::WorkerResult fishProtocol::mimetype(const QUrl &u) 1611 { 1612 myDebug(<< "@@@@@@@@@ mimetype " << u); 1613 setHostInternal(u); 1614 url = u; 1615 if (const auto result = openConnection(); !result.success()) { 1616 return result; 1617 } 1618 url = url.adjusted(QUrl::NormalizePathSegments); 1619 if (url.path().isEmpty()) { 1620 sendCommand(FISH_PWD); 1621 } else { 1622 recvLen = 1024; 1623 sendCommand(FISH_READ, "0", "1024", E(url.path())); 1624 } 1625 return run(); 1626 } 1627 /** list a directory */ 1628 KIO::WorkerResult fishProtocol::listDir(const QUrl &u) 1629 { 1630 myDebug(<< "@@@@@@@@@ listDir " << u); 1631 setHostInternal(u); 1632 url = u; 1633 if (const auto result = openConnection(); !result.success()) { 1634 return result; 1635 } 1636 url = url.adjusted(QUrl::NormalizePathSegments); 1637 if (url.path().isEmpty()) { 1638 sendCommand(FISH_PWD); 1639 } else { 1640 listReason = LIST; 1641 sendCommand(FISH_LIST, E(url.path())); 1642 } 1643 return run(); 1644 } 1645 /** create a directory */ 1646 KIO::WorkerResult fishProtocol::mkdir(const QUrl &u, int permissions) 1647 { 1648 myDebug(<< "@@@@@@@@@ mkdir " << u << " " << permissions); 1649 setHostInternal(u); 1650 url = u; 1651 if (const auto result = openConnection(); !result.success()) { 1652 return result; 1653 } 1654 url = url.adjusted(QUrl::NormalizePathSegments); 1655 if (url.path().isEmpty()) { 1656 sendCommand(FISH_PWD); 1657 } else { 1658 sendCommand(FISH_MKD, E(url.path())); 1659 if (permissions > -1) 1660 sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path())); 1661 } 1662 return run(); 1663 } 1664 /** rename a file */ 1665 KIO::WorkerResult fishProtocol::rename(const QUrl &s, const QUrl &d, KIO::JobFlags flags) 1666 { 1667 myDebug(<< "@@@@@@@@@ rename " << s << " " << d << " " << (flags & KIO::Overwrite)); 1668 if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) { 1669 return error(ERR_UNSUPPORTED_ACTION, s.toDisplayString()); 1670 } 1671 setHostInternal(s); 1672 url = d; 1673 if (const auto result = openConnection(); !result.success()) { 1674 return result; 1675 } 1676 QUrl src = s; 1677 url = url.adjusted(QUrl::NormalizePathSegments); 1678 src = src.adjusted(QUrl::NormalizePathSegments); 1679 if (url.path().isEmpty()) { 1680 sendCommand(FISH_PWD); 1681 } else { 1682 if (!(flags & KIO::Overwrite)) { 1683 listReason = CHECK; 1684 checkOverwrite = false; 1685 sendCommand(FISH_LIST, E(url.path())); 1686 } 1687 sendCommand(FISH_RENAME, E(src.path()), E(url.path())); 1688 } 1689 return run(); 1690 } 1691 /** create a symlink */ 1692 KIO::WorkerResult fishProtocol::symlink(const QString &target, const QUrl &u, KIO::JobFlags flags) 1693 { 1694 myDebug(<< "@@@@@@@@@ symlink " << target << " " << u << " " << (flags & KIO::Overwrite)); 1695 setHostInternal(u); 1696 url = u; 1697 if (const auto result = openConnection(); !result.success()) { 1698 return result; 1699 } 1700 url = url.adjusted(QUrl::NormalizePathSegments); 1701 if (url.path().isEmpty()) { 1702 sendCommand(FISH_PWD); 1703 } else { 1704 if (!(flags & KIO::Overwrite)) { 1705 listReason = CHECK; 1706 checkOverwrite = false; 1707 sendCommand(FISH_LIST, E(url.path())); 1708 } 1709 sendCommand(FISH_SYMLINK, E(target), E(url.path())); 1710 } 1711 return run(); 1712 } 1713 /** change file permissions */ 1714 KIO::WorkerResult fishProtocol::chmod(const QUrl &u, int permissions) 1715 { 1716 myDebug(<< "@@@@@@@@@ chmod " << u << " " << permissions); 1717 setHostInternal(u); 1718 url = u; 1719 if (const auto result = openConnection(); !result.success()) { 1720 return result; 1721 } 1722 url = url.adjusted(QUrl::NormalizePathSegments); 1723 if (url.path().isEmpty()) { 1724 sendCommand(FISH_PWD); 1725 } else { 1726 // TODO: no error? 1727 if (permissions < 0) { 1728 return KIO::WorkerResult::pass(); 1729 } 1730 sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path())); 1731 } 1732 return run(); 1733 } 1734 /** copies a file */ 1735 KIO::WorkerResult fishProtocol::copy(const QUrl &s, const QUrl &d, int permissions, KIO::JobFlags flags) 1736 { 1737 myDebug(<< "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << (flags & KIO::Overwrite)); 1738 if (s.host() != d.host() || s.port() != d.port() || s.userName() != d.userName()) { 1739 return error(ERR_UNSUPPORTED_ACTION, s.toDisplayString()); 1740 } 1741 // myDebug( << s << endl << d); 1742 setHostInternal(s); 1743 url = d; 1744 if (const auto result = openConnection(); !result.success()) { 1745 return result; 1746 } 1747 QUrl src = s; 1748 url = url.adjusted(QUrl::NormalizePathSegments); 1749 src = src.adjusted(QUrl::NormalizePathSegments); 1750 if (src.path().isEmpty()) { 1751 sendCommand(FISH_PWD); 1752 } else { 1753 if (!(flags & KIO::Overwrite)) { 1754 listReason = CHECK; 1755 checkOverwrite = false; 1756 sendCommand(FISH_LIST, E(url.path())); 1757 } 1758 sendCommand(FISH_COPY, E(src.path()), E(url.path())); 1759 if (permissions > -1) 1760 sendCommand(FISH_CHMOD, E(QString::number(permissions, 8)), E(url.path())); 1761 } 1762 return run(); 1763 } 1764 /** removes a file or directory */ 1765 KIO::WorkerResult fishProtocol::del(const QUrl &u, bool isFile) 1766 { 1767 myDebug(<< "@@@@@@@@@ del " << u << " " << isFile); 1768 setHostInternal(u); 1769 url = u; 1770 if (const auto result = openConnection(); !result.success()) { 1771 return result; 1772 } 1773 url = url.adjusted(QUrl::NormalizePathSegments); 1774 if (url.path().isEmpty()) { 1775 sendCommand(FISH_PWD); 1776 } else { 1777 sendCommand((isFile ? FISH_DELE : FISH_RMD), E(url.path())); 1778 } 1779 return run(); 1780 } 1781 /** special like background execute */ 1782 KIO::WorkerResult fishProtocol::special(const QByteArray &data) 1783 { 1784 int tmp; 1785 1786 QDataStream stream(data); 1787 1788 stream >> tmp; 1789 switch (tmp) { 1790 case FISH_EXEC_CMD: // SSH EXEC 1791 { 1792 QUrl u; 1793 QString command; 1794 stream >> u; 1795 stream >> command; 1796 myDebug(<< "@@@@@@@@@ exec " << u << " " << command); 1797 setHostInternal(u); 1798 url = u; 1799 if (const auto result = openConnection(); !result.success()) { 1800 return result; 1801 } 1802 sendCommand(FISH_EXEC, E(command), E(url.path())); 1803 return run(); 1804 } 1805 default: 1806 // Some command we don't understand. 1807 return error(ERR_UNSUPPORTED_ACTION, QString().setNum(tmp)); 1808 } 1809 } 1810 /** report status */ 1811 void fishProtocol::worker_status() 1812 { 1813 myDebug(<< "@@@@@@@@@ worker_status"); 1814 if (childPid > 0) 1815 workerStatus(connectionHost, isLoggedIn); 1816 else 1817 workerStatus(QString(), false); 1818 } 1819 1820 #include "fish.moc"